diff --git a/static/audio/chomp.mp3 b/static/audio/chomp.mp3 deleted file mode 100644 index f55a949..0000000 Binary files a/static/audio/chomp.mp3 and /dev/null differ diff --git a/static/css/styles.css b/static/css/styles.css deleted file mode 100644 index 42ed193..0000000 --- a/static/css/styles.css +++ /dev/null @@ -1,833 +0,0 @@ -/* ===== Global Reset ===== */ -* { - box-sizing: border-box; - gap: 6px !important; - padding: 0; -} - -/* ===== Body ===== */ -body { - font-family: 'Press Start 2P', monospace; - background-color: #0e0e0e; - color: #28E060; - font-size: 13px; - line-height: 1.6; - display: flex; - flex-direction: column; - min-height: 100vh; - justify-content: center; - align-items: center; - padding: 20px; -} - -@media (max-width: 600px) { - #sitemap-section, - #password-change-section, - #server-update-section, - #server-status-section, - #server-logs-section, - #system-settings-section { - padding: 20px; - margin-bottom: 20px; - } - - #sitemap-section li, - #server-status-section li { - font-size: 0.9em; - padding: 6px; - } - - #logContainer { - font-size: 0.9em; - padding: 10px; - } - - body { - font-size: 11px; - padding: 10px; - } - - .button-group, - .admin-button-grid { - flex-direction: column; - align-items: center; - } - - .button-group button, - .admin-button-grid button { - min-width: 75%; - max-width: 75%; - } - - header { - flex-direction: column; - height: auto; - padding-inline: 15px; - padding-block: 20px; - } - - .logo-container { - flex-direction: column; - align-items: center; - } - - .logo-container img { - height: 100px !important; - margin-top: -15px !important; - } - - .logo-text { - margin-left: 0 !important; - text-align: center; - } - - .logo-text h1 { - font-size: 1.4em; - margin-top: -30px !important; - margin-left: 0 !important; - text-align: center !important; - } - - .logo-text p { - font-size: 0.8em; - margin-left: 0 !important; - text-align: center !important; - } - - .admin-button-grid { - grid-template-columns: 1fr; - } - - .status-list { - width: 100%; - max-width: 400px; - padding-left: 0; - list-style: none; - word-wrap: break-word; - overflow-wrap: break-word; - } -} - -/* ===== Header ===== */ -header { - display: flex; - justify-content: center; - align-items: center; - background-color: #111; - border-radius: 12px; - box-shadow: 0 0 15px #28E060; - width: 100%; - max-width: 800px; - margin-bottom: 25px; - padding: 25px; - height: 200px; -} - -.logo-container { - display: flex; - align-items: center; -} - -.logo-container img { - height: 200px; - width: auto; -} - -.logo-text h1 { - font-size: clamp(1.4em, 6vw, 2.8em); - word-break: break-word; - overflow-wrap: break-word; - color: #28E060; - margin: 0; - margin-left: -30px; /* overlap effect */ - text-align: left; -} - -.logo-text p { - font-size: 1.2em; - color: #28E060; - margin: 0; - margin-left: -30px; - text-align: left; -} - - -/* ===== Main Layout ===== */ -main { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - max-width: 800px; - padding: 0; -} - -/* ===== Card Styling ===== */ -.card { - background-color: #1e1e1e; - padding: 25px; - width: 100%; - border-radius: 12px; - box-shadow: 0 0 15px #28E060; - text-align: center; -} - -/* ===== Form Group Styling ===== */ -.form-group { - display: flex !important; - flex-direction: column; - align-items: center; - max-width: 725px; - width: 100%; -} - -.status-list { - width: 100%; - max-width: 400px; - padding-left: 0; - list-style: none; - word-wrap: break-word; - overflow-wrap: break-word; -} - - -/* ===== Inputs, Textareas, Selects ===== */ - -button, -select, -input, -textarea { - font-family: 'Press Start 2P', monospace; - font-size: 12px !important; - letter-spacing: 0.5px; -} - -input, -textarea, -select, -input[type="file"] { - width: 80%; - max-width: 500px; - padding-inline: 20px; - padding-block: 12px; - border: 1px solid #28E060; - border-radius: 8px; - background-color: #2c2f33; - color: #28E060; - text-align: left; - transition: 0.3s; - min-height: 50px; -} - -select { - text-align: center; -} - -textarea { - min-height: 140px; - resize: none; -} - -/* ===== File Input Customization ===== */ -input[type="file"] { - border: 2px dashed #28E060; - cursor: pointer; - color: #28E060; - background-color: #2c2f33; -} - -input[type="file"]::file-selector-button { - font-family: 'Press Start 2P', monospace; - font-size: 12px; - background-color: #2c2f33; - color: #28E060; - border: 2px solid #28E060; - padding-inline: 10px; - padding-block: 8px; - margin-right: 10px; - border-radius: 6px; - text-transform: uppercase; - cursor: pointer; - transition: 0.3s ease; -} - -input[type="file"]::file-selector-button:hover { - background-color: #28E060; - color: #000; - box-shadow: 0 0 10px #28E060; -} - -/* ===== Focus Effects ===== */ -input:focus, -textarea:focus, -select:focus { - outline: none; - box-shadow: 0 0 10px #28E060; -} - -/* ===== Textareas Specific Widths ===== */ -#input-text, -#output-text { - width: 80%; - max-width: 500px; - height: 140px; -} - -/* ===== Button Group Styling ===== */ -.button-group { - display: flex; - flex-wrap: nowrap; - justify-content: center; - width: 100%; -} - -button { - padding-inline: 20px; - padding-block: 10px; - border: none; - border-radius: 8px; - background-color: #2c2f33; - color: #28E060; - font-size: 1em; - cursor: pointer; - transition: 0.3s; - width: auto; - min-width: 225px; - max-width: 300px; - height: 45px; -} - - button:hover { - background-color: #28E060; - color: #121212; - box-shadow: 0 0 10px #28E060; - } - -.danger-button { - background-color: #5f3131; - box-shadow: 0 0 10px #991717; -} - -.danger-button:hover { - background-color: #af0000; - color: #121212; - box-shadow: 0 0 40px #ff0000; -} - -.admin-button-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - justify-items: center; - width: 100%; - max-width: 640px; - margin: 0 auto; - } - - .admin-button-grid button { - width: 100%; - max-width: 280px; - font-family: 'Press Start 2P', monospace; - font-size: 12px; - } - - - -/* ===== Toggle Switch Styling ===== */ -.toggle-container { - display: flex; - align-items: center; - justify-content: center; - } - - .toggle-label { - font-family: 'Press Start 2P', monospace; - font-size: 12px; - color: #28E060; - } - - .material-switch { - position: relative; - display: inline-block; - width: 60px; - height: 34px; - } - - .material-switch input { - opacity: 0; - width: 0; - height: 0; - } - - .material-slider { - position: absolute; - cursor: pointer; - top: 0; left: 0; right: 0; bottom: 0; - background-color: #222; - border: 2px solid #28E060; - border-radius: 34px; - transition: 0.4s; - margin: unset; - } - - .material-slider::before { - position: absolute; - content: ""; - height: 26px; - width: 26px; - left: 2px; - bottom: 2px; - background-color: #28E060; - border-radius: 50%; - transition: 0.4s; - box-shadow: 0 0 6px #28E060; - } - - .material-switch input:checked + .material-slider { - background-color: #28E060; - } - - .material-switch input:checked + .material-slider::before { - transform: translateX(26px); - background-color: #000; - } - - - /* Label beside switch */ - #toggle-label { - font-family: 'Press Start 2P', monospace; - color: #28E060; - margin-left: 20px; - font-size: 12px; - } - -.toggle-container { - display: flex; - align-items: center; - justify-content: center; - width: 100%; -} - -/* Make sure the switch aligns well */ -.switch { - position: relative; - display: flex; - align-items: center; /* <-- Ensures vertical centering */ - justify-content: center; - width: 70px; - height: 34px; -} - - /* Hide the checkbox */ - .switch input { - opacity: 0; - width: 0; - height: 0; - } - -/* The slider */ -.slider { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #2c2f33; - border: 2px solid #28E060; - border-radius: 34px; - transition: .4s; - display: flex; - align-items: center; -} - - /* The circle knob */ - .slider::before { - content: ""; - height: 22px; - width: 22px; - background-color: #28E060; - border-radius: 50%; - transition: .4s; - transform: translateX(2px); - position: absolute; - left: auto; - bottom: auto; - } - -input:checked + .slider::before { - transform: translateX(36px); -} - -/* Toggle Labels */ -.labels { - position: relative; - width: 100px; - display: flex; - justify-content: space-between; - font-size: 0.9em; - color: #28E060; - margin-top: 5px; -} - - .labels::before, - .labels::after { - content: attr(data-on); - width: 50%; - text-align: center; - } - - .labels::after { - content: attr(data-off); - } - -/* ===== Toast Notifications ===== */ -.toast { - visibility: hidden; - width: 80%; - max-width: 500px; - min-height: 50px; - background-color: #333; - color: #28E060; - text-align: center; - border-radius: 8px; - padding: 14px; - margin: 10px auto 0 auto; - font-size: 1em; - display: flex; - align-items: center; - justify-content: center; -} - - .toast.show { - visibility: visible; - animation: fadein 0.5s, fadeout 0.5s 2.5s; - } - -@keyframes fadein { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -@keyframes fadeout { - from { - opacity: 1; - } - - to { - opacity: 0; - } -} - -/* ===== Footer ===== */ -footer { - text-align: center; - padding: 25px; - background-color: #1c1c1c; - color: #28E060; - border-radius: 12px; - box-shadow: 0 0 15px #28E060; - width: 100%; - max-width: 800px; - margin-top: 25px; -} - - footer a { - color: #28E060; - text-decoration: none; - } - - footer a:hover { - color: #ff0066; - } - -/* ===== Responsive Design ===== */ -@media (max-width: 600px) { - input, - textarea, - select, - #input-text, - #output-text { - width: 100% !important; - max-width: 90% !important; - } -} - -/* ===== Copy Feedback Message ===== */ -.copy-feedback, #shared-link-feedback { - background-color: #2c2f33; - padding-inline: 12px; - padding-block: 6px; - margin-top: 6px; - border-radius: 6px; - color: #28E060; - font-size: 0.9em; - display: none; - opacity: 0; - text-align: center; - max-width: 500px; - margin-left: auto; - margin-right: auto; - transition: opacity 0.3s ease; -} - -.copy-feedback.show, #shared-link-feedback.show { - display: block; - opacity: 1; -} - -.share-link-container { - display: flex; - flex-direction: column; - align-items: center; - margin-top: 12px; - margin-bottom: 12px; -} - -#share-link { - display: block; - background-color: #2c2f33; - padding-inline: 16px; - padding-block: 8px; - border-radius: 6px; - color: #28E060; - font-size: 0.9em; - text-align: center; - max-width: 720px; - width: 100%; - word-break: break-all; - text-decoration: none; - transition: all 0.3s ease; -} - -#share-link:hover { - color: #00cc77; - background-color: #36393f; -} - -/* ===== Form Styling ===== */ -form { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; -} - - -/* ===== Section Card Styling ===== */ -section.card { - display: flex; - flex-direction: column; - align-items: center; -} - -/* ===== Pacman Game Styling ===== */ -#pacmanCanvas { - background-color: black; - display: block; - border: 2px solid #28E060; - border-radius: 12px; - max-width: 700px; - width: 100%; - aspect-ratio: 4/3; - object-fit: contain; -} - -#pacman-section { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 25px; - max-width: 725px; - width: 100%; -} - -.pacman-wrapper { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - padding: 0; - margin: 0; -} - -/* ===== Utility Classes ===== */ -.hidden { - display: none !important; -} - -/* ===== Section Spacing ===== */ -#password-generator-section { - margin-bottom: 25px; -} - -#encoding-section { - margin-bottom: 25px; -} - -/* Pickup page sections */ -#pickup-section { - margin-bottom: 25px; -} - -#security-notice-section { - margin-bottom: 25px; -} - -/* ===== File Input Section ===== */ -#encoding-section #file-section { - display: none; -} - -#encoding-section #file-section:not(.hidden) { - display: flex; -} - -/* Ensure PacCrypt sharing file uploader is always visible */ -#sharing-section #file-section { - display: flex; -} - -/* Mobile-friendly download button */ -.download-btn { - width: 100%; - padding: 12px; - font-size: 16px; - cursor: pointer; - background-color: var(--primary-color); - color: white; - border: none; - border-radius: 4px; - transition: background-color 0.3s; -} - -.download-btn:hover { - background-color: var(--primary-hover); -} - -/* Mobile form adjustments */ -.pickup-form { - max-width: 100%; - margin: 0 auto; -} - -.pickup-form input[type="password"] { - width: 100%; - padding: 12px; - margin-bottom: 10px; - font-size: 16px; - border: 1px solid var(--border-color); - border-radius: 4px; - background-color: var(--input-bg); - color: var(--text-color); -} - -/* Mobile-specific styles */ -@media (max-width: 768px) { - .download-btn { - padding: 15px; - font-size: 18px; - } - - .pickup-form input[type="password"] { - padding: 15px; - font-size: 18px; - } -} - -/* ===== Admin Section Styling ===== */ -#sitemap-section, -#password-change-section, -#server-update-section, -#server-status-section, -#server-logs-section, -#system-settings-section { - margin-bottom: 25px; - padding: 25px; - background-color: #1e1e1e; - border-radius: 12px; - box-shadow: 0 0 15px #28E060; -} - -.sitemap-header { - display: flex; - justify-content: space-between; - align-items: center; - margin: 15px 0; -} - -.sitemap-header h3 { - color: #28E060; - margin: 0; -} - -.collapse-btn { - background: none; - border: none; - color: #28E060; - font-size: 1.2em; - cursor: pointer; - padding-inline: 10px; - padding-block: 5px; - transition: transform 0.3s ease; -} - -.collapse-btn:hover { - transform: scale(1.1); -} - -.sitemap-content { - transition: all 0.3s ease; - margin-bottom: 15px; -} - -#sitemap-section ul, -#server-status-section ul { - list-style: none; - padding-left: 0; - margin-top: 15px; -} - -#sitemap-section li, -#server-status-section li { - margin-bottom: 6px; - padding: 8px; - background-color: #2c2f33; - border-radius: 6px; - color: #28E060; -} - -#server-logs-section button { - margin-bottom: 15px; - width: 100%; - max-width: 300px; -} - -#logLoader { - color: #28E060; - text-align: center; - padding: 10px; -} - -#logContainer { - background-color: #2c2f33; - color: #28E060; - padding: 15px; - border-radius: 8px; - max-height: 400px; - overflow-y: auto; - font-family: monospace; - white-space: pre-wrap; -} - -#system-settings-section { - margin-bottom: unset !important; - padding: 25px; - background-color: #1e1e1e; - border-radius: 12px; - box-shadow: 0 0 15px #28E060; -} \ No newline at end of file diff --git a/static/fonts/PressStart2P-Regular.ttf b/static/fonts/PressStart2P-Regular.ttf deleted file mode 100644 index 2442aff..0000000 Binary files a/static/fonts/PressStart2P-Regular.ttf and /dev/null differ diff --git a/static/img/Github_logo.png b/static/img/Github_logo.png deleted file mode 100644 index 84ed908..0000000 Binary files a/static/img/Github_logo.png and /dev/null differ diff --git a/static/img/PacCrypt.png b/static/img/PacCrypt.png deleted file mode 100644 index e0318ee..0000000 Binary files a/static/img/PacCrypt.png and /dev/null differ diff --git a/static/img/PacCrypt_W-Background.png b/static/img/PacCrypt_W-Background.png deleted file mode 100644 index 081558b..0000000 Binary files a/static/img/PacCrypt_W-Background.png and /dev/null differ diff --git a/static/img/PacCrypt_W-Background_Name.png b/static/img/PacCrypt_W-Background_Name.png deleted file mode 100644 index 898563e..0000000 Binary files a/static/img/PacCrypt_W-Background_Name.png and /dev/null differ diff --git a/static/img/PacCrypt_W-Name.png b/static/img/PacCrypt_W-Name.png deleted file mode 100644 index fda9b45..0000000 Binary files a/static/img/PacCrypt_W-Name.png and /dev/null differ diff --git a/static/img/sitemap.png b/static/img/sitemap.png deleted file mode 100644 index 095fc38..0000000 Binary files a/static/img/sitemap.png and /dev/null differ diff --git a/static/js/encryption.js b/static/js/encryption.js deleted file mode 100644 index 23862e0..0000000 --- a/static/js/encryption.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Encryption module. - * Handles cryptographic operations using Web Crypto API. - * Implements AES-GCM encryption with PBKDF2 key derivation. - */ - -// ===== Constants ===== -const SALT_LENGTH = 16; -const IV_LENGTH = 12; -const PBKDF2_ITERATIONS = 200_000; -const KEY_LENGTH = 256; - -// ===== Binary-safe Base64 Helpers ===== -function base64Encode(buffer) { - const binary = Array.from(new Uint8Array(buffer)) - .map(byte => String.fromCharCode(byte)) - .join(''); - return btoa(binary); -} - -function base64Decode(b64str) { - const binary = atob(b64str); - const bytes = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); - } - return bytes; -} - -// ===== Key Derivation ===== -/** - * Derives an AES-GCM key from a password using PBKDF2. - * @param {string} password - User-supplied password. - * @param {Uint8Array} salt - Randomly generated salt. - * @returns {Promise} - Derived cryptographic key. - */ -export async function deriveKey(password, salt) { - const encoder = new TextEncoder(); - const keyMaterial = await crypto.subtle.importKey( - 'raw', - encoder.encode(password), - { name: 'PBKDF2' }, - false, - ['deriveKey'] - ); - - return crypto.subtle.deriveKey( - { - name: 'PBKDF2', - salt, - iterations: PBKDF2_ITERATIONS, - hash: 'SHA-256' - }, - keyMaterial, - { name: 'AES-GCM', length: KEY_LENGTH }, - false, - ['encrypt', 'decrypt'] - ); -} - -// ===== Encryption ===== -/** - * Encrypts a message using AES-GCM with a derived key. - * @param {string} message - Plaintext message to encrypt. - * @param {string} password - User password for key derivation. - * @returns {Promise} - Base64-encoded encrypted string. - */ -export async function encryptAdvanced(message, password) { - const encoder = new TextEncoder(); - const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH)); - const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH)); - const key = await deriveKey(password, salt); - const encoded = encoder.encode(message); - - const ciphertext = await crypto.subtle.encrypt( - { name: 'AES-GCM', iv }, - key, - encoded - ); - - const output = new Uint8Array(salt.length + iv.length + ciphertext.byteLength); - output.set(salt); - output.set(iv, salt.length); - output.set(new Uint8Array(ciphertext), salt.length + iv.length); - - return base64Encode(output.buffer); -} - -// ===== Decryption ===== -/** - * Decrypts an AES-GCM encrypted string. - * @param {string} encryptedData - Base64-encoded ciphertext. - * @param {string} password - Password used to derive the decryption key. - * @returns {Promise} - Decrypted plaintext. - */ -export async function decryptAdvanced(encryptedData, password) { - const encrypted = base64Decode(encryptedData); - - const salt = encrypted.slice(0, SALT_LENGTH); - const iv = encrypted.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH); - const ciphertext = encrypted.slice(SALT_LENGTH + IV_LENGTH); - const key = await deriveKey(password, salt); - - const decrypted = await crypto.subtle.decrypt( - { name: 'AES-GCM', iv }, - key, - ciphertext - ); - - return new TextDecoder().decode(decrypted); -} - -// ===== Module Initialization ===== -/** - * Initializes the encryption module and logs its status. - */ -export function setupEncryption() { - console.log('[Encryption] Module loaded'); -} diff --git a/static/js/fileops.js b/static/js/fileops.js deleted file mode 100644 index 449f803..0000000 --- a/static/js/fileops.js +++ /dev/null @@ -1,162 +0,0 @@ -import { deriveKey } from "./encryption.js"; // assuming shared deriveKey() - -const SALT_LENGTH = 16; -const IV_LENGTH = 12; -const KEY_LENGTH = 256; - -/** - * Encrypts a full file and downloads the encrypted version. - */ -export async function encryptFile(fileInput, password) { - const file = fileInput.files[0]; - if (!file) return; - - try { - const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH)); - const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH)); - const key = await deriveKey(password, salt); - const fileBuffer = new Uint8Array(await file.arrayBuffer()); - - const ciphertext = await crypto.subtle.encrypt( - { name: "AES-GCM", iv }, - key, - fileBuffer - ); - - const ctBytes = new Uint8Array(ciphertext); - const result = new Uint8Array(salt.length + iv.length + ctBytes.length); - result.set(salt); - result.set(iv, salt.length); - result.set(ctBytes, salt.length + iv.length); - - const blob = new Blob([result], { type: "application/octet-stream" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = file.name + ".encrypted"; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } catch (error) { - alert("Error encrypting file: " + error.message); - } -} - -export async function decryptFile(fileInput, password) { - const file = fileInput.files[0]; - if (!file) return; - - try { - const data = new Uint8Array(await file.arrayBuffer()); - const salt = data.slice(0, SALT_LENGTH); - const iv = data.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH); - const ciphertext = data.slice(SALT_LENGTH + IV_LENGTH); - const key = await deriveKey(password, salt); - - const decrypted = await crypto.subtle.decrypt( - { name: "AES-GCM", iv }, - key, - ciphertext - ); - - const blob = new Blob([decrypted], { type: "application/octet-stream" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = file.name.replace(".encrypted", ""); - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } catch (error) { - alert("Error decrypting file: " + error.message); - } -} - -// ===== File Processing ===== -async function processFile(file, password, isEncrypt) { - const chunks = []; - const totalChunks = Math.ceil(file.size / CHUNK_SIZE); - let processedChunks = 0; - - for (let start = 0; start < file.size; start += CHUNK_SIZE) { - const chunk = file.slice(start, start + CHUNK_SIZE); - const arrayBuffer = await chunk.arrayBuffer(); - const uint8Array = new Uint8Array(arrayBuffer); - - const processedChunk = await processChunk(uint8Array, password, isEncrypt); - chunks.push(processedChunk); - - processedChunks++; - updateProgress(processedChunks, totalChunks); - } - - return chunks; -} - -async function processChunk(data, password, isEncrypt) { - const payload = { - "encryption-type": "advanced", - operation: isEncrypt ? "encrypt" : "decrypt", - message: Array.from(data).join(','), - password: password - }; - - const response = await fetch("/", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload) - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const result = await response.json(); - return new Uint8Array(result.result.split(',').map(Number)); -} - -// ===== File Download ===== -function downloadEncryptedFile(chunks, originalName) { - const blob = new Blob(chunks, { type: 'application/octet-stream' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = originalName + '.encrypted'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); -} - -function downloadDecryptedFile(chunks, originalName) { - const blob = new Blob(chunks, { type: 'application/octet-stream' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = originalName.replace('.encrypted', ''); - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); -} - -// ===== Progress Tracking ===== -function updateProgress(processed, total) { - const progressBar = document.getElementById("file-progress"); - const progressText = document.getElementById("file-progress-text"); - - if (progressBar && progressText) { - const percent = Math.round((processed / total) * 100); - progressBar.style.width = percent + "%"; - progressText.textContent = `Processing: ${percent}%`; - - if (processed === total) { - setTimeout(() => { - progressBar.style.width = "0%"; - progressText.textContent = ""; - }, 1000); - } - } -} diff --git a/static/js/main.js b/static/js/main.js deleted file mode 100644 index 61cfb31..0000000 --- a/static/js/main.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Main application entry point. - * Initializes UI and game components when the DOM is loaded. - */ - -import { setupUI } from './ui.js'; -import { setupGame } from './pacman.js'; - -// Initialize application when DOM is fully loaded -window.addEventListener("DOMContentLoaded", () => { - setupUI(); - setupGame(); -}); diff --git a/static/js/pacman.js b/static/js/pacman.js deleted file mode 100644 index c24abf5..0000000 --- a/static/js/pacman.js +++ /dev/null @@ -1,376 +0,0 @@ -/** - * Pacman game module. - * Handles game logic, rendering, and user interaction. - */ - -// ===== Game Constants ===== -const PACMAN_SPEED = 40; -const ENEMY_SPEED = 20; -const CELL_SIZE = 40; -const DOT_SIZE = 5; - -// ===== Game State ===== -let canvas, ctx, pacman, enemy, walls, dots, score; -let cols, rows, randSeed, gameInterval; - -// ===== Public Interface ===== -export function setupGame() { - console.log('[PacMan] Game module loaded.'); - window.startPacman = startPacman; - window.exitGame = exitGame; -} - -export function startPacman() { - // Scroll to the Pacman section - const pacmanSection = document.getElementById("pacman-section"); - if (pacmanSection) { - pacmanSection.scrollIntoView({ behavior: 'smooth' }); - } - - // Initialize game state - initializeGame(); - setupGameLoop(); -} - -export function stopPacman() { - clearInterval(gameInterval); - if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height); - - // Restore scrolling - document.body.style.overflow = ''; - document.removeEventListener('wheel', preventScroll); - document.removeEventListener('touchmove', preventScroll); -} - -export function resetGame() { - stopPacman(); - startPacman(); -} - -export function exitGame() { - stopPacman(); - document.getElementById("input-text").value = ""; - document.getElementById("pacman-section").style.display = "none"; - document.getElementById("encoding-section").style.display = "block"; -} - -// ===== Game Initialization ===== -function initializeGame() { - canvas = document.getElementById("pacmanCanvas"); - ctx = canvas.getContext("2d"); - - cols = Math.floor(canvas.width / CELL_SIZE); - rows = Math.floor(canvas.height / CELL_SIZE); - walls = []; - dots = []; - score = 0; - - clearInterval(gameInterval); - - // Get seed from generated password or use default - const passwordField = document.getElementById("generated-password"); - const seedSource = passwordField?.value || "pacman"; - randSeed = [...seedSource].reduce((s, c) => s + c.charCodeAt(0), 0); - - generateWalls(); - generateDots(); - - pacman = spawn(); - do { enemy = spawn(); } while (enemy.x === pacman.x && enemy.y === pacman.y); - - pacman.dx = pacman.dy = 0; - document.addEventListener("keydown", movePacman); - - // Prevent scrolling - document.body.style.overflow = 'hidden'; - document.addEventListener('wheel', preventScroll, { passive: false }); - document.addEventListener('touchmove', preventScroll, { passive: false }); - - // Add touch controls - let touchStartX = 0; - let touchStartY = 0; - - canvas.addEventListener('touchstart', (e) => { - e.preventDefault(); - touchStartX = e.touches[0].clientX; - touchStartY = e.touches[0].clientY; - }, { passive: false }); - - canvas.addEventListener('touchmove', (e) => { - e.preventDefault(); - }, { passive: false }); - - canvas.addEventListener('touchend', (e) => { - e.preventDefault(); - const touchEndX = e.changedTouches[0].clientX; - const touchEndY = e.changedTouches[0].clientY; - - const dx = touchEndX - touchStartX; - const dy = touchEndY - touchStartY; - - // Determine swipe direction based on the larger movement - if (Math.abs(dx) > Math.abs(dy)) { - // Horizontal swipe - if (dx > 0) { - pacman.dx = PACMAN_SPEED; - pacman.dy = 0; - } else { - pacman.dx = -PACMAN_SPEED; - pacman.dy = 0; - } - } else { - // Vertical swipe - if (dy > 0) { - pacman.dx = 0; - pacman.dy = PACMAN_SPEED; - } else { - pacman.dx = 0; - pacman.dy = -PACMAN_SPEED; - } - } - }, { passive: false }); -} - -function setupGameLoop() { - gameInterval = setInterval(gameLoop, 150); -} - -// ===== Game Setup Helpers ===== -function spawn() { - const options = []; - for (let c = 1; c < cols - 1; c++) { - for (let r = 1; r < rows - 1; r++) { - if (!walls.some(w => w.c === c && w.r === r)) { - const neighbors = [ - { c: c + 1, r }, { c: c - 1, r }, - { c, r: r + 1 }, { c, r: r - 1 } - ]; - if (neighbors.some(n => !walls.some(w => w.c === n.c && w.r === n.r))) { - options.push({ c, r }); - } - } - } - } - const s = options[Math.floor(rand() * options.length)]; - return { - x: s.c * CELL_SIZE + CELL_SIZE / 2, - y: s.r * CELL_SIZE + CELL_SIZE / 2, - size: CELL_SIZE / 2 - 5, - dx: 0, - dy: 0 - }; -} - -function rand() { - const x = Math.sin(randSeed++) * 10000; - return x - Math.floor(x); -} - -function generateWalls() { - // First pass: generate initial walls - for (let c = 0; c < cols; c++) { - for (let r = 0; r < rows; r++) { - if (c === 0 || r === 0 || c === cols - 1 || r === rows - 1 || rand() < 0.2) { - walls.push({ c, r }); - } - } - } - - // Second pass: check for enclosed spaces - for (let c = 1; c < cols - 1; c++) { - for (let r = 1; r < rows - 1; r++) { - // Skip if already a wall - if (walls.some(w => w.c === c && w.r === r)) continue; - - // Check all four sides - const hasWallAbove = walls.some(w => w.c === c && w.r === r - 1); - const hasWallBelow = walls.some(w => w.c === c && w.r === r + 1); - const hasWallLeft = walls.some(w => w.c === c - 1 && w.r === r); - const hasWallRight = walls.some(w => w.c === c + 1 && w.r === r); - - // If all sides are walls, make this spot a wall too - if (hasWallAbove && hasWallBelow && hasWallLeft && hasWallRight) { - walls.push({ c, r }); - } - } - } -} - -function generateDots() { - dots = []; - for (let c = 1; c < cols - 1; c++) { - for (let r = 1; r < rows - 1; r++) { - if (walls.some(w => w.c === c && w.r === r)) continue; - - const isEnclosed = - walls.some(w => w.c === c + 1 && w.r === r) && - walls.some(w => w.c === c - 1 && w.r === r) && - walls.some(w => w.c === c && w.r === r + 1) && - walls.some(w => w.c === c && w.r === r - 1); - - if (!isEnclosed) dots.push({ c, r }); - } - } -} - -// ===== Game Loop & Rendering ===== -function gameLoop() { - ctx.clearRect(0, 0, canvas.width, canvas.height); - drawWalls(); - moveChar(pacman); - moveEnemy(); - drawChar(pacman, "yellow"); - drawChar(enemy, "red"); - eatDots(); - drawScore(); - checkGameOver(); -} - -function drawWalls() { - ctx.fillStyle = "blue"; - walls.forEach(w => { - ctx.fillRect(w.c * CELL_SIZE, w.r * CELL_SIZE, CELL_SIZE, CELL_SIZE); - }); -} - -function drawChar(ch, color) { - ctx.beginPath(); - ctx.arc(ch.x, ch.y, ch.size, 0, Math.PI * 2); - ctx.fillStyle = color; - ctx.fill(); -} - -function drawScore() { - ctx.fillStyle = "white"; - ctx.font = "20px Poppins"; - ctx.textAlign = "left"; - // Add padding to prevent clipping - const padding = 10; - ctx.fillText("Score: " + score, padding, 25); -} - -function checkGameOver() { - if ( - Math.abs(pacman.x - enemy.x) < pacman.size && - Math.abs(pacman.y - enemy.y) < pacman.size - ) { - ctx.fillStyle = "#00ff99"; - ctx.font = "40px Poppins"; - ctx.textAlign = "center"; - ctx.fillText("Game Over!", canvas.width / 2, canvas.height / 2); - clearInterval(gameInterval); - } -} - -// ===== Movement Logic ===== -function movePacman(e) { - const k = e.key; - if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(k)) return; - e.preventDefault(); - - if (k === "ArrowUp") { pacman.dx = 0; pacman.dy = -PACMAN_SPEED; } - if (k === "ArrowDown") { pacman.dx = 0; pacman.dy = PACMAN_SPEED; } - if (k === "ArrowLeft") { pacman.dx = -PACMAN_SPEED; pacman.dy = 0; } - if (k === "ArrowRight") { pacman.dx = PACMAN_SPEED; pacman.dy = 0; } -} - -function moveChar(ch) { - const nx = ch.x + ch.dx; - const ny = ch.y + ch.dy; - if (!willCollide(nx, ny, ch.size)) { - ch.x = nx; - ch.y = ny; - } -} - -function moveEnemy() { - const options = []; - const moves = [[ENEMY_SPEED, 0], [-ENEMY_SPEED, 0], [0, ENEMY_SPEED], [0, -ENEMY_SPEED]]; - - moves.forEach(([dx, dy]) => { - const nx = enemy.x + dx; - const ny = enemy.y + dy; - if (!willCollide(nx, ny, enemy.size)) options.push({ dx, dy }); - }); - - if (!options.length) return; - - let best = options[0]; - let bestDist = dist(enemy.x + best.dx, enemy.y + best.dy, pacman.x, pacman.y); - - for (const opt of options) { - const d = dist(enemy.x + opt.dx, enemy.y + opt.dy, pacman.x, pacman.y); - if (d < bestDist) { - best = opt; - bestDist = d; - } - } - - enemy.x += best.dx; - enemy.y += best.dy; -} - -function dist(x1, y1, x2, y2) { - return Math.abs(x1 - x2) + Math.abs(y1 - y2); -} - -function willCollide(x, y, size) { - const left = x - size, right = x + size; - const top = y - size, bottom = y + size; - - return walls.some(w => { - const wx1 = w.c * CELL_SIZE, wy1 = w.r * CELL_SIZE; - const wx2 = wx1 + CELL_SIZE, wy2 = wy1 + CELL_SIZE; - return right > wx1 && left < wx2 && bottom > wy1 && top < wy2; - }); -} - -function eatDots() { - const chompSound = document.getElementById("chomp-sound"); - - dots = dots.filter(d => { - const dx = d.c * CELL_SIZE + CELL_SIZE / 2; - const dy = d.r * CELL_SIZE + CELL_SIZE / 2; - - if (Math.abs(pacman.x - dx) < pacman.size && Math.abs(pacman.y - dy) < pacman.size) { - score++; - if (chompSound) { - chompSound.currentTime = 0; - chompSound.volume = 0.4; - chompSound.play(); - } - return false; - } - return true; - }); - - // Check if all dots are eaten - if (dots.length === 0) { - // Trigger password generator for new random map - const generateBtn = document.getElementById("generate-btn"); - if (generateBtn) { - generateBtn.click(); - } - - // Auto-restart the game after a short delay - setTimeout(() => { - resetGame(); - }, 1000); - } - - ctx.fillStyle = "white"; - dots.forEach(d => { - ctx.beginPath(); - ctx.arc(d.c * CELL_SIZE + CELL_SIZE / 2, d.r * CELL_SIZE + CELL_SIZE / 2, DOT_SIZE, 0, Math.PI * 2); - ctx.fill(); - }); -} - -// ===== Global Functions ===== -window.resetGame = resetGame; -window.exitGame = exitGame; - -// Add scroll prevention function -function preventScroll(e) { - e.preventDefault(); -} diff --git a/static/js/ui.js b/static/js/ui.js deleted file mode 100644 index e721547..0000000 --- a/static/js/ui.js +++ /dev/null @@ -1,332 +0,0 @@ -/** - * UI management module. - * Handles user interface interactions and form handling. - */ - -import { encryptFile, decryptFile } from './fileops.js'; - -// ===== UI Initialization ===== -export function setupUI() { - const removeBtn = document.getElementById("remove-file-btn"); - if (removeBtn) { - removeBtn.style.display = "none"; - } - initializeEventListeners(); -} - -function initializeEventListeners() { - const elements = { - encryptionType: document.getElementById("encryption-type"), - inputText: document.getElementById("input-text"), - form: document.getElementById("crypto-form"), - removeFileBtn: document.getElementById("remove-file-btn"), - clearAllBtn: document.getElementById("clear-all-btn"), - generateBtn: document.getElementById("generate-btn"), - copyPasswordBtn: document.getElementById("copy-btn"), - copyOutputBtn: document.getElementById("copy-output-btn"), - toggleSwitch: document.getElementById("operation-toggle"), - copyShareBtn: document.getElementById("copy-share-btn"), - shareLink: document.getElementById("share-link") - }; - - if (validateElements(elements)) { - setupElementListeners(elements); - } -} - -function validateElements(elements) { - return elements.encryptionType && elements.inputText && elements.form && - elements.removeFileBtn && elements.clearAllBtn && elements.generateBtn && - elements.copyPasswordBtn && elements.toggleSwitch; -} - -function setupElementListeners(elements) { - elements.encryptionType.addEventListener("change", toggleEncryptionOptions); - elements.inputText.addEventListener("input", handleInputChange); - elements.form.addEventListener("submit", handleSubmit); - elements.removeFileBtn.addEventListener("click", removeFile); - elements.clearAllBtn.addEventListener("click", clearAll); - elements.generateBtn.addEventListener("click", generateRandomPassword); - elements.copyPasswordBtn.addEventListener("click", () => copyToClipboard("generated-password", "password-copy-feedback")); - elements.copyOutputBtn?.addEventListener("click", () => copyToClipboard("output-text", "output-copy-feedback")); - elements.toggleSwitch.addEventListener("change", () => { - console.log("Mode:", elements.toggleSwitch.checked ? "Decrypt" : "Encrypt"); - }); - - const fileInput = document.getElementById("file-input"); - if (fileInput) { - fileInput.addEventListener("change", () => { - const removeBtn = document.getElementById("remove-file-btn"); - if (removeBtn) { - removeBtn.style.display = fileInput.files.length > 0 ? "inline-block" : "none"; - } - }); - } - - setupShareLinkListeners(elements); -} - -function setupShareLinkListeners(elements) { - if (elements.copyShareBtn && elements.shareLink) { - elements.copyShareBtn.addEventListener("click", () => { - const linkText = elements.shareLink.textContent.trim(); - navigator.clipboard.writeText(linkText).then(() => { - const feedback = document.getElementById("shared-link-feedback"); - if (feedback) { - feedback.style.display = "block"; - feedback.classList.add("show"); - setTimeout(() => { - feedback.classList.remove("show"); - setTimeout(() => { - feedback.style.display = "none"; - }, 300); - }, 3000); - } - }); - }); - } -} - -function toggleEncryptionOptions() { - const type = document.getElementById("encryption-type").value.trim().toLowerCase(); - const passwordInputWrapper = document.getElementById("password-input"); - const fileSection = document.querySelector("#encoding-section #file-section"); - const isAdvanced = type.includes("advanced"); - - if (passwordInputWrapper) { - passwordInputWrapper.classList.toggle("hidden", !isAdvanced); - } - - if (fileSection) { - fileSection.classList.toggle("hidden", !isAdvanced); - } - - updateToggleLabels(); - toggleInputMode(); -} - -function updateToggleLabels() { - const type = document.getElementById("encryption-type")?.value; - const leftLabel = document.getElementById("toggle-left-label"); - const rightLabel = document.getElementById("toggle-right-label"); - - if (!type || !leftLabel || !rightLabel) return; - - const isAdvanced = type.toLowerCase().includes("advanced"); - leftLabel.textContent = isAdvanced ? "Encrypt" : "Encode"; - rightLabel.textContent = isAdvanced ? "Decrypt" : "Decode"; -} - -function toggleInputMode() { - const fileInput = document.getElementById("file-input"); - const textValue = document.getElementById("input-text")?.value.trim(); - const isAdvanced = document.getElementById("encryption-type")?.value === "advanced"; - - const textSection = document.getElementById("text-section"); - const fileSection = document.getElementById("file-section"); - const removeBtn = document.getElementById("remove-file-btn"); - - if (!fileInput || !textSection || !fileSection || !removeBtn) return; - - const fileSelected = fileInput.files.length > 0; - - textSection.style.display = fileSelected ? "none" : "flex"; - fileSection.style.display = (isAdvanced && !textValue) ? "flex" : "none"; - removeBtn.style.display = fileSelected ? "inline-block" : "none"; -} - -async function handleSubmit(event) { - event.preventDefault(); - - const encryptionType = document.getElementById("encryption-type")?.value; - const password = document.getElementById("password")?.value; - const fileInput = document.getElementById("file-input"); - const isDecrypt = document.getElementById("operation-toggle").checked; - const operation = isDecrypt ? "decrypt" : "encrypt"; - - if (!encryptionType || !fileInput) return; - - if (encryptionType === "advanced" && !password) { - return alert("Password is required for advanced encryption."); - } - - if (fileInput.files.length > 0) { - return (operation === "encrypt") - ? encryptFile(fileInput, password) - : decryptFile(fileInput, password); - } - - await handleTextOperation(encryptionType, operation, password); -} - -async function handleTextOperation(encryptionType, operation, password) { - const payload = { - "encryption-type": encryptionType, - operation: operation, - message: document.getElementById("input-text")?.value, - password: password - }; - - try { - const response = await fetch("/", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload) - }); - - const data = await response.json(); - - const outputField = document.getElementById("output-text"); - if (outputField) { - outputField.value = data.result || "[Error] No response received."; - } - } catch (err) { - alert("Error processing request: " + err.message); - } -} - -function removeFile() { - const fileInput = document.getElementById("file-input"); - if (fileInput) fileInput.value = ""; - const removeBtn = document.getElementById("remove-file-btn"); - if (removeBtn) removeBtn.style.display = 'none'; - toggleInputMode(); -} - -function generateRandomPassword() { - const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}|;:,.<>?/~"; - const length = 30; - const password = Array.from({ length }, () => - charset.charAt(Math.floor(Math.random() * charset.length)) - ).join(""); - const passwordField = document.getElementById("generated-password"); - if (passwordField) { - passwordField.value = password; - checkForPacman(); - } -} - -function copyToClipboard(elementId, feedbackId) { - const el = document.getElementById(elementId); - const feedback = document.getElementById(feedbackId); - - if (!el || !el.value) return; - - const textarea = document.createElement('textarea'); - textarea.value = el.value; - textarea.style.position = 'fixed'; - textarea.style.opacity = '0'; - document.body.appendChild(textarea); - - textarea.select(); - textarea.setSelectionRange(0, 99999); - - try { - navigator.clipboard.writeText(el.value).then(() => { - showFeedback(feedback); - }).catch(() => { - document.execCommand('copy'); - showFeedback(feedback); - }); - } catch (err) { - document.execCommand('copy'); - showFeedback(feedback); - } - - document.body.removeChild(textarea); -} - -function showFeedback(feedback) { - if (feedback) { - feedback.style.display = "block"; - feedback.classList.add("show"); - setTimeout(() => { - feedback.classList.remove("show"); - setTimeout(() => { - feedback.style.display = "none"; - }, 300); - }, 3000); - } -} - -function clearAll() { - const fields = ["input-text", "output-text", "file-input", "password"]; - fields.forEach(id => { - const el = document.getElementById(id); - if (el) el.value = ""; - }); - removeFile(); - toggleInputMode(); - document.getElementById("pacman-section")?.style.setProperty("display", "none"); - document.getElementById("encoding-section")?.style.setProperty("display", "block"); -} - -function handleInputChange() { - toggleInputMode(); - checkForPacman(); -} - -function checkForPacman() { - const val = document.getElementById("input-text").value.trim().toLowerCase(); - const pacSection = document.getElementById("pacman-section"); - const encSection = document.getElementById("encoding-section"); - - if (val.includes("pacman") && pacSection.style.display !== "block") { - pacSection.style.display = "block"; - encSection.style.display = "none"; - window.startPacman(); - } else if (pacSection.style.display === "block" && !val.includes("pacman")) { - window.exitGame(); - } -} - -function copyShareLink() { - const linkEl = document.getElementById("share-link"); - const feedback = document.getElementById("shared-link-feedback"); - - if (!linkEl) return; - - const linkText = linkEl.href || linkEl.textContent.trim(); - - if (navigator.clipboard && window.isSecureContext) { - navigator.clipboard.writeText(linkText).then(() => { - showCopyFeedback(feedback); - }).catch(() => { - fallbackCopy(linkText, feedback); - }); - } else { - fallbackCopy(linkText, feedback); - } -} - -function fallbackCopy(text, feedbackEl) { - const tempInput = document.createElement("input"); - tempInput.value = text; - document.body.appendChild(tempInput); - tempInput.select(); - - try { - document.execCommand("copy"); - showCopyFeedback(feedbackEl); - } catch (err) { - alert("Copy failed. Please copy manually."); - } - - document.body.removeChild(tempInput); -} - -function showCopyFeedback(feedbackEl) { - if (!feedbackEl) return; - feedbackEl.style.display = "block"; - feedbackEl.classList.add("show"); - - setTimeout(() => { - feedbackEl.classList.remove("show"); - setTimeout(() => { - feedbackEl.style.display = "none"; - }, 300); - }, 3000); -} - -function startPacman() { } -function exitGame() { } \ No newline at end of file