From 265dff3329d5c5d4ce2b68ac167409ce03e20b46 Mon Sep 17 00:00:00 2001 From: Tyler <68524461+TySP-Dev@users.noreply.github.com> Date: Sun, 27 Apr 2025 23:22:56 -1000 Subject: [PATCH] Version .3 Some changes to UI, file encryption, the pacman game and the readme. --- README.md | 173 +++++++++++++++++++++++++++--------- app.py | 38 ++++++-- requirements.txt | 9 +- start_dev.bat | 5 ++ start_dev.sh | 4 + start_prod.bat | 5 ++ start_prod.sh | 4 + static/audio/chomp.mp3 | Bin 0 -> 31764 bytes static/css/styles.css | 107 +++++++++++++---------- static/js/script.js | 193 ++++++++++++++++++----------------------- templates/403.html | 42 +++++++++ templates/404.html | 42 +++++++++ templates/500.html | 42 +++++++++ templates/index.html | 56 +++++++----- 14 files changed, 492 insertions(+), 228 deletions(-) create mode 100644 start_dev.bat create mode 100644 start_dev.sh create mode 100644 start_prod.bat create mode 100644 start_prod.sh create mode 100644 static/audio/chomp.mp3 create mode 100644 templates/403.html create mode 100644 templates/404.html create mode 100644 templates/500.html diff --git a/README.md b/README.md index e297b29..7ce1f36 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,172 @@ # PacCrypt WebApp -**PacCrypt** is a web-based application designed to provide secure encoding, encryption, and password generation. It allows users to easily encrypt and decrypt text and files, with both basic and advanced encryption options. It also features a password generator and a simple Pac-Man game as an Easter egg! +**PacCrypt** is a web-based platform that allows you to securely encrypt/decrypt text and files, generate passwords, and even enjoy a hidden Pac-Man game! +Built using Python (Flask), JavaScript, and AES-GCM encryption. -## Features +Official Website: [paccrypt.unnaturalll.dev](http://paccrypt.unnaturalll.dev) -- **Basic and Advanced Encryption**: Choose between simple encryption (Caesar Cipher) or more secure AES-GCM encryption. -- **File Encryption/Decryption**: Encrypt or decrypt files with a password. -- **Password Generator**: Generate secure random passwords with customizable length and complexity. -- **Pac-Man Game**: A fun Easter egg! Play a Pac-Man game when you type "pacman" in the text area. -- **Copy to Clipboard**: Copy generated passwords or encrypted results with one click. -- **Responsive Design**: Fully responsive web design that works across different screen sizes. +--- -## Installation +## ✨ Features -### Prerequisites +- 🔒 **Basic and Advanced Encryption** (Text and Files) +- 🔑 **Password Generator** +- đŸ„šī¸ **Pac-Man Easter Egg** (Type `pacman` to unlock!) +- 📱 **Responsive Design** (Mobile Friendly) +- ⚡ **One-Click Start Scripts** (Dev and Production modes) +- 🎨 **Modern Animated UI** (Dark Mode + Green Neon Theme) + +--- + +## 👨‍đŸ’ģ Installation + +### 📋 Prerequisites - **Python 3.7+** -- **Nginx** (for reverse proxy and SSL configuration for hosting) +- **Flask 3+** +- **Cryptography 42+** +- **Waitress 2.1+** +- **Nginx** (Recommended for production) -Official PacCrypt website: paccrypt.unnaturalll.dev +--- -### Steps to Set Up Locally: +### ⚡ Quick Setup 1. Clone the repository: + + ```bash git clone https://github.com/TySP-Dev/PacCrypt.git - cd paccrypt-webapp + cd paccrypt-webapp-final + ``` 2. Create and activate a virtual environment: - python3 -m venv venv - source venv/bin/activate # On Windows, use `venv\Scripts\activate` -3. Install the required Python dependencies: + ```bash + python -m venv venv + source venv/bin/activate # Windows: venv\Scripts\activate + ``` + +3. Install required Python packages: + + ```bash pip install -r requirements.txt + ``` -4. Run the Flask app: - python app.py +4. Start the app: -5. Open http://127.0.0.1:5000 to access the app locally. + **Windows**: + ```bash + start_dev.bat # For Development + start_prod.bat # For Production + ``` -## Usage + **Linux / Mac**: + ```bash + chmod +x start_dev.sh start_prod.sh + ./start_dev.sh # For Development + ./start_prod.sh # For Production + ``` -### Encryption and Decryption +5. Access the app at: + [http://127.0.0.1:5000](http://127.0.0.1:5000) -#### For text encryption/decryption: +--- --- Select the encryption type (Basic or Advanced). +## 🚀 Usage Guide --- Choose whether to Encrypt or Decrypt. +### 🔒 Text Encryption/Decryption --- Enter text in the Input Text area. +- Select **Encryption Type** (Basic or Advanced) +- Enter text +- Provide password (Advanced only) +- Choose **Encrypt** or **Decrypt** +- Click **Submit** --- Enter a password (if using advanced encryption). +### 📁 File Encryption/Decryption --- Click submit. +- Select **Advanced** encryption +- Upload a file +- Provide password +- Choose **Encrypt** or **Decrypt** +- Click **Submit** -#### For file encryption/decryption: +### 🔑 Password Generator --- Select encryption type **Advanced.** +- Click **Generate** to create a secure password +- Click **Copy** to save it to clipboard --- Choose whether to Encrypt or Decrypt. +### 🎮 Pac-Man Easter Egg --- Upload a file. +- Type **`pacman`** into the input box to unlock the hidden Pac-Man game! --- Enter a password for encryption/decryption. +--- --- Click submit. +## đŸ›Ąī¸ Hosting with Nginx (optional) -### Password Generation: +Recommended for secure public deployment. -Click the Generate button to create a random password, then use the Copy button to copy it to your clipboard. +Example minimal Nginx config: -### Pac-Man Game (Easter Egg): +```nginx +server { + listen 80; + server_name yourdomain.com; -Type the word "pacman" in the input box to unlock the Pac-Man game! + location / { + proxy_pass http://127.0.0.1:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` -### Contributing: +> Tip: Set up SSL with Let's Encrypt for HTTPS security! 🔐 -Feel free to open an issue or submit a pull request for improvements, bug fixes, or new features! +--- -### License +## 📂 Project Structure -This project is open source and available under the MIT License. +``` +paccrypt-webapp-final/ +├── app.py +├── requirements.txt +├── templates/ +│ ├── index.html +│ ├── 404.html +│ └── 403.html +│ └── 500.html +├── static/ +│ ├── css/ +│ │ └── styles.css +│ ├── js/ +│ │ └── script.js +│ ├── img/ +│ │ └── PacCrypt.png +│ └── audio/ +│ └── chomp.mp3 +├── start_dev.bat +├── start_prod.bat +├── start_dev.sh +├── start_prod.sh +├── README.md +``` + +--- + +## 🤝 Contributing + +Contributions are welcome! + +- Add new features +- Fix bugs +- Improve performance +- Expand the Pac-Man Easter Egg 🎮 + +--- + +## 📄 License + +This project is licensed under the **MIT License**. + +--- \ No newline at end of file diff --git a/app.py b/app.py index a14fbb5..b91ccf4 100644 --- a/app.py +++ b/app.py @@ -1,15 +1,17 @@ +## DEV DEV DEV + +import os from flask import Flask, render_template, request, jsonify import html -import os import base64 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.hashes import SHA256 from cryptography.hazmat.primitives.ciphers.aead import AESGCM -from waitress import serve app = Flask(__name__) -# Basic Encoder/Decoder +# ====== Your App Code ====== + ALPHABET = list('abcdefghijklmnopqrstuvwxyz') def simple_encode(text: str) -> str: @@ -24,7 +26,6 @@ def simple_decode(text: str) -> str: for c in text.lower() ) -# Advanced Encrypt/Decrypt using AES-GCM def derive_key(password: str, salt: bytes) -> bytes: kdf = PBKDF2HMAC( algorithm=SHA256(), @@ -56,7 +57,6 @@ def advanced_decrypt(token_b64: str, password: str) -> str: except Exception: return "[Error] Invalid password or corrupted data!" -# Combined Route for Page & AJAX @app.route("/", methods=["GET", "POST"]) def index(): if request.method == 'POST': @@ -83,6 +83,30 @@ def index(): encryption_type="advanced" ) +# ====== Smart Server Startup ====== + +@app.errorhandler(404) +def page_not_found(e): + return render_template('404.html'), 404 + +@app.errorhandler(500) +def server_error(e): + return render_template('500.html'), 500 + +@app.errorhandler(403) +def forbidden(e): + return render_template('403.html'), 403 + if __name__ == "__main__": - # Use Waitress to serve the app in production - serve(app, host="0.0.0.0", port=5000) + PRODUCTION = os.getenv("PRODUCTION", "false").lower() == "true" + + if PRODUCTION: + from waitress import serve + print("[INFO] Running in PRODUCTION mode with Waitress.") + serve(app, host="0.0.0.0", port=5000) + else: + print("[INFO] Running in DEVELOPMENT mode with Flask server.") + app.run(debug=True, host="0.0.0.0", port=5000) + + +## DEV DEV DEV diff --git a/requirements.txt b/requirements.txt index 1d89f7f..f1d587b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,8 @@ ### **requirements.txt** -Flask==2.1.2 -cryptography==3.4.8 -nginx==1.21.0 # Only needed for Nginx integration, not installed via pip \ No newline at end of file +flask==3.0.3 +cryptography==42.0.5 +waitress==2.1.2 + +# nginx - Only needed for Nginx integration, not installed via pip +# Run pip install -r requirements.txt \ No newline at end of file diff --git a/start_dev.bat b/start_dev.bat new file mode 100644 index 0000000..8214467 --- /dev/null +++ b/start_dev.bat @@ -0,0 +1,5 @@ +@echo off +echo Starting PacCrypt in DEVELOPMENT mode... +set PRODUCTION=false +python app.py +pause diff --git a/start_dev.sh b/start_dev.sh new file mode 100644 index 0000000..6fe5464 --- /dev/null +++ b/start_dev.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Starting PacCrypt in DEVELOPMENT mode..." +export PRODUCTION=false +python3 app.py diff --git a/start_prod.bat b/start_prod.bat new file mode 100644 index 0000000..2902d46 --- /dev/null +++ b/start_prod.bat @@ -0,0 +1,5 @@ +@echo off +echo Starting PacCrypt in PRODUCTION mode... +set PRODUCTION=true +python app.py +pause diff --git a/start_prod.sh b/start_prod.sh new file mode 100644 index 0000000..50b9bac --- /dev/null +++ b/start_prod.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Starting PacCrypt in PRODUCTION mode..." +export PRODUCTION=true +python3 app.py \ No newline at end of file diff --git a/static/audio/chomp.mp3 b/static/audio/chomp.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f55a94917cfc1535a21050ab588d13c799051725 GIT binary patch literal 31764 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 literal 0 HcmV?d00001 diff --git a/static/css/styles.css b/static/css/styles.css index 5e32031..9b36441 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -9,23 +9,25 @@ body { font-family: 'Poppins', sans-serif; background-color: #121212; - color: #f0f0f0; + color: #00ff99; display: flex; flex-direction: column; min-height: 100vh; - justify-content: center; /* Vertically center content */ - align-items: center; /* Horizontally center content */ + justify-content: center; + align-items: center; padding: 20px; } /* ===== Header ===== */ header { text-align: center; - padding: 30px 20px; + padding: 25px; background-color: #1c1c1c; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5); + border-radius: 12px; + box-shadow: 0 0 15px rgba(0, 255, 153, 0.4); width: 100%; max-width: 800px; + margin-bottom: 30px; } header h1 { @@ -35,7 +37,7 @@ header { } header p { - font-size: 1.1em; + font-size: 1.2em; color: #00ff99; } @@ -49,13 +51,12 @@ main { max-width: 800px; padding: 20px; gap: 30px; - justify-content: center; } /* ===== Section Card Styling ===== */ .card { background-color: #1e1e1e; - padding: 20px 25px; + padding: 25px; width: 100%; max-width: 800px; border-radius: 12px; @@ -68,7 +69,7 @@ main { display: flex; flex-direction: column; align-items: center; - gap: 15px; + gap: 20px; width: 100%; } @@ -92,7 +93,8 @@ textarea { resize: none; } -input[type="password"] { +input[type="password"], +#password { min-height: 50px; } @@ -123,7 +125,7 @@ select:focus { box-shadow: 0 0 8px rgba(0, 255, 153, 0.8); } -/* ===== Match input and output textarea sizes ===== */ +/* ===== Match input and output sizes ===== */ #input-text, #output-text { width: 80%; @@ -138,8 +140,8 @@ select:focus { display: flex; flex-wrap: wrap; justify-content: center; - gap: 8px; - margin-top: 8px; + gap: 15px; + margin-top: 15px; width: 100%; } @@ -152,8 +154,8 @@ button { font-size: 1em; cursor: pointer; transition: 0.3s; - width: 100%; /* Makes buttons stretch to fill container */ - max-width: 200px; /* Restricts button width */ + width: 100%; + max-width: 200px; } button:hover { @@ -161,7 +163,7 @@ button { color: #121212; } -/* ===== Toggle Buttons ===== */ +/* ===== Toggle Buttons (Encode/Decode, Encrypt/Decrypt) ===== */ .radio-group { display: flex; justify-content: center; @@ -174,13 +176,15 @@ button { display: inline-flex; align-items: center; justify-content: center; - padding: 8px 18px; + padding: 1px 1px; border: 2px solid #00ff99; border-radius: 8px; background-color: #2c2f33; color: #00ff99; cursor: pointer; transition: 0.3s; + position: relative; + box-shadow: none; } .radio-button:hover { @@ -188,21 +192,27 @@ button { color: #121212; } + /* Hide the actual radio input */ .radio-button input { display: none; } + /* When selected, make the ENTIRE BUTTON glow */ .radio-button input:checked + span { - background-color: #00ff99; - color: #121212; - padding: 8px 18px; + background-color: #2c2f33; + color: #00ff99; + box-shadow: 0 0 15px rgba(0, 255, 153, 0.7); border-radius: 8px; - display: inline-block; + padding: 8px 18px; + display: inline-flex; + align-items: center; + justify-content: center; } + /* ===== Remove File Button ===== */ #remove-file-btn { - display: none; /* only shows when a file is selected */ + display: none; margin-top: 8px; padding: 8px 16px; border: 2px solid #ff5555; @@ -221,14 +231,19 @@ button { /* ===== Toast Notifications ===== */ .toast { visibility: hidden; - min-width: 250px; + width: 80%; + max-width: 500px; + min-height: 50px; background-color: #333; color: #00ff99; text-align: center; - border-radius: 6px; - padding: 10px; - margin-top: 8px; - font-size: 0.9em; + border-radius: 8px; + padding: 14px; + margin: 10px auto 0 auto; + font-size: 1em; + display: flex; + align-items: center; + justify-content: center; animation: fadein 0.5s, fadeout 0.5s 2.5s; } @@ -274,11 +289,14 @@ button { /* ===== Footer ===== */ footer { text-align: center; - padding: 18px; + padding: 25px; background-color: #1c1c1c; color: #00ff99; - margin-top: auto; + border-radius: 12px; + box-shadow: 0 0 15px rgba(0, 255, 153, 0.4); + margin-top: 30px; width: 100%; + max-width: 800px; } footer a { @@ -290,22 +308,17 @@ footer { color: #ff0066; } -/* ===== Password Input Field ===== */ -#password-input { - display: flex; /* Password input is visible by default */ - margin-top: 15px; - flex-direction: column; - gap: 10px; - width: 100%; - max-width: 500px; -} - - #password-input input { - padding: 12px; - font-size: 1em; - border: 2px solid #00ff99; - border-radius: 8px; - background-color: #2c2f33; - color: #00ff99; - width: 100%; /* Ensure the password field takes full width */ +/* ===== Responsive Tweaks ===== */ +@media (max-width: 600px) { + input, + textarea, + select, + #input-text, + #output-text, + #password-field, + #password, + #file-password { + width: 100%; + max-width: 90%; } +} diff --git a/static/js/script.js b/static/js/script.js index 2785547..3628aa1 100644 --- a/static/js/script.js +++ b/static/js/script.js @@ -1,41 +1,31 @@ -// ===== AES Encryption ===== +// ===== AES Advanced Encryption ===== async function encryptAdvanced(message, password) { - // Create a random salt for key derivation const salt = crypto.getRandomValues(new Uint8Array(16)); - - // Derive a key from the password using PBKDF2 and the salt const key = await deriveKey(password, salt); - - // Create a random initialization vector (IV) const iv = crypto.getRandomValues(new Uint8Array(12)); - // Encode the message as a Uint8Array const encoder = new TextEncoder(); const encodedMessage = encoder.encode(message); - // Encrypt the message using AES-GCM const encryptedMessage = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, encodedMessage ); - // Combine salt, IV, and encrypted message const encryptedArray = new Uint8Array(salt.length + iv.length + encryptedMessage.byteLength); encryptedArray.set(salt); encryptedArray.set(iv, salt.length); encryptedArray.set(new Uint8Array(encryptedMessage), salt.length + iv.length); - // Convert the result to base64 to send to the server - return btoa(String.fromCharCode.apply(null, encryptedArray)); + return btoa(String.fromCharCode(...encryptedArray)); } -// Derive a key from the password using PBKDF2 async function deriveKey(password, salt) { const encoder = new TextEncoder(); const passwordBuffer = encoder.encode(password); - const key = await crypto.subtle.importKey( + const keyMaterial = await crypto.subtle.importKey( 'raw', passwordBuffer, { name: 'PBKDF2' }, @@ -47,37 +37,31 @@ async function deriveKey(password, salt) { { name: 'PBKDF2', salt: salt, - iterations: 100000, + iterations: 200000, hash: 'SHA-256', }, - key, + keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ); } -// ===== AES Decryption ===== async function decryptAdvanced(encryptedData, password) { - // Decode the base64-encoded encrypted data - const encryptedArray = new Uint8Array(atob(encryptedData).split("").map(char => char.charCodeAt(0))); + const encryptedArray = new Uint8Array(atob(encryptedData).split("").map(c => c.charCodeAt(0))); - // Extract salt, IV, and encrypted message from the encrypted data const salt = encryptedArray.slice(0, 16); const iv = encryptedArray.slice(16, 28); const encryptedMessage = encryptedArray.slice(28); - // Derive the key from the password and salt const key = await deriveKey(password, salt); - // Decrypt the message using AES-GCM const decryptedMessage = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, key, encryptedMessage ); - // Decode the decrypted message to text const decoder = new TextDecoder(); return decoder.decode(decryptedMessage); } @@ -85,81 +69,54 @@ async function decryptAdvanced(encryptedData, password) { // ===== UI Toggles ===== function toggleEncryptionOptions() { const type = document.getElementById("encryption-type").value; - const pwdContainer = document.getElementById("password-input"); - pwdContainer.style.display = (type === 'advanced') ? 'flex' : 'none'; + document.getElementById("password-input").style.display = (type === 'advanced') ? 'flex' : 'none'; + if (type === 'basic') removeFile(); + toggleInputMode(); - document.getElementById("encrypt-label").textContent = - (type === 'basic') ? "Encode" : "Encrypt"; - document.getElementById("decrypt-label").textContent = - (type === 'basic') ? "Decode" : "Decrypt"; + document.getElementById("encrypt-label").textContent = (type === 'basic') ? "Encode" : "Encrypt"; + document.getElementById("decrypt-label").textContent = (type === 'basic') ? "Decode" : "Decrypt"; } -// ===== Remove File Button ===== function removeFile() { - document.getElementById("file-input").value = ""; // Clear the file input - document.getElementById("remove-file-btn").style.display = 'none'; // Hide the remove file button - toggleInputMode(); // Reapply the input mode logic - document.getElementById("file-password-input").style.display = 'none'; // Hide the file password input + document.getElementById("file-input").value = ""; + document.getElementById("remove-file-btn").style.display = 'none'; + toggleInputMode(); } -// ===== Input vs. File Toggle ===== function toggleInputMode() { const textValue = document.getElementById("input-text").value.trim(); const fileSelected = document.getElementById("file-input").files.length > 0; const isAdvanced = document.getElementById("encryption-type").value === 'advanced'; - // Show/hide text area based on file selection - document.getElementById("text-section").style.display = - fileSelected ? 'none' : 'flex'; + document.getElementById("text-section").style.display = fileSelected ? 'none' : 'flex'; + document.getElementById("file-section").style.display = (isAdvanced && !textValue) ? 'flex' : 'none'; + document.getElementById("remove-file-btn").style.display = fileSelected ? 'inline-block' : 'none'; - // Show/hide file input section when in advanced mode and no text input is given - document.getElementById("file-section").style.display = - (isAdvanced && !textValue) ? 'flex' : 'none'; - - // Show/hide the remove file button - document.getElementById("remove-file-btn").style.display = - fileSelected ? 'inline-block' : 'none'; - - // ALWAYS show the password input in advanced mode if (isAdvanced) { document.getElementById("password-input").style.display = 'flex'; - } else { - document.getElementById("password-input").style.display = 'none'; - } - - // Show the dedicated password input for file encryption if a file is selected - if (fileSelected) { - document.getElementById("file-password-input").style.display = 'flex'; // Show password input for files - } else { - document.getElementById("file-password-input").style.display = 'none'; // Hide when no file is selected } } -// ===== Validate and Submit Form ===== +// ===== Form Submission ===== async function handleSubmit(event) { event.preventDefault(); - // If the encryption type is advanced, ensure password is provided const password = document.getElementById("password").value; - const filePassword = document.getElementById("file-password") ? document.getElementById("file-password").value : ''; const encryptionType = document.getElementById("encryption-type").value; - if (encryptionType === 'advanced' && !password && !filePassword) { + if (encryptionType === 'advanced' && !password) { alert("Password is required for advanced encryption."); return; } - // Prepare the form data const payload = { "encryption-type": encryptionType, operation: document.querySelector('input[name="operation"]:checked').value, message: document.getElementById("input-text").value, - password: password, - "file-password": filePassword + password: password }; - // Handle file upload encryption/decryption const fileInput = document.getElementById("file-input"); if (fileInput.files.length > 0) { const op = document.querySelector('input[name="operation"]:checked').value; @@ -168,7 +125,6 @@ async function handleSubmit(event) { return; } - // Handle text encryption/decryption try { const resp = await fetch("/", { method: "POST", @@ -185,30 +141,35 @@ async function handleSubmit(event) { // ===== File Encryption / Decryption ===== function encryptFile() { const f = document.getElementById("file-input"); - const pwd = document.getElementById("file-password").value; + const pwd = document.getElementById("password").value; if (!pwd) return alert("Please enter a password!"); if (!f.files.length) return alert("Please select a file!"); + const reader = new FileReader(); reader.onload = async (e) => { - const raw = e.target.result; - let encryptedMessage = await encryptAdvanced(raw, pwd); - downloadFile(encryptedMessage, f.files[0].name + ".enc"); + const raw = new Uint8Array(e.target.result); + const base64Raw = btoa(String.fromCharCode(...raw)); + const encrypted = await encryptAdvanced(base64Raw, pwd); + downloadFile(encrypted, f.files[0].name + ".enc"); }; - reader.readAsText(f.files[0]); + reader.readAsArrayBuffer(f.files[0]); } function decryptFile() { const f = document.getElementById("file-input"); - const pwd = document.getElementById("file-password").value; + const pwd = document.getElementById("password").value; if (!pwd) return alert("Please enter a password!"); if (!f.files.length) return alert("Please select a file!"); + const reader = new FileReader(); reader.onload = async (e) => { try { - const enc = e.target.result; - const decryptedMessage = await decryptAdvanced(enc, pwd); - downloadFile(decryptedMessage, f.files[0].name.replace(/\.enc$/, '')); - } catch { + const encryptedText = e.target.result; + const decryptedBase64 = await decryptAdvanced(encryptedText, pwd); + const byteString = atob(decryptedBase64); + const byteArray = new Uint8Array([...byteString].map(c => c.charCodeAt(0))); + downloadFileBinary(byteArray, f.files[0].name.replace(/\.enc$/, '')); + } catch (err) { alert("Decryption failed: wrong password or corrupted file."); } }; @@ -225,6 +186,16 @@ function downloadFile(content, filename) { URL.revokeObjectURL(url); } +function downloadFileBinary(byteArray, filename) { + const blob = new Blob([byteArray], { type: "application/octet-stream" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); +} + // ===== Password Generator ===== function generateRandomPassword() { const length = 30; @@ -240,13 +211,12 @@ function generateRandomPassword() { function copyToClipboard(elementId, toastId) { const copyText = document.getElementById(elementId); copyText.select(); - copyText.setSelectionRange(0, 99999); // For mobile devices + copyText.setSelectionRange(0, 99999); document.execCommand("copy"); - // Show toast notification const toast = document.getElementById(toastId); toast.classList.add("show"); - setTimeout(() => toast.classList.remove("show"), 2000); // Remove toast after 2 seconds + setTimeout(() => toast.classList.remove("show"), 2000); } // ===== Pacman Easter Egg ===== @@ -277,6 +247,28 @@ function resetGame() { startPacman(); } +// ===== Clear All ===== +function clearAll() { + document.getElementById("input-text").value = ""; + document.getElementById("output-text").value = ""; + document.getElementById("file-input").value = ""; + document.getElementById("password").value = ""; + + document.getElementById("pacman-section").style.display = "none"; + document.getElementById("encoding-section").style.display = "block"; + + removeFile(); + toggleInputMode(); +} + +// ===== Initialize ===== +document.addEventListener("DOMContentLoaded", () => { + toggleEncryptionOptions(); + toggleInputMode(); + document.getElementById("input-text").addEventListener("input", checkForPacman); +}); + + // ===== Pacman Game Variables & Logic ===== let canvas, ctx, pacman, enemy, walls, dots, score; let pacmanSpeed = 40, enemySpeed = 20, cellSize = 40, dotSize = 5; @@ -441,18 +433,27 @@ function drawChar(ch,color) { } function eatDots() { - dots = dots.filter(d=>{ - const dx = d.c*cellSize+cellSize/2, dy = d.r*cellSize+cellSize/2; - if (Math.abs(pacman.x-dx) { + const dx = d.c * cellSize + cellSize / 2; + const dy = d.r * cellSize + cellSize / 2; + if (Math.abs(pacman.x - dx) < pacman.size && Math.abs(pacman.y - dy) < pacman.size) { score++; - return false; + if (chompSound) { + chompSound.currentTime = 0; // Reset sound + chompSound.volume = 0.4; + chompSound.play(); + } + return false; // Remove dot } return true; }); - ctx.fillStyle="white"; - dots.forEach(d=>{ + + ctx.fillStyle = "white"; + dots.forEach(d => { ctx.beginPath(); - ctx.arc(d.c*cellSize+cellSize/2, d.r*cellSize+cellSize/2, dotSize,0,Math.PI*2); + ctx.arc(d.c * cellSize + cellSize / 2, d.r * cellSize + cellSize / 2, dotSize, 0, Math.PI * 2); ctx.fill(); }); } @@ -471,26 +472,4 @@ function checkGameOver() { ctx.fillText("Game Over!", canvas.width/2, canvas.height/2); clearInterval(gameInterval); } -} - -// ===== Clear All Functionality ===== -function clearAll() { - document.getElementById("input-text").value = ""; - document.getElementById("output-text").value = ""; - document.getElementById("file-input").value = ""; - document.getElementById("password").value = ""; - document.getElementById("file-password").value = ""; - - document.getElementById("pacman-section").style.display = "none"; - document.getElementById("encoding-section").style.display = "block"; - - removeFile(); - toggleInputMode(); -} - -// ===== Initialize ===== -document.addEventListener("DOMContentLoaded", () => { - toggleEncryptionOptions(); - toggleInputMode(); - document.getElementById("input-text").addEventListener("input", checkForPacman); -}); +} \ No newline at end of file diff --git a/templates/403.html b/templates/403.html new file mode 100644 index 0000000..7b5ca2b --- /dev/null +++ b/templates/403.html @@ -0,0 +1,42 @@ + + + + + + 403 - PacCrypt + + + + + + +
+

