Version .3

Some changes to UI, file encryption, the pacman game and the readme.
This commit is contained in:
Tyler
2025-04-27 23:22:56 -10:00
committed by GitHub
parent 9e45c34365
commit 265dff3329
14 changed files with 492 additions and 228 deletions
+131 -42
View File
@@ -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**.
---
+31 -7
View File
@@ -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
+6 -3
View File
@@ -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
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
+5
View File
@@ -0,0 +1,5 @@
@echo off
echo Starting PacCrypt in DEVELOPMENT mode...
set PRODUCTION=false
python app.py
pause
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
echo "Starting PacCrypt in DEVELOPMENT mode..."
export PRODUCTION=false
python3 app.py
+5
View File
@@ -0,0 +1,5 @@
@echo off
echo Starting PacCrypt in PRODUCTION mode...
set PRODUCTION=true
python app.py
pause
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
echo "Starting PacCrypt in PRODUCTION mode..."
export PRODUCTION=true
python3 app.py
Binary file not shown.
+60 -47
View File
@@ -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%;
}
}
+85 -106
View File
@@ -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)<pacman.size && Math.abs(pacman.y-dy)<pacman.size) {
const chompSound = document.getElementById("chomp-sound");
dots = dots.filter(d => {
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();
});
}
@@ -472,25 +473,3 @@ function checkGameOver() {
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);
});
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>403 - PacCrypt</title>
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body class="dark">
<header class="card" style="margin-bottom: 20px;">
<h1>PacCrypt</h1>
<p>Secure Encoding, Encryption and Password Generation</p>
</header>
<main>
<section class="card" style="padding: 50px 30px;">
<h2 style="color: #00ff99; font-size: 2.5em;">403 - Forbidden</h2>
<p style="margin-top: 20px; font-size: 1.2em; color: #cccccc;">
Looks like this area is locked behind a secret ghost door! 🛡️👻
</p>
<div class="button-group" style="margin-top: 30px;">
<a href="{{ url_for('index') }}">
<button type="button">Return Home</button>
</a>
</div>
</section>
</main>
<footer class="card" style="margin-top: 20px;">
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Flogos-world.net%2Fwp-content%2Fuploads%2F2020%2F11%2FGitHub-Logo.png&f=1&nofb=1&ipt=b9d67651e313b2cdbeae8a7ec9320dadb278a21a2e7217810b839c233c04f265"
alt="GitHub Logo" width="100">
</a>
</footer>
</body>
</html>
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 - PacCrypt</title>
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body class="dark">
<header>
<h1>PacCrypt</h1>
<p>Secure Encoding, Encryption and Password Generation</p>
</header>
<main>
<section class="card">
<h2>404 - Page Not Found</h2>
<p>Oops! The page you're looking for doesn't exist.</p>
<div class="button-group" style="margin-top: 20px;">
<a href="{{ url_for('index') }}">
<button type="button">Return Home</button>
</a>
</div>
</section>
</main>
<footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Flogos-world.net%2Fwp-content%2Fuploads%2F2020%2F11%2FGitHub-Logo.png&f=1&nofb=1&ipt=b9d67651e313b2cdbeae8a7ec9320dadb278a21a2e7217810b839c233c04f265"
alt="GitHub Logo" width="100">
</a>
</footer>
</body>
</html>
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>500 - PacCrypt</title>
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body class="dark">
<header class="card" style="margin-bottom: 20px;">
<h1>PacCrypt</h1>
<p>Secure Encoding, Encryption and Password Generation</p>
</header>
<main>
<section class="card" style="padding: 50px 30px;">
<h2 style="color: #00ff99; font-size: 2.5em;">500 - Server Error</h2>
<p style="margin-top: 20px; font-size: 1.2em; color: #cccccc;">
Uh oh! The ghosts chomped the server. 🧟‍♂️
</p>
<div class="button-group" style="margin-top: 30px;">
<a href="{{ url_for('index') }}">
<button type="button">Return Home</button>
</a>
</div>
</section>
</main>
<footer class="card" style="margin-top: 20px;">
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Flogos-world.net%2Fwp-content%2Fuploads%2F2020%2F11%2FGitHub-Logo.png&f=1&nofb=1&ipt=b9d67651e313b2cdbeae8a7ec9320dadb278a21a2e7217810b839c233c04f265"
alt="GitHub Logo" width="100">
</a>
</footer>
</body>
</html>
+34 -22
View File
@@ -4,29 +4,31 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PacCrypt</title>
<!-- Favicon Link -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}">
<!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}" />
<!-- Styles and Scripts -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
<script defer src="{{ url_for('static', filename='js/script.js') }}"></script>
</head>
<body class="dark">
<!-- Header -->
<header>
<h1>PacCrypt</h1>
<p>Secure Encoding, Encryption and Password Generation</p>
</header>
<main>
<!-- Password Generator Section -->
<section id="password-section" class="card">
<h2>Password Generator</h2>
<div class="form-group">
<input
type="text"
id="password-field"
placeholder="Generated password will appear here"
readonly
/>
<input type="text" id="password-field" placeholder="Generated password will appear here" readonly />
<div class="button-group">
<button type="button" onclick="generateRandomPassword()">Generate</button>
<button type="button" onclick="copyToClipboard('password-field', 'password-toast')">Copy</button>
@@ -35,6 +37,7 @@
</div>
</section>
<!-- Pacman Game Section -->
<section id="pacman-section" class="card" style="display: none;">
<div class="pacman-wrapper">
<canvas id="pacmanCanvas" width="800" height="600"></canvas>
@@ -46,15 +49,19 @@
</div>
</section>
<!-- Text Encoder/Decoder & File Encrypt/Decrypt Section -->
<section id="encoding-section" class="card">
<h2>Text Encoder / Decoder & File Encryption</h2>
<form id="main-form" class="form-group" method="POST" onsubmit="handleSubmit(event)">
<!-- Encryption Type Dropdown -->
<label for="encryption-type">Select Encryption Type:</label>
<select id="encryption-type" name="encryption-type" onchange="toggleEncryptionOptions()">
<option value="basic">Basic (Less Secure)</option>
<option value="advanced" selected>Advanced (More Secure)</option>
</select>
<!-- Encrypt / Decrypt Radio Buttons -->
<div id="encryption-options" class="radio-group">
<label class="radio-button">
<input type="radio" name="operation" value="encrypt" id="encrypt-radio" checked />
@@ -66,41 +73,45 @@
</label>
</div>
<!-- Text Area Input -->
<div id="text-section" class="form-group">
<textarea
id="input-text"
name="message"
placeholder="Enter text here..."
oninput="toggleInputMode()"
></textarea>
<div id="password-input">
<input type="password" id="password" name="password" placeholder="Enter Password" />
</div>
<textarea id="input-text" name="message" placeholder="Enter text here..." oninput="toggleInputMode()"></textarea>
</div>
<!-- Password Input (shared for file + text) -->
<div id="password-input" class="form-group">
<input type="password" id="password" name="password" placeholder="Enter Password" />
</div>
<!-- File Upload Section -->
<div id="file-section" class="form-group" style="display: none;">
<input type="file" id="file-input" onchange="toggleInputMode()" />
<button type="button" id="remove-file-btn" onclick="removeFile()">Remove File</button>
</div>
<div id="file-password-input" style="display: none;">
<input type="password" id="file-password" name="file-password" placeholder="Enter Password for File" />
<!-- Submit Button -->
<div class="button-group">
<button type="submit" class="submit-button">Submit</button>
</div>
<button type="button" class="submit-button" onclick="handleSubmit(event)">Submit</button>
</form>
<!-- Output Text Area -->
<div style="height: 20px;"></div>
<textarea id="output-text" readonly placeholder="Result will appear here">{{ result }}</textarea>
<!-- Output Controls -->
<div class="button-group">
<button type="button" onclick="copyToClipboard('output-text', 'output-toast')">Copy Output</button>
<button type="button" onclick="clearAll()">Clear All</button>
</div>
<!-- Output Toast Notification -->
<div id="output-toast" class="toast">Copied to Clipboard!</div>
</section>
</main>
<!-- Footer -->
<footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
@@ -108,5 +119,6 @@
alt="GitHub Logo" width="100" />
</a>
</footer>
</body>
</html>