Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 37 additions & 5 deletions projects/text-encryption-decryption/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,43 @@
<body>
<h1>Text Encryption / Decryption Tool</h1>
<div id="tool-container">
<!-- TODO: Textarea for input text -->
<!-- TODO: Buttons for Encrypt and Decrypt -->
<!-- TODO: Display encrypted/decrypted result -->
<!-- TODO: Option to copy result to clipboard -->
<!-- TODO: Select encryption algorithm (basic, e.g., Caesar cipher) -->
<div class="field-group">
<label for="inputText">Input text</label>
<textarea id="inputText" placeholder="Type or paste your text here..."></textarea>
<div class="meta-row">
<small id="charCount">0 characters</small>
</div>
</div>

<div class="options-row">
<div class="option">
<label for="algorithm">Algorithm</label>
<select id="algorithm" aria-label="Encryption algorithm">
<option value="caesar" selected>Caesar cipher</option>
<option value="rot13">ROT13</option>
<option value="atbash">Atbash</option>
</select>
</div>
<div class="option" id="shiftGroup">
<label for="shift">Shift</label>
<input id="shift" type="number" min="-25" max="25" step="1" value="3" aria-label="Caesar shift" />
</div>
</div>

<div class="actions">
<button id="encryptBtn" class="btn primary" type="button">Encrypt</button>
<button id="decryptBtn" class="btn" type="button">Decrypt</button>
<button id="clearBtn" class="btn ghost" type="button">Clear</button>
</div>

<div class="field-group">
<label for="outputText">Result</label>
<textarea id="outputText" readonly placeholder="Your result will appear here..."></textarea>
<div class="actions compact">
<button id="copyBtn" class="btn" type="button">Copy</button>
<small id="copyStatus" role="status" aria-live="polite"></small>
</div>
</div>
</div>
<script src="main.js"></script>
</body>
Expand Down
156 changes: 147 additions & 9 deletions projects/text-encryption-decryption/main.js
Original file line number Diff line number Diff line change
@@ -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);
145 changes: 144 additions & 1 deletion projects/text-encryption-decryption/styles.css
Original file line number Diff line number Diff line change
@@ -1 +1,144 @@
/* TODO: Style tool container, textarea, buttons, result display, algorithm selector */
: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; }
}