From 44a2ac4cbd87a73201defaa9252980775201934a Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Mon, 18 Aug 2025 20:00:58 +0200 Subject: [PATCH] Initial commit: Text Corruptor PWA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Features: - Zalgo text generator with adjustable intensity (1-10) - Real-time text corruption as you type - Click-to-copy functionality with visual feedback - Progressive Web App with offline support - Responsive design for mobile and desktop - Dark theme with glitch-inspired aesthetics Technical implementation: - Pure JavaScript implementation (no frameworks) - Service Worker for offline functionality - PWA manifest for installability - Python development server - Comprehensive linting setup (ESLint, Prettier, Black, Pylint) πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .eslintrc.json | 20 +++ .gitignore | 13 ++ .prettierrc | 10 ++ CLAUDE.md | 114 +++++++++++++ app/app.js | 122 ++++++++++++++ app/create-icons.js | 96 +++++++++++ app/generate-icons.html | 156 ++++++++++++++++++ app/icons/icon-128.svg | 5 + app/icons/icon-144.svg | 5 + app/icons/icon-152.svg | 5 + app/icons/icon-192.svg | 5 + app/icons/icon-384.svg | 5 + app/icons/icon-512.svg | 5 + app/icons/icon-72.svg | 5 + app/icons/icon-96.svg | 5 + app/index.html | 66 ++++++++ app/manifest.json | 62 ++++++++ app/styles.css | 344 ++++++++++++++++++++++++++++++++++++++++ app/sw.js | 90 +++++++++++ app/zalgo.js | 267 +++++++++++++++++++++++++++++++ bun.lock | 216 +++++++++++++++++++++++++ package.json | 17 ++ server.py | 39 +++++ 23 files changed, 1672 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 CLAUDE.md create mode 100644 app/app.js create mode 100644 app/create-icons.js create mode 100644 app/generate-icons.html create mode 100644 app/icons/icon-128.svg create mode 100644 app/icons/icon-144.svg create mode 100644 app/icons/icon-152.svg create mode 100644 app/icons/icon-192.svg create mode 100644 app/icons/icon-384.svg create mode 100644 app/icons/icon-512.svg create mode 100644 app/icons/icon-72.svg create mode 100644 app/icons/icon-96.svg create mode 100644 app/index.html create mode 100644 app/manifest.json create mode 100644 app/styles.css create mode 100644 app/sw.js create mode 100644 app/zalgo.js create mode 100644 bun.lock create mode 100644 package.json create mode 100644 server.py diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..83e5cb0 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "browser": true, + "es2021": true, + "serviceworker": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + "indent": ["error", 4], + "linebreak-style": ["error", "unix"], + "quotes": ["error", "single"], + "semi": ["error", "always"], + "no-unused-vars": ["warn"], + "no-console": ["warn", { "allow": ["warn", "error", "log"] }] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f433538 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules/ +*.pyc +__pycache__/ +.DS_Store +*.log +.env +.vscode/ +.idea/ +*.swp +*.swo +*~ +dist/ +build/ \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..afe699d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "printWidth": 100, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..db77d13 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,114 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Text Corruptor is a Progressive Web App (PWA) that generates zalgo text (corrupted/glitched text using Unicode combining characters). The app features real-time text corruption with adjustable intensity and click-to-copy functionality. + +## Project Structure + +``` +zalgo/ +β”œβ”€β”€ app/ # Main application directory (served as document root) +β”‚ β”œβ”€β”€ index.html # Main HTML page +β”‚ β”œβ”€β”€ zalgo.js # Zalgo text generation logic +β”‚ β”œβ”€β”€ app.js # Main application logic and PWA registration +β”‚ β”œβ”€β”€ styles.css # Application styles +β”‚ β”œβ”€β”€ sw.js # Service worker for offline functionality +β”‚ β”œβ”€β”€ manifest.json # PWA manifest +β”‚ β”œβ”€β”€ icons/ # PWA icons (SVG placeholders) +β”‚ β”œβ”€β”€ create-icons.js # Node.js script to generate PNG icons +β”‚ └── generate-icons.html # Browser-based icon generator +β”œβ”€β”€ server.py # Development server (serves app/ as root) +β”œβ”€β”€ package.json # Node dependencies and scripts +β”œβ”€β”€ bun.lockb # Bun lockfile +β”œβ”€β”€ .eslintrc.json # ESLint configuration +└── .prettierrc # Prettier configuration +``` + +## Development Commands + +### Running the Application +```bash +# Start development server (serves on http://localhost:8000/) +python3 server.py + +# Or using the npm script +bun run serve +``` + +### Code Quality and Linting +```bash +# Lint JavaScript files +bun run lint:js + +# Lint and auto-fix JavaScript +bun run lint:js:fix + +# Format all code with Prettier +bun run format + +# Check formatting without changes +bun run check + +# Lint Python code +black server.py +pylint server.py +``` + +### Generating Icons +```bash +# Install canvas package first (optional, for PNG generation) +bun add canvas + +# Generate PNG icons programmatically +node app/create-icons.js + +# Or open app/generate-icons.html in browser and manually save icons +``` + +## Architecture and Key Components + +### Zalgo Text Generation (zalgo.js) +- **ZalgoGenerator class**: Core text corruption engine +- Uses Unicode combining diacritical marks (above, middle, below) +- Intensity scale 1-10 controls corruption amount +- `generate()`: Creates zalgo text from input +- `clean()`: Removes corruption from text + +### Application Logic (app.js) +- Real-time text corruption as user types +- Click-to-copy functionality with visual feedback +- Keyboard shortcuts (Ctrl+K to clear) +- PWA installation handling +- Service worker registration + +### PWA Implementation +- **Service Worker (sw.js)**: Enables offline functionality with cache-first strategy +- **Manifest (manifest.json)**: PWA configuration for installability +- Caches all essential files for offline use +- Automatic cache cleanup on update + +### Styling (styles.css) +- Dark theme with glitch-inspired aesthetics +- Responsive design for mobile and desktop +- CSS animations for glitch effects +- Print-friendly styles + +## Deployment Notes + +- The `app/` directory should be served as the document root +- All paths in the app are relative to assume `app/` is the root +- Service worker requires HTTPS in production (except localhost) +- Icons are currently SVG placeholders; generate PNGs for production + +## Testing Checklist + +When making changes, ensure: +1. Text corruption works with various Unicode characters +2. Copy functionality works on both desktop and mobile +3. PWA installs correctly +4. Offline mode functions properly +5. Responsive design works on all screen sizes +6. All linting passes (JavaScript, Python, HTML validation) \ No newline at end of file diff --git a/app/app.js b/app/app.js new file mode 100644 index 0000000..14d3930 --- /dev/null +++ b/app/app.js @@ -0,0 +1,122 @@ +/** + * Main application logic for Text Corruptor + * Handles UI interactions and PWA registration + */ + +/* global ZalgoGenerator */ + +// Initialize zalgo generator +const zalgo = new ZalgoGenerator(); + +// DOM elements +const inputText = document.getElementById('inputText'); +const outputText = document.getElementById('outputText'); +const intensitySlider = document.getElementById('intensity'); +const intensityValue = document.getElementById('intensityValue'); +const clearBtn = document.getElementById('clearBtn'); +const copyNotification = document.getElementById('copyNotification'); + +/** + * Update the corrupted text output + */ +function updateOutput() { + const text = inputText.value; + const intensity = parseInt(intensitySlider.value); + + if (text) { + outputText.value = zalgo.generate(text, intensity); + } else { + outputText.value = ''; + } +} + +/** + * Copy text to clipboard and show notification + */ +async function copyToClipboard() { + if (!outputText.value) return; + + try { + await navigator.clipboard.writeText(outputText.value); + + // Show copy notification + copyNotification.classList.add('show'); + setTimeout(() => { + copyNotification.classList.remove('show'); + }, 2000); + } catch (err) { + // Fallback for older browsers + outputText.select(); + document.execCommand('copy'); + + // Show copy notification + copyNotification.classList.add('show'); + setTimeout(() => { + copyNotification.classList.remove('show'); + }, 2000); + } +} + +/** + * Clear all text fields + */ +function clearAll() { + inputText.value = ''; + outputText.value = ''; + inputText.focus(); +} + +// Event listeners +inputText.addEventListener('input', updateOutput); + +intensitySlider.addEventListener('input', () => { + intensityValue.textContent = intensitySlider.value; + updateOutput(); +}); + +outputText.addEventListener('click', copyToClipboard); + +clearBtn.addEventListener('click', clearAll); + +// Handle keyboard shortcuts +document.addEventListener('keydown', e => { + // Ctrl/Cmd + K to clear + if ((e.ctrlKey || e.metaKey) && e.key === 'k') { + e.preventDefault(); + clearAll(); + } + + // Ctrl/Cmd + C when output is focused to copy + if ((e.ctrlKey || e.metaKey) && e.key === 'c' && document.activeElement === outputText) { + copyToClipboard(); + } +}); + +// Register service worker for PWA functionality +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker + .register('sw.js') + .then(registration => { + console.log('Service Worker registered successfully:', registration.scope); + }) + .catch(error => { + console.log('Service Worker registration failed:', error); + }); + }); +} + +// Handle install prompt for PWA +window.addEventListener('beforeinstallprompt', e => { + // Prevent the mini-infobar from appearing on mobile + e.preventDefault(); + // Store the event so it can be triggered later if needed + // Currently not used but kept for potential future install button + window.deferredPrompt = e; + console.log('Install prompt ready'); +}); + +// Focus input on load +window.addEventListener('load', () => { + inputText.focus(); +}); diff --git a/app/create-icons.js b/app/create-icons.js new file mode 100644 index 0000000..1ebadde --- /dev/null +++ b/app/create-icons.js @@ -0,0 +1,96 @@ +/** + * Node.js script to generate PWA icons programmatically + * Run with: node create-icons.js + * Requires: npm install canvas + */ + +/* eslint-env node */ +/* eslint-disable no-undef */ + +const fs = require('fs'); +const path = require('path'); + +// Check if we're in a browser or Node.js environment +const isBrowser = typeof window !== 'undefined'; + +if (!isBrowser) { + // Node.js environment - use canvas package + try { + const { createCanvas } = require('canvas'); + + // Icon sizes needed for PWA + const sizes = [72, 96, 128, 144, 152, 192, 384, 512]; + + // Create icons directory if it doesn't exist + const iconsDir = path.join(__dirname, 'icons'); + if (!fs.existsSync(iconsDir)) { + fs.mkdirSync(iconsDir, { recursive: true }); + } + + const createIcon = function (size) { + const canvas = createCanvas(size, size); + const ctx = canvas.getContext('2d'); + + // Background gradient (solid color fallback for canvas) + ctx.fillStyle = '#1a1a2e'; + ctx.fillRect(0, 0, size, size); + + // Center circle + const centerX = size / 2; + const centerY = size / 2; + const radius = size * 0.35; + + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); + ctx.fillStyle = '#00ff88'; + ctx.fill(); + + // Letter "T" + ctx.font = `bold ${size * 0.4}px Arial`; + ctx.fillStyle = '#0f0f14'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('T', centerX, centerY); + + return canvas; + }; + + // Generate all icons + sizes.forEach(size => { + const canvas = createIcon(size); + const buffer = canvas.toBuffer('image/png'); + const filename = path.join(iconsDir, `icon-${size}.png`); + fs.writeFileSync(filename, buffer); + console.log(`Created ${filename}`); + }); + + console.log('All icons created successfully!'); + } catch (error) { + console.log('Canvas package not installed. Creating placeholder icons instead...'); + + // Fallback: Create placeholder SVG icons + const sizes = [72, 96, 128, 144, 152, 192, 384, 512]; + const iconsDir = path.join(__dirname, 'icons'); + + if (!fs.existsSync(iconsDir)) { + fs.mkdirSync(iconsDir, { recursive: true }); + } + + sizes.forEach(size => { + const svg = ` + + + T + `; + + const filename = path.join(iconsDir, `icon-${size}.svg`); + fs.writeFileSync(filename, svg); + console.log(`Created ${filename} (SVG placeholder)`); + }); + + console.log('\nNote: SVG placeholders created. For PNG icons, install canvas package:'); + console.log('npm install canvas'); + } +} else { + console.log('This script should be run in Node.js, not in a browser.'); +} diff --git a/app/generate-icons.html b/app/generate-icons.html new file mode 100644 index 0000000..0954490 --- /dev/null +++ b/app/generate-icons.html @@ -0,0 +1,156 @@ + + + + + Generate PWA Icons + + +

