diff --git a/projects/text-encryption-decryption/index.html b/projects/text-encryption-decryption/index.html index 8fe3990..0bcf532 100644 --- a/projects/text-encryption-decryption/index.html +++ b/projects/text-encryption-decryption/index.html @@ -9,11 +9,43 @@

Text Encryption / Decryption Tool

- - - - - +
+ + +
+ 0 characters +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + + +
+ +
+ + +
+ + +
+
diff --git a/projects/text-encryption-decryption/main.js b/projects/text-encryption-decryption/main.js index b57141e..dbe0663 100644 --- a/projects/text-encryption-decryption/main.js +++ b/projects/text-encryption-decryption/main.js @@ -1,14 +1,152 @@ -// TODO: Encrypt input text (e.g., Caesar cipher) -// TODO: Decrypt input text -// TODO: Handle input and output display -// TODO: Copy result to clipboard -// TODO: Select encryption algorithm +// Basic algorithms: Caesar, ROT13, Atbash + +function normalizeShift(n) { + // Normalize to range [0, 25] + const mod = ((n % 26) + 26) % 26; + return mod; +} + +function shiftChar(c, shift) { + const code = c.charCodeAt(0); + if (code >= 65 && code <= 90) { + // A-Z + const base = 65; + return String.fromCharCode(((code - base + shift + 26) % 26) + base); + } + if (code >= 97 && code <= 122) { + // a-z + const base = 97; + return String.fromCharCode(((code - base + shift + 26) % 26) + base); + } + return c; // non-letters unchanged +} + +function caesarEncrypt(text, shift) { + const s = normalizeShift(shift); + return Array.from(text).map(ch => shiftChar(ch, s)).join(""); +} + +function caesarDecrypt(text, shift) { + const s = normalizeShift(shift); + return Array.from(text).map(ch => shiftChar(ch, -s)).join(""); +} + +function rot13(text) { + return Array.from(text).map(ch => shiftChar(ch, 13)).join(""); +} + +function atbash(text) { + return Array.from(text).map(ch => { + const code = ch.charCodeAt(0); + if (code >= 65 && code <= 90) { + // A <-> Z mapping + return String.fromCharCode(65 + (25 - (code - 65))); + } + if (code >= 97 && code <= 122) { + return String.fromCharCode(97 + (25 - (code - 97))); + } + return ch; + }).join(""); +} function initTextEncryptionDecryption() { - // TODO: Handle text input - // TODO: Encrypt and decrypt functions - // TODO: Display result - // TODO: Copy to clipboard + const inputEl = document.getElementById('inputText'); + const outputEl = document.getElementById('outputText'); + const encryptBtn = document.getElementById('encryptBtn'); + const decryptBtn = document.getElementById('decryptBtn'); + const clearBtn = document.getElementById('clearBtn'); + const copyBtn = document.getElementById('copyBtn'); + const copyStatus = document.getElementById('copyStatus'); + const algorithmEl = document.getElementById('algorithm'); + const shiftGroup = document.getElementById('shiftGroup'); + const shiftEl = document.getElementById('shift'); + const charCount = document.getElementById('charCount'); + + function updateCharCount() { + const len = inputEl.value.length; + charCount.textContent = `${len} ${len === 1 ? 'character' : 'characters'}`; + } + + function setOutput(text) { + outputEl.value = text; + } + + function getAlgorithm() { + return algorithmEl.value; + } + + function isCaesar() { + return getAlgorithm() === 'caesar'; + } + + function updateShiftVisibility() { + // Show shift only for Caesar + shiftGroup.style.display = isCaesar() ? 'flex' : 'none'; + } + + function getShiftValue() { + const v = parseInt(shiftEl.value, 10); + if (Number.isNaN(v)) return 0; + // Clamp within [-25, 25] for UX; algorithm normalizes anyway + return Math.max(-25, Math.min(25, v)); + } + + function transform(action) { + const text = inputEl.value || ''; + const algo = getAlgorithm(); + let result = ''; + if (algo === 'caesar') { + const shift = getShiftValue(); + result = action === 'encrypt' ? caesarEncrypt(text, shift) : caesarDecrypt(text, shift); + } else if (algo === 'rot13') { + // ROT13 is symmetric + result = rot13(text); + } else if (algo === 'atbash') { + // Atbash is symmetric + result = atbash(text); + } + setOutput(result); + } + + async function copyToClipboard() { + const val = outputEl.value; + if (!val) { + copyStatus.textContent = 'Nothing to copy'; + return; + } + try { + await navigator.clipboard.writeText(val); + copyStatus.textContent = 'Copied!'; + } catch (e) { + // Fallback + outputEl.select(); + document.execCommand && document.execCommand('copy'); + copyStatus.textContent = 'Copied (fallback)'; + } + setTimeout(() => (copyStatus.textContent = ''), 1500); + } + + // Event hookups + encryptBtn.addEventListener('click', () => transform('encrypt')); + decryptBtn.addEventListener('click', () => transform('decrypt')); + clearBtn.addEventListener('click', () => { + inputEl.value = ''; + setOutput(''); + updateCharCount(); + }); + copyBtn.addEventListener('click', copyToClipboard); + algorithmEl.addEventListener('change', () => { + updateShiftVisibility(); + }); + inputEl.addEventListener('input', updateCharCount); + shiftEl.addEventListener('input', () => { + // normalize shown value to be within range + shiftEl.value = String(getShiftValue()); + }); + + // Init UI state + updateShiftVisibility(); + updateCharCount(); } window.addEventListener('DOMContentLoaded', initTextEncryptionDecryption); \ No newline at end of file diff --git a/projects/text-encryption-decryption/styles.css b/projects/text-encryption-decryption/styles.css index b05f384..7743230 100644 --- a/projects/text-encryption-decryption/styles.css +++ b/projects/text-encryption-decryption/styles.css @@ -1 +1,144 @@ -/* TODO: Style tool container, textarea, buttons, result display, algorithm selector */ \ No newline at end of file +:root { + --bg: #0b1020; + --card: #111933; + --text: #e6eaf3; + --muted: #9aa3b2; + --primary: #5b8cff; + --primary-700: #4878f2; + --border: #253153; + --success: #22c55e; +} + +html, body { + height: 100%; +} + +body { + margin: 0; + font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; + background: radial-gradient(1200px 1200px at 80% -10%, #1a2452 0%, transparent 40%), var(--bg); + color: var(--text); + display: flex; + flex-direction: column; + align-items: center; + padding: 32px 16px 48px; +} + +h1 { + font-size: 1.8rem; + font-weight: 700; + letter-spacing: 0.2px; + margin: 8px 0 20px; +} + +#tool-container { + width: 100%; + max-width: 860px; + background: var(--card); + border: 1px solid var(--border); + border-radius: 14px; + padding: 20px; + box-shadow: 0 8px 24px rgba(0,0,0,0.35); +} + +.field-group { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 16px; +} + +label { + font-size: 0.95rem; + color: var(--muted); +} + +textarea { + width: 100%; + min-height: 120px; + resize: vertical; + background: #0e1630; + color: var(--text); + border: 1px solid var(--border); + border-radius: 10px; + padding: 12px 12px; + line-height: 1.4; + font-size: 0.98rem; +} + +.meta-row { + display: flex; + justify-content: space-between; + align-items: center; +} + +.options-row { + display: flex; + gap: 12px; + align-items: flex-end; + flex-wrap: wrap; + margin-bottom: 8px; +} + +.option { + display: flex; + flex-direction: column; + gap: 6px; +} + +select, input[type="number"] { + background: #0e1630; + color: var(--text); + border: 1px solid var(--border); + border-radius: 10px; + padding: 10px 12px; + font-size: 0.95rem; +} + +.actions { + display: flex; + gap: 10px; + align-items: center; + margin: 10px 0 18px; +} + +.actions.compact { + margin-top: 6px; +} + +.btn { + appearance: none; + border: 1px solid var(--border); + background: #101a36; + color: var(--text); + padding: 10px 14px; + border-radius: 10px; + cursor: pointer; + font-weight: 600; + transition: transform 0.06s ease, background 0.2s ease, border-color 0.2s ease; +} + +.btn:hover { + transform: translateY(-1px); + border-color: #31406f; +} + +.btn.primary { + background: linear-gradient(180deg, var(--primary) 0%, var(--primary-700) 100%); + border: none; +} + +.btn.ghost { + background: transparent; +} + +#copyStatus { + color: var(--success); + margin-left: 6px; +} + +@media (max-width: 600px) { + h1 { font-size: 1.4rem; } + #tool-container { padding: 14px; } + textarea { min-height: 100px; } +} \ No newline at end of file