Actually working swipe controls for pacman game
This commit is contained in:
@@ -1,177 +1,177 @@
|
|||||||
# PacCrypt WebApp
|
# PacCrypt WebApp
|
||||||
|
|
||||||
**PacCrypt** is a secure, feature-rich web app for encrypting and decrypting text and files — built with Flask, JavaScript, and AES-GCM encryption.
|
**PacCrypt** is a secure, feature-rich web app for encrypting and decrypting text and files — built with Flask, JavaScript, and AES-GCM encryption.
|
||||||
Now with an admin control panel, GitHub updater, and a built-in Pac-Man easter egg! 🕹️
|
Now with an admin control panel, GitHub updater, and a built-in Pac-Man easter egg! 🕹️
|
||||||
|
|
||||||
Live demo: [paccrypt.unnaturalll.dev](http://paccrypt.unnaturalll.dev)
|
Live demo: [paccrypt.unnaturalll.dev](http://paccrypt.unnaturalll.dev)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- 🔒 Basic and Advanced Encryption for Text & Files
|
- 🔒 Basic and Advanced Encryption for Text & Files
|
||||||
- 📁 Secure File Uploads with Pickup Passwords
|
- 📁 Secure File Uploads with Pickup Passwords
|
||||||
- 🔑 Random Password Generator
|
- 🔑 Random Password Generator
|
||||||
- 🎮 Hidden Pac-Man Game — type `pacman` to play
|
- 🎮 Hidden Pac-Man Game — type `pacman` to play
|
||||||
- 🧠 Smart UI: Auto-switches input sections, toggles encryption labels
|
- 🧠 Smart UI: Auto-switches input sections, toggles encryption labels
|
||||||
- 📋 Clipboard Copy Feedback with styled status boxes
|
- 📋 Clipboard Copy Feedback with styled status boxes
|
||||||
- 🧾 Admin Panel:
|
- 🧾 Admin Panel:
|
||||||
- Site map with live route list
|
- Site map with live route list
|
||||||
- Server restart & GitHub update button
|
- Server restart & GitHub update button
|
||||||
- Secure admin credential management
|
- Secure admin credential management
|
||||||
- Server logs & upload cleanup
|
- Server logs & upload cleanup
|
||||||
- 🧩 System Settings Page for upload config
|
- 🧩 System Settings Page for upload config
|
||||||
- 📜 Custom 403, 404, and 500 Error Pages
|
- 📜 Custom 403, 404, and 500 Error Pages
|
||||||
- 🤖 robots.txt and /sitemap for crawlers
|
- 🤖 robots.txt and /sitemap for crawlers
|
||||||
- 📱 Mobile-Responsive UI
|
- 📱 Mobile-Responsive UI
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 👨💻 Installation
|
## 👨💻 Installation
|
||||||
|
|
||||||
### 📋 Prerequisites
|
### 📋 Prerequisites
|
||||||
|
|
||||||
- Python 3.7+
|
- Python 3.7+
|
||||||
- Flask 3+
|
- Flask 3+
|
||||||
- Cryptography 42+
|
- Cryptography 42+
|
||||||
- Waitress 2.1+
|
- Waitress 2.1+
|
||||||
- Git (for update feature)
|
- Git (for update feature)
|
||||||
- Nginx (recommended)
|
- Nginx (recommended)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### ⚡ Quick Setup
|
### ⚡ Quick Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/TySP-Dev/PacCrypt.git
|
git clone https://github.com/TySP-Dev/PacCrypt.git
|
||||||
cd paccrypt-webapp-final
|
cd paccrypt-webapp-final
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
source venv/bin/activate # or venv\Scripts\activate on Windows
|
source venv/bin/activate # or venv\Scripts\activate on Windows
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
Then run:
|
Then run:
|
||||||
|
|
||||||
- Development Mode:
|
- Development Mode:
|
||||||
```bash
|
```bash
|
||||||
./start_dev.sh # or start_dev.bat
|
./start_dev.sh # or start_dev.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
- Production Mode:
|
- Production Mode:
|
||||||
```bash
|
```bash
|
||||||
./start_prod.sh # or start_prod.bat
|
./start_prod.sh # or start_prod.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
Visit [http://127.0.0.1:5000](http://127.0.0.1:5000)
|
Visit [http://127.0.0.1:5000](http://127.0.0.1:5000)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧭 Navigation & Usage
|
## 🧭 Navigation & Usage
|
||||||
|
|
||||||
### 🔐 Encrypt & Decrypt
|
### 🔐 Encrypt & Decrypt
|
||||||
|
|
||||||
- Choose between Basic Cipher or Advanced AES
|
- Choose between Basic Cipher or Advanced AES
|
||||||
- Type your message or upload a file
|
- Type your message or upload a file
|
||||||
- Enter password (if AES)
|
- Enter password (if AES)
|
||||||
- Select mode using toggle (Encrypt/Decrypt)
|
- Select mode using toggle (Encrypt/Decrypt)
|
||||||
- Hit Execute
|
- Hit Execute
|
||||||
|
|
||||||
### 📤 Share Files
|
### 📤 Share Files
|
||||||
|
|
||||||
- Upload a file with two passwords:
|
- Upload a file with two passwords:
|
||||||
- Encryption password
|
- Encryption password
|
||||||
- Pickup password
|
- Pickup password
|
||||||
- Get a shareable URL and click 📋 Copy Link
|
- Get a shareable URL and click 📋 Copy Link
|
||||||
|
|
||||||
### 🔑 Generate Passwords
|
### 🔑 Generate Passwords
|
||||||
|
|
||||||
- Click Generate
|
- Click Generate
|
||||||
- Then hit 📋 Copy
|
- Then hit 📋 Copy
|
||||||
|
|
||||||
### 🎮 Pac-Man Game
|
### 🎮 Pac-Man Game
|
||||||
|
|
||||||
- Type `pacman` in the input box
|
- Type `pacman` in the input box
|
||||||
- Game appears with Restart/Exit controls
|
- Game appears with Restart/Exit controls
|
||||||
- Classic arrow key controls 🕹️
|
- Classic arrow key controls 🕹️
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ Admin Panel
|
## 🛠️ Admin Panel
|
||||||
|
|
||||||
Visit `/adminpage` after setting up credentials at `/admin-setup`.
|
Visit `/adminpage` after setting up credentials at `/admin-setup`.
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
- 🔄 Restart server
|
- 🔄 Restart server
|
||||||
- 🔃 Update from GitHub (git pull)
|
- 🔃 Update from GitHub (git pull)
|
||||||
- 🧽 Clear uploads
|
- 🧽 Clear uploads
|
||||||
- 🔐 Change admin password
|
- 🔐 Change admin password
|
||||||
- 📝 View logs
|
- 📝 View logs
|
||||||
- ⚙️ Adjust upload settings
|
- ⚙️ Adjust upload settings
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛡️ Deployment Tips
|
## 🛡️ Deployment Tips
|
||||||
|
|
||||||
Minimal Nginx config:
|
Minimal Nginx config:
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name yourdomain.com;
|
server_name yourdomain.com;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://127.0.0.1:5000;
|
proxy_pass http://127.0.0.1:5000;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Use Let's Encrypt to add SSL/TLS support.
|
Use Let's Encrypt to add SSL/TLS support.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🗂️ Project Structure
|
## 🗂️ Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
paccrypt-webapp-final/
|
paccrypt-webapp-final/
|
||||||
├── app.py
|
├── app.py
|
||||||
├── requirements.txt
|
├── requirements.txt
|
||||||
├── README.md
|
├── README.md
|
||||||
├── templates/
|
├── templates/
|
||||||
│ ├── index.html
|
│ ├── index.html
|
||||||
│ ├── 404.html
|
│ ├── 404.html
|
||||||
│ └── 403.html
|
│ └── 403.html
|
||||||
│ └── 500.html
|
│ └── 500.html
|
||||||
│ └── admin.html
|
│ └── admin.html
|
||||||
│ └── admin_login.html
|
│ └── admin_login.html
|
||||||
│ └── admin_settings.html
|
│ └── admin_settings.html
|
||||||
│ └── admin_setup.html
|
│ └── admin_setup.html
|
||||||
│ └── pickup.html
|
│ └── pickup.html
|
||||||
├── static/
|
├── static/
|
||||||
│ ├── css/
|
│ ├── css/
|
||||||
│ │ └── styles.css
|
│ │ └── styles.css
|
||||||
│ ├── js/
|
│ ├── js/
|
||||||
│ │ └── ui.js
|
│ │ └── ui.js
|
||||||
│ │ └── pacman.js
|
│ │ └── pacman.js
|
||||||
│ │ └── main.js
|
│ │ └── main.js
|
||||||
│ │ └── fileops.js
|
│ │ └── fileops.js
|
||||||
│ │ └── encryption.js
|
│ │ └── encryption.js
|
||||||
│ ├── img/
|
│ ├── img/
|
||||||
│ │ └── PacCrypt.png
|
│ │ └── PacCrypt.png
|
||||||
│ │ └── Github_logo.png
|
│ │ └── Github_logo.png
|
||||||
│ │ └── sitemap.png
|
│ │ └── sitemap.png
|
||||||
│ └── audio/
|
│ └── audio/
|
||||||
│ └── chomp.mp3
|
│ └── chomp.mp3
|
||||||
├── start_dev.bat
|
├── start_dev.bat
|
||||||
├── start_prod.bat
|
├── start_prod.bat
|
||||||
├── start_dev.sh
|
├── start_dev.sh
|
||||||
├── start_prod.sh
|
├── start_prod.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
MIT © [TySP-Dev](https://github.com/TySP-Dev)
|
MIT © [TySP-Dev](https://github.com/TySP-Dev)
|
||||||
|
|||||||
+8
-8
@@ -1,9 +1,9 @@
|
|||||||
### **requirements.txt**
|
### **requirements.txt**
|
||||||
|
|
||||||
flask==3.0.3
|
flask==3.0.3
|
||||||
cryptography==42.0.5
|
cryptography==42.0.5
|
||||||
waitress==2.1.2
|
waitress==2.1.2
|
||||||
werkzeug==3.0.1
|
werkzeug==3.0.1
|
||||||
|
|
||||||
# nginx - Only needed for Nginx integration, not installed via pip
|
# nginx - Only needed for Nginx integration, not installed via pip
|
||||||
# Run pip install -r requirements.txt
|
# Run pip install -r requirements.txt
|
||||||
+104
-104
@@ -1,104 +1,104 @@
|
|||||||
/**
|
/**
|
||||||
* Encryption module.
|
* Encryption module.
|
||||||
* Handles cryptographic operations using Web Crypto API.
|
* Handles cryptographic operations using Web Crypto API.
|
||||||
* Implements AES-GCM encryption with PBKDF2 key derivation.
|
* Implements AES-GCM encryption with PBKDF2 key derivation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ===== Constants =====
|
// ===== Constants =====
|
||||||
const SALT_LENGTH = 16;
|
const SALT_LENGTH = 16;
|
||||||
const IV_LENGTH = 12;
|
const IV_LENGTH = 12;
|
||||||
const PBKDF2_ITERATIONS = 200_000;
|
const PBKDF2_ITERATIONS = 200_000;
|
||||||
const KEY_LENGTH = 256;
|
const KEY_LENGTH = 256;
|
||||||
|
|
||||||
// ===== Key Derivation =====
|
// ===== Key Derivation =====
|
||||||
/**
|
/**
|
||||||
* Derives an AES-GCM key from a password using PBKDF2.
|
* Derives an AES-GCM key from a password using PBKDF2.
|
||||||
* @param {string} password - User-supplied password.
|
* @param {string} password - User-supplied password.
|
||||||
* @param {Uint8Array} salt - Randomly generated salt.
|
* @param {Uint8Array} salt - Randomly generated salt.
|
||||||
* @returns {Promise<CryptoKey>} - Derived cryptographic key.
|
* @returns {Promise<CryptoKey>} - Derived cryptographic key.
|
||||||
*/
|
*/
|
||||||
export async function deriveKey(password, salt) {
|
export async function deriveKey(password, salt) {
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const keyMaterial = await crypto.subtle.importKey(
|
const keyMaterial = await crypto.subtle.importKey(
|
||||||
'raw',
|
'raw',
|
||||||
encoder.encode(password),
|
encoder.encode(password),
|
||||||
{ name: 'PBKDF2' },
|
{ name: 'PBKDF2' },
|
||||||
false,
|
false,
|
||||||
['deriveKey']
|
['deriveKey']
|
||||||
);
|
);
|
||||||
|
|
||||||
return crypto.subtle.deriveKey(
|
return crypto.subtle.deriveKey(
|
||||||
{
|
{
|
||||||
name: 'PBKDF2',
|
name: 'PBKDF2',
|
||||||
salt,
|
salt,
|
||||||
iterations: PBKDF2_ITERATIONS,
|
iterations: PBKDF2_ITERATIONS,
|
||||||
hash: 'SHA-256'
|
hash: 'SHA-256'
|
||||||
},
|
},
|
||||||
keyMaterial,
|
keyMaterial,
|
||||||
{ name: 'AES-GCM', length: KEY_LENGTH },
|
{ name: 'AES-GCM', length: KEY_LENGTH },
|
||||||
false,
|
false,
|
||||||
['encrypt', 'decrypt']
|
['encrypt', 'decrypt']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Encryption =====
|
// ===== Encryption =====
|
||||||
/**
|
/**
|
||||||
* Encrypts a message using AES-GCM with a derived key.
|
* Encrypts a message using AES-GCM with a derived key.
|
||||||
* @param {string} message - Plaintext message to encrypt.
|
* @param {string} message - Plaintext message to encrypt.
|
||||||
* @param {string} password - User password for key derivation.
|
* @param {string} password - User password for key derivation.
|
||||||
* @returns {Promise<string>} - Base64-encoded encrypted string.
|
* @returns {Promise<string>} - Base64-encoded encrypted string.
|
||||||
*/
|
*/
|
||||||
export async function encryptAdvanced(message, password) {
|
export async function encryptAdvanced(message, password) {
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
|
const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
|
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
|
||||||
const key = await deriveKey(password, salt);
|
const key = await deriveKey(password, salt);
|
||||||
const encoded = encoder.encode(message);
|
const encoded = encoder.encode(message);
|
||||||
|
|
||||||
const ciphertext = await crypto.subtle.encrypt(
|
const ciphertext = await crypto.subtle.encrypt(
|
||||||
{ name: 'AES-GCM', iv },
|
{ name: 'AES-GCM', iv },
|
||||||
key,
|
key,
|
||||||
encoded
|
encoded
|
||||||
);
|
);
|
||||||
|
|
||||||
const output = new Uint8Array(salt.length + iv.length + ciphertext.byteLength);
|
const output = new Uint8Array(salt.length + iv.length + ciphertext.byteLength);
|
||||||
output.set(salt);
|
output.set(salt);
|
||||||
output.set(iv, salt.length);
|
output.set(iv, salt.length);
|
||||||
output.set(new Uint8Array(ciphertext), salt.length + iv.length);
|
output.set(new Uint8Array(ciphertext), salt.length + iv.length);
|
||||||
|
|
||||||
return btoa(String.fromCharCode(...output));
|
return btoa(String.fromCharCode(...output));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Decryption =====
|
// ===== Decryption =====
|
||||||
/**
|
/**
|
||||||
* Decrypts an AES-GCM encrypted string.
|
* Decrypts an AES-GCM encrypted string.
|
||||||
* @param {string} encryptedData - Base64-encoded ciphertext.
|
* @param {string} encryptedData - Base64-encoded ciphertext.
|
||||||
* @param {string} password - Password used to derive the decryption key.
|
* @param {string} password - Password used to derive the decryption key.
|
||||||
* @returns {Promise<string>} - Decrypted plaintext.
|
* @returns {Promise<string>} - Decrypted plaintext.
|
||||||
*/
|
*/
|
||||||
export async function decryptAdvanced(encryptedData, password) {
|
export async function decryptAdvanced(encryptedData, password) {
|
||||||
const encrypted = new Uint8Array(
|
const encrypted = new Uint8Array(
|
||||||
atob(encryptedData).split('').map(c => c.charCodeAt(0))
|
atob(encryptedData).split('').map(c => c.charCodeAt(0))
|
||||||
);
|
);
|
||||||
|
|
||||||
const salt = encrypted.slice(0, SALT_LENGTH);
|
const salt = encrypted.slice(0, SALT_LENGTH);
|
||||||
const iv = encrypted.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
const iv = encrypted.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
||||||
const ciphertext = encrypted.slice(SALT_LENGTH + IV_LENGTH);
|
const ciphertext = encrypted.slice(SALT_LENGTH + IV_LENGTH);
|
||||||
const key = await deriveKey(password, salt);
|
const key = await deriveKey(password, salt);
|
||||||
|
|
||||||
const decrypted = await crypto.subtle.decrypt(
|
const decrypted = await crypto.subtle.decrypt(
|
||||||
{ name: 'AES-GCM', iv },
|
{ name: 'AES-GCM', iv },
|
||||||
key,
|
key,
|
||||||
ciphertext
|
ciphertext
|
||||||
);
|
);
|
||||||
|
|
||||||
return new TextDecoder().decode(decrypted);
|
return new TextDecoder().decode(decrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Module Initialization =====
|
// ===== Module Initialization =====
|
||||||
/**
|
/**
|
||||||
* Initializes the encryption module and logs its status.
|
* Initializes the encryption module and logs its status.
|
||||||
*/
|
*/
|
||||||
export function setupEncryption() {
|
export function setupEncryption() {
|
||||||
console.log('[Encryption] Module loaded');
|
console.log('[Encryption] Module loaded');
|
||||||
}
|
}
|
||||||
|
|||||||
+119
-119
@@ -1,119 +1,119 @@
|
|||||||
/**
|
/**
|
||||||
* File operations module.
|
* File operations module.
|
||||||
* Handles file encryption and decryption operations.
|
* Handles file encryption and decryption operations.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ===== Constants =====
|
// ===== Constants =====
|
||||||
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
|
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
|
||||||
|
|
||||||
// ===== Public Interface =====
|
// ===== Public Interface =====
|
||||||
export async function encryptFile(fileInput, password) {
|
export async function encryptFile(fileInput, password) {
|
||||||
const file = fileInput.files[0];
|
const file = fileInput.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const encryptedChunks = await processFile(file, password, true);
|
const encryptedChunks = await processFile(file, password, true);
|
||||||
downloadEncryptedFile(encryptedChunks, file.name);
|
downloadEncryptedFile(encryptedChunks, file.name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Error encrypting file: " + error.message);
|
alert("Error encrypting file: " + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function decryptFile(fileInput, password) {
|
export async function decryptFile(fileInput, password) {
|
||||||
const file = fileInput.files[0];
|
const file = fileInput.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const decryptedChunks = await processFile(file, password, false);
|
const decryptedChunks = await processFile(file, password, false);
|
||||||
downloadDecryptedFile(decryptedChunks, file.name);
|
downloadDecryptedFile(decryptedChunks, file.name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Error decrypting file: " + error.message);
|
alert("Error decrypting file: " + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== File Processing =====
|
// ===== File Processing =====
|
||||||
async function processFile(file, password, isEncrypt) {
|
async function processFile(file, password, isEncrypt) {
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
|
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
|
||||||
let processedChunks = 0;
|
let processedChunks = 0;
|
||||||
|
|
||||||
for (let start = 0; start < file.size; start += CHUNK_SIZE) {
|
for (let start = 0; start < file.size; start += CHUNK_SIZE) {
|
||||||
const chunk = file.slice(start, start + CHUNK_SIZE);
|
const chunk = file.slice(start, start + CHUNK_SIZE);
|
||||||
const arrayBuffer = await chunk.arrayBuffer();
|
const arrayBuffer = await chunk.arrayBuffer();
|
||||||
const uint8Array = new Uint8Array(arrayBuffer);
|
const uint8Array = new Uint8Array(arrayBuffer);
|
||||||
|
|
||||||
const processedChunk = await processChunk(uint8Array, password, isEncrypt);
|
const processedChunk = await processChunk(uint8Array, password, isEncrypt);
|
||||||
chunks.push(processedChunk);
|
chunks.push(processedChunk);
|
||||||
|
|
||||||
processedChunks++;
|
processedChunks++;
|
||||||
updateProgress(processedChunks, totalChunks);
|
updateProgress(processedChunks, totalChunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
return chunks;
|
return chunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processChunk(data, password, isEncrypt) {
|
async function processChunk(data, password, isEncrypt) {
|
||||||
const payload = {
|
const payload = {
|
||||||
"encryption-type": "advanced",
|
"encryption-type": "advanced",
|
||||||
operation: isEncrypt ? "encrypt" : "decrypt",
|
operation: isEncrypt ? "encrypt" : "decrypt",
|
||||||
message: Array.from(data).join(','),
|
message: Array.from(data).join(','),
|
||||||
password: password
|
password: password
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch("/", {
|
const response = await fetch("/", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
return new Uint8Array(result.result.split(',').map(Number));
|
return new Uint8Array(result.result.split(',').map(Number));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== File Download =====
|
// ===== File Download =====
|
||||||
function downloadEncryptedFile(chunks, originalName) {
|
function downloadEncryptedFile(chunks, originalName) {
|
||||||
const blob = new Blob(chunks, { type: 'application/octet-stream' });
|
const blob = new Blob(chunks, { type: 'application/octet-stream' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = originalName + '.encrypted';
|
a.download = originalName + '.encrypted';
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadDecryptedFile(chunks, originalName) {
|
function downloadDecryptedFile(chunks, originalName) {
|
||||||
const blob = new Blob(chunks, { type: 'application/octet-stream' });
|
const blob = new Blob(chunks, { type: 'application/octet-stream' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = originalName.replace('.encrypted', '');
|
a.download = originalName.replace('.encrypted', '');
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Progress Tracking =====
|
// ===== Progress Tracking =====
|
||||||
function updateProgress(processed, total) {
|
function updateProgress(processed, total) {
|
||||||
const progressBar = document.getElementById("file-progress");
|
const progressBar = document.getElementById("file-progress");
|
||||||
const progressText = document.getElementById("file-progress-text");
|
const progressText = document.getElementById("file-progress-text");
|
||||||
|
|
||||||
if (progressBar && progressText) {
|
if (progressBar && progressText) {
|
||||||
const percent = Math.round((processed / total) * 100);
|
const percent = Math.round((processed / total) * 100);
|
||||||
progressBar.style.width = percent + "%";
|
progressBar.style.width = percent + "%";
|
||||||
progressText.textContent = `Processing: ${percent}%`;
|
progressText.textContent = `Processing: ${percent}%`;
|
||||||
|
|
||||||
if (processed === total) {
|
if (processed === total) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
progressBar.style.width = "0%";
|
progressBar.style.width = "0%";
|
||||||
progressText.textContent = "";
|
progressText.textContent = "";
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ export function setupGame() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function startPacman() {
|
export function startPacman() {
|
||||||
|
// Scroll to the Pacman section
|
||||||
|
const pacmanSection = document.getElementById("pacman-section");
|
||||||
|
if (pacmanSection) {
|
||||||
|
pacmanSection.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize game state
|
||||||
initializeGame();
|
initializeGame();
|
||||||
setupGameLoop();
|
setupGameLoop();
|
||||||
}
|
}
|
||||||
@@ -28,6 +35,11 @@ export function startPacman() {
|
|||||||
export function stopPacman() {
|
export function stopPacman() {
|
||||||
clearInterval(gameInterval);
|
clearInterval(gameInterval);
|
||||||
if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height);
|
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() {
|
export function resetGame() {
|
||||||
@@ -68,6 +80,55 @@ function initializeGame() {
|
|||||||
|
|
||||||
pacman.dx = pacman.dy = 0;
|
pacman.dx = pacman.dy = 0;
|
||||||
document.addEventListener("keydown", movePacman);
|
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() {
|
function setupGameLoop() {
|
||||||
@@ -283,6 +344,20 @@ function eatDots() {
|
|||||||
return true;
|
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";
|
ctx.fillStyle = "white";
|
||||||
dots.forEach(d => {
|
dots.forEach(d => {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@@ -294,3 +369,8 @@ function eatDots() {
|
|||||||
// ===== Global Functions =====
|
// ===== Global Functions =====
|
||||||
window.resetGame = resetGame;
|
window.resetGame = resetGame;
|
||||||
window.exitGame = exitGame;
|
window.exitGame = exitGame;
|
||||||
|
|
||||||
|
// Add scroll prevention function
|
||||||
|
function preventScroll(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|||||||
+301
-272
@@ -1,272 +1,301 @@
|
|||||||
/**
|
/**
|
||||||
* UI management module.
|
* UI management module.
|
||||||
* Handles user interface interactions and form handling.
|
* Handles user interface interactions and form handling.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { encryptFile, decryptFile } from './fileops.js';
|
import { encryptFile, decryptFile } from './fileops.js';
|
||||||
|
|
||||||
// ===== UI Initialization =====
|
// ===== UI Initialization =====
|
||||||
export function setupUI() {
|
export function setupUI() {
|
||||||
// Set initial state of remove button to hidden
|
// Set initial state of remove button to hidden
|
||||||
const removeBtn = document.getElementById("remove-file-btn");
|
const removeBtn = document.getElementById("remove-file-btn");
|
||||||
if (removeBtn) {
|
if (removeBtn) {
|
||||||
removeBtn.style.display = "none";
|
removeBtn.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeEventListeners();
|
initializeEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Event Listeners =====
|
// ===== Event Listeners =====
|
||||||
function initializeEventListeners() {
|
function initializeEventListeners() {
|
||||||
const elements = {
|
const elements = {
|
||||||
encryptionType: document.getElementById("encryption-type"),
|
encryptionType: document.getElementById("encryption-type"),
|
||||||
inputText: document.getElementById("input-text"),
|
inputText: document.getElementById("input-text"),
|
||||||
form: document.getElementById("crypto-form"),
|
form: document.getElementById("crypto-form"),
|
||||||
removeFileBtn: document.getElementById("remove-file-btn"),
|
removeFileBtn: document.getElementById("remove-file-btn"),
|
||||||
clearAllBtn: document.getElementById("clear-all-btn"),
|
clearAllBtn: document.getElementById("clear-all-btn"),
|
||||||
generateBtn: document.getElementById("generate-btn"),
|
generateBtn: document.getElementById("generate-btn"),
|
||||||
copyPasswordBtn: document.getElementById("copy-btn"),
|
copyPasswordBtn: document.getElementById("copy-btn"),
|
||||||
copyOutputBtn: document.getElementById("copy-output-btn"),
|
copyOutputBtn: document.getElementById("copy-output-btn"),
|
||||||
toggleSwitch: document.getElementById("operation-toggle"),
|
toggleSwitch: document.getElementById("operation-toggle"),
|
||||||
copyShareBtn: document.getElementById("copy-share-btn"),
|
copyShareBtn: document.getElementById("copy-share-btn"),
|
||||||
shareLink: document.getElementById("share-link")
|
shareLink: document.getElementById("share-link")
|
||||||
};
|
};
|
||||||
|
|
||||||
if (validateElements(elements)) {
|
if (validateElements(elements)) {
|
||||||
setupElementListeners(elements);
|
setupElementListeners(elements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateElements(elements) {
|
function validateElements(elements) {
|
||||||
return elements.encryptionType && elements.inputText && elements.form &&
|
return elements.encryptionType && elements.inputText && elements.form &&
|
||||||
elements.removeFileBtn && elements.clearAllBtn && elements.generateBtn &&
|
elements.removeFileBtn && elements.clearAllBtn && elements.generateBtn &&
|
||||||
elements.copyPasswordBtn && elements.toggleSwitch;
|
elements.copyPasswordBtn && elements.toggleSwitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupElementListeners(elements) {
|
function setupElementListeners(elements) {
|
||||||
elements.encryptionType.addEventListener("change", toggleEncryptionOptions);
|
elements.encryptionType.addEventListener("change", toggleEncryptionOptions);
|
||||||
elements.inputText.addEventListener("input", handleInputChange);
|
elements.inputText.addEventListener("input", handleInputChange);
|
||||||
elements.form.addEventListener("submit", handleSubmit);
|
elements.form.addEventListener("submit", handleSubmit);
|
||||||
elements.removeFileBtn.addEventListener("click", removeFile);
|
elements.removeFileBtn.addEventListener("click", removeFile);
|
||||||
elements.clearAllBtn.addEventListener("click", clearAll);
|
elements.clearAllBtn.addEventListener("click", clearAll);
|
||||||
elements.generateBtn.addEventListener("click", generateRandomPassword);
|
elements.generateBtn.addEventListener("click", generateRandomPassword);
|
||||||
elements.copyPasswordBtn.addEventListener("click", () => copyToClipboard("generated-password", "password-copy-feedback"));
|
elements.copyPasswordBtn.addEventListener("click", () => copyToClipboard("generated-password", "password-copy-feedback"));
|
||||||
elements.copyOutputBtn?.addEventListener("click", () => copyToClipboard("output-text", "output-copy-feedback"));
|
elements.copyOutputBtn?.addEventListener("click", () => copyToClipboard("output-text", "output-copy-feedback"));
|
||||||
elements.toggleSwitch.addEventListener("change", updateToggleLabels);
|
elements.toggleSwitch.addEventListener("change", updateToggleLabels);
|
||||||
|
|
||||||
// Add file input change listener
|
// Add file input change listener
|
||||||
const fileInput = document.getElementById("file-input");
|
const fileInput = document.getElementById("file-input");
|
||||||
if (fileInput) {
|
if (fileInput) {
|
||||||
fileInput.addEventListener("change", () => {
|
fileInput.addEventListener("change", () => {
|
||||||
const removeBtn = document.getElementById("remove-file-btn");
|
const removeBtn = document.getElementById("remove-file-btn");
|
||||||
if (removeBtn) {
|
if (removeBtn) {
|
||||||
removeBtn.style.display = fileInput.files.length > 0 ? "inline-block" : "none";
|
removeBtn.style.display = fileInput.files.length > 0 ? "inline-block" : "none";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setupShareLinkListeners(elements);
|
setupShareLinkListeners(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupShareLinkListeners(elements) {
|
function setupShareLinkListeners(elements) {
|
||||||
if (elements.copyShareBtn && elements.shareLink) {
|
if (elements.copyShareBtn && elements.shareLink) {
|
||||||
elements.copyShareBtn.addEventListener("click", () => {
|
elements.copyShareBtn.addEventListener("click", () => {
|
||||||
const linkText = elements.shareLink.textContent.trim();
|
const linkText = elements.shareLink.textContent.trim();
|
||||||
navigator.clipboard.writeText(linkText).then(() => {
|
navigator.clipboard.writeText(linkText).then(() => {
|
||||||
const feedback = document.getElementById("shared-link-feedback");
|
const feedback = document.getElementById("shared-link-feedback");
|
||||||
if (feedback) {
|
if (feedback) {
|
||||||
feedback.style.display = "block";
|
feedback.style.display = "block";
|
||||||
feedback.classList.add("show");
|
feedback.classList.add("show");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
feedback.classList.remove("show");
|
feedback.classList.remove("show");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
feedback.style.display = "none";
|
feedback.style.display = "none";
|
||||||
}, 300);
|
}, 300);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== UI State Management =====
|
// ===== UI State Management =====
|
||||||
function toggleEncryptionOptions() {
|
function toggleEncryptionOptions() {
|
||||||
const type = document.getElementById("encryption-type").value.trim().toLowerCase();
|
const type = document.getElementById("encryption-type").value.trim().toLowerCase();
|
||||||
const passwordInputWrapper = document.getElementById("password-input");
|
const passwordInputWrapper = document.getElementById("password-input");
|
||||||
const fileSection = document.querySelector("#encoding-section #file-section");
|
const fileSection = document.querySelector("#encoding-section #file-section");
|
||||||
const isAdvanced = type.includes("advanced");
|
const isAdvanced = type.includes("advanced");
|
||||||
|
|
||||||
if (passwordInputWrapper) {
|
if (passwordInputWrapper) {
|
||||||
if (isAdvanced) {
|
if (isAdvanced) {
|
||||||
passwordInputWrapper.classList.remove("hidden");
|
passwordInputWrapper.classList.remove("hidden");
|
||||||
} else {
|
} else {
|
||||||
passwordInputWrapper.classList.add("hidden");
|
passwordInputWrapper.classList.add("hidden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileSection) {
|
if (fileSection) {
|
||||||
if (isAdvanced) {
|
if (isAdvanced) {
|
||||||
fileSection.classList.remove("hidden");
|
fileSection.classList.remove("hidden");
|
||||||
} else {
|
} else {
|
||||||
fileSection.classList.add("hidden");
|
fileSection.classList.add("hidden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateToggleLabels();
|
updateToggleLabels();
|
||||||
toggleInputMode();
|
toggleInputMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateToggleLabels() {
|
function updateToggleLabels() {
|
||||||
const type = document.getElementById("encryption-type")?.value;
|
const type = document.getElementById("encryption-type")?.value;
|
||||||
const leftLabel = document.getElementById("toggle-left-label");
|
const leftLabel = document.getElementById("toggle-left-label");
|
||||||
const rightLabel = document.getElementById("toggle-right-label");
|
const rightLabel = document.getElementById("toggle-right-label");
|
||||||
|
|
||||||
if (!type || !leftLabel || !rightLabel) return;
|
if (!type || !leftLabel || !rightLabel) return;
|
||||||
|
|
||||||
const isAdvanced = type.toLowerCase().includes("advanced");
|
const isAdvanced = type.toLowerCase().includes("advanced");
|
||||||
leftLabel.textContent = isAdvanced ? "Encrypt" : "Encode";
|
leftLabel.textContent = isAdvanced ? "Encrypt" : "Encode";
|
||||||
rightLabel.textContent = isAdvanced ? "Decrypt" : "Decode";
|
rightLabel.textContent = isAdvanced ? "Decrypt" : "Decode";
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleInputMode() {
|
function toggleInputMode() {
|
||||||
const fileInput = document.getElementById("file-input");
|
const fileInput = document.getElementById("file-input");
|
||||||
const textValue = document.getElementById("input-text")?.value.trim();
|
const textValue = document.getElementById("input-text")?.value.trim();
|
||||||
const isAdvanced = document.getElementById("encryption-type")?.value === "advanced";
|
const isAdvanced = document.getElementById("encryption-type")?.value === "advanced";
|
||||||
|
|
||||||
const textSection = document.getElementById("text-section");
|
const textSection = document.getElementById("text-section");
|
||||||
const fileSection = document.getElementById("file-section");
|
const fileSection = document.getElementById("file-section");
|
||||||
const removeBtn = document.getElementById("remove-file-btn");
|
const removeBtn = document.getElementById("remove-file-btn");
|
||||||
|
|
||||||
if (!fileInput || !textSection || !fileSection || !removeBtn) return;
|
if (!fileInput || !textSection || !fileSection || !removeBtn) return;
|
||||||
|
|
||||||
const fileSelected = fileInput.files.length > 0;
|
const fileSelected = fileInput.files.length > 0;
|
||||||
|
|
||||||
textSection.style.display = fileSelected ? "none" : "flex";
|
textSection.style.display = fileSelected ? "none" : "flex";
|
||||||
fileSection.style.display = (isAdvanced && !textValue) ? "flex" : "none";
|
fileSection.style.display = (isAdvanced && !textValue) ? "flex" : "none";
|
||||||
removeBtn.style.display = fileSelected ? "inline-block" : "none";
|
removeBtn.style.display = fileSelected ? "inline-block" : "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Form Handling =====
|
// ===== Form Handling =====
|
||||||
async function handleSubmit(event) {
|
async function handleSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const encryptionType = document.getElementById("encryption-type")?.value;
|
const encryptionType = document.getElementById("encryption-type")?.value;
|
||||||
const password = document.getElementById("password")?.value;
|
const password = document.getElementById("password")?.value;
|
||||||
const fileInput = document.getElementById("file-input");
|
const fileInput = document.getElementById("file-input");
|
||||||
const isDecrypt = document.getElementById("operation-toggle").checked;
|
const isDecrypt = document.getElementById("operation-toggle").checked;
|
||||||
const operation = isDecrypt ? "decrypt" : "encrypt";
|
const operation = isDecrypt ? "decrypt" : "encrypt";
|
||||||
|
|
||||||
if (!encryptionType || !fileInput) return;
|
if (!encryptionType || !fileInput) return;
|
||||||
|
|
||||||
if (encryptionType === "advanced" && !password) {
|
if (encryptionType === "advanced" && !password) {
|
||||||
return alert("Password is required for advanced encryption.");
|
return alert("Password is required for advanced encryption.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileInput.files.length > 0) {
|
if (fileInput.files.length > 0) {
|
||||||
return (operation === "encrypt")
|
return (operation === "encrypt")
|
||||||
? encryptFile(fileInput, password)
|
? encryptFile(fileInput, password)
|
||||||
: decryptFile(fileInput, password);
|
: decryptFile(fileInput, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
await handleTextOperation(encryptionType, operation, password);
|
await handleTextOperation(encryptionType, operation, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTextOperation(encryptionType, operation, password) {
|
async function handleTextOperation(encryptionType, operation, password) {
|
||||||
const payload = {
|
const payload = {
|
||||||
"encryption-type": encryptionType,
|
"encryption-type": encryptionType,
|
||||||
operation: operation,
|
operation: operation,
|
||||||
message: document.getElementById("input-text")?.value,
|
message: document.getElementById("input-text")?.value,
|
||||||
password: password
|
password: password
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/", {
|
const response = await fetch("/", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
document.getElementById("output-text").value = data.result;
|
document.getElementById("output-text").value = data.result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert("Error processing request: " + err.message);
|
alert("Error processing request: " + err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Utility Functions =====
|
// ===== Utility Functions =====
|
||||||
function removeFile() {
|
function removeFile() {
|
||||||
const fileInput = document.getElementById("file-input");
|
const fileInput = document.getElementById("file-input");
|
||||||
if (fileInput) fileInput.value = "";
|
if (fileInput) fileInput.value = "";
|
||||||
const removeBtn = document.getElementById("remove-file-btn");
|
const removeBtn = document.getElementById("remove-file-btn");
|
||||||
if (removeBtn) removeBtn.style.display = 'none';
|
if (removeBtn) removeBtn.style.display = 'none';
|
||||||
toggleInputMode();
|
toggleInputMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateRandomPassword() {
|
function generateRandomPassword() {
|
||||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}|;:,.<>?/~";
|
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}|;:,.<>?/~";
|
||||||
const length = 30;
|
const length = 30;
|
||||||
const password = Array.from({ length }, () =>
|
const password = Array.from({ length }, () =>
|
||||||
charset.charAt(Math.floor(Math.random() * charset.length))
|
charset.charAt(Math.floor(Math.random() * charset.length))
|
||||||
).join("");
|
).join("");
|
||||||
const passwordField = document.getElementById("generated-password");
|
const passwordField = document.getElementById("generated-password");
|
||||||
if (passwordField) {
|
if (passwordField) {
|
||||||
passwordField.value = password;
|
passwordField.value = password;
|
||||||
// Check if we should start Pacman
|
// Check if we should start Pacman
|
||||||
checkForPacman();
|
checkForPacman();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyToClipboard(elementId, feedbackId) {
|
function copyToClipboard(elementId, feedbackId) {
|
||||||
const el = document.getElementById(elementId);
|
const el = document.getElementById(elementId);
|
||||||
const feedback = document.getElementById(feedbackId);
|
const feedback = document.getElementById(feedbackId);
|
||||||
|
|
||||||
if (!el || !el.value) return;
|
if (!el || !el.value) return;
|
||||||
|
|
||||||
navigator.clipboard.writeText(el.value).then(() => {
|
// Create a temporary textarea element
|
||||||
if (feedback) {
|
const textarea = document.createElement('textarea');
|
||||||
feedback.style.display = "block";
|
textarea.value = el.value;
|
||||||
feedback.classList.add("show");
|
textarea.style.position = 'fixed';
|
||||||
setTimeout(() => {
|
textarea.style.opacity = '0';
|
||||||
feedback.classList.remove("show");
|
document.body.appendChild(textarea);
|
||||||
setTimeout(() => {
|
|
||||||
feedback.style.display = "none";
|
// Select and copy the text
|
||||||
}, 300); // Wait for fade-out animation to complete
|
textarea.select();
|
||||||
}, 3000);
|
textarea.setSelectionRange(0, 99999); // For mobile devices
|
||||||
}
|
|
||||||
});
|
try {
|
||||||
}
|
// Try using the modern clipboard API first
|
||||||
|
navigator.clipboard.writeText(el.value).then(() => {
|
||||||
function clearAll() {
|
showFeedback(feedback);
|
||||||
const fields = ["input-text", "output-text", "file-input", "password"];
|
}).catch(() => {
|
||||||
fields.forEach(id => {
|
// Fallback to execCommand for older browsers
|
||||||
const el = document.getElementById(id);
|
document.execCommand('copy');
|
||||||
if (el) el.value = "";
|
showFeedback(feedback);
|
||||||
});
|
});
|
||||||
removeFile();
|
} catch (err) {
|
||||||
toggleInputMode();
|
// Final fallback
|
||||||
document.getElementById("pacman-section")?.style.setProperty("display", "none");
|
document.execCommand('copy');
|
||||||
document.getElementById("encoding-section")?.style.setProperty("display", "block");
|
showFeedback(feedback);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInputChange() {
|
// Clean up
|
||||||
toggleInputMode();
|
document.body.removeChild(textarea);
|
||||||
checkForPacman();
|
}
|
||||||
}
|
|
||||||
|
function showFeedback(feedback) {
|
||||||
function checkForPacman() {
|
if (feedback) {
|
||||||
const val = document.getElementById("input-text").value.trim().toLowerCase();
|
feedback.style.display = "block";
|
||||||
const pacSection = document.getElementById("pacman-section");
|
feedback.classList.add("show");
|
||||||
const encSection = document.getElementById("encoding-section");
|
setTimeout(() => {
|
||||||
|
feedback.classList.remove("show");
|
||||||
if (val.includes("pacman") && pacSection.style.display !== "block") {
|
setTimeout(() => {
|
||||||
pacSection.style.display = "block";
|
feedback.style.display = "none";
|
||||||
encSection.style.display = "none";
|
}, 300);
|
||||||
window.startPacman();
|
}, 3000);
|
||||||
} else if (pacSection.style.display === "block" && !val.includes("pacman")) {
|
}
|
||||||
window.exitGame();
|
}
|
||||||
}
|
|
||||||
}
|
function clearAll() {
|
||||||
|
const fields = ["input-text", "output-text", "file-input", "password"];
|
||||||
function startPacman() { }
|
fields.forEach(id => {
|
||||||
function exitGame() { }
|
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 startPacman() { }
|
||||||
|
function exitGame() { }
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user