PacCrypt

+

Secure Encoding, Encryption and Password Generation

+
+ +
+
+

403 - Forbidden

+

+ Looks like this area is locked behind a secret ghost door! đŸ›Ąī¸đŸ‘ģ +

+ + +
+
+ +
+

© 2025 UnNaturalll-Dev. All rights reserved.

+ + GitHub Logo + +
+ + + diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..2eb2f41 --- /dev/null +++ b/templates/404.html @@ -0,0 +1,42 @@ + + + + + + 404 - PacCrypt + + + + + + +
+

PacCrypt

+

Secure Encoding, Encryption and Password Generation

+
+ +
+ +
+

404 - Page Not Found

+

Oops! The page you're looking for doesn't exist.

+ + +
+ +
+ +
+

© 2025 UnNaturalll-Dev. All rights reserved.

+ + GitHub Logo + +
+ + + diff --git a/templates/500.html b/templates/500.html new file mode 100644 index 0000000..a2dc67c --- /dev/null +++ b/templates/500.html @@ -0,0 +1,42 @@ + + + + + + 500 - PacCrypt + + + + + + +
+

PacCrypt

+

Secure Encoding, Encryption and Password Generation

+
+ +
+
+

500 - Server Error

+

+ Uh oh! The ghosts chomped the server. đŸ§Ÿâ€â™‚ī¸ +

+ + +
+
+ +
+

© 2025 UnNaturalll-Dev. All rights reserved.

+ + GitHub Logo + +
+ + + diff --git a/templates/index.html b/templates/index.html index 88a2754..b733739 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,29 +4,31 @@ PacCrypt - - + + + + + + +

PacCrypt

Secure Encoding, Encryption and Password Generation

+ +

Password Generator

- +
@@ -35,6 +37,7 @@
+ +

Text Encoder / Decoder & File Encryption

+ + +
+
- -
- -
+
+ +
+ +
+ + -
+
+ +