Icon Generator for Text Corruptor

+

Right-click each canvas and save as PNG to the icons folder

+ +
+ + + + + + diff --git a/app/icons/icon-128.svg b/app/icons/icon-128.svg new file mode 100644 index 0000000..a514f45 --- /dev/null +++ b/app/icons/icon-128.svg @@ -0,0 +1,5 @@ + + + + T + \ No newline at end of file diff --git a/app/icons/icon-144.svg b/app/icons/icon-144.svg new file mode 100644 index 0000000..2325a0f --- /dev/null +++ b/app/icons/icon-144.svg @@ -0,0 +1,5 @@ + + + + T + \ No newline at end of file diff --git a/app/icons/icon-152.svg b/app/icons/icon-152.svg new file mode 100644 index 0000000..152b598 --- /dev/null +++ b/app/icons/icon-152.svg @@ -0,0 +1,5 @@ + + + + T + \ No newline at end of file diff --git a/app/icons/icon-192.svg b/app/icons/icon-192.svg new file mode 100644 index 0000000..10c332a --- /dev/null +++ b/app/icons/icon-192.svg @@ -0,0 +1,5 @@ + + + + T + \ No newline at end of file diff --git a/app/icons/icon-384.svg b/app/icons/icon-384.svg new file mode 100644 index 0000000..5f9d1eb --- /dev/null +++ b/app/icons/icon-384.svg @@ -0,0 +1,5 @@ + + + + T + \ No newline at end of file diff --git a/app/icons/icon-512.svg b/app/icons/icon-512.svg new file mode 100644 index 0000000..016d83a --- /dev/null +++ b/app/icons/icon-512.svg @@ -0,0 +1,5 @@ + + + + T + \ No newline at end of file diff --git a/app/icons/icon-72.svg b/app/icons/icon-72.svg new file mode 100644 index 0000000..a70b935 --- /dev/null +++ b/app/icons/icon-72.svg @@ -0,0 +1,5 @@ + + + + T + \ No newline at end of file diff --git a/app/icons/icon-96.svg b/app/icons/icon-96.svg new file mode 100644 index 0000000..72685c8 --- /dev/null +++ b/app/icons/icon-96.svg @@ -0,0 +1,5 @@ + + + + T + \ No newline at end of file diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..a35af67 --- /dev/null +++ b/app/index.html @@ -0,0 +1,66 @@ + + + + + + + + Text Corruptor - Zalgo Text Generator + + + + + + + +
+
+

Text Corruptor

+

Transform your text into corrupted chaos

+
+ +
+
+ + + 5 +
+ +
+ + +
+ +
+ + +
Copied!
+
+ + +
+ +
+

Click the corrupted text to copy it to your clipboard

+
+
+ + + + + diff --git a/app/manifest.json b/app/manifest.json new file mode 100644 index 0000000..b0794fb --- /dev/null +++ b/app/manifest.json @@ -0,0 +1,62 @@ +{ + "name": "Text Corruptor - Zalgo Text Generator", + "short_name": "Text Corruptor", + "description": "Transform your text into corrupted zalgo text with adjustable intensity", + "start_url": "/", + "display": "standalone", + "background_color": "#0f0f14", + "theme_color": "#1a1a2e", + "orientation": "portrait-primary", + "icons": [ + { + "src": "icons/icon-72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "icons/icon-96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "icons/icon-128.png", + "sizes": "128x128", + "type": "image/png" + }, + { + "src": "icons/icon-144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "icons/icon-152.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "icons/icon-384.png", + "sizes": "384x384", + "type": "image/png" + }, + { + "src": "icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ], + "categories": ["utilities", "productivity"], + "screenshots": [ + { + "src": "screenshots/screenshot1.png", + "type": "image/png", + "sizes": "1280x720" + } + ] +} \ No newline at end of file diff --git a/app/styles.css b/app/styles.css new file mode 100644 index 0000000..7099903 --- /dev/null +++ b/app/styles.css @@ -0,0 +1,344 @@ +/** + * Styles for Text Corruptor + * Dark theme with glitch-inspired aesthetics + */ + +:root { + --bg-primary: #0f0f14; + --bg-secondary: #1a1a2e; + --bg-tertiary: #16213e; + --text-primary: #eaeaea; + --text-secondary: #a8a8b3; + --accent: #00ff88; + --accent-hover: #00cc6a; + --error: #ff3366; + --border: #2a2a3e; + --shadow: rgba(0, 255, 136, 0.1); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: var(--bg-primary); + color: var(--text-primary); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + background-image: repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + rgba(0, 255, 136, 0.03) 2px, + rgba(0, 255, 136, 0.03) 4px + ); +} + +.container { + width: 100%; + max-width: 800px; + background: var(--bg-secondary); + border-radius: 16px; + padding: 40px; + box-shadow: + 0 10px 40px rgba(0, 0, 0, 0.5), + 0 0 100px var(--shadow); + border: 1px solid var(--border); +} + +header { + text-align: center; + margin-bottom: 40px; +} + +h1 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 10px; + background: linear-gradient(135deg, var(--accent), #00ffff); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 0 40px var(--shadow); + animation: glitch 3s infinite; +} + +@keyframes glitch { + 0%, + 100% { + text-shadow: 0 0 40px var(--shadow); + } + 25% { + text-shadow: + -2px 0 var(--error), + 2px 0 var(--accent); + } + 50% { + text-shadow: + 2px 0 var(--error), + -2px 0 var(--accent); + } + 75% { + text-shadow: 0 0 40px var(--shadow); + } +} + +.subtitle { + color: var(--text-secondary); + font-size: 1.1rem; +} + +.controls { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 30px; + padding: 20px; + background: var(--bg-tertiary); + border-radius: 12px; + border: 1px solid var(--border); +} + +.controls label { + color: var(--text-secondary); + font-size: 0.95rem; +} + +#intensity { + flex: 1; + -webkit-appearance: none; + appearance: none; + height: 6px; + background: var(--border); + border-radius: 3px; + outline: none; + transition: all 0.3s ease; +} + +#intensity::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + background: var(--accent); + border-radius: 50%; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 0 10px var(--shadow); +} + +#intensity::-moz-range-thumb { + width: 20px; + height: 20px; + background: var(--accent); + border-radius: 50%; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 0 10px var(--shadow); +} + +#intensity:hover::-webkit-slider-thumb { + transform: scale(1.2); + box-shadow: 0 0 20px var(--shadow); +} + +#intensity:hover::-moz-range-thumb { + transform: scale(1.2); + box-shadow: 0 0 20px var(--shadow); +} + +#intensityValue { + min-width: 30px; + text-align: center; + font-weight: 600; + color: var(--accent); + font-size: 1.1rem; +} + +.text-area-container { + margin-bottom: 25px; + position: relative; +} + +.text-area-container label { + display: block; + margin-bottom: 10px; + color: var(--text-secondary); + font-size: 0.95rem; + text-transform: uppercase; + letter-spacing: 1px; +} + +textarea { + width: 100%; + min-height: 150px; + padding: 15px; + background: var(--bg-tertiary); + border: 2px solid var(--border); + border-radius: 12px; + color: var(--text-primary); + font-size: 1rem; + font-family: 'Courier New', monospace; + resize: vertical; + outline: none; + transition: all 0.3s ease; +} + +textarea:focus { + border-color: var(--accent); + box-shadow: 0 0 20px var(--shadow); +} + +#outputText { + cursor: pointer; + position: relative; +} + +#outputText:hover { + background: rgba(0, 255, 136, 0.05); +} + +.copy-notification { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: var(--accent); + color: var(--bg-primary); + padding: 12px 24px; + border-radius: 8px; + font-weight: 600; + opacity: 0; + pointer-events: none; + transition: all 0.3s ease; + z-index: 10; +} + +.copy-notification.show { + opacity: 1; + animation: pulse 0.5s ease; +} + +@keyframes pulse { + 0% { + transform: translate(-50%, -50%) scale(0.8); + } + 50% { + transform: translate(-50%, -50%) scale(1.1); + } + 100% { + transform: translate(-50%, -50%) scale(1); + } +} + +.clear-btn { + width: 100%; + padding: 15px; + background: transparent; + border: 2px solid var(--accent); + border-radius: 12px; + color: var(--accent); + font-size: 1rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + cursor: pointer; + transition: all 0.3s ease; + margin-top: 20px; +} + +.clear-btn:hover { + background: var(--accent); + color: var(--bg-primary); + box-shadow: 0 0 30px var(--shadow); + transform: translateY(-2px); +} + +.clear-btn:active { + transform: translateY(0); +} + +footer { + text-align: center; + margin-top: 30px; + padding-top: 20px; + border-top: 1px solid var(--border); +} + +footer p { + color: var(--text-secondary); + font-size: 0.9rem; +} + +/* Mobile responsiveness */ +@media (max-width: 600px) { + .container { + padding: 25px; + } + + h1 { + font-size: 2rem; + } + + .controls { + flex-direction: column; + align-items: stretch; + gap: 10px; + } + + textarea { + min-height: 120px; + font-size: 0.95rem; + } +} + +/* Glitch effect for corrupted text */ +#outputText { + animation: subtle-glitch 5s infinite; +} + +@keyframes subtle-glitch { + 0%, + 100% { + filter: none; + } + 92% { + filter: none; + } + 93% { + filter: drop-shadow(-2px 0 var(--error)) drop-shadow(2px 0 var(--accent)); + } + 94% { + filter: none; + } +} + +/* Loading state */ +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* Print styles */ +@media print { + body { + background: white; + color: black; + } + + .container { + box-shadow: none; + border: 1px solid #ccc; + } + + .clear-btn { + display: none; + } +} diff --git a/app/sw.js b/app/sw.js new file mode 100644 index 0000000..8395370 --- /dev/null +++ b/app/sw.js @@ -0,0 +1,90 @@ +/** + * Service Worker for Text Corruptor PWA + * Enables offline functionality and caching + */ + +const CACHE_NAME = 'text-corruptor-v1'; +const urlsToCache = ['/', '/index.html', '/styles.css', '/zalgo.js', '/app.js', '/manifest.json']; + +// Install event - cache essential files +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(CACHE_NAME) + .then(cache => { + console.log('Opened cache'); + return cache.addAll(urlsToCache); + }) + .then(() => { + // Force the service worker to become active immediately + return self.skipWaiting(); + }) + ); +}); + +// Fetch event - serve from cache when offline +self.addEventListener('fetch', event => { + event.respondWith( + caches.match(event.request).then(response => { + // Cache hit - return response + if (response) { + return response; + } + + // Clone the request because it's a stream + const fetchRequest = event.request.clone(); + + return fetch(fetchRequest) + .then(response => { + // Check if we received a valid response + if (!response || response.status !== 200 || response.type !== 'basic') { + return response; + } + + // Clone the response because it's a stream + const responseToCache = response.clone(); + + caches.open(CACHE_NAME).then(cache => { + cache.put(event.request, responseToCache); + }); + + return response; + }) + .catch(() => { + // Network request failed, serve offline fallback if available + return caches.match('/index.html'); + }); + }) + ); +}); + +// Activate event - clean up old caches +self.addEventListener('activate', event => { + const cacheWhitelist = [CACHE_NAME]; + + event.waitUntil( + caches + .keys() + .then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + if (cacheWhitelist.indexOf(cacheName) === -1) { + console.log('Deleting old cache:', cacheName); + return caches.delete(cacheName); + } + }) + ); + }) + .then(() => { + // Ensure the service worker takes control immediately + return self.clients.claim(); + }) + ); +}); + +// Handle messages from the client +self.addEventListener('message', event => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); diff --git a/app/zalgo.js b/app/zalgo.js new file mode 100644 index 0000000..1b05d8d --- /dev/null +++ b/app/zalgo.js @@ -0,0 +1,267 @@ +/** + * Zalgo Text Generator + * Generates corrupted/glitched text using Unicode combining characters + */ + +/* exported ZalgoGenerator */ + +class ZalgoGenerator { + constructor() { + // Combining diacritical marks that appear above characters + this.above = [ + '\u0300', + '\u0301', + '\u0302', + '\u0303', + '\u0304', + '\u0305', + '\u0306', + '\u0307', + '\u0308', + '\u0309', + '\u030A', + '\u030B', + '\u030C', + '\u030D', + '\u030E', + '\u030F', + '\u0310', + '\u0311', + '\u0312', + '\u0313', + '\u0314', + '\u0315', + '\u0316', + '\u0317', + '\u0318', + '\u0319', + '\u031A', + '\u031B', + '\u031C', + '\u031D', + '\u031E', + '\u031F', + '\u0320', + '\u0321', + '\u0322', + '\u0323', + '\u0324', + '\u0325', + '\u0326', + '\u0327', + '\u0328', + '\u0329', + '\u032A', + '\u032B', + '\u032C', + '\u032D', + '\u032E', + '\u032F', + '\u0330', + '\u0331', + '\u0332', + '\u0333', + '\u0334', + '\u0335', + '\u0336', + '\u0337', + '\u0338', + '\u0339', + '\u033A', + '\u033B', + '\u033C', + '\u033D', + '\u033E', + '\u033F', + '\u0340', + '\u0341', + '\u0342', + '\u0343', + '\u0344', + '\u0345', + '\u0346', + '\u0347', + '\u0348', + '\u0349', + '\u034A', + '\u034B', + '\u034C', + '\u034D', + '\u034E', + '\u034F', + '\u0350', + '\u0351', + '\u0352', + '\u0353', + '\u0354', + '\u0355', + '\u0356', + '\u0357', + '\u0358', + '\u0359', + '\u035A', + '\u035B', + '\u035C', + '\u035D', + '\u035E', + '\u035F', + '\u0360', + '\u0361', + '\u0362', + '\u0363', + '\u0364', + '\u0365', + '\u0366', + '\u0367', + '\u0368', + '\u0369', + '\u036A', + '\u036B', + '\u036C', + '\u036D', + '\u036E', + '\u036F', + ]; + + // Combining diacritical marks that appear in the middle + this.middle = [ + '\u0315', + '\u031B', + '\u0340', + '\u0341', + '\u0358', + '\u0321', + '\u0322', + '\u0327', + '\u0328', + '\u0334', + '\u0335', + '\u0336', + '\u034F', + '\u035C', + '\u035D', + '\u035E', + '\u035F', + '\u0360', + '\u0362', + '\u0338', + '\u0337', + '\u0361', + '\u0489', + ]; + + // Combining diacritical marks that appear below characters + this.below = [ + '\u0316', + '\u0317', + '\u0318', + '\u0319', + '\u031C', + '\u031D', + '\u031E', + '\u031F', + '\u0320', + '\u0324', + '\u0325', + '\u0326', + '\u0329', + '\u032A', + '\u032B', + '\u032C', + '\u032D', + '\u032E', + '\u032F', + '\u0330', + '\u0331', + '\u0332', + '\u0333', + '\u0339', + '\u033A', + '\u033B', + '\u033C', + '\u0345', + '\u0347', + '\u0348', + '\u0349', + '\u034D', + '\u034E', + '\u0353', + '\u0354', + '\u0355', + '\u0356', + '\u0359', + '\u035A', + '\u0323', + ]; + } + + /** + * Get a random character from an array + */ + randomChar(array) { + return array[Math.floor(Math.random() * array.length)]; + } + + /** + * Generate a specified number of random characters from an array + */ + randomChars(array, count) { + let result = ''; + for (let i = 0; i < count; i++) { + result += this.randomChar(array); + } + return result; + } + + /** + * Generate zalgo text with specified intensity + * @param {string} text - The input text to corrupt + * @param {number} intensity - Corruption intensity (1-10) + * @returns {string} - The corrupted zalgo text + */ + generate(text, intensity = 5) { + if (!text) return ''; + + // Normalize intensity to 1-10 range + intensity = Math.max(1, Math.min(10, intensity)); + + let result = ''; + + for (let char of text) { + result += char; + + // Skip whitespace and newlines + if (/\s/.test(char)) { + continue; + } + + // Calculate how many combining characters to add based on intensity + // Higher intensity = more corruption + const factor = intensity / 10; + + // Add characters above (0-3 based on intensity) + const aboveCount = Math.floor(Math.random() * (4 * factor)); + result += this.randomChars(this.above, aboveCount); + + // Add characters in middle (0-2 based on intensity) + const middleCount = Math.floor(Math.random() * (3 * factor)); + result += this.randomChars(this.middle, middleCount); + + // Add characters below (0-3 based on intensity) + const belowCount = Math.floor(Math.random() * (4 * factor)); + result += this.randomChars(this.below, belowCount); + } + + return result; + } + + /** + * Remove zalgo corruption from text + * @param {string} text - The corrupted text + * @returns {string} - Clean text without combining characters + */ + clean(text) { + // Remove all combining characters (Unicode category Mn) + return text.replace(/[\u0300-\u036f\u0489]/g, ''); + } +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..a1ebfad --- /dev/null +++ b/bun.lock @@ -0,0 +1,216 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "text-corruptor", + "devDependencies": { + "eslint": "^8.57.0", + "prettier": "^3.2.5", + "vnu-jar": "^24.10.17", + }, + }, + }, + "packages": { + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], + + "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], + + "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], + + "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "vnu-jar": ["vnu-jar@24.10.17", "", {}, "sha512-YT7gNrRY5PiJrI1GavlWRHWIwqq2o52COc6J9QeXPfoldKRiZ9BeGP4shNLLaVfi0naA+/LMksdYWkKCr4pnVg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9103aa7 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "text-corruptor", + "version": "1.0.0", + "description": "Zalgo text generator PWA", + "scripts": { + "serve": "python3 server.py", + "lint:js": "eslint app/*.js", + "lint:js:fix": "eslint app/*.js --fix", + "format": "prettier --write app/*.{js,html,css}", + "check": "prettier --check app/*.{js,html,css}" + }, + "devDependencies": { + "eslint": "^8.57.0", + "prettier": "^3.2.5", + "vnu-jar": "^24.10.17" + } +} \ No newline at end of file diff --git a/server.py b/server.py new file mode 100644 index 0000000..8cdbcd6 --- /dev/null +++ b/server.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +""" +Simple HTTP server for testing the Text Corruptor PWA +Run with: python3 server.py +Then open http://localhost:8000/ in your browser +""" + +import http.server +import socketserver +import os + +PORT = 8000 +# Serve from the app directory +DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "app") + + +class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): + """Custom HTTP request handler for serving the Text Corruptor PWA.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=DIRECTORY, **kwargs) + + def end_headers(self): + # Add headers for PWA and CORS + self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") + self.send_header("Service-Worker-Allowed", "/") + self.send_header("X-Content-Type-Options", "nosniff") + super().end_headers() + + +if __name__ == "__main__": + with socketserver.TCPServer(("", PORT), MyHTTPRequestHandler) as httpd: + print(f"Server running at http://localhost:{PORT}/") + print(f"Serving files from: {DIRECTORY}") + print("Press Ctrl+C to stop the server") + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nServer stopped.")