diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 836de0e..2c3e499 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -8,7 +8,7 @@ This workflow automatically builds and deploys the project to GitHub Pages whene 1. **Build Stage:** - Checks out the repository - - Sets up Node.js (v20) with npm cache enabled + - Sets up Node.js (v24) with npm cache enabled - Installs dependencies with `npm ci` - Runs test suite with `npm run test:all` - Runs `npm run build` which: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 892aab0..ea99a33 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -24,12 +24,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: '20' + node-version: '24' cache: 'npm' - name: Install dependencies @@ -52,7 +52,7 @@ jobs: echo "✅ All critical build files present" - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: path: 'dist/' retention-days: 7 @@ -67,4 +67,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a90add..b6cd8a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,7 @@ P4RS3LT0NGV3/ ├── index.template.html # HTML shell; tool *script* tags updated by inject-tool-scripts ├── css/ │ ├── style.css +│ ├── themes-atmosphere.css # Optional per-theme atmosphere / component overrides │ └── notification.css ├── js/ │ ├── app.js # Vue app entry @@ -37,7 +38,8 @@ P4RS3LT0NGV3/ │ │ ├── glitchTokens.js │ │ ├── history.js │ │ ├── notifications.js -│ │ └── theme.js +│ │ ├── openrouterModels.js # Fetch/cache OpenRouter model lists for Settings UI +│ │ └── theme.js # Theme registry + applyTheme / cycleTheme │ └── tools/ # One *Tool.js per tab (extends Tool.js) │ ├── Tool.js │ ├── AntiClassifierTool.js @@ -57,13 +59,14 @@ P4RS3LT0NGV3/ │ └── transformers/ # Transformer sources → bundled to dist/js/bundles/ │ ├── BaseTransformer.js │ ├── index.js # Generated by npm run build:index (gitignored) -│ ├── ancient/ │ ├── case/ │ ├── cipher/ +│ ├── concealment/ │ ├── encoding/ -│ ├── fantasy/ │ ├── format/ +│ ├── signwriting/ │ ├── special/ +│ ├── symbol/ │ ├── technical/ │ ├── unicode/ │ └── visual/ @@ -89,14 +92,16 @@ P4RS3LT0NGV3/ │ ├── fetch-glitch-data.js │ ├── inject-tool-scripts.js # Discovers tools; updates index.template.html + toolRegistry │ ├── inject-tool-templates.js # Builds dist/index.html from index.template.html + templates/ -│ └── readme-transform-section.js # Maintainer helper for README transform list +│ ├── build-code-vendor.js # Bundles QR/barcode libs → js/vendor/ +│ └── build-alphabet-transforms.js # Regenerates symbol/*.js from data/alphabets/ ├── tests/ │ ├── test_universal.js │ └── test_steganography_options.js ├── docs/ │ ├── TOOL-SYSTEM.md │ ├── TOOL_ARCHITECTURE.md -│ └── UI-COMPONENTS.md +│ ├── UI-COMPONENTS.md +│ └── THEMES.md # Adding / editing themes ├── README.md └── CONTRIBUTING.md @@ -116,7 +121,7 @@ dist/ # npm run build — gitignored - **`js/core/`** — Shared business logic and infrastructure (not tab-specific) - Examples: `decoder.js` (DecodeTool, decoder pipeline), `steganography.js` (EmojiTool, steg engine), `toolRegistry.js` (registers tools, merges Vue surface), `transformOptions.js` (shared transform UI helpers) -- **`js/utils/`** — Cross-cutting helpers (`clipboard`, `EmojiUtils` in `emoji.js`, notifications, theme, etc.) +- **`js/utils/`** — Cross-cutting helpers (`clipboard`, `EmojiUtils` in `emoji.js`, notifications, `theme.js`, `openrouterModels.js`, etc.) - **`js/data/`** — Committed static payloads (models, prompts, glitch token data, end sequences, `emojiCompatibility.js`). **`emojiData.js`** is **not** edited here — it is **generated** to `dist/js/data/emojiData.js` by `npm run build:emoji`. - **`src/`** — `emojiWordMap.js` feeds the emoji build; `transformers/` holds transformer modules - **Generated bundle** — `npm run build:transforms` writes `dist/js/bundles/transforms-bundle.js` (a legacy `js/bundles/transforms-bundle.js` path may exist for older workflows and is gitignored) @@ -172,7 +177,6 @@ Transformers are the core text transformation logic. See `src/transformers/READM export default new BaseTransformer({ name: 'My Cipher', priority: 60, // See priority guide in transformers/README.md - category: 'ciphers', func: function(text) { // Encoding logic return encoded; @@ -203,7 +207,10 @@ Transformers are the core text transformation logic. See `src/transformers/READM - Add test cases to `tests/test_universal.js` - Run `npm test` to verify -**Important:** Transformers are automatically discovered and bundled. No manual registration needed! +6. Update documentation: + - Add a bullet to the matching category under **Text Transformations** in the root `README.md` + +**Important:** Transformers are automatically discovered and bundled. Category is set from the parent folder at build time — no manual registration needed. ### 2. Adding a New Tool (New Tab/Feature) @@ -309,7 +316,18 @@ Utilities are shared helper functions used across the app. Currently, utility fu - Document with JSDoc comments - Consider adding to existing modules if functionality is related -**Note:** Prefer `js/utils/` for shared helpers (clipboard, emoji, escapeParser, focus, glitchTokens, history, notifications, theme). Use `js/config/` for constants. +**Note:** Prefer `js/utils/` for shared helpers (clipboard, emoji, escapeParser, focus, glitchTokens, history, notifications, theme, openrouterModels). Use `js/config/` for constants. + +### 4. Adding or Updating a Theme + +Themes are registry entries plus CSS token blocks—not Vue components. + +1. Register the theme in `js/utils/theme.js` (`themes` array). +2. Add a `body.theme- { … }` token block in `css/style.css` (copy from `body.theme-dark` or an existing custom theme). +3. Optionally add atmosphere rules in `css/themes-atmosphere.css`. +4. `npm run build`, then verify in the browser (dropdown, **`D`** cycle, nav, utility dock, toggles, mobile header utility button). + +Full step-by-step guide, token checklist, and testing list: **[docs/THEMES.md](docs/THEMES.md)**. ## 🧪 Testing @@ -413,6 +431,7 @@ This: - **Tool system**: `docs/TOOL-SYSTEM.md` — Templates, injection, UI vocabulary - **Tool architecture**: `docs/TOOL_ARCHITECTURE.md` - **UI components**: `docs/UI-COMPONENTS.md` +- **Themes**: `docs/THEMES.md` — Adding and editing themes ## ✅ Checklist Before Submitting diff --git a/README.md b/README.md index 027106c..dca6541 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# 🐍 P4RS3LT0NGV3 - Universal Text Translator +# 🐍 P4RS3LT0NGV3 4.0 — Universal Text Translator -A powerful web-based text transformation and steganography tool with **159** built-in text transforms spanning encodings, classical and modern ciphers, Unicode styles, formatting, and niche alphabets. Think of it as a universal translator for ALL alphabets and writing systems! +A powerful web-based text transformation and steganography tool with **222** built-in text transforms spanning encodings, classical and modern ciphers, Unicode styles, formatting, and niche alphabets. Think of it as a universal translator for ALL alphabets and writing systems! + +**Version 4.0** brings a redesigned desktop app shell, seven themes (including WCAG 2.1 AA **Accessible**), mobile utility panels, OpenRouter model curation, and responsive UI polish across all tools. The app is a **static site**: run **`npm run build`** (after `npm install`), then open **`dist/index.html`** in your browser—no local server required. **Alternatively**, you can run it as a local app over HTTP with **`npm start`** or **`npx serve dist -l 8080`** (see [Getting Started](#getting-started) below). Core transforms, decoder, and steganography work **without** calling the cloud; features that use [OpenRouter](https://openrouter.ai/) need **network access** and an API key (see below). @@ -15,22 +17,22 @@ The app is a **static site**: run **`npm run build`** (after `npm install`), the Categories match the Transform tab and the folders under `src/transformers/` (each transformer’s `name` as shown in the UI). Short descriptions explain what each transform does. -#### **Ancient** -- **Elder Futhark** - Germanic Elder Futhark runes -- **Hieroglyphics** - Egyptian hieroglyph-style mapping -- **Ogham (Celtic)** - Celtic Ogham tree alphabet -- **Roman Numerals** - Arabic numerals ↔ Roman numerals - #### **Case** - **Alternating Case** - Alternate uppercase and lowercase per letter (first letter upper or lower) - **camelCase** - lowerCamelCase for identifiers +- **Capitalize Words** - Capitalize the first letter of each word - **kebab-case** - kebab-case for slugs and identifiers +- **Lowercase All** - Lowercase entire text - **Random Case** - Random casing per character - **Sentence Case** - Capitalize the first letter of each sentence - **snake_case** - snake_case for identifiers - **Title Case** - Capitalize each word +- **Toggle Case** - Swap case of each letter +- **Uppercase All** - Uppercase entire text #### **Cipher** +- **A1Z26** - A=1 … Z=26 letter numbering +- **Acéré Cipher** - Solfege / duration encoding for musical steganography - **ADFGX Cipher** - WWI ADFGVX-style polybius + column transposition - **Affine Cipher** - Affine substitution (ax + b mod 26) - **Atbash Cipher** - Reverse-alphabet substitution (A↔Z) @@ -39,16 +41,21 @@ Categories match the Transform tab and the folders under `src/transformers/` (ea - **Beaufort Cipher** - Beaufort key-table polyalphabetic cipher - **Bifid Cipher** - Polybius square + row/column interleaving - **Caesar Cipher** - Classic alphabet shift (configurable) +- **Codons (Genetic Code)** - Letters A–Z as DNA triplets - **Columnar Transposition** - Columnar transposition with a keyword +- **Double Transposition** - Two keyed columnar transpositions - **Four-Square Cipher** - Four 5×5 squares; digraph substitution +- **Fractionated Morse** - Morse with fractionation pattern - **Gronsfeld Cipher** - Vigenère family with numeric key - **Hill Cipher** - Matrix-based multi-letter substitution - **Homophonic Cipher** - Multiple ciphertext symbols per plaintext letter +- **Multiplicative Cipher** - Multiply by key mod 26 - **Nihilist Cipher** - Keyed Polybius + additive encryption -- **Pigpen Cipher** - Masonic / pigpen grid symbols - **Playfair Cipher** - Digraph cipher on a 5×5 square - **Polybius Square** - Letter ↔ grid coordinates - **Porta Cipher** - Porta table polyalphabetic cipher +- **QWERTY Right Shift** - Map keys to the key to the right on QWERTY +- **Route Cipher** - Read ciphertext along a grid route - **Rail Fence** - Zig-zag rail-fence transposition - **ROT128** - UTF-16 code unit rotation by 128 - **ROT13** - Rotate Latin letters by 13 places @@ -57,11 +64,24 @@ Categories match the Transform tab and the folders under `src/transformers/` (ea - **ROT5** - Rotate digits 0–9 by 5 - **ROT8000** - Plane-0 Unicode BMP rotation cipher - **Scytale Cipher** - Wrap-around strip (scytale) transposition +- **Tap Code** - Polybius / tap / prison code - **Trifid Cipher** - Three Polybius cubes + trifid grouping +- **Trithemius Cipher** - Progressive Caesar (tabula recta) - **Two-Square Cipher** - Digraph cipher with two Playfair squares +- **Vernam Cipher** - One-time pad XOR (mod 26) - **Vigenère Cipher** - Polyalphabetic cipher with repeating keyword - **XOR Cipher** - XOR with a repeating key +#### **Concealment** +- **Acrostic** - First letter of each line or word spells a message +- **Cardan Grille** - Hide text through a rotating grille +- **Homoglyph Generator** - Latin letters to Cyrillic homoglyphs +- **Invisible Text** - Unicode Tags / invisible carrier encoding +- **Null Cipher** - Fixed letter position in each cover word +- **Trevanion Cipher** - Letters N positions after punctuation marks +- **Whitespace Steganography** - Hide bits in whitespace patterns +- **Zero-Width Steganography** - Hide data with zero-width characters + #### **Encoding** - **ASCII85** - Ascii85 / Adobe-style base-85 encoding - **Base122** - Binary → 122 printable ASCII characters @@ -73,46 +93,39 @@ Categories match the Transform tab and the folders under `src/transformers/` (ea - **Base64** - Standard Base64 - **Base64 URL** - Base64url (URL-safe alphabet) - **Base91** - basE91 / Ascii91 encoding +- **Bibi-binary Code** - UTF-8 bytes via Bibi-binary syllables - **Baudot Code (ITA2)** - Five-bit telegraph / ITA2 - **Binary Coded Decimal** - Decimal digits as BCD nibbles - **Binary** - Text bytes ↔ binary strings +- **Bitwise NOT** - UTF-8 bytes NOT'd per byte; encode output is hex (decode pastes hex back to text) +- **Brainfuck** - Text ↔ Brainfuck program +- **Decabit Code** - Ten-bit patterns for decimal digits - **EBCDIC** - EBCDIC byte encoding -- **Emoji Encoding** - Payload encoded with emoji +- **Base256Emoji** - Multiformats multibase encoding (1 byte → 1 emoji) - **Gray Code** - Binary Gray code - **Hexadecimal** - Hex encode/decode bytes - **HTML Entities** - HTML entity escape / unescape -- **Invisible Text** - Unicode Tags / invisible carrier encoding +- **Manchester Code** - Manchester line coding +- **Metaphone** - Metaphone phonetic encoding - **Quoted-Printable** - MIME quoted-printable +- **Shadoks Numeral System** - UTF-8 bytes as Shadoks base-4 words - **Unicode Code Points** - Characters ↔ U+XXXX code points - **URL Encode** - application/x-www-form-urlencoded - **Uuencoding** - Classic uuencode / uudecode - **YEnc** - yEnc line-oriented binary encoding - **Z85** - ZeroMQ Z85 encoding -#### **Fantasy** -- **Aurebesh (Star Wars)** - Galactic Basic Aurebesh alphabet -- **Dovahzul (Dragon)** - Skyrim Dovahzul transliteration -- **Klingon** - Klingon transliteration -- **Quenya (Tolkien Elvish)** - Tolkien Quenya mapping -- **Tengwar Script** - Elvish Tengwar script - #### **Format** -- **Bitwise NOT** - UTF-8 bytes NOT'd per byte; encode output is hex (decode pastes hex back to text) - **Boustrophedon** - Serpentine / alternating line direction -- **Capitalize Words** - Capitalize the first letter of each word +- **Group Letters** - Insert separators between letters - **Indent** - Add leading spaces to each line (configurable width) -- **Javanais** - French “javanais” vowel-insertion game -- **Latin Gibberish** - Latin-flavored pseudo-text -- **Leetspeak** - 1337-style character substitutions - **Letters Only** - Keep letters; strip other characters - **Letters & Numbers Only** - Alphanumeric only - **Line Numbers** - Prefix lines with numbers (start and column width configurable) -- **Louchebem** - French argot (loucherbem-style) -- **Lowercase All** - Lowercase entire text +- **Leading Zeros** - Pad numbers with leading zeros +- **List Deduplicate** - Remove duplicate lines from a list - **Mirror Digits** - Mirror digits 0–9 visually - **Numbers Only** - Digits only -- **Pig Latin** - English Pig Latin -- **QWERTY Right Shift** - Map keys to the key to the right on QWERTY - **Remove Accents** - Strip diacritics / combining marks - **Remove Consonants** - Remove consonant letters - **Remove Duplicates** - Remove duplicate lines @@ -127,35 +140,83 @@ Categories match the Transform tab and the folders under `src/transformers/` (ea - **Reverse Text** - Reverse character order - **Shuffle Characters** - Shuffle characters (random order) - **Shuffle Words** - Shuffle word order +- **Shuffled Letters** - Randomize letter order within each word - **Spaces Remover** - Remove space characters - **Text Justify** - Pad each line to a fixed width (left, right, or center); not word-spacing justify -- **Uppercase All** - Uppercase entire text -- **Toggle Case** - Swap case of each letter -- **Whitespace Steganography** - Hide bits in whitespace patterns +- **Typoglycemia** - Scrambled middle letters (readable chaos) +- **Word Letter Add** - Insert a letter at a fixed position in each word +- **Word Letter Change** - Replace a letter at a fixed position in each word +- **Word Letter Remove** - Remove a letter at a fixed position in each word - **Word Wrap** - Break long lines at spaces so each line fits a maximum width -- **Zero-Width Steganography** - Hide data with zero-width characters + +#### **SignWriting** +- **ASL SignWriting** - American Sign Language fingerspelling (ISWA) +- **LIBRAS SignWriting** - Brazilian Sign Language fingerspelling +- **JSL SignWriting** - Japanese Sign Language (hiragana input) +- **IPA Lip-reading** - IPA symbols in SignWriting +- **Morse Blink** - Morse as SignWriting blink marks +- **Tactile SignWriting** - Tactile SignWriting notation #### **Special** - **Random Mix** - Pick random transforms and chain them #### **Technical** -- **A1Z26** - A=1 … Z=26 letter numbering -- **Braille** - Unicode Braille patterns -- **Brainfuck** - Text ↔ Brainfuck program - **ICAO Spelling Alphabet** - ICAO radiotelephony spelling - **ITU Spelling Alphabet** - ITU phonetic / spelling alphabet +- **DTMF Tones** - Dual-tone multi-frequency telephone codes - **Maritime Signal Flags** - International maritime signal flags - **Morse Code** - International Morse code +- **Navajo Code** - WWII Navajo word code for A–Z - **NATO Phonetic** - NATO phonetic alphabet - **Semaphore Flags** - Flag semaphore arm positions -- **Tap Code** - Polybius / tap / prison code +- **T9 (Predictive Text)** - Phone T9 multi-tap encoding +- **Phone Keypad** - Digits from phone keypad groups + +#### **Symbol** +- **Alchemical Symbols** - Alchemical symbol alphabet +- **Aurebesh (Star Wars)** - Galactic Basic Aurebesh alphabet +- **Babylonian Numerals** - A1Z26 as cuneiform numerals +- **Braille** - Unicode Braille patterns +- **Celestial Alphabet** - Celestial / angelic script +- **Chemical Symbols** - Chemical element symbols +- **Daedric (Elder Scrolls)** - Daedric alphabet +- **Dancing Men (Sherlock Holmes)** - Dancing figure cipher +- **Dominos in Digits** - Digits as domino tile notation +- **Dovahzul (Dragon)** - Skyrim Dovahzul transliteration +- **Egyptian Numerals** - A1Z26 as hieroglyph numerals +- **Elder Futhark** - Germanic Elder Futhark runes +- **Enochian** - Enochian angelic alphabet +- **Eye of Horus (Wedjat)** - Wedjat fraction hieroglyphs +- **Friderici Cipher (Windows)** - 1685 window-pane cipher +- **Greek Letters** - Greek letter replacements +- **Hieroglyphics** - Egyptian hieroglyph-style mapping +- **Hiragana** - Rough Romaji → Hiragana +- **Klingon** - Klingon transliteration +- **Katakana** - Rough Romaji → Katakana +- **Malachim** - Malachim angelic alphabet +- **Mary Stuart Cipher** - Mary Queen of Scots cipher +- **Mayan Numerals** - A1Z26 as Mayan numeral glyphs +- **Moon Alphabet** - Moon type for blind readers +- **Ogham (Celtic)** - Celtic Ogham tree alphabet +- **Passing the River** - Golden Dawn Passing the River script +- **Periodic Table Cipher** - Letters A–Z as element symbols +- **Pigpen Cipher** - Masonic / pigpen grid symbols +- **Quenya (Tolkien Elvish)** - Tolkien Quenya mapping +- **Roman Numerals** - Arabic numerals ↔ Roman numerals +- **Rosicrucian** - Rosicrucian cipher alphabet +- **Seven-Segment Display** - Digits as 7-segment ASCII art +- **Standard Galactic (Minecraft)** - Enchanting Table / SGA alphabet +- **Templars Cipher** - Templar pigpen variant +- **Tengwar Script** - Elvish Tengwar script +- **Theban Alphabet** - Witches' Theban script +- **Wingdings** - Wingdings-style symbol mapping +- **Younger Futhark** - Medieval Younger Futhark runes #### **Unicode** - **Bold Italic** - Mathematical sans-serif bold italic - **Bold** - Mathematical bold -- **Bubble** - Circled / “bubble” letters -- **Chemical Symbols** - Chemical element symbols -- **Circled** - Circled Unicode letters +- **Bubble** - Circled letters (upper and lower case) and circled digits 0–9 +- **Circled** - Circled uppercase letters and digits (no lowercase) - **Cursive** - Mathematical script / cursive - **Cyrillic Stylized** - Latin → Cyrillic lookalike letters - **Dashed Underline** - Combining dashed underline @@ -163,10 +224,7 @@ Categories match the Transform tab and the folders under `src/transformers/` (ea - **Double-Struck** - Mathematical double-struck - **Fraktur** - Mathematical Fraktur / Gothic - **Full Width** - Fullwidth Latin (and related) forms -- **Greek Letters** - Greek letter replacements -- **Hiragana** - Rough Romaji → Hiragana - **Italic** - Mathematical italic -- **Katakana** - Rough Romaji → Katakana - **Mathematical Notation** - Mathematical alphanumeric symbols - **Medieval** - Medieval Unicode letterforms - **Mirror Text** - Left–right mirrored characters @@ -185,12 +243,16 @@ Categories match the Transform tab and the folders under `src/transformers/` (ea - **Vaporwave** - Fullwidth + aesthetic spacing - **Wavy Underline** - Wavy underline combining marks - **Wide Spacing** - Insert wide spaces between characters -- **Wingdings** - Wingdings-style symbol mapping - **Zalgo** - Stacked combining marks (“glitch” text) #### **Visual** - **Disemvowel** - Remove vowels (speech game) - **Emoji Speak** - Emoji-heavy “speak” transform +- **Javanais** - French “javanais” vowel-insertion game +- **Latin Gibberish** - Latin-flavored pseudo-text +- **Leetspeak** - 1337-style character substitutions +- **Louchebem** - French argot (loucherbem-style) +- **Pig Latin** - English Pig Latin - **Rövarspråket** - Swedish consonant-doubling game - **Ubbi Dubbi** - Insert “ub” before vowel sounds @@ -200,7 +262,7 @@ Tabs appear in **UI order** below. **OpenRouter** (optional or required per tool ### 🔤 **Transform** -- **159 Transforms**: Encodings, ciphers, Unicode styles, formats, and more (full catalog above). +- **222 Transforms**: Encodings, ciphers, Unicode styles, formats, and more (full catalog above). - **Categories**: Grouped sections you can **reorder**; quick-jump legend; **randomizer** last. - **Favorites & last used**: Pin transforms and recall recent picks. - **Per-transform options**: Gear icon where a transform exposes settings. @@ -225,7 +287,7 @@ Tabs appear in **UI order** below. **OpenRouter** (optional or required per tool - **Real-time**: Updates as you type. - **Script & language hints**: Unicode script ranges and Latin word-marker heuristics for common languages. - **AI translate to English** (optional, OpenRouter): When text looks foreign, optional one-shot translate to English. -- **Keyboard shortcut**: **D**. +- **Deep link**: `#decoder` in the URL opens this tab directly. ### 😀 **Emoji** (Steganography) @@ -266,11 +328,31 @@ Tabs appear in **UI order** below. **OpenRouter** (optional or required per tool - **Transform chain**: Optionally run transforms on each piece. - **Wrapping**: Start/end templates; `{n}` iterator marker; single-line vs multiline copy. +### 🔤 **Spelling Alphabets** + +- **Custom ICAO-style alphabets**: One word per letter A–Z, like NATO/ICAO phonetic spelling. +- **OpenRouter (optional)**: Enter a category/theme and generate a full alphabet; edit any letter before saving. +- **Manual mode**: No API key required — fill in all 26 letters yourself. +- **Saved locally**: Alphabets persist in browser `localStorage` as JSON. +- **Transforms integration**: Each saved alphabet appears on the Transforms page under `custom_spelling`. + ### 💬 **Gibberish** - **Dictionary mode**: Seeded random gibberish over a configurable character set. - **Removal mode**: Random or **specific** letter removal with batch **variations** and min/max strip lengths. +### 📷 **Codes** (QR & barcodes) + +*Tab id: `codes` — deep link: `#codes` (generate) or `#codes/decode` (scan).* + +- **Generate**: QR codes and common 1D barcodes from any text or numeric payload. +- **Formats**: **QR Code** (PNG download), **Code 128**, **EAN-13**, and **Code 39** (SVG download). +- **QR options**: Pixel size, quiet-zone margin, and error-correction level (L / M / Q / H). +- **Barcode options**: Bar height, module width, and optional human-readable label under the bars. +- **Decode**: Upload a PNG, JPEG, GIF, or WebP image to read QR codes and barcodes — **fully client-side** (image never leaves the browser). +- **Copy & reuse**: Send decoded text to the Generate tab with one click. +- **Offline-friendly**: QR/barcode libraries (`qrcode`, JsBarcode, ZXing) are bundled into `js/vendor/` at build time — no runtime CDN for this tab. + ### 🪄 **PromptCraft** (via OpenRouter) - **9 Mutation Strategies**: Rephrase, Obfuscate, Role-Play Wrap, Multi-Language, Expand, Compress, Metaphor, Fragment, and Custom @@ -286,23 +368,29 @@ models - **Same key**: Uses the same OpenRouter API key as Translation and PromptCraft. ### 📱 **User Experience** -- **Dark/Light Theme**: Toggle between themes +- **Themes**: **Advanced Settings** → Theme (Dark, Light, **Accessible**, BT6, Pliny, Cyberpunk, Wild West) — press **`D`** to cycle; choice saved in the browser +- **Desktop layout**: Left tool nav, main workspace, right utility dock (Copy History, Glitch Tokens, End Sequences, Advanced Settings) +- **Mobile / narrow screens**: Tool picker dropdown; utility panels slide in from the right (open via the **columns** icon in the header) - **Copy History**: Track all copied content with timestamps -- **Auto-copy**: Automatically copy transformed text -- **Keyboard Shortcuts**: Quick access to features +- **Auto-copy**: Automatically copy transformed text (where enabled per tool) +- **Keyboard Shortcuts**: Quick access to features (including **`D`** for theme cycle) - **Responsive Design**: Works on all device sizes - **Accessibility**: Screen reader friendly with proper ARIA labels -- **Side panels**: Glitch token browser (optional data), end-sequence / delimiter strings for research, and **Advanced Settings** (OpenRouter key, steganography tuning) +- **Side panels**: Glitch token browser (optional data), end-sequence / delimiter strings for research, and **Advanced Settings** (OpenRouter API key, model curation, steganography tuning) +- **Deep links**: Open a specific tool tab via URL hash — e.g. `#decoder`, `#steganography`, `#codes`, `#codes/decode` (browser back/forward supported) + +Contributors: see **[docs/THEMES.md](docs/THEMES.md)** for how to add or edit themes. ### 🔑 **OpenRouter API Key Setup** -**AI Translation**, **PromptCraft**, and **Anti-Classifier** require an [OpenRouter](https://openrouter.ai/) API key. **Decoder**’s optional “translate to English” also uses OpenRouter when enabled. +**AI Translation**, **PromptCraft**, **Anti-Classifier**, and **Spelling Alphabets** (optional generate) require an [OpenRouter](https://openrouter.ai/) API key. **Decoder**’s optional “translate to English” also uses OpenRouter when enabled. The Spelling Alphabets tool works fully without a key if you enter letters manually. 1. Create an account at [openrouter.ai](https://openrouter.ai/) 2. Generate an API key (starts with `sk-or-...`) -3. In P4RS3LT0NGV3, click the **sliders icon** (top-right) to open **Advanced Settings** +3. In P4RS3LT0NGV3, open **Advanced Settings** from the utility dock (desktop) or the **columns** icon in the header (mobile) 4. Paste your key and click **Save Key** -5. Your key is stored locally in your browser only — never sent anywhere except OpenRouter +5. Choose which OpenRouter models appear in Translation, PromptCraft, Anti-Classifier, and related dropdowns +6. Your key is stored locally in your browser only — never sent anywhere except OpenRouter > **Tip:** Some models (like Gemma 3) are free on OpenRouter. Frontier models (Claude, GPT, Gemini Pro) require credits. @@ -342,11 +430,12 @@ Notes: npm install # Build all assets (required before use). Order matches package.json: -# build:tools → build:copy → build:index → build:transforms → build:emoji → build:templates +# build:tools → build:codes-vendor → build:copy → build:index → build:transforms → build:emoji → build:templates npm run build # Or build individual components: npm run build:tools # Auto-discover tools, inject script tags into dist/index.html +npm run build:codes-vendor # Bundle qrcode, JsBarcode, ZXing → dist/js/vendor/ npm run build:copy # Copy static files to dist/ npm run build:index # Generate src/transformers/index.js (ES module index) npm run build:transforms # Bundle all transformers to dist/js/bundles/transforms-bundle.js @@ -373,7 +462,7 @@ npm run preview # npm run build, then serve dist/ | [build/README.md](build/README.md) | What each `build:*` script does | | [templates/README.md](templates/README.md) | Editing tool HTML templates | -**Keeping the transform list in this README in sync:** when you add or rename a transformer, add a one-line description to `DESCRIPTIONS` in `build/readme-transform-section.js`, run `node build/readme-transform-section.js`, and replace the **Text Transformations** section here (details in [src/transformers/README.md](src/transformers/README.md)). +**Keeping the transform list in this README in sync:** when you add, rename, or move a transformer, update the **Text Transformations** section below manually — one bullet per transform under the correct category heading (`case`, `cipher`, `concealment`, `encoding`, `format`, `signwriting`, `special`, `symbol`, `technical`, `unicode`, `visual`). Use the transform’s UI `name` and a short description. Category comes from the folder under `src/transformers/` (see [src/transformers/README.md](src/transformers/README.md)). ## 🛠️ **Technical Details** @@ -382,12 +471,13 @@ npm run preview # npm run build, then serve dist/ - **Tool System**: Modular tool registry with build-time template injection - **Encoding**: UTF-8 with proper Unicode handling - **Steganography**: Variation selectors and Tags Unicode block -- **Transforms**: Individual transformer modules live under `src/transformers/` (159; the bundle is generated by `npm run build:transforms`) +- **Transforms**: Individual transformer modules live under `src/transformers/` (222; the bundle is generated by `npm run build:transforms`) - **Build Process**: - `npm run build` writes the runnable app under `dist/` (ignored by git in most setups) - Transformers are bundled from `src/transformers/` to `dist/js/bundles/transforms-bundle.js` - Tool templates are injected from `templates/` into `dist/index.html` - Emoji data is generated to `dist/js/data/` + - QR/barcode vendors (`qrcode`, `JsBarcode`, `@zxing/library`) are bundled to `dist/js/vendor/` via `npm run build:codes-vendor` ### **Browser Support** - Chrome/Edge 80+ @@ -400,6 +490,15 @@ npm run preview # npm run build, then serve dist/ - **Memory Efficient**: Streams large text without loading into memory - **Optimized Rendering**: Efficient DOM updates with Vue.js +## 🔧 **What's New in 4.0** + +- 🆕 **Version 4.0 release**: Major UI refresh — desktop three-column layout, theme system, and mobile utility dock +- 🆕 **Theme system**: Seven themes (Dark, Light, **Accessible**, BT6, Pliny, Cyberpunk, Wild West) with token-based CSS and atmosphere layers +- 🆕 **Desktop app shell**: Left tool nav, main workspace, right utility dock (Copy History, Glitch Tokens, End Sequences, Settings) +- 🆕 **Mobile utility panels**: Slide-over panels with responsive tab bar (no horizontal scroll) +- 🆕 **OpenRouter model settings**: Curate which models appear in AI tool dropdowns (Settings) +- 🆕 **Responsive tool polish**: Bijection, Tokenade, transform cards, and topbar layout improvements + ## 🔧 **Recent Fixes & Improvements** ### **Fixed Issues** @@ -409,10 +508,12 @@ npm run preview # npm run build, then serve dist/ - ✅ **Reverse Functions**: Added missing reverse functions for many transforms ### **New Features** +- 🆕 **Codes tool**: Generate QR codes and barcodes (Code 128, EAN-13, Code 39); decode from uploaded images client-side +- 🆕 **URL deep links**: Jump to any tool tab with `#tab` hashes (e.g. `#codes/decode`) - 🆕 **AI Translation**: Translate to 20+ languages (including dead/exotic) via OpenRouter using TranslateGemma prompt format - 🆕 **PromptCraft Tool**: AI-powered prompt mutation with 9 strategies and 48+ models - 🆕 **OpenRouter Integration**: Unified API key management for all AI-powered features -- 🆕 **159 Transformations**: Full catalog of encodings, ciphers, Unicode styles, fantasy and ancient scripts, and technical codes (see README transform list) +- 🆕 **222 Transformations**: Full catalog of encodings, ciphers, Unicode styles, symbol alphabets, SignWriting, and technical codes (see README transform list) - 🆕 **More Encodings/Ciphers**: Base58, Base62, Vigenère, Rail Fence, Roman Numerals - 🆕 **Category Organization**: Better organized transform categories - 🆕 **Enhanced Styling**: New color schemes for each category @@ -423,7 +524,7 @@ npm run preview # npm run build, then serve dist/ ### **Creative Writing** - Create unique text styles for stories - Encode secret messages in plain sight -- Generate fantasy language text +- Generate symbolic or script-style text ### **Education** - Learn about different writing systems @@ -468,7 +569,8 @@ This project is open source. See LICENSE file for details. - **Star Wars** creators for Aurebesh - **Bethesda** for Dovahzul language - **Unicode Consortium** for character standards +- **[RaidedCluster](https://github.com/RaidedCluster)** — SignWriting transforms --- -**P4RS3LT0NGV3** - Because sometimes you need to speak in tongues that don't exist! 🐉✨ +**P4RS3LT0NGV3 4.0** - Because sometimes you need to speak in tongues that don't exist! 🐉✨ diff --git a/build/README.md b/build/README.md index 197af96..854c107 100644 --- a/build/README.md +++ b/build/README.md @@ -55,8 +55,24 @@ Injects tool templates from `templates/` into `dist/index.html` npm run build:templates ``` +### `build-alphabet-transforms.js` +Regenerates hand-maintained symbol alphabet files from `data/alphabets/*.json` into `src/transformers/symbol/`. + +```bash +npm run build:alphabets +``` + +Runs automatically before `build-transforms.js` (`npm run build:transforms`). + +### `build-code-vendor.js` +Bundles QR and barcode libraries into `js/vendor/` for the Codes tool (no CDN). + +```bash +npm run build:codes-vendor +``` + ### `build-index.js` -Generates transformer index +Generates `src/transformers/index.js` (Node/test import index). ```bash npm run build:index @@ -66,12 +82,13 @@ npm run build:index ```bash npm run build # Runs all scripts in order: -# 1. build:copy - Copy static files to dist/ -# 2. build:index - Generate transformer index -# 3. build:transforms - Bundle transformers to dist/js/bundles/ -# 4. build:emoji - Generate emoji data to dist/js/data/ -# 5. build:tools - Inject tool scripts -# 6. build:templates - Inject templates to dist/index.html +# 1. build:tools - Inject tool scripts into index.template.html + toolRegistry +# 2. build:codes-vendor - Bundle qrcode, JsBarcode, ZXing → js/vendor/ +# 3. build:copy - Copy static files to dist/ +# 4. build:index - Generate src/transformers/index.js +# 5. build:transforms - build:alphabets + bundle transformers → dist/js/bundles/ +# 6. build:emoji - Generate emoji data to dist/js/data/ +# 7. build:templates - Inject templates → dist/index.html ``` ## Output Structure diff --git a/build/build-alphabet-transforms.js b/build/build-alphabet-transforms.js new file mode 100644 index 0000000..dcc1ada --- /dev/null +++ b/build/build-alphabet-transforms.js @@ -0,0 +1,178 @@ +#!/usr/bin/env node +/** + * Generate symbol-alphabet transforms from data/alphabets/*.json + * Output: src/transformers/symbol/.js (regenerated each build) + */ + +const fs = require('fs'); +const path = require('path'); + +const root = path.join(__dirname, '..'); +const dataDir = path.join(root, 'data', 'alphabets'); +const outDir = path.join(root, 'src', 'transformers', 'symbol'); +const GENERATED_HEADER = '// @generated from data/alphabets — do not edit by hand\n'; + +function slugToExportName(slug) { + return slug.replace(/-/g, '_'); +} + +function escapeJsString(s) { + return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); +} + +function escapeRegexCharClass(ch) { + return ch.replace(/\\/g, '\\\\').replace(/\]/g, '\\]'); +} + +function buildDetectorBlock(entry) { + if (entry.detectorHint) { + return `, + detector: function(text) { + return new RegExp('${escapeJsString(entry.detectorHint)}', 'u').test(text); + }`; + } + const chars = [...new Set(Object.values(entry.map))]; + if (chars.length === 0) return ''; + const classContent = chars.map(ch => escapeRegexCharClass(ch)).join(''); + return `, + detector: function(text) { + return /[${classContent}]/u.test(text); + }`; +} + +function buildMapObject(map) { + const lines = []; + for (const [key, value] of Object.entries(map)) { + lines.push(` '${escapeJsString(key)}': '${escapeJsString(value)}'`); + } + return lines.join(',\n'); +} + +function generateTransformFile(entry, slug) { + const name = entry.name || slug; + const priority = entry.priority != null ? entry.priority : 100; + const description = entry.description ? `\n description: '${escapeJsString(entry.description)}',` : ''; + const mapBlock = buildMapObject(entry.map); + + return `${GENERATED_HEADER}import BaseTransformer from '../BaseTransformer.js'; + +export default new BaseTransformer({ + name: '${escapeJsString(name)}', + priority: ${priority}, + category: 'symbol',${description} + map: { +${mapBlock} + }, + func: function(text) { + return [...text].map(c => this.map[c] || this.map[c.toUpperCase()] || c).join(''); + }, + preview: function(text) { + if (!text) return '[${escapeJsString(slug)}]'; + return this.func(text.slice(0, 6)) + (text.length > 6 ? '…' : ''); + }${buildDetectorBlock(entry)} +}); +`; +} + +function validateEntry(entry, file) { + if (!entry.name || !entry.map || typeof entry.map !== 'object') { + throw new Error(`${file}: requires "name" and "map" object`); + } + const keys = Object.keys(entry.map); + if (keys.length < 26) { + console.warn(`⚠️ ${file}: map has only ${keys.length} entries`); + } +} + +function loadUnicodeMaps() { + const unicodeDir = path.join(root, 'src', 'transformers', 'unicode'); + const maps = []; + if (!fs.existsSync(unicodeDir)) return maps; + for (const file of fs.readdirSync(unicodeDir)) { + if (!file.endsWith('.js')) continue; + const content = fs.readFileSync(path.join(unicodeDir, file), 'utf8'); + const nameMatch = content.match(/name:\s*'([^']+)'/); + const map = {}; + for (const match of content.matchAll(/'([^']+)':\s*'([^']+)'/g)) { + map[match[1]] = match[2]; + } + if (Object.keys(map).length >= 20) { + maps.push({ name: nameMatch ? nameMatch[1] : file, map }); + } + } + return maps; +} + +function overlapScore(entryMap, existingMap) { + let matched = 0; + let total = 0; + for (const [key, value] of Object.entries(entryMap)) { + total++; + const variants = [key, key.toLowerCase(), key.toUpperCase()]; + for (const variant of variants) { + if (existingMap[variant] === value) { + matched++; + break; + } + } + } + return { matched, total }; +} + +function checkOverlap(entry, file, unicodeMaps) { + for (const existing of unicodeMaps) { + const { matched, total } = overlapScore(entry.map, existing.map); + if (total >= 20 && matched / total >= 0.9) { + throw new Error( + `${file}: ${matched}/${total} glyphs match existing Unicode transform "${existing.name}" — use that instead or pick distinct symbols` + ); + } + if (total >= 20 && matched / total >= 0.75) { + console.warn(`⚠️ ${file}: ${matched}/${total} glyphs overlap "${existing.name}"`); + } + } +} + +function main() { + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + console.log('Created data/alphabets/ (add JSON files to generate symbol transforms)'); + return; + } + + fs.mkdirSync(outDir, { recursive: true }); + + // Remove previously generated symbol transforms + for (const file of fs.readdirSync(outDir)) { + if (!file.endsWith('.js')) continue; + const full = path.join(outDir, file); + const head = fs.readFileSync(full, 'utf8').slice(0, 80); + if (head.includes('@generated')) { + fs.unlinkSync(full); + } + } + + const jsonFiles = fs.readdirSync(dataDir).filter(f => f.endsWith('.json')).sort(); + if (!jsonFiles.length) { + console.log('No alphabet JSON files in data/alphabets/'); + return; + } + + let count = 0; + const unicodeMaps = loadUnicodeMaps(); + for (const file of jsonFiles) { + const slug = file.replace(/\.json$/, ''); + const raw = fs.readFileSync(path.join(dataDir, file), 'utf8'); + const entry = JSON.parse(raw); + validateEntry(entry, file); + checkOverlap(entry, file, unicodeMaps); + const outPath = path.join(outDir, `${slug}.js`); + fs.writeFileSync(outPath, generateTransformFile(entry, slug), 'utf8'); + console.log(`✅ Alphabet: ${slug} → symbol/${slug}.js`); + count++; + } + + console.log(`\n✨ Generated ${count} symbol alphabet transform(s)`); +} + +main(); diff --git a/build/build-code-vendor.js b/build/build-code-vendor.js new file mode 100644 index 0000000..5953bd6 --- /dev/null +++ b/build/build-code-vendor.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node +/** + * Bundle QR/barcode vendor libraries for the Codes tool (local copies, no CDN). + */ + +const fs = require('fs'); +const path = require('path'); +const esbuild = require('esbuild'); + +const projectRoot = path.join(__dirname, '..'); +const vendorDir = path.join(projectRoot, 'js/vendor'); + +if (!fs.existsSync(vendorDir)) { + fs.mkdirSync(vendorDir, { recursive: true }); +} + +const bundles = [ + { + entry: path.join(projectRoot, 'node_modules/qrcode/lib/browser.js'), + outfile: path.join(vendorDir, 'qrcode.min.js'), + globalName: 'QRCode' + }, + { + entry: path.join(projectRoot, 'node_modules/jsbarcode/bin/JsBarcode.js'), + outfile: path.join(vendorDir, 'JsBarcode.min.js'), + globalName: 'JsBarcode' + } +]; + +console.log('📦 Building Codes tool vendor bundles...\n'); + +bundles.forEach(function(spec) { + esbuild.buildSync({ + entryPoints: [spec.entry], + bundle: true, + format: 'iife', + globalName: spec.globalName, + outfile: spec.outfile, + minify: true, + platform: 'browser' + }); + const sizeKb = (fs.statSync(spec.outfile).size / 1024).toFixed(1); + console.log('✅ ' + path.basename(spec.outfile) + ' (' + sizeKb + 'KB)'); +}); + +const zxingSrc = path.join(projectRoot, 'node_modules/@zxing/library/umd/index.min.js'); +const zxingDest = path.join(vendorDir, 'zxing.min.js'); +fs.copyFileSync(zxingSrc, zxingDest); +console.log('✅ zxing.min.js (' + (fs.statSync(zxingDest).size / 1024).toFixed(1) + 'KB)'); +console.log('\n✨ Codes vendor bundles ready\n'); diff --git a/build/build-transforms.js b/build/build-transforms.js index 5166b45..504ccf5 100755 --- a/build/build-transforms.js +++ b/build/build-transforms.js @@ -95,14 +95,25 @@ for (const [name, filePath] of Object.entries(transforms)) { } // Extract the object definition (remove comments, import, and export statements) - const cleanContent = content + let cleanContent = content .replace(/^\/\/.*$/gm, '') // Remove single-line comments .replace(/import\s+.*?from\s+['"].*?['"]\s*;?\s*/g, '') // Remove import statements .replace(/export default\s*/g, '') // Remove export statement .trim(); - + + if (/^(const|let|var|function)\s/m.test(cleanContent)) { + console.error(`❌ ${name}: top-level declarations break the bundle — wrap in export default (function(){ ... return new BaseTransformer(...); })();`); + process.exit(1); + } + output += `// ${name} (from ${filePath})\n`; - output += `transforms['${name}'] = ${cleanContent}\n\n`; + if (/^\(function\s*\(\)/.test(cleanContent)) { + output += `transforms['${name}'] = ${cleanContent}\n\n`; + } else if (/^new BaseTransformer\s*\(/.test(cleanContent)) { + output += `transforms['${name}'] = ${cleanContent}\n\n`; + } else { + output += `transforms['${name}'] = ${cleanContent}\n\n`; + } console.log(`✅ Bundled: ${name} (category: ${category})`); } catch (error) { diff --git a/build/inject-tool-templates.js b/build/inject-tool-templates.js index d17796c..8256fd2 100644 --- a/build/inject-tool-templates.js +++ b/build/inject-tool-templates.js @@ -21,7 +21,9 @@ const templateFiles = [ 'tokenizer.html', 'bijection.html', 'splitter.html', - 'gibberish.html' + 'gibberish.html', + 'spellingalphabet.html', + 'codes.html' ]; @@ -52,21 +54,23 @@ if (!fs.existsSync(templatePath)) { } let indexContent = fs.readFileSync(templatePath, 'utf8'); +indexContent = indexContent.replace(/\r\n/g, '\n'); // Find the tool-content-container -const startMarker = '
'; -const endMarker = '
\n\n \n\n '; +const startMarker = '
', startIndex); +const endIndex = indexContent.indexOf(endMarker, startTagEnd); -if (startIndex === -1 || endIndex === -1) { +if (startIndex === -1 || startTagEnd === -1 || endIndex === -1) { console.error('\n❌ Could not find tool content container markers'); process.exit(1); } // Build the replacement content -const before = indexContent.substring(0, startIndex + startMarker.length); +const before = indexContent.substring(0, startTagEnd + 1); const after = indexContent.substring(endIndex); const replacement = ` diff --git a/css/notification.css b/css/notification.css index 6c6c33d..d417de4 100644 --- a/css/notification.css +++ b/css/notification.css @@ -3,11 +3,12 @@ position: fixed; bottom: 20px; right: 20px; - background-color: #25282c; - color: #00FF41; + background-color: var(--secondary-bg); + color: var(--success-color); padding: 10px 20px; border-radius: 5px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + border: 1px solid var(--input-border); + box-shadow: var(--panel-shadow-soft); z-index: 1000; display: flex; align-items: center; @@ -16,7 +17,7 @@ } .copy-notification.error { - color: #ff4141; + color: #e53935; } .copy-notification.fade-out { diff --git a/css/style.css b/css/style.css index 174c914..384c906 100644 --- a/css/style.css +++ b/css/style.css @@ -1,3 +1,9 @@ +/* SignWriting font (Google Fonts) — ISWA 2010 glyphs (U+1D800–U+1DAFF) */ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SignWriting&display=swap'); +/* Premium theme display fonts */ +@import url('https://fonts.googleapis.com/css2?family=Bitter:ital,wght@0,400;0,600;1,400&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@500;700&family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Orbitron:wght@500;700&family=Roboto+Condensed:wght@400;600;700&family=Rye&display=swap'); +@import url('themes-atmosphere.css'); + .danger-modal-backdrop { display: none; } .danger-modal-backdrop.danger-active { display: none; } .danger-modal { display: none; } @@ -7,61 +13,69 @@ .danger-proceed { background:var(--danger-color); border:1px solid var(--danger-border); color:var(--danger-text); padding:var(--spacing-sm) var(--spacing-sm); border-radius:6px; cursor:pointer; box-shadow: 0 0 12px rgba(var(--danger-text-rgb),.35) inset, 0 0 16px rgba(var(--danger-text-rgb),.2); } /* Modern dark theme styling */ :root { - --main-bg-color: #1a1a1a; - --secondary-bg: #242424; - --text-color: #e0e0e0; - --text-muted: #a0a0a0; - --accent-color: #64b5f6; - --accent-color-rgb: 100, 181, 246; - --accent-hover: #90caf9; - --success-color: #81c784; - --button-bg: #2d2d2d; - --button-hover-bg: #404040; - --button-active-bg: #64b5f6; - --input-bg: #2d2d2d; - --input-border: #404040; - --section-divider: #404040; - --focus-shadow: 0 0 0 2px rgba(100, 181, 246, 0.4); - --panel-shadow-soft: 0 2px 8px rgba(0, 0, 0, 0.1); - --sidebar-edge-shadow: -5px 0 15px rgba(0, 0, 0, 0.3); + /* Layout & shared tokens (theme-agnostic) */ --sidebar-width: 420px; + --nav-width: 196px; + --utility-width: 300px; + --topbar-height: 52px; - /* Transform category colors */ - --encoding-color: #7e57c2; /* Purple for encoding/decoding */ + /* Transform category colors — one distinct hue per category */ + --case-color: #7986cb; /* Periwinkle — case transforms */ + --case-color-rgb: 121, 134, 203; + --cipher-color: #00897b; /* Teal — classical ciphers */ + --cipher-color-rgb: 0, 137, 123; + --concealment-color: #7cb342; /* Olive — steganography / hidden text */ + --concealment-color-rgb: 124, 179, 66; + --encoding-color: #7e57c2; /* Purple — byte & data encodings */ --encoding-color-rgb: 126, 87, 194; - --cipher-color: #26a69a; /* Teal for ciphers */ - --cipher-color-rgb: 38, 166, 154; - --visual-color: #ef5350; /* Red for visual transformations */ - --visual-color-rgb: 239, 83, 80; - --format-color: #ffb74d; /* Orange for formatting */ - --format-color-rgb: 255, 183, 77; - --unicode-color: #42a5f5; /* Blue for unicode transformations */ + --format-color: #fb8c00; /* Orange — text formatting */ + --format-color-rgb: 251, 140, 0; + --signwriting-color: #8d6e63; /* Brown — SignWriting notation */ + --signwriting-color-rgb: 141, 110, 99; + --special-color: #43a047; /* Green — misc / randomizer-adjacent */ + --special-color-rgb: 67, 160, 71; + --symbol-color: #d4af37; /* Gold — scripts & symbol alphabets */ + --symbol-color-rgb: 212, 175, 55; + --technical-color: #00acc1; /* Cyan — Morse, NATO, phone codes */ + --technical-color-rgb: 0, 172, 193; + --unicode-color: #42a5f5; /* Blue — Unicode letter styles */ --unicode-color-rgb: 66, 165, 245; - --special-color: #66bb6a; /* Green for special transformations */ - --special-color-rgb: 102, 187, 106; - --fantasy-color: #ff6b9d; /* Pink for fantasy languages */ - --fantasy-color-rgb: 255, 107, 157; - --ancient-color: #d4af37; /* Gold for ancient scripts */ - --ancient-color-rgb: 212, 175, 55; - --technical-color: #00bcd4; /* Cyan for technical codes */ - --technical-color-rgb: 0, 188, 212; - --randomizer-color: #9c27b0; /* Purple for randomizer */ - --randomizer-color-rgb: 156, 39, 176; - --case-color: #9575cd; /* Indigo for case transformations */ - --case-color-rgb: 149, 117, 205; + --visual-color: #ef5350; /* Coral — visual / spoken effects */ + --visual-color-rgb: 239, 83, 80; + --randomizer-color: #ec407a; /* Pink — random mix */ + --randomizer-color-rgb: 236, 64, 122; + /* Randomizer hero button — contrast-safe on encoding / randomizer fills */ + --randomizer-button-bg: var(--encoding-color); + --randomizer-button-text: #ffffff; + --randomizer-button-border: rgba(255, 255, 255, 0.28); + --randomizer-button-preview-bg: rgba(0, 0, 0, 0.32); + --randomizer-button-preview-text: rgba(255, 255, 255, 0.96); + --randomizer-button-preview-border: rgba(255, 255, 255, 0.28); + --randomizer-button-active-bg: var(--randomizer-color); + --randomizer-button-active-text: #ffffff; + --randomizer-button-active-preview-bg: rgba(0, 0, 0, 0.28); + --randomizer-button-active-preview-text: rgba(255, 255, 255, 0.96); + --randomizer-button-active-preview-border: rgba(255, 255, 255, 0.35); + --custom-spelling-color: #ff7043; /* Deep orange — user spelling alphabets */ + --custom-spelling-color-rgb: 255, 112, 67; + --translate-color: #b388ff; /* Violet — AI translation (inline tool) */ + --translate-color-rgb: 179, 136, 255; - /* Active state gradient end colors */ + /* Active state secondary colors (legacy tokens) */ + --case-active-end: #9fa8da; + --cipher-active-end: #26a69a; + --concealment-active-end: #aed581; --encoding-active-end: #9575cd; - --cipher-active-end: #4db6ac; - --visual-active-end: #e57373; - --format-active-end: #ffcc80; - --unicode-active-end: #64b5f6; - --special-active-end: #81c784; - --fantasy-active-end: #ff8aad; - --ancient-active-end: #e6c84c; + --format-active-end: #ffb74d; + --signwriting-active-end: #a1887f; + --special-active-end: #66bb6a; + --symbol-active-end: #e6c84c; --technical-active-end: #26c6da; - --randomizer-active-end: #ab47bc; - --case-active-end: #b39ddb; + --unicode-active-end: #64b5f6; + --visual-active-end: #e57373; + --randomizer-active-end: #f06292; + --custom-spelling-active-end: #ff8a65; + --translate-active-end: #d1b3ff; /* Spacing scale */ --spacing-xs: 4px; @@ -75,10 +89,669 @@ --danger-border: #2e7d32; --danger-text: #69f0ae; --danger-text-rgb: 105, 240, 174; - - /* Logo glitch color */ +} + +/* Dark theme (default) */ +body.theme-dark, +body.dark-theme { + --main-bg-color: #1a1a1a; + --secondary-bg: #242424; + --nav-bg: #121212; + --utility-tab-bg: #1c1c1c; + --text-color: #e0e0e0; + --text-muted: #a0a0a0; + --text-on-accent: #ffffff; + --text-on-category: #ffffff; + --placeholder-color: rgba(224, 224, 224, 0.5); + --accent-color: #64b5f6; + --accent-color-rgb: 100, 181, 246; + --accent-hover: #90caf9; + --accent-tint: rgba(100, 181, 246, 0.1); + --accent-tint-hover: rgba(100, 181, 246, 0.15); + --accent-tint-strong: rgba(100, 181, 246, 0.2); + --accent-tint-subtle: rgba(100, 181, 246, 0.05); + --surface-hover: rgba(255, 255, 255, 0.04); + --success-color: #81c784; + --status-success: #69f0ae; + --error-text: #f07178; + --error-bg: rgba(240, 113, 120, 0.08); + --error-border: rgba(240, 113, 120, 0.35); + --error-text-soft: #ffabab; + --destructive-color: #ff6b6b; + --destructive-hover: #e85d4c; + --action-copy-color: #90caf9; + --action-copy-hover: #bbdefb; + --action-download-color: #69f0ae; + --status-banner-bg: rgba(27, 94, 32, 0.15); + --status-banner-border: #1b5e20; + --status-banner-text: #69f0ae; + --favorite-color: #ffd700; + --favorite-color-rgb: 255, 215, 0; + --preview-active-bg: rgba(0, 0, 0, 0.3); + --preview-active-text: rgba(255, 255, 255, 0.92); + --preview-active-border: rgba(255, 255, 255, 0.15); + --tooltip-bg: rgba(20, 20, 25, 0.98); + --tooltip-text: #ffffff; + --button-bg: #2d2d2d; + --button-hover-bg: #404040; + --button-active-bg: #64b5f6; + --input-bg: #2d2d2d; + --input-border: #404040; + --section-divider: #404040; + --focus-shadow: 0 0 0 2px rgba(100, 181, 246, 0.4); + --panel-shadow-soft: 0 2px 8px rgba(0, 0, 0, 0.1); + --sidebar-edge-shadow: -5px 0 15px rgba(0, 0, 0, 0.3); + --shell-bg: #141414; + --surface-overlay: rgba(0, 0, 0, 0.12); + --surface-overlay-strong: rgba(0, 0, 0, 0.25); + --surface-border-subtle: rgba(0, 0, 0, 0.06); + --backdrop-scrim: rgba(0, 0, 0, 0.45); + --fab-shadow: 0 4px 16px rgba(0, 0, 0, 0.35); --glitch-color: #00ff00; --glitch-color-rgb: 0, 255, 0; + --switch-border: #1565a0; + --switch-surface-glow: rgba(100, 181, 246, 0.15); + --switch-track-bg: #0d2137; + --switch-track-border: #1565a0; + --switch-track-checked-bg: #1565c0; + --switch-track-checked-glow: rgba(100, 181, 246, 0.45); + --switch-thumb-bg: #90caf9; + --switch-thumb-glow: rgba(144, 202, 249, 0.6); + --switch-thumb-checked-bg: #64b5f6; + --switch-thumb-checked-glow: rgba(100, 181, 246, 0.75); + /* Legacy aliases used by older component styles */ + --text-primary: var(--text-color); + --text-secondary: var(--text-muted); + --muted-color: var(--text-muted); + --border-color: var(--input-border); + --bg-darkest: var(--input-bg); + --bg-darker: var(--button-bg); + color-scheme: dark; +} + +/* Light theme */ +body.theme-light, +body.light-theme { + --main-bg-color: #eef1f5; + --secondary-bg: #ffffff; + --nav-bg: #e4e9ef; + --utility-tab-bg: #f0f3f7; + --text-color: #1b2430; + --text-muted: #5c6573; + --text-on-accent: #ffffff; + --text-on-category: #ffffff; + --placeholder-color: rgba(27, 36, 48, 0.45); + --accent-color: #1565c0; + --accent-color-rgb: 21, 101, 192; + --accent-hover: #1976d2; + --accent-tint: rgba(21, 101, 192, 0.1); + --accent-tint-hover: rgba(21, 101, 192, 0.14); + --accent-tint-strong: rgba(21, 101, 192, 0.2); + --accent-tint-subtle: rgba(21, 101, 192, 0.06); + --surface-hover: rgba(15, 23, 42, 0.06); + --success-color: #2e7d32; + --status-success: #1b5e20; + --error-text: #c62828; + --error-bg: rgba(198, 40, 40, 0.08); + --error-border: rgba(198, 40, 40, 0.28); + --error-text-soft: #b71c1c; + --destructive-color: #d32f2f; + --destructive-hover: #b71c1c; + --action-copy-color: #1565c0; + --action-copy-hover: #0d47a1; + --action-download-color: #2e7d32; + --action-download-hover: #1b5e20; + --status-banner-bg: rgba(46, 125, 50, 0.08); + --status-banner-border: rgba(46, 125, 50, 0.35); + --status-banner-text: #1b5e20; + --favorite-color: #b8860b; + --favorite-color-rgb: 184, 134, 11; + --preview-active-bg: rgba(255, 255, 255, 0.22); + --preview-active-text: rgba(255, 255, 255, 0.95); + --preview-active-border: rgba(255, 255, 255, 0.25); + --tooltip-bg: rgba(27, 36, 48, 0.96); + --tooltip-text: #f8fafc; + --button-bg: #e8edf3; + --button-hover-bg: #dce3ec; + --button-active-bg: #1565c0; + --input-bg: #ffffff; + --input-border: #c5ced9; + --section-divider: #d5dde7; + --focus-shadow: 0 0 0 2px rgba(21, 101, 192, 0.35); + --panel-shadow-soft: 0 2px 8px rgba(15, 23, 42, 0.08); + --sidebar-edge-shadow: -5px 0 15px rgba(15, 23, 42, 0.12); + --shell-bg: #dfe5ec; + --surface-overlay: rgba(15, 23, 42, 0.06); + --surface-overlay-strong: rgba(15, 23, 42, 0.1); + --surface-border-subtle: rgba(15, 23, 42, 0.08); + --backdrop-scrim: rgba(15, 23, 42, 0.35); + --fab-shadow: 0 4px 16px rgba(15, 23, 42, 0.18); + --glitch-color: #1b5e20; + --glitch-color-rgb: 27, 94, 32; + --switch-border: rgba(21, 101, 192, 0.55); + --switch-surface-glow: rgba(21, 101, 192, 0.12); + --switch-track-bg: #e3edf7; + --switch-track-border: rgba(21, 101, 192, 0.45); + --switch-track-checked-bg: #1565c0; + --switch-track-checked-glow: rgba(21, 101, 192, 0.35); + --switch-thumb-bg: #90caf9; + --switch-thumb-glow: rgba(21, 101, 192, 0.25); + --switch-thumb-checked-bg: #ffffff; + --switch-thumb-checked-glow: rgba(21, 101, 192, 0.4); + --text-primary: var(--text-color); + --text-secondary: var(--text-muted); + --muted-color: var(--text-muted); + --border-color: var(--input-border); + --bg-darkest: var(--input-bg); + --bg-darker: var(--button-bg); + color-scheme: light; +} + +/* BT6 — Basilisk Team Six / bt6.gg (true black, blood red, serif + mono) */ +body.theme-bt6 { + --main-bg-color: #050505; + --secondary-bg: #0a0a0a; + --nav-bg: #000000; + --utility-tab-bg: #080808; + --text-color: #f5f2ef; + --text-muted: #8a8178; + --text-on-accent: #ffffff; + --text-on-category: #ffffff; + --placeholder-color: rgba(138, 129, 120, 0.55); + --accent-color: #962020; + --accent-color-rgb: 150, 32, 32; + --accent-hover: #b52828; + --accent-tint: rgba(150, 32, 32, 0.1); + --accent-tint-hover: rgba(150, 32, 32, 0.16); + --accent-tint-strong: rgba(150, 32, 32, 0.22); + --accent-tint-subtle: rgba(150, 32, 32, 0.05); + --surface-hover: rgba(255, 255, 255, 0.03); + --success-color: #6b8f6e; + --status-success: #8aab8d; + --error-text: #c44d3a; + --error-bg: rgba(196, 77, 58, 0.1); + --error-border: rgba(196, 77, 58, 0.35); + --error-text-soft: #e07a6a; + --destructive-color: #962020; + --destructive-hover: #b52828; + --action-copy-color: #c9a84c; + --action-copy-hover: #dbbc6a; + --action-download-color: #8aab8d; + --action-download-hover: #a8c9ab; + --status-banner-bg: rgba(150, 32, 32, 0.1); + --status-banner-border: rgba(150, 32, 32, 0.35); + --status-banner-text: #d4a0a0; + --favorite-color: #c9a84c; + --favorite-color-rgb: 201, 168, 76; + --preview-active-bg: rgba(0, 0, 0, 0.4); + --preview-active-text: rgba(245, 242, 239, 0.95); + --preview-active-border: rgba(150, 32, 32, 0.35); + --tooltip-bg: rgba(0, 0, 0, 0.96); + --tooltip-text: #f5f2ef; + --button-bg: #111111; + --button-hover-bg: #181818; + --button-active-bg: #962020; + --input-bg: #0a0a0a; + --input-border: #222222; + --section-divider: #1a1a1a; + --focus-shadow: 0 0 0 1px rgba(150, 32, 32, 0.6); + --panel-shadow-soft: none; + --sidebar-edge-shadow: none; + --shell-bg: #000000; + --surface-overlay: rgba(150, 32, 32, 0.04); + --surface-overlay-strong: rgba(0, 0, 0, 0.35); + --surface-border-subtle: rgba(150, 32, 32, 0.12); + --backdrop-scrim: rgba(0, 0, 0, 0.75); + --fab-shadow: 0 2px 12px rgba(0, 0, 0, 0.6); + --glitch-color: #962020; + --glitch-color-rgb: 150, 32, 32; + --switch-border: #962020; + --switch-surface-glow: rgba(150, 32, 32, 0.15); + --switch-track-bg: #1a0808; + --switch-track-border: #962020; + --switch-track-checked-bg: #962020; + --switch-track-checked-glow: rgba(150, 32, 32, 0.45); + --switch-thumb-bg: #c9a84c; + --switch-thumb-glow: rgba(201, 168, 76, 0.5); + --switch-thumb-checked-bg: #dbbc6a; + --switch-thumb-checked-glow: rgba(201, 168, 76, 0.75); + --theme-secondary: #c9a84c; + --theme-secondary-rgb: 201, 168, 76; + --theme-display-font: 'Playfair Display', Georgia, 'Times New Roman', serif; + --theme-ui-font: 'Inter', system-ui, -apple-system, sans-serif; + --theme-mono-font: 'JetBrains Mono', 'Courier New', monospace; + --theme-radius: 0; + --text-primary: var(--text-color); + --text-secondary: var(--text-muted); + --muted-color: var(--text-muted); + --border-color: var(--input-border); + --bg-darkest: var(--input-bg); + --bg-darker: var(--button-bg); + color-scheme: dark; +} + +/* Pliny — pliny.gg (true black, neon green CRT, white sans + mono leetspeak) */ +body.theme-pliny { + --main-bg-color: #020402; + --secondary-bg: #050805; + --nav-bg: #000000; + --utility-tab-bg: #030503; + --text-color: #ffffff; + --text-muted: #9aa39a; + --text-on-accent: #000000; + --text-on-category: #000000; + --placeholder-color: rgba(154, 163, 154, 0.55); + --accent-color: #00ff41; + --accent-color-rgb: 0, 255, 65; + --accent-hover: #5cff7a; + --accent-tint: rgba(0, 255, 65, 0.08); + --accent-tint-hover: rgba(0, 255, 65, 0.14); + --accent-tint-strong: rgba(0, 255, 65, 0.2); + --accent-tint-subtle: rgba(0, 255, 65, 0.04); + --surface-hover: rgba(0, 255, 65, 0.04); + --success-color: #00ff41; + --status-success: #00ff41; + --error-text: #ff6b8a; + --error-bg: rgba(255, 107, 138, 0.08); + --error-border: rgba(255, 107, 138, 0.32); + --error-text-soft: #ff9eb3; + --destructive-color: #ff4466; + --destructive-hover: #ff2244; + --action-copy-color: #00ff41; + --action-copy-hover: #5cff7a; + --action-download-color: #00ff41; + --action-download-hover: #5cff7a; + --status-banner-bg: rgba(0, 255, 65, 0.08); + --status-banner-border: rgba(0, 255, 65, 0.35); + --status-banner-text: #00ff41; + --favorite-color: #00ff41; + --favorite-color-rgb: 0, 255, 65; + --preview-active-bg: rgba(0, 0, 0, 0.45); + --preview-active-text: rgba(255, 255, 255, 0.95); + --preview-active-border: rgba(0, 255, 65, 0.35); + --tooltip-bg: rgba(0, 0, 0, 0.96); + --tooltip-text: #ffffff; + --button-bg: #080a08; + --button-hover-bg: #0e120e; + --button-active-bg: #00ff41; + --input-bg: #050805; + --input-border: #1a221a; + --section-divider: #141a14; + --focus-shadow: 0 0 0 1px rgba(0, 255, 65, 0.55), 0 0 12px rgba(0, 255, 65, 0.2); + --panel-shadow-soft: none; + --sidebar-edge-shadow: none; + --shell-bg: #000000; + --surface-overlay: rgba(0, 255, 65, 0.04); + --surface-overlay-strong: rgba(0, 0, 0, 0.35); + --surface-border-subtle: rgba(0, 255, 65, 0.1); + --backdrop-scrim: rgba(0, 0, 0, 0.78); + --fab-shadow: 0 0 20px rgba(0, 255, 65, 0.25); + --glitch-color: #00ff41; + --glitch-color-rgb: 0, 255, 65; + --switch-border: #00ff41; + --switch-surface-glow: rgba(0, 255, 65, 0.15); + --switch-track-bg: #0b2e13; + --switch-track-border: #1b5e20; + --switch-track-checked-bg: #00aa2a; + --switch-track-checked-glow: rgba(0, 255, 65, 0.45); + --switch-thumb-bg: #a5d6a7; + --switch-thumb-glow: rgba(0, 255, 65, 0.5); + --switch-thumb-checked-bg: #00ff41; + --switch-thumb-checked-glow: rgba(0, 255, 65, 0.75); + --theme-secondary: #00ff41; + --theme-secondary-rgb: 0, 255, 65; + --theme-display-font: 'Inter', system-ui, -apple-system, sans-serif; + --theme-ui-font: 'Inter', system-ui, -apple-system, sans-serif; + --theme-mono-font: 'JetBrains Mono', 'Courier New', monospace; + --theme-radius: 0; + --text-primary: var(--text-color); + --text-secondary: var(--text-muted); + --muted-color: var(--text-muted); + --border-color: var(--input-border); + --bg-darkest: var(--input-bg); + --bg-darker: var(--button-bg); + color-scheme: dark; +} + +/* Cyberpunk — neon magenta + cyan on midnight purple */ +body.theme-cyberpunk { + --main-bg-color: #12001f; + --secondary-bg: #1a0029; + --nav-bg: #060010; + --utility-tab-bg: #0e0018; + --text-color: #e8d4ff; + --text-muted: #9a7ab8; + --text-on-accent: #0a0014; + --text-on-category: #ffffff; + --placeholder-color: rgba(232, 212, 255, 0.45); + --accent-color: #ff2a6d; + --accent-color-rgb: 255, 42, 109; + --accent-hover: #ff5c8d; + --accent-tint: rgba(255, 42, 109, 0.12); + --accent-tint-hover: rgba(255, 42, 109, 0.18); + --accent-tint-strong: rgba(255, 42, 109, 0.24); + --accent-tint-subtle: rgba(255, 42, 109, 0.06); + --surface-hover: rgba(5, 217, 232, 0.06); + --success-color: #05d9e8; + --status-success: #05d9e8; + --error-text: #ff5c8d; + --error-bg: rgba(255, 42, 109, 0.1); + --error-border: rgba(255, 42, 109, 0.35); + --error-text-soft: #ff9ec0; + --destructive-color: #ff2a6d; + --destructive-hover: #ff0055; + --action-copy-color: #05d9e8; + --action-copy-hover: #5ce8f5; + --action-download-color: #f7f048; + --action-download-hover: #fff176; + --status-banner-bg: rgba(5, 217, 232, 0.1); + --status-banner-border: rgba(5, 217, 232, 0.35); + --status-banner-text: #05d9e8; + --favorite-color: #f7f048; + --favorite-color-rgb: 247, 240, 72; + --preview-active-bg: rgba(0, 0, 0, 0.4); + --preview-active-text: rgba(255, 255, 255, 0.95); + --preview-active-border: rgba(255, 42, 109, 0.25); + --tooltip-bg: rgba(6, 0, 16, 0.98); + --tooltip-text: #e8d4ff; + --button-bg: #220033; + --button-hover-bg: #2e0044; + --button-active-bg: #ff2a6d; + --input-bg: #1a0029; + --input-border: #4a2066; + --section-divider: #4a2066; + --focus-shadow: 0 0 0 2px rgba(255, 42, 109, 0.45), 0 0 12px rgba(5, 217, 232, 0.15); + --panel-shadow-soft: 0 2px 12px rgba(255, 42, 109, 0.15); + --sidebar-edge-shadow: -5px 0 20px rgba(255, 42, 109, 0.12); + --shell-bg: #0a0014; + --surface-overlay: rgba(255, 42, 109, 0.08); + --surface-overlay-strong: rgba(0, 0, 0, 0.32); + --surface-border-subtle: rgba(5, 217, 232, 0.1); + --backdrop-scrim: rgba(10, 0, 20, 0.65); + --fab-shadow: 0 4px 20px rgba(255, 42, 109, 0.35); + --glitch-color: #05d9e8; + --glitch-color-rgb: 5, 217, 232; + --switch-border: #ff2a6d; + --switch-surface-glow: rgba(255, 42, 109, 0.18); + --switch-track-bg: #2a0038; + --switch-track-border: #ff2a6d; + --switch-track-checked-bg: #ff2a6d; + --switch-track-checked-glow: rgba(255, 42, 109, 0.5); + --switch-thumb-bg: #05d9e8; + --switch-thumb-glow: rgba(5, 217, 232, 0.6); + --switch-thumb-checked-bg: #05d9e8; + --switch-thumb-checked-glow: rgba(5, 217, 232, 0.85); + --theme-secondary: #f7f048; + --theme-secondary-rgb: 247, 240, 72; + --theme-display-font: 'Orbitron', 'Courier New', sans-serif; + --theme-radius: 0; + --text-primary: var(--text-color); + --text-secondary: var(--text-muted); + --muted-color: var(--text-muted); + --border-color: var(--input-border); + --bg-darkest: var(--input-bg); + --bg-darker: var(--button-bg); + color-scheme: dark; +} + +/* Wild West — vintage saloon: cream, sage, dusty rose */ +body.theme-wildwest { + --main-bg-color: #f2e6c5; + --secondary-bg: #f2e6c5; + --nav-bg: #8bb69d; + --utility-tab-bg: #7fab91; + --text-color: #1a1a1a; + --text-muted: #6b5e4f; + --text-on-accent: #ffffff; + --text-on-category: #ffffff; + --placeholder-color: rgba(107, 94, 79, 0.55); + --accent-color: #d67c7c; + --accent-color-rgb: 214, 124, 124; + --accent-hover: #c46969; + --accent-tint: rgba(214, 124, 124, 0.14); + --accent-tint-hover: rgba(214, 124, 124, 0.2); + --accent-tint-strong: rgba(214, 124, 124, 0.28); + --accent-tint-subtle: rgba(214, 124, 124, 0.08); + --surface-hover: rgba(214, 124, 124, 0.08); + --success-color: #6b8f6e; + --status-success: #8bb69d; + --error-text: #b84233; + --error-bg: rgba(196, 77, 58, 0.1); + --error-border: rgba(196, 77, 58, 0.35); + --error-text-soft: #c46969; + --destructive-color: #b84233; + --destructive-hover: #962f22; + --action-copy-color: #d67c7c; + --action-copy-hover: #c46969; + --action-download-color: #6b8f6e; + --action-download-hover: #8bb69d; + --status-banner-bg: rgba(214, 124, 124, 0.12); + --status-banner-border: rgba(214, 124, 124, 0.35); + --status-banner-text: #1a1a1a; + --favorite-color: #d67c7c; + --favorite-color-rgb: 214, 124, 124; + --preview-active-bg: rgba(255, 255, 255, 0.65); + --preview-active-text: #1a1a1a; + --preview-active-border: rgba(214, 124, 124, 0.35); + --tooltip-bg: rgba(26, 26, 26, 0.92); + --tooltip-text: #f2e6c5; + --button-bg: #fff8ea; + --button-hover-bg: #fffdf5; + --button-active-bg: #d67c7c; + --input-bg: #fffdf8; + --input-border: #c4b5a0; + --section-divider: #d4c4a8; + --focus-shadow: 0 0 0 2px rgba(214, 124, 124, 0.45); + --panel-shadow-soft: 0 1px 4px rgba(26, 26, 26, 0.08); + --sidebar-edge-shadow: -2px 0 8px rgba(26, 26, 26, 0.06); + --shell-bg: #ebe3cc; + --surface-overlay: rgba(214, 124, 124, 0.06); + --surface-overlay-strong: rgba(26, 26, 26, 0.06); + --surface-border-subtle: rgba(139, 182, 157, 0.35); + --backdrop-scrim: rgba(26, 26, 26, 0.45); + --fab-shadow: 0 2px 10px rgba(26, 26, 26, 0.15); + --glitch-color: #1a1a1a; + --glitch-color-rgb: 26, 26, 26; + --switch-border: #d67c7c; + --switch-surface-glow: rgba(214, 124, 124, 0.18); + --switch-track-bg: #fff8ea; + --switch-track-border: #d67c7c; + --switch-track-checked-bg: #d67c7c; + --switch-track-checked-glow: rgba(214, 124, 124, 0.35); + --switch-thumb-bg: #8bb69d; + --switch-thumb-glow: rgba(139, 182, 157, 0.45); + --switch-thumb-checked-bg: #ffffff; + --switch-thumb-checked-glow: rgba(214, 124, 124, 0.4); + --theme-secondary: #8bb69d; + --theme-secondary-rgb: 139, 182, 157; + --theme-cream: #f2e6c5; + --theme-cream-rgb: 242, 230, 197; + --theme-charcoal: #1a1a1a; + --theme-charcoal-rgb: 26, 26, 26; + --theme-display-font: 'Rye', Georgia, serif; + --theme-ui-font: 'Roboto Condensed', 'Arial Narrow', sans-serif; + --theme-body-font: 'Bitter', Georgia, serif; + --theme-radius: 2px; + --text-primary: var(--text-color); + --text-secondary: var(--text-muted); + --muted-color: var(--text-muted); + --border-color: var(--input-border); + --bg-darkest: var(--input-bg); + --bg-darker: var(--button-bg); + color-scheme: light; +} + +/* + * Accessible — WCAG 2.1 Level AA high-contrast light theme. + * Contrast pairs verified at 4.5:1+ for normal text and 3:1+ for UI borders. + * See docs/THEMES.md and themes-atmosphere.css (body.theme-accessible). + */ +body.theme-accessible { + --main-bg-color: #ffffff; + --secondary-bg: #ffffff; + --nav-bg: #f5f5f5; + --utility-tab-bg: #eeeeee; + --text-color: #000000; + --text-muted: #595959; + --text-on-accent: #ffffff; + --text-on-category: #ffffff; + --placeholder-color: #595959; + --accent-color: #004080; + --accent-color-rgb: 0, 64, 128; + --accent-hover: #002d5c; + --accent-tint: rgba(0, 64, 128, 0.1); + --accent-tint-hover: rgba(0, 64, 128, 0.14); + --accent-tint-strong: rgba(0, 64, 128, 0.2); + --accent-tint-subtle: rgba(0, 64, 128, 0.06); + --surface-hover: rgba(0, 0, 0, 0.06); + --success-color: #006400; + --status-success: #006400; + --error-text: #cc0000; + --error-bg: rgba(204, 0, 0, 0.08); + --error-border: #cc0000; + --error-text-soft: #990000; + --destructive-color: #cc0000; + --destructive-hover: #990000; + --action-copy-color: #004080; + --action-copy-hover: #002d5c; + --action-download-color: #006400; + --action-download-hover: #004d00; + --status-banner-bg: rgba(0, 100, 0, 0.1); + --status-banner-border: #006400; + --status-banner-text: #004d00; + --favorite-color: #6d4c00; + --favorite-color-rgb: 109, 76, 0; + --preview-active-bg: rgba(255, 255, 255, 0.25); + --preview-active-text: #ffffff; + --preview-active-border: rgba(255, 255, 255, 0.5); + --tooltip-bg: #000000; + --tooltip-text: #ffffff; + --button-bg: #f0f0f0; + --button-hover-bg: #e0e0e0; + --button-active-bg: #004080; + --input-bg: #ffffff; + --input-border: #555555; + --section-divider: #555555; + --focus-shadow: 0 0 0 3px #004080; + --panel-shadow-soft: none; + --sidebar-edge-shadow: none; + --shell-bg: #ffffff; + --surface-overlay: rgba(0, 0, 0, 0.06); + --surface-overlay-strong: rgba(0, 0, 0, 0.1); + --surface-border-subtle: rgba(0, 0, 0, 0.2); + --backdrop-scrim: rgba(0, 0, 0, 0.65); + --fab-shadow: none; + --glitch-color: #000000; + --glitch-color-rgb: 0, 0, 0; + --switch-border: #004080; + --switch-surface-glow: transparent; + --switch-track-bg: #ffffff; + --switch-track-border: #004080; + --switch-track-checked-bg: #004080; + --switch-track-checked-glow: transparent; + --switch-thumb-bg: #ffffff; + --switch-thumb-glow: transparent; + --switch-thumb-checked-bg: #ffffff; + --switch-thumb-checked-glow: transparent; + --theme-display-font: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + --theme-ui-font: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + --theme-mono-font: Consolas, 'Courier New', monospace; + --theme-radius: 2px; + --focus-ring-color: #004080; + --focus-ring-offset: 2px; + /* WCAG AA category fills (white label text on active cards) */ + --case-color: #303f9f; + --case-color-rgb: 48, 63, 159; + --cipher-color: #00695c; + --cipher-color-rgb: 0, 105, 92; + --concealment-color: #33691e; + --concealment-color-rgb: 51, 105, 30; + --encoding-color: #4527a0; + --encoding-color-rgb: 69, 39, 160; + --format-color: #bf360c; + --format-color-rgb: 191, 54, 12; + --signwriting-color: #4e342e; + --signwriting-color-rgb: 78, 52, 46; + --special-color: #2e7d32; + --special-color-rgb: 46, 125, 50; + --symbol-color: #6d4c00; + --symbol-color-rgb: 109, 76, 0; + --technical-color: #00838f; + --technical-color-rgb: 0, 131, 143; + --unicode-color: #1565c0; + --unicode-color-rgb: 21, 101, 192; + --visual-color: #c62828; + --visual-color-rgb: 198, 40, 40; + --randomizer-color: #ad1457; + --randomizer-color-rgb: 173, 20, 87; + --randomizer-button-bg: var(--button-bg); + --randomizer-button-text: var(--text-color); + --randomizer-button-border: var(--input-border); + --randomizer-button-preview-bg: var(--input-bg); + --randomizer-button-preview-text: var(--text-color); + --randomizer-button-preview-border: var(--input-border); + --randomizer-button-active-bg: var(--randomizer-color); + --randomizer-button-active-text: #ffffff; + --randomizer-button-active-preview-bg: rgba(0, 0, 0, 0.28); + --randomizer-button-active-preview-text: rgba(255, 255, 255, 0.96); + --randomizer-button-active-preview-border: rgba(255, 255, 255, 0.35); + --custom-spelling-color: #bf360c; + --custom-spelling-color-rgb: 191, 54, 12; + --translate-color: #4527a0; + --translate-color-rgb: 69, 39, 160; + --text-primary: var(--text-color); + --text-secondary: var(--text-muted); + --muted-color: var(--text-muted); + --border-color: var(--input-border); + --bg-darkest: var(--input-bg); + --bg-darker: var(--button-bg); + color-scheme: light; +} + +body.theme-light .app-logo h1, +body.light-theme .app-logo h1 { + text-shadow: 0 0 4px rgba(var(--glitch-color-rgb), 0.35), 0 0 8px rgba(var(--glitch-color-rgb), 0.25); + border-color: rgba(var(--glitch-color-rgb), 0.35); + background: rgba(var(--glitch-color-rgb), 0.06); +} + +body.theme-light .app-nav .tab-buttons button.active, +body.light-theme .app-nav .tab-buttons button.active { + color: var(--accent-color); +} + +body.theme-accessible .app-nav .tab-buttons button.active, +body.theme-accessible .utility-tab-btn.active { + color: var(--accent-color); + font-weight: 700; + text-decoration: underline; + text-underline-offset: 3px; +} + +.app-main, +.app-utility, +.utility-panel-body, +.tab-content, +.input-section, +.output-section, +.decode-section { + color: var(--text-color); +} + +h1, h2, h3, h4, h5, h6 { + color: var(--text-color); +} + +small, .hint, .help-text { + color: var(--text-muted); +} + +a { + color: var(--accent-color); +} + +a:hover { + color: var(--accent-hover); } * { @@ -88,12 +761,15 @@ } body { - background-color: var(--main-bg-color); + background-color: var(--shell-bg); color: var(--text-color); font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; - padding: 20px; + padding: 0; + margin: 0; min-height: 100vh; + height: 100vh; + overflow: hidden; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -192,7 +868,7 @@ body { font-size: 0.75em; font-weight: normal; color: var(--text-muted); - background-color: rgba(100, 181, 246, 0.1); + background-color: var(--accent-tint); padding: 2px 8px; border-radius: 4px; } @@ -212,7 +888,7 @@ body { gap: 10px; padding: 10px 12px; border: 1px solid var(--input-border); - background: rgba(100, 181, 246, 0.1); + background: var(--accent-tint); border-left: 3px solid var(--accent-color); border-radius: 6px; margin: 12px 0; @@ -378,13 +1054,18 @@ body { min-height: 46px; } +/* Transform picker dropdown (Splitter, Decoder, etc.) */ +select.transform-select { + width: auto; + max-width: min(100%, 320px); + min-width: min(200px, 100%); +} + /* Decoder: method select beside label (override full-width select defaults) */ select.decoder-method-select { margin-left: 15px; padding: 5px 10px; border-radius: 6px; - width: auto; - max-width: min(100%, 320px); } /* Tokenade text payload block label */ @@ -411,7 +1092,7 @@ select.decoder-method-select { .btn-primary { background: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); border-color: var(--accent-color); } @@ -438,8 +1119,7 @@ select.decoder-method-select { input[type="text"], input[type="number"], input[type="email"], -input[type="password"], -select { +input[type="password"] { width: 100%; max-width: 100%; min-width: 0; /* Allow shrinking below content width */ @@ -455,16 +1135,37 @@ select { box-sizing: border-box; } -/* Select-specific styles for better overflow handling */ select { + width: 100%; + max-width: 100%; + min-width: 0; + padding: 8px 28px 8px 10px; + background-color: var(--input-bg); + color: var(--text-color); + border: 1px solid var(--input-border); + border-radius: 4px; + font-family: inherit; + font-size: 0.9rem; + line-height: 1.4; + transition: all 0.2s ease; + box-sizing: border-box; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - appearance: none; /* Remove default arrow to control width better */ - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23e0e0e0' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + appearance: none; + -webkit-appearance: none; background-repeat: no-repeat; background-position: right 8px center; - padding-right: 28px; /* Space for custom arrow */ +} + +body.theme-dark select, +body.dark-theme select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23e0e0e0' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); +} + +body.theme-light select, +body.light-theme select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%235c6573' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); } /* Option elements inherit text color */ @@ -488,7 +1189,15 @@ input[type="password"]:focus, select:focus { outline: none; border-color: var(--accent-color); - box-shadow: 0 0 0 2px rgba(100, 181, 246, 0.2); + box-shadow: var(--focus-shadow); +} + +input[type="text"]::placeholder, +input[type="number"]::placeholder, +input[type="email"]::placeholder, +input[type="password"]::placeholder, +textarea::placeholder { + color: var(--placeholder-color); } /* Disabled state */ @@ -519,10 +1228,6 @@ textarea { box-sizing: border-box; } -textarea::placeholder { - color: rgba(224, 224, 224, 0.5); -} - textarea:focus { outline: none; border-color: var(--accent-color); @@ -589,15 +1294,304 @@ label small { margin-top: -2px; } -.container { - max-width: 900px; +.container, +.app-root { width: 100%; - margin: 0 auto; - padding: 24px; - background-color: var(--secondary-bg); - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + height: 100vh; + max-width: none; + margin: 0; + padding: 0; + background-color: var(--shell-bg); + border-radius: 0; + box-shadow: none; box-sizing: border-box; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.app-topbar, +header.app-topbar { + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; + flex-shrink: 0; + gap: 8px; + min-height: var(--topbar-height); + height: auto; + margin-bottom: 0; + padding: 0 12px; + border-bottom: 1px solid var(--input-border); + background: var(--secondary-bg); +} + +.app-topbar .app-logo, +.app-topbar .logo { + min-width: 0; + flex: 0 1 auto; + overflow: hidden; +} + +.app-logo h1, +.app-topbar .logo h1 { + display: inline-block; + width: fit-content; + max-width: 100%; + font-size: clamp(0.68rem, 2.8vw, 0.95rem); + letter-spacing: 1.5px; + padding: 6px 8px; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + animation: none; + text-shadow: 0 0 4px var(--glitch-color), 0 0 8px rgba(var(--glitch-color-rgb), 0.5); + box-sizing: border-box; +} + +.app-logo::before, +.app-logo::after, +.app-logo h1::before, +.app-logo h1::after, +.app-topbar .logo::before, +.app-topbar .logo::after, +.app-topbar .logo h1::before, +.app-topbar .logo h1::after { + display: none !important; + content: none !important; +} + +.topbar-actions { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +.topbar-utility-btn { + display: none; + background: var(--button-bg); + color: var(--text-color); + border: 1px solid var(--input-border); + border-radius: 4px; + padding: 8px; + cursor: pointer; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + font-size: 1rem; + transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease; +} + +.topbar-utility-btn:hover { + background: var(--button-hover-bg); + border-color: var(--accent-color); + color: var(--accent-color); +} + +.topbar-utility-btn[aria-expanded="true"] { + border-color: var(--accent-color); + color: var(--accent-color); + background: rgba(var(--accent-color-rgb), 0.1); +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.settings-theme-field { + display: flex; + flex-direction: column; + gap: 6px; + margin: 0; +} + +.settings-theme-select { + font-size: 0.85rem; + font-weight: 500; + color: var(--text-color); + padding: 8px 28px 8px 10px; + border: 1px solid var(--input-border); + border-radius: 4px; + background-color: var(--input-bg); + cursor: pointer; + transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease; + min-height: 38px; + width: 100%; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; +} + +.settings-theme-select:hover, +.settings-theme-select:focus { + color: var(--text-color); + border-color: var(--accent-color); + background-color: var(--button-hover-bg); + outline: none; + box-shadow: var(--focus-shadow); +} + +body.theme-light .settings-theme-select, +body.light-theme .settings-theme-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%235c6573' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); +} + +@media (max-width: 768px) { + body.theme-cyberpunk .app-topbar .logo h1, + body.theme-pliny .app-topbar .logo h1, + body.theme-bt6 .app-topbar .logo h1 { + letter-spacing: 0.06em; + font-size: clamp(0.62rem, 3.2vw, 0.75rem); + padding: 4px 6px; + } +} + +@media (max-width: 480px) { + body.theme-cyberpunk .app-topbar .logo h1 { + letter-spacing: 0.04em; + font-size: 0.62rem; + } + + body.theme-wildwest .app-topbar .logo h1 { + font-size: 0.72rem; + } +} + +.app-shell { + flex: 1; + display: grid; + grid-template-columns: var(--nav-width) minmax(0, 1fr) var(--utility-width); + min-height: 0; + overflow: hidden; +} + +.app-nav { + background: var(--nav-bg); + border-right: 1px solid var(--input-border); + overflow-y: auto; + overflow-x: hidden; + padding: 8px 6px; +} + +.app-main { + overflow-y: auto; + overflow-x: hidden; + min-width: 0; + padding: 16px 20px 24px; + background: var(--main-bg-color); +} + +.tool-content-container { + max-width: 1100px; +} + +.app-utility { + display: flex; + flex-direction: column; + min-height: 0; + background: var(--secondary-bg); + border-left: 1px solid var(--input-border); + overflow: hidden; + container-type: inline-size; +} + +.utility-tab-bar { + display: flex; + flex-wrap: nowrap; + gap: 0; + border-bottom: 1px solid var(--input-border); + background: var(--utility-tab-bg); + flex-shrink: 0; + overflow: hidden; +} + +.utility-tab-btn { + flex: 1 1 0; + min-width: 0; + border: none; + border-bottom: 2px solid transparent; + background: transparent; + color: var(--text-muted); + font-size: clamp(0.52rem, 2.4cqi, 0.68rem); + font-weight: 600; + letter-spacing: 0.02em; + text-transform: none; + padding: 10px 4px; + cursor: pointer; + transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease; + white-space: normal; + line-height: 1.15; + text-align: center; + overflow-wrap: anywhere; + touch-action: manipulation; +} + +.utility-tab-btn:hover { + color: var(--text-color); + background: var(--surface-hover); +} + +.utility-tab-btn.active { + color: var(--accent-color); + border-bottom-color: var(--accent-color); + background: rgba(var(--accent-color-rgb), 0.08); +} + +.utility-backdrop { + display: none; + position: fixed; + inset: var(--topbar-height) 0 0 0; + z-index: 110; + background: var(--backdrop-scrim); +} + +.utility-panels { + flex: 1; + min-height: 0; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.utility-panel { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.utility-panel-toolbar { + display: flex; + justify-content: flex-end; + padding: 8px 12px 0; + flex-shrink: 0; +} + +.utility-panel-body { + flex: 1; + overflow-y: auto; + padding: 12px; + min-height: 0; +} + +.utility-panel-body.copy-history-content, +.utility-panel-body.glitch-token-content, +.utility-panel-body.end-sequence-content, +.utility-panel-body.unicode-panel-content { + padding: 12px; } header { @@ -614,18 +1608,22 @@ header { } .logo h1 { + display: inline-block; + width: fit-content; + max-width: 100%; font-family: 'Courier New', monospace; font-weight: bold; letter-spacing: 2px; text-transform: uppercase; color: var(--glitch-color); text-shadow: 0 0 5px var(--glitch-color), 0 0 10px rgba(var(--glitch-color-rgb), 0.8); - background: linear-gradient(90deg, transparent 0%, rgba(var(--glitch-color-rgb), 0.2) 50%, transparent 100%); + background: rgba(var(--glitch-color-rgb), 0.12); padding: var(--spacing-sm); border: 1px solid rgba(var(--glitch-color-rgb), 0.3); position: relative; overflow: hidden; animation: glitch 3s infinite; + box-sizing: border-box; } /* Glitch animation */ @@ -664,24 +1662,10 @@ header { } } -/* Adding scanline effect */ +/* Logo scanline pseudo disabled — see .app-logo h1::before rule above */ .logo h1::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient( - transparent 0%, - transparent 50%, - rgba(0, 255, 0, 0.2) 50%, - rgba(0, 255, 0, 0.2) 100% - ); - background-size: 100% 4px; - z-index: 1; - pointer-events: none; - opacity: 0.3; + display: none; + content: none; } .theme-toggle { @@ -715,9 +1699,53 @@ h1, h2, h3, h4, h5 { margin-bottom: 15px; } -/* Tabs styling */ +/* Tabs styling — vertical nav in desktop shell */ .tabs { - margin-top: 16px; + margin-top: 0; +} + +.app-nav .tab-buttons { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + gap: 2px; + margin-bottom: 0; +} + +.app-nav .tab-buttons button { + width: 100%; + justify-content: flex-start; + border-radius: 6px; + border: none; + border-left: 3px solid transparent; + padding: 10px 12px; + font-size: 0.82rem; + background: transparent; +} + +.app-nav .tab-buttons button i { + width: 18px; + text-align: center; + flex-shrink: 0; +} + +.app-nav .tab-buttons button .nav-label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.app-nav .tab-buttons button.active { + background: rgba(var(--accent-color-rgb), 0.15); + color: var(--accent-color); + border-left-color: var(--accent-color); + font-weight: 600; +} + +.app-nav .tab-buttons button:hover:not(.active) { + background: var(--surface-hover); + border-color: transparent; + color: var(--text-color); } .tab-buttons { @@ -747,7 +1775,7 @@ h1, h2, h3, h4, h5 { .tab-buttons button.active { background-color: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); border-color: var(--accent-color); font-weight: 500; } @@ -777,11 +1805,11 @@ h1, h2, h3, h4, h5 { .mobile-tool-dropdown { width: 100%; min-height: 44px; - padding: 10px 12px; + padding: 10px 36px 10px 12px; font-size: 0.95rem; border-radius: 6px; border: 1px solid var(--input-border); - background: var(--input-bg); + background-color: var(--input-bg); color: var(--text-color); cursor: pointer; -webkit-appearance: none; @@ -789,16 +1817,21 @@ h1, h2, h3, h4, h5 { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; - padding-right: 36px; } -body.dark-theme .mobile-tool-dropdown { +body.dark-theme .mobile-tool-dropdown, +body.theme-dark .mobile-tool-dropdown { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23b0b0b0' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); } +body.light-theme .mobile-tool-dropdown, +body.theme-light .mobile-tool-dropdown { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%235c6573' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); +} + .transform-button.active { background-color: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); border-color: var(--accent-color); } @@ -853,7 +1886,7 @@ body.dark-theme .mobile-tool-dropdown { font-size: 0.85em; font-weight: normal; color: var(--text-muted); - background-color: rgba(100, 181, 246, 0.1); + background-color: var(--accent-tint); padding: 4px 10px; border-radius: 4px; } @@ -869,7 +1902,7 @@ body.dark-theme .mobile-tool-dropdown { font-size: 0.7em; font-weight: normal; color: var(--text-muted); - background-color: rgba(100, 181, 246, 0.1); + background-color: var(--accent-tint); padding: 2px 8px; border-radius: 12px; } @@ -984,7 +2017,7 @@ body.dark-theme .mobile-tool-dropdown { left: 0; width: 100%; height: 3px; - background: linear-gradient(to right, var(--special-color), var(--encoding-color), var(--cipher-color), var(--visual-color)); + background: var(--accent-color); } .emoji-library-title { @@ -1021,7 +2054,7 @@ body.dark-theme .mobile-tool-dropdown { align-items: center; gap: 6px; padding: 4px 8px; - background-color: rgba(0, 0, 0, 0.1); + background-color: var(--surface-overlay); border-radius: 4px; max-width: fit-content; } @@ -1068,7 +2101,7 @@ body.dark-theme .mobile-tool-dropdown { left: 0; width: 100%; height: 4px; - background: linear-gradient(to right, var(--unicode-color), var(--special-color), var(--encoding-color)); + background: var(--accent-color); z-index: 1; } @@ -1084,7 +2117,7 @@ body.dark-theme .mobile-tool-dropdown { } .emoji-grid-note { - background: linear-gradient(to right, rgba(102, 187, 106, 0.05), rgba(126, 87, 194, 0.05)); + background: rgba(102, 187, 106, 0.05); padding: 10px 15px; border-radius: 6px; margin-bottom: 12px; @@ -1111,7 +2144,7 @@ body.dark-theme .mobile-tool-dropdown { margin-bottom: 15px; text-align: center; border-bottom: 1px solid var(--input-border); - background: linear-gradient(to right, rgba(66, 165, 245, 0.02), rgba(126, 87, 194, 0.02)); + background: rgba(66, 165, 245, 0.03); } .emoji-header h3 { @@ -1154,7 +2187,7 @@ body.dark-theme .mobile-tool-dropdown { padding: 4px 8px; margin-top: 8px; border-radius: 12px; - background: linear-gradient(to right, rgba(66, 165, 245, 0.05), rgba(126, 87, 194, 0.05)); + background: rgba(66, 165, 245, 0.05); display: inline-block; margin-left: auto; margin-right: auto; @@ -1221,7 +2254,7 @@ body.dark-theme .mobile-tool-dropdown { .emoji-category-tab.active, .emoji-category-tab:hover { background: var(--accent-color); - color: white; + color: var(--text-on-accent); } /* Emoji grid container with increased specificity */ @@ -1270,7 +2303,7 @@ body.dark-theme .mobile-tool-dropdown { left: 0; width: 100%; height: 3px; - background: linear-gradient(to right, var(--special-color), var(--encoding-color)); + background: var(--accent-color); opacity: 0; transition: opacity 0.2s ease; } @@ -1326,7 +2359,7 @@ body.dark-theme .mobile-tool-dropdown { .emoji-button.selected { background-color: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); border-color: var(--accent-color); } @@ -1351,7 +2384,7 @@ body.dark-theme .mobile-tool-dropdown { .emoji-category-tab.active { background-color: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); border-color: var(--accent-color); } @@ -1390,7 +2423,7 @@ body.dark-theme .mobile-tool-dropdown { .invisible-button.active { background-color: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); border-style: solid; } @@ -1415,7 +2448,184 @@ body.dark-theme .mobile-tool-dropdown { border: 1px solid var(--input-border); } -/* Transform category legend styling */ +/* Transform filter bar (search + category filters) */ +.transform-filter-bar { + display: flex; + flex-direction: column; + gap: 14px; + margin-bottom: 16px; + padding: 14px 16px; + border-radius: 10px; + border: 1px solid var(--input-border); + background: color-mix(in srgb, var(--button-bg) 65%, var(--main-bg-color)); +} + +.transform-search-wrap { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + padding: 0 14px; + min-height: 42px; + border-radius: 8px; + border: 1px solid var(--input-border); + background: var(--input-bg); + box-sizing: border-box; +} + +.transform-search-icon { + color: var(--text-color); + opacity: 0.45; + flex-shrink: 0; + font-size: 0.9rem; +} + +.transform-search-wrap input[type="search"] { + flex: 1; + min-width: 0; + height: 40px; + padding: 0; + border: none; + background: transparent; + color: var(--text-color); + font: inherit; + font-size: 0.92rem; + outline: none; + line-height: 1.4; +} + +.transform-search-wrap input[type="search"]::placeholder { + color: var(--text-color); + opacity: 0.4; +} + +.transform-search-wrap input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; +} + +.transform-filter-clear { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border: none; + background: var(--button-bg); + color: var(--text-color); + opacity: 0.7; + cursor: pointer; + border-radius: 6px; + flex-shrink: 0; +} + +.transform-filter-clear:hover { + opacity: 1; + background: var(--button-hover-bg); +} + +.transform-category-filters { + display: flex; + flex-direction: column; + gap: 8px; +} + +.transform-filter-label { + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--text-color); + opacity: 0.5; +} + +.transform-category-chips { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.category-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + font-size: 0.8rem; + font-weight: 500; + line-height: 1.2; + border-radius: 999px; + border: 1px solid var(--input-border); + background: var(--main-bg-color); + color: var(--text-color); + cursor: pointer; + transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; + text-transform: capitalize; + box-shadow: none; +} + +.category-chip::before { + content: ''; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--chip-accent, var(--accent-color)); + flex-shrink: 0; + opacity: 0.9; +} + +.category-chip.category-chip-all::before { + background: var(--accent-color); +} + +.category-chip:hover { + border-color: color-mix(in srgb, var(--chip-accent, var(--accent-color)) 45%, var(--input-border)); + background: var(--button-hover-bg); +} + +.category-chip.active-filter { + background: var(--chip-accent, var(--accent-color)); + border-color: var(--chip-accent, var(--accent-color)); + color: var(--text-on-accent); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15); +} + +.category-chip.active-filter::before { + background: var(--text-on-accent); + opacity: 0.9; +} + +.category-chip.transform-category-case { --chip-accent: var(--case-color); } +.category-chip.transform-category-cipher { --chip-accent: var(--cipher-color); } +.category-chip.transform-category-concealment { --chip-accent: var(--concealment-color); } +.category-chip.transform-category-encoding { --chip-accent: var(--encoding-color); } +.category-chip.transform-category-format { --chip-accent: var(--format-color); } +.category-chip.transform-category-signwriting { --chip-accent: var(--signwriting-color); } +.category-chip.transform-category-special { --chip-accent: var(--special-color); } +.category-chip.transform-category-symbol { --chip-accent: var(--symbol-color); } +.category-chip.transform-category-technical { --chip-accent: var(--technical-color); } +.category-chip.transform-category-unicode { --chip-accent: var(--unicode-color); } +.category-chip.transform-category-visual { --chip-accent: var(--visual-color); } +.category-chip.transform-category-randomizer { --chip-accent: var(--randomizer-color); } +.category-chip.transform-category-custom_spelling { --chip-accent: var(--custom-spelling-color); } + +.transform-filter-empty { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + padding: 24px 16px; + text-align: center; + color: var(--text-color); + opacity: 0.8; + border: 1px dashed var(--input-border); + border-radius: 8px; +} + +.transform-filter-empty i { + font-size: 1.25rem; + opacity: 0.6; +} + +/* Legacy alias — category filter chips reuse legend-item styles */ .transform-category-legend { display: flex; flex-wrap: wrap; @@ -1458,55 +2668,64 @@ body.dark-theme .mobile-tool-dropdown { background-color: var(--button-hover-bg); } +.legend-item.active-filter, .legend-item.active-category { background-color: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); transform: translateY(-1px); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } -.legend-item.transform-category-encoding { - border-left-color: var(--encoding-color); +.legend-item.transform-category-case { + border-left-color: var(--case-color); } .legend-item.transform-category-cipher { border-left-color: var(--cipher-color); } -.legend-item.transform-category-visual { - border-left-color: var(--visual-color); +.legend-item.transform-category-concealment { + border-left-color: var(--concealment-color); +} + +.legend-item.transform-category-encoding { + border-left-color: var(--encoding-color); } .legend-item.transform-category-format { border-left-color: var(--format-color); } -.legend-item.transform-category-unicode { - border-left-color: var(--unicode-color); +.legend-item.transform-category-signwriting { + border-left-color: var(--signwriting-color); } .legend-item.transform-category-special { border-left-color: var(--special-color); } -.legend-item.transform-category-fantasy { - border-left-color: var(--fantasy-color); -} - -.legend-item.transform-category-ancient { - border-left-color: var(--ancient-color); +.legend-item.transform-category-symbol { + border-left-color: var(--symbol-color); } .legend-item.transform-category-technical { border-left-color: var(--technical-color); } +.legend-item.transform-category-unicode { + border-left-color: var(--unicode-color); +} + +.legend-item.transform-category-visual { + border-left-color: var(--visual-color); +} + .legend-item.transform-category-randomizer { border-left-color: var(--randomizer-color); } -.legend-item.transform-category-case { - border-left-color: var(--case-color); +.legend-item.transform-category-custom_spelling { + border-left-color: var(--custom-spelling-color); } .transform-section:focus-within { @@ -1612,7 +2831,6 @@ body.dark-theme .mobile-tool-dropdown { } .last-used-section { - border: 2px dashed var(--accent-color); background: rgba(var(--accent-color-rgb), 0.05); } @@ -1622,61 +2840,68 @@ body.dark-theme .mobile-tool-dropdown { } .last-used-section .transform-buttons .transform-button-group, -#category-randomizer .transform-buttons .transform-button-group, .favorites-section .transform-buttons .transform-button-group { - width: 100%; + min-width: 0; } .legend-item { position: relative; } -.category-title.transform-category-encoding { - border-left-color: var(--encoding-color); +.category-title.transform-category-case { + border-left-color: var(--case-color); } .category-title.transform-category-cipher { border-left-color: var(--cipher-color); } -.category-title.transform-category-visual { - border-left-color: var(--visual-color); +.category-title.transform-category-concealment { + border-left-color: var(--concealment-color); +} + +.category-title.transform-category-encoding { + border-left-color: var(--encoding-color); } .category-title.transform-category-format { border-left-color: var(--format-color); } -.category-title.transform-category-unicode { - border-left-color: var(--unicode-color); +.category-title.transform-category-signwriting { + border-left-color: var(--signwriting-color); } .category-title.transform-category-special { border-left-color: var(--special-color); } -.category-title.transform-category-fantasy { - border-left-color: var(--fantasy-color); -} - -.category-title.transform-category-ancient { - border-left-color: var(--ancient-color); +.category-title.transform-category-symbol { + border-left-color: var(--symbol-color); } .category-title.transform-category-technical { border-left-color: var(--technical-color); } +.category-title.transform-category-unicode { + border-left-color: var(--unicode-color); +} + +.category-title.transform-category-visual { + border-left-color: var(--visual-color); +} + .category-title.transform-category-randomizer { border-left-color: var(--randomizer-color); } -.category-title.transform-category-case { - border-left-color: var(--case-color); +.category-title.transform-category-custom_spelling { + border-left-color: var(--custom-spelling-color); } .category-title.transform-category-translate { - border-left-color: var(--ancient-color); + border-left-color: var(--translate-color); text-transform: none; margin-bottom: 10px; padding-bottom: 6px; @@ -1699,11 +2924,11 @@ body.dark-theme .mobile-tool-dropdown { } .pc-error { - color: #f07178; + color: var(--error-text); font-size: 0.85rem; padding: 8px 10px; - background: rgba(240, 113, 120, 0.08); - border: 1px solid rgba(240, 113, 120, 0.35); + background: var(--error-bg); + border: 1px solid var(--error-border); border-radius: 4px; } @@ -1717,17 +2942,187 @@ body.dark-theme .mobile-tool-dropdown { margin-bottom: 10px; } -.translate-model-select { +/* Shared OpenRouter model dropdown (all AI tools) */ +.openrouter-model-picker { + display: flex; + flex-direction: column; + gap: 6px; width: 100%; - padding: 6px 10px; + margin-bottom: 10px; +} + +.openrouter-model-label { + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-muted); +} + +.openrouter-model-row { + display: flex; + align-items: stretch; + gap: 8px; +} + +.openrouter-model-select, +.translate-model-select { + flex: 1; + min-width: 0; + width: 100%; + padding: 6px 28px 6px 10px; font-size: 0.8rem; border-radius: 4px; border: 1px solid var(--input-border); - background: var(--input-bg); + background-color: var(--input-bg); color: var(--text-color); box-sizing: border-box; } +.openrouter-model-refresh { + flex-shrink: 0; + width: 36px; + min-height: 32px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--input-border); + border-radius: 4px; + background: var(--button-bg); + color: var(--text-muted); + cursor: pointer; +} + +.openrouter-model-refresh:hover:not(:disabled) { + color: var(--accent-color); + border-color: var(--accent-color); + background: var(--button-hover-bg); +} + +.openrouter-model-refresh:disabled { + opacity: 0.6; + cursor: wait; +} + +.openrouter-model-hint { + display: block; + font-size: 0.72rem; + color: var(--text-muted); + line-height: 1.35; +} + +.openrouter-model-hint-error { + color: var(--error-text); +} + +.openrouter-model-hint-router { + color: var(--accent-color); +} + +.openrouter-models-section { + margin-bottom: 12px; +} + +.openrouter-models-toolbar { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: 8px 0; + align-items: center; +} + +.openrouter-models-search { + flex: 1 1 160px; + min-width: 0; + padding: 6px 10px; + font-size: 0.8rem; + border: 1px solid var(--input-border); + border-radius: 4px; + background: var(--input-bg); + color: var(--text-color); +} + +.openrouter-models-action { + padding: 6px 10px; + font-size: 0.75rem; + border: 1px solid var(--input-border); + border-radius: 4px; + background: var(--button-bg); + color: var(--text-color); + cursor: pointer; +} + +.openrouter-models-action:hover:not(:disabled) { + border-color: var(--accent-color); + color: var(--accent-color); +} + +.openrouter-models-action:disabled { + opacity: 0.6; + cursor: wait; +} + +.openrouter-models-summary { + display: block; + margin-bottom: 8px; + font-size: 0.72rem; + color: var(--text-muted); +} + +.openrouter-models-list { + max-height: 280px; + overflow-y: auto; + border: 1px solid var(--input-border); + border-radius: 6px; + background: var(--input-bg); + padding: 4px; +} + +.openrouter-model-toggle-row { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 6px 8px; + border-radius: 4px; + cursor: pointer; + font-size: 0.78rem; + color: var(--text-color); +} + +.openrouter-model-toggle-row:hover { + background: var(--surface-hover); +} + +.openrouter-model-toggle-row.is-virtual-router { + background: var(--accent-tint-subtle); + border: 1px solid var(--accent-tint); + margin-bottom: 4px; +} + +.openrouter-model-toggle-row input[type="checkbox"] { + margin-top: 2px; + flex-shrink: 0; + accent-color: var(--accent-color); +} + +.openrouter-model-toggle-text { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.openrouter-model-toggle-name { + line-height: 1.3; +} + +.openrouter-model-toggle-summary { + color: var(--text-muted); + font-size: 0.68rem; + line-height: 1.35; +} + .translate-subsection { margin-bottom: 10px; } @@ -1774,13 +3169,13 @@ button.translate-custom-label-btn { button.translate-custom-label-btn:hover { opacity: 1; - background: rgba(var(--ancient-color-rgb), 0.08); + background: rgba(var(--translate-color-rgb), 0.08); border-color: var(--input-border); } button.translate-custom-label-btn:focus { outline: none; - box-shadow: 0 0 0 2px rgba(var(--ancient-color-rgb), 0.35); + box-shadow: 0 0 0 2px rgba(var(--translate-color-rgb), 0.35); } .translate-custom-toggle-end { @@ -1798,8 +3193,8 @@ button.translate-custom-label-btn:focus { button.translate-custom-label-btn:hover .translate-custom-toggle-end { background: var(--button-hover-bg); - border-color: var(--ancient-color); - color: var(--ancient-color); + border-color: var(--translate-color); + color: var(--translate-color); } .translate-lang-grid { @@ -1810,6 +3205,10 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { box-sizing: border-box; } +.translate-lang-card { + min-width: 0; +} + .translate-lang-btn { position: relative; display: flex; @@ -1827,12 +3226,14 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { cursor: pointer; transition: all 0.2s ease; box-sizing: border-box; - background: linear-gradient(to right, rgba(var(--ancient-color-rgb), 0.06), var(--button-bg)); + background: var(--button-bg); + border-left: 3px solid rgba(var(--translate-color-rgb), 0.45); } .translate-lang-btn:hover:not(:disabled) { - background: linear-gradient(to right, rgba(var(--ancient-color-rgb), 0.14), var(--button-hover-bg)); - box-shadow: 0 2px 8px rgba(var(--ancient-color-rgb), 0.18); + background: var(--button-hover-bg); + border-left-color: var(--translate-color); + box-shadow: none; } .translate-lang-btn:disabled { @@ -1841,7 +3242,8 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { } .translate-lang-exotic { - background: linear-gradient(to right, rgba(var(--ancient-color-rgb), 0.09), var(--button-bg)); + background: var(--button-bg); + border-left-color: rgba(var(--translate-color-rgb), 0.65); } .translate-lang-custom { @@ -1856,7 +3258,7 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { } .translate-custom-flag { - color: var(--ancient-color); + color: var(--translate-color); font-size: 1.1rem; } @@ -1883,7 +3285,7 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { .translate-remove:hover { opacity: 1; - color: #f07178; + color: var(--error-text); } .translate-add-form { @@ -1917,7 +3319,7 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { .translate-add-btn:hover:not(:disabled) { background: var(--button-hover-bg); - border-color: var(--ancient-color); + border-color: var(--translate-color); } .translate-empty-custom { @@ -1927,25 +3329,45 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { } .transform-buttons { - display: flex; - flex-wrap: wrap; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 8px; margin-bottom: 16px; - width: 100%; /* Ensure flex doesn't overflow container */ + width: 100%; box-sizing: border-box; } +.transform-button-group { + min-width: 0; + container-type: inline-size; + container-name: transform-card; +} + +/* Under-card actions (Options / Favorite) — visible only in Accessible theme */ +.transform-card-actions { + display: none; +} + #category-randomizer .transform-buttons { - display: flex; + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 8px; +} + +#category-randomizer .transform-buttons .transform-button-group { + grid-column: 1 / -1; + width: 100%; + min-width: 0; } #category-randomizer .transform-buttons .transform-button { width: 100%; + min-width: 0; } #category-randomizer .transform-buttons .transform-button .transform-preview { - max-width: 300px; + max-width: none; } /* Carrier styling */ @@ -1969,22 +3391,28 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { font-weight: 500; } -/* Preview styling */ +/* Preview styling — scales with card width; wraps long encoded output */ .transform-preview { margin-top: auto; width: 100%; - font-size: 0.65rem; - padding: 3px 4px; - background-color: rgba(0, 0, 0, 0.12); - border-radius: 3px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.15; max-width: 100%; - display: block; - border: 1px solid rgba(0, 0, 0, 0.06); + font-size: clamp(0.55rem, 6.8cqi, 0.84rem); + font-family: 'Fira Code', monospace; + padding: 4px 5px; + background-color: var(--surface-overlay-strong); + border-radius: 3px; + white-space: normal; + word-break: break-all; + overflow-wrap: anywhere; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + line-height: 1.25; + min-height: calc(2 * 1.25em + 8px); + border: 1px solid var(--surface-border-subtle); opacity: 0.85; + transition: all 0.2s ease; } .preview-label { @@ -2004,7 +3432,7 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { .encoded-preview { display: block; padding: 6px 8px; - background-color: rgba(0, 0, 0, 0.25); + background-color: var(--surface-overlay-strong); border-radius: 4px; font-family: 'Fira Code', monospace; word-break: break-all; @@ -2023,7 +3451,7 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { position: absolute; top: 4px; right: 4px; - background: rgba(255, 255, 255, 0.1); + background: var(--surface-overlay-strong); border-radius: 3px; } @@ -2073,7 +3501,7 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { } .end-sequence-header h3 { - color: #e85d4c; + color: var(--visual-color); } .unicode-panel-header { @@ -2110,11 +3538,11 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { } .app-sidebar-header .clear-history-button:hover { - color: #ff6b6b; + color: var(--destructive-color); } .end-sequence-header .close-button:hover { - color: #e85d4c; + color: var(--destructive-hover); } /* Scrollable body */ @@ -2216,7 +3644,7 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end { .endsequence-category h4 { font-size: 0.95rem; margin: 0 0 10px; - color: #e85d4c; + color: var(--visual-color); border-bottom: 1px solid var(--input-border); padding-bottom: 6px; } @@ -2247,7 +3675,7 @@ button.endsequence-item { button.endsequence-item:hover { background: var(--button-bg); - border-color: rgba(232, 93, 76, 0.45); + border-color: rgba(var(--visual-color-rgb), 0.45); } button.endsequence-item:focus { @@ -2255,7 +3683,7 @@ button.endsequence-item:focus { } button.endsequence-item:focus-visible { - outline: 2px solid #e85d4c; + outline: 2px solid var(--visual-color); outline-offset: 2px; } @@ -2273,7 +3701,7 @@ button.endsequence-item:focus-visible { align-items: center; padding: 2px 0 0 4px; opacity: 0.55; - color: #e85d4c; + color: var(--visual-color); pointer-events: none; } @@ -2338,7 +3766,7 @@ button.endsequence-item:hover .endsequence-copy-affordance { .copy-token-button:hover { background-color: var(--accent-color); - color: var(--button-bg); + color: var(--text-on-accent); border-color: var(--accent-color); } @@ -2513,9 +3941,9 @@ button.endsequence-item:hover .endsequence-copy-affordance { } .remove-history-button:hover { - background-color: #ff6b6b; - border-color: #ff6b6b; - color: white; + background-color: var(--destructive-color); + border-color: var(--destructive-color); + color: var(--text-on-accent); } .history-button { @@ -2545,200 +3973,185 @@ button.endsequence-item:hover .endsequence-copy-affordance { color: var(--text-color); } -.transform-preview { - font-size: 0.75rem; - font-family: 'Fira Code', monospace; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 100%; - max-width: 180px; - padding: 3px 6px; - border-radius: 3px; - background-color: rgba(0, 0, 0, 0.2); - margin-top: 4px; - transition: all 0.2s ease; -} - -.transform-category-encoding .transform-preview { - color: rgba(var(--encoding-color-rgb), 0.9); +.transform-category-case .transform-preview { + color: rgba(var(--case-color-rgb), 0.9); } .transform-category-cipher .transform-preview { color: rgba(var(--cipher-color-rgb), 0.9); } -.transform-category-visual .transform-preview { - color: rgba(var(--visual-color-rgb), 0.9); +.transform-category-concealment .transform-preview { + color: rgba(var(--concealment-color-rgb), 0.9); +} + +.transform-category-encoding .transform-preview { + color: rgba(var(--encoding-color-rgb), 0.9); } .transform-category-format .transform-preview { color: rgba(var(--format-color-rgb), 0.9); } -.transform-category-unicode .transform-preview { - color: rgba(var(--unicode-color-rgb), 0.9); +.transform-category-signwriting .transform-preview { + color: rgba(var(--signwriting-color-rgb), 0.9); + font-family: 'Noto Sans SignWriting', 'Fira Code', monospace; + line-height: 1.6; + letter-spacing: 0.05em; + -webkit-line-clamp: 2; + font-size: clamp(0.48rem, 5.8cqi, 0.68rem); } .transform-category-special .transform-preview { color: rgba(var(--special-color-rgb), 0.9); } -.transform-category-fantasy .transform-preview { - color: rgba(var(--fantasy-color-rgb), 0.9); -} - -.transform-category-ancient .transform-preview { - color: rgba(var(--ancient-color-rgb), 0.9); +.transform-category-symbol .transform-preview { + color: rgba(var(--symbol-color-rgb), 0.9); } .transform-category-technical .transform-preview { color: rgba(var(--technical-color-rgb), 0.9); } -.transform-category-randomizer .transform-preview { - color: #e1baff; - text-shadow: 0 0 8px rgba(225,186,255,.35); +.transform-category-unicode .transform-preview { + color: rgba(var(--unicode-color-rgb), 0.9); } -.transform-category-case .transform-preview { - color: rgba(var(--case-color-rgb), 0.9); +.transform-category-visual .transform-preview { + color: rgba(var(--visual-color-rgb), 0.9); +} + +.transform-category-randomizer .transform-preview { + color: rgba(var(--randomizer-color-rgb), 0.95); + text-shadow: 0 0 8px rgba(var(--randomizer-color-rgb), 0.35); +} + +.transform-category-custom_spelling .transform-preview { + color: rgba(var(--custom-spelling-color-rgb), 0.9); +} + +.transform-category-translate .transform-preview { + color: rgba(var(--translate-color-rgb), 0.9); +} + +.output-container.signwriting-output textarea { + font-family: 'Noto Sans SignWriting', 'Fira Code', 'Courier New', monospace; + line-height: 2; + letter-spacing: 0.1em; } .transform-button:hover .transform-preview { - background-color: rgba(0, 0, 0, 0.18); + background-color: var(--surface-overlay-strong); opacity: 1; } .transform-button.active .transform-preview { - background-color: rgba(0, 0, 0, 0.3); - color: rgba(255, 255, 255, 0.9); + background-color: var(--preview-active-bg); + color: var(--preview-active-text); + border-color: var(--preview-active-border); } .transform-button { - padding: 8px; + padding: 8px 6px; height: auto; border-radius: 4px; font-size: 0.8rem; position: relative; display: flex; flex-direction: column; - align-items: center; + align-items: stretch; gap: 4px; width: 100%; + min-width: 0; font-weight: 500; text-align: center; border: 1px solid var(--input-border); background: var(--button-bg); color: var(--text-color); transition: all 0.2s ease; - overflow: visible; - min-height: 70px; + overflow: hidden; + min-height: 88px; +} + +.transform-button-name { + display: -webkit-box; + width: 100%; + max-width: 100%; + min-width: 0; + line-height: 1.25; + font-size: clamp(0.65rem, 7.2cqi, 0.88rem); + overflow-wrap: anywhere; + word-break: break-word; + hyphens: auto; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + padding: 0 14px; + flex-shrink: 0; } [class*="transform-category-"] { - background: linear-gradient(to right, transparent, var(--button-bg)); - transition: all 0.2s ease; + background: var(--button-bg); + transition: border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease; + border-left: 3px solid transparent; } -.transform-category-encoding { - background: linear-gradient(to right, rgba(var(--encoding-color-rgb), 0.05), var(--button-bg)); -} +.transform-category-case { border-left-color: rgba(var(--case-color-rgb), 0.45); } +.transform-category-case:hover { background: var(--button-hover-bg); border-left-color: var(--case-color); } +.transform-category-case.active { background: var(--case-color); border-color: var(--case-color); border-left-color: var(--case-color); box-shadow: none; } -.transform-category-encoding:hover { - background: linear-gradient(to right, rgba(var(--encoding-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--encoding-color-rgb), 0.2); -} +.transform-category-cipher { border-left-color: rgba(var(--cipher-color-rgb), 0.45); } +.transform-category-cipher:hover { background: var(--button-hover-bg); border-left-color: var(--cipher-color); } +.transform-category-cipher.active { background: var(--cipher-color); border-color: var(--cipher-color); border-left-color: var(--cipher-color); box-shadow: none; } -.transform-category-cipher { - background: linear-gradient(to right, rgba(var(--cipher-color-rgb), 0.05), var(--button-bg)); -} +.transform-category-concealment { border-left-color: rgba(var(--concealment-color-rgb), 0.45); } +.transform-category-concealment:hover { background: var(--button-hover-bg); border-left-color: var(--concealment-color); } +.transform-category-concealment.active { background: var(--concealment-color); border-color: var(--concealment-color); border-left-color: var(--concealment-color); box-shadow: none; } -.transform-category-cipher:hover { - background: linear-gradient(to right, rgba(var(--cipher-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--cipher-color-rgb), 0.2); -} +.transform-category-encoding { border-left-color: rgba(var(--encoding-color-rgb), 0.45); } +.transform-category-encoding:hover { background: var(--button-hover-bg); border-left-color: var(--encoding-color); } +.transform-category-encoding.active { background: var(--encoding-color); border-color: var(--encoding-color); border-left-color: var(--encoding-color); box-shadow: none; } -.transform-category-visual { - background: linear-gradient(to right, rgba(var(--visual-color-rgb), 0.05), var(--button-bg)); -} +.transform-category-format { border-left-color: rgba(var(--format-color-rgb), 0.45); } +.transform-category-format:hover { background: var(--button-hover-bg); border-left-color: var(--format-color); } +.transform-category-format.active { background: var(--format-color); border-color: var(--format-color); border-left-color: var(--format-color); box-shadow: none; } -.transform-category-visual:hover { - background: linear-gradient(to right, rgba(var(--visual-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--visual-color-rgb), 0.2); -} +.transform-category-signwriting { border-left-color: rgba(var(--signwriting-color-rgb), 0.45); } +.transform-category-signwriting:hover { background: var(--button-hover-bg); border-left-color: var(--signwriting-color); } +.transform-category-signwriting.active { background: var(--signwriting-color); border-color: var(--signwriting-color); border-left-color: var(--signwriting-color); box-shadow: none; } -.transform-category-format { - background: linear-gradient(to right, rgba(var(--format-color-rgb), 0.05), var(--button-bg)); -} +.transform-category-special { border-left-color: rgba(var(--special-color-rgb), 0.45); } +.transform-category-special:hover { background: var(--button-hover-bg); border-left-color: var(--special-color); } +.transform-category-special.active { background: var(--special-color); border-color: var(--special-color); border-left-color: var(--special-color); box-shadow: none; } -.transform-category-format:hover { - background: linear-gradient(to right, rgba(var(--format-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--format-color-rgb), 0.2); -} +.transform-category-symbol { border-left-color: rgba(var(--symbol-color-rgb), 0.45); } +.transform-category-symbol:hover { background: var(--button-hover-bg); border-left-color: var(--symbol-color); } +.transform-category-symbol.active { background: var(--symbol-color); border-color: var(--symbol-color); border-left-color: var(--symbol-color); box-shadow: none; } -.transform-category-unicode { - background: linear-gradient(to right, rgba(var(--unicode-color-rgb), 0.05), var(--button-bg)); -} +.transform-category-technical { border-left-color: rgba(var(--technical-color-rgb), 0.45); } +.transform-category-technical:hover { background: var(--button-hover-bg); border-left-color: var(--technical-color); } +.transform-category-technical.active { background: var(--technical-color); border-color: var(--technical-color); border-left-color: var(--technical-color); box-shadow: none; } -.transform-category-unicode:hover { - background: linear-gradient(to right, rgba(var(--unicode-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--unicode-color-rgb), 0.2); -} +.transform-category-unicode { border-left-color: rgba(var(--unicode-color-rgb), 0.45); } +.transform-category-unicode:hover { background: var(--button-hover-bg); border-left-color: var(--unicode-color); } +.transform-category-unicode.active { background: var(--unicode-color); border-color: var(--unicode-color); border-left-color: var(--unicode-color); box-shadow: none; } -.transform-category-special { - background: linear-gradient(to right, rgba(var(--special-color-rgb), 0.05), var(--button-bg)); -} +.transform-category-visual { border-left-color: rgba(var(--visual-color-rgb), 0.45); } +.transform-category-visual:hover { background: var(--button-hover-bg); border-left-color: var(--visual-color); } +.transform-category-visual.active { background: var(--visual-color); border-color: var(--visual-color); border-left-color: var(--visual-color); box-shadow: none; } -.transform-category-special:hover { - background: linear-gradient(to right, rgba(var(--special-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--special-color-rgb), 0.2); -} +.transform-category-randomizer { border-left-color: rgba(var(--randomizer-color-rgb), 0.45); } +.transform-category-randomizer:hover { background: var(--button-hover-bg); border-left-color: var(--randomizer-color); } +.transform-category-randomizer.active { background: var(--randomizer-color); border-color: var(--randomizer-color); border-left-color: var(--randomizer-color); box-shadow: none; } -.transform-category-fantasy { - background: linear-gradient(to right, rgba(var(--fantasy-color-rgb), 0.05), var(--button-bg)); -} +.transform-category-custom_spelling { border-left-color: rgba(var(--custom-spelling-color-rgb), 0.45); } +.transform-category-custom_spelling:hover { background: var(--button-hover-bg); border-left-color: var(--custom-spelling-color); } +.transform-category-custom_spelling.active { background: var(--custom-spelling-color); border-color: var(--custom-spelling-color); border-left-color: var(--custom-spelling-color); box-shadow: none; } -.transform-category-fantasy:hover { - background: linear-gradient(to right, rgba(var(--fantasy-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--fantasy-color-rgb), 0.2); -} - -.transform-category-ancient { - background: linear-gradient(to right, rgba(var(--ancient-color-rgb), 0.05), var(--button-bg)); -} - -.transform-category-ancient:hover { - background: linear-gradient(to right, rgba(var(--ancient-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--ancient-color-rgb), 0.2); -} - -.transform-category-technical { - background: linear-gradient(to right, rgba(var(--technical-color-rgb), 0.05), var(--button-bg)); -} - -.transform-category-technical:hover { - background: linear-gradient(to right, rgba(var(--technical-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--technical-color-rgb), 0.2); -} - -.transform-category-randomizer { - background: linear-gradient(to right, rgba(var(--randomizer-color-rgb), 0.05), var(--button-bg)); -} - -.transform-category-randomizer:hover { - background: linear-gradient(to right, rgba(var(--randomizer-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--randomizer-color-rgb), 0.2); -} - -.transform-category-case { - background: linear-gradient(to right, rgba(var(--case-color-rgb), 0.05), var(--button-bg)); -} - -.transform-category-case:hover { - background: linear-gradient(to right, rgba(var(--case-color-rgb), 0.15), var(--button-hover-bg)); - box-shadow: 0 3px 10px rgba(var(--case-color-rgb), 0.2); -} +.transform-category-translate { border-left-color: rgba(var(--translate-color-rgb), 0.45); } +.transform-category-translate:hover { background: var(--button-hover-bg); border-left-color: var(--translate-color); } +.transform-category-translate.active { background: var(--translate-color); border-color: var(--translate-color); border-left-color: var(--translate-color); box-shadow: none; } .transform-button:before { content: ''; @@ -2756,8 +4169,6 @@ button.endsequence-item:hover .endsequence-copy-affordance { .transform-button:hover { background: var(--button-hover-bg); border-color: var(--accent-color); - transform: translateY(-1px); - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); } .transform-button:hover:before { @@ -2767,10 +4178,9 @@ button.endsequence-item:hover .endsequence-copy-affordance { .transform-button.active { background: var(--button-active-bg); - color: var(--main-bg-color); + color: var(--text-on-accent); border-color: var(--button-active-bg); - box-shadow: 0 2px 8px rgba(100, 181, 246, 0.25); - transform: translateY(-1px); + box-shadow: none; } .transform-button.active:before { @@ -2779,59 +4189,19 @@ button.endsequence-item:hover .endsequence-copy-affordance { } /* Category-specific active states */ -.transform-category-encoding.active { - background: linear-gradient(to right, var(--encoding-color), var(--encoding-active-end)); - box-shadow: 0 3px 12px rgba(var(--encoding-color-rgb), 0.4); +[class*="transform-category-"].active { + color: var(--text-on-category); } -.transform-category-cipher.active { - background: linear-gradient(to right, var(--cipher-color), var(--cipher-active-end)); - box-shadow: 0 3px 12px rgba(var(--cipher-color-rgb), 0.4); +[class*="transform-category-"].active .transform-button-name, +[class*="transform-category-"].active .transform-preview { + color: var(--text-on-category); } -.transform-category-visual.active { - background: linear-gradient(to right, var(--visual-color), var(--visual-active-end)); - box-shadow: 0 3px 12px rgba(var(--visual-color-rgb), 0.4); -} - -.transform-category-format.active { - background: linear-gradient(to right, var(--format-color), var(--format-active-end)); - box-shadow: 0 3px 12px rgba(var(--format-color-rgb), 0.4); -} - -.transform-category-unicode.active { - background: linear-gradient(to right, var(--unicode-color), var(--unicode-active-end)); - box-shadow: 0 3px 12px rgba(var(--unicode-color-rgb), 0.4); -} - -.transform-category-special.active { - background: linear-gradient(to right, var(--special-color), var(--special-active-end)); - box-shadow: 0 3px 12px rgba(var(--special-color-rgb), 0.4); -} - -.transform-category-fantasy.active { - background: linear-gradient(to right, var(--fantasy-color), var(--fantasy-active-end)); - box-shadow: 0 3px 12px rgba(var(--fantasy-color-rgb), 0.4); -} - -.transform-category-ancient.active { - background: linear-gradient(to right, var(--ancient-color), var(--ancient-active-end)); - box-shadow: 0 3px 12px rgba(var(--ancient-color-rgb), 0.4); -} - -.transform-category-technical.active { - background: linear-gradient(to right, var(--technical-color), var(--technical-active-end)); - box-shadow: 0 3px 12px rgba(var(--technical-color-rgb), 0.4); -} - -.transform-category-randomizer.active { - background: linear-gradient(to right, var(--randomizer-color), var(--randomizer-active-end)); - box-shadow: 0 3px 12px rgba(var(--randomizer-color-rgb), 0.4); -} - -.transform-category-case.active { - background: linear-gradient(to right, var(--case-color), var(--case-active-end)); - box-shadow: 0 3px 12px rgba(var(--case-color-rgb), 0.4); +[class*="transform-category-"].active .transform-preview { + background-color: var(--preview-active-bg); + color: var(--preview-active-text); + border-color: var(--preview-active-border); } /* Add a subtle indicator for clickable buttons */ @@ -2869,17 +4239,17 @@ button.endsequence-item:hover .endsequence-copy-affordance { .favorite-icon:hover { opacity: 1; transform: scale(1.2); - color: #ffd700; + color: var(--favorite-color); } .favorite-icon.favorited { opacity: 1; - color: #ffd700; + color: var(--favorite-color); } .favorite-icon.favorited:hover { transform: scale(1.3); - color: #ffed4e; + filter: brightness(1.15); } /* Per-transform options (gear) */ @@ -2913,7 +4283,7 @@ body.transform-options-modal-open { align-items: flex-end; justify-content: center; padding: 0; - background: rgba(0, 0, 0, 0.55); + background: var(--backdrop-scrim); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); } @@ -3012,11 +4382,11 @@ body.transform-options-modal-open { .transform-options-select { width: 100%; max-width: 100%; - padding: 12px 14px; + padding: 12px 28px 12px 14px; font-size: 1rem; border-radius: 8px; border: 1px solid var(--input-border); - background: var(--button-bg); + background-color: var(--button-bg); color: var(--text-color); min-height: 48px; } @@ -3073,7 +4443,7 @@ body.transform-options-modal-open { .transform-options-btn-primary { background: var(--accent-color); border-color: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); } .transform-options-btn-primary:hover { @@ -3084,14 +4454,26 @@ body.transform-options-modal-open { background: var(--button-hover-bg); } +.transform-category-section.favorites-section, +.transform-category-section.last-used-section { + border: 2px dashed; +} + .favorites-section { - border: 2px dashed #ffd700; - background: rgba(255, 215, 0, 0.05); + background: rgba(var(--favorite-color-rgb), 0.06); +} + +.transform-category-section.favorites-section { + border-color: var(--favorite-color); +} + +.transform-category-section.last-used-section { + border-color: var(--accent-color); } .favorites-section .category-title { - color: #ffd700; - border-left-color: #ffd700; + color: var(--favorite-color); + border-left-color: var(--favorite-color); } /* Output section */ @@ -3130,7 +4512,7 @@ body.transform-options-modal-open { opacity: 0.7; padding: 6px; border-radius: 4px; - background-color: rgba(0, 0, 0, 0.2); + background: var(--surface-overlay-strong); margin-top: 8px; } @@ -3176,7 +4558,7 @@ body.transform-options-modal-open { /* Classy secondary action buttons (Copy All / Download) */ .action-button { - background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02)); + background: var(--button-bg); border: 1px solid var(--input-border); color: var(--text-color); padding: 8px 10px; @@ -3264,7 +4646,7 @@ body.transform-options-modal-open { /* Special styling for randomizer section */ .randomizer-special { - background: linear-gradient(135deg, rgba(88, 28, 135, 0.22), rgba(74, 20, 140, 0.22)); + background: rgba(88, 28, 135, 0.18); border: 2px solid rgba(186, 104, 200, 0.35); border-radius: 14px; padding: 22px; @@ -3319,34 +4701,69 @@ body.transform-options-modal-open { } .randomizer-button { - background: linear-gradient(135deg, var(--encoding-color), var(--randomizer-active-end)); - color: #f8f8ff; + background: var(--randomizer-button-bg); + color: var(--randomizer-button-text); font-weight: 800; position: relative; overflow: hidden; - border: 2px solid rgba(200, 170, 255, 0.35); + border: 2px solid var(--randomizer-button-border); padding: var(--spacing-md) var(--spacing-lg); letter-spacing: .3px; justify-content: center; text-align: center; + width: 100%; + min-width: 0; +} + +.transform-button.transform-category-randomizer.randomizer-button { + background: var(--randomizer-button-bg); + color: var(--randomizer-button-text); + border-color: var(--randomizer-button-border); +} + +.transform-button.transform-category-randomizer.randomizer-button:hover { + background: var(--randomizer-button-bg); + color: var(--randomizer-button-text); + border-left-color: var(--randomizer-color); + filter: brightness(1.08); +} + +.transform-button.transform-category-randomizer.randomizer-button.active { + background: var(--randomizer-button-active-bg); + border-color: var(--randomizer-button-active-bg); + border-left-color: var(--randomizer-button-active-bg); + color: var(--randomizer-button-active-text); +} + +.transform-button.randomizer-button .transform-preview, +.transform-category-randomizer.randomizer-button .transform-preview { + color: var(--randomizer-button-preview-text); + background-color: var(--randomizer-button-preview-bg); + border-color: var(--randomizer-button-preview-border); + text-shadow: none; + opacity: 1; +} + +.transform-button.transform-category-randomizer.randomizer-button.active .transform-preview { + color: var(--randomizer-button-active-preview-text, var(--randomizer-button-preview-text)); + background-color: var(--randomizer-button-active-preview-bg, var(--randomizer-button-preview-bg)); + border-color: var(--randomizer-button-active-preview-border, var(--randomizer-button-preview-border)); +} + +.transform-button.randomizer-button .favorite-icon { + color: var(--randomizer-button-text); + opacity: 0.88; +} + +.transform-button.randomizer-button .favorite-icon.favorited { + color: var(--favorite-color); + opacity: 1; } .randomizer-button::after { - content: ''; - position: absolute; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - background: radial-gradient(circle at center, rgba(255,255,255,0.18), rgba(255,255,255,0) 60%); - transform: rotate(25deg); - opacity: 0; - transition: opacity .25s ease; + display: none; } -.randomizer-button:hover::after { opacity: 1; } -.randomizer-button:hover { filter: brightness(1.08) saturate(1.05); } - /* Randomizer chaos animations */ .randomizer-special { position: relative; } .chaos-overlay { @@ -3462,53 +4879,53 @@ body.transform-options-modal-open { .unicode-panel-header .close-button:active { transform: scale(0.95); } -.apply-status { color:#69f0ae; opacity:.9; } -.apply-steg-options.applied { border-color:#2e7d32; box-shadow: 0 0 12px rgba(105,240,174,.35) inset, 0 0 16px rgba(105,240,174,.2); } +.apply-status { color: var(--status-success); opacity: .9; } +.apply-steg-options.applied { border-color: var(--success-color); box-shadow: 0 0 12px rgba(var(--glitch-color-rgb), .25) inset, 0 0 16px rgba(var(--glitch-color-rgb), .15); } /* API Key Settings */ .settings-section { margin-bottom: 12px; } .settings-section h4 { color: var(--accent-color); margin: 0 0 6px 0; font-size: 0.85rem; } -.settings-hint { color: var(--muted-color, #555566); display: block; margin-bottom: 8px; font-size: 0.75rem; } -.settings-divider { border: none; border-top: 1px solid var(--border-color, #2A2A30); margin: 14px 0; } +.settings-hint { color: var(--text-muted); display: block; margin-bottom: 8px; font-size: 0.75rem; } +.settings-divider { border: none; border-top: 1px solid var(--input-border); margin: 14px 0; } .api-key-input-row { display: flex; gap: 6px; margin-bottom: 8px; } .api-key-input { flex: 1; - background: var(--bg-darkest, #0A0A0A); - border: 1px solid var(--border-color, #2A2A30); - color: var(--text-primary, #e0e0e0); + background: var(--input-bg); + border: 1px solid var(--input-border); + color: var(--text-color); padding: 6px 10px; border-radius: 4px; - font-family: var(--font-mono, monospace); + font-family: 'Fira Code', monospace; font-size: 0.8rem; letter-spacing: 1px; } -.api-key-input:focus { border-color: var(--accent-color, #FFD700); outline: none; } +.api-key-input:focus { border-color: var(--accent-color); outline: none; } .api-key-toggle { - background: var(--bg-darker, #111114); - border: 1px solid var(--border-color, #2A2A30); - color: var(--text-secondary, #B0E0E6); + background: var(--button-bg); + border: 1px solid var(--input-border); + color: var(--text-muted); padding: 6px 10px; border-radius: 4px; cursor: pointer; } -.api-key-toggle:hover { border-color: var(--accent-color, #FFD700); } +.api-key-toggle:hover { border-color: var(--accent-color); color: var(--text-color); } .api-key-actions { display: flex; gap: 8px; align-items: center; } .api-key-save, .api-key-clear { - background: var(--bg-darker, #111114); - border: 1px solid var(--border-color, #2A2A30); - color: var(--text-primary, #e0e0e0); + background: var(--button-bg); + border: 1px solid var(--input-border); + color: var(--text-color); padding: 5px 12px; border-radius: 4px; cursor: pointer; font-size: 0.8rem; } -.api-key-save:hover { border-color: var(--success-color, #50C878); color: var(--success-color, #50C878); } -.api-key-clear:hover { border-color: var(--danger-color, #722F37); color: var(--danger-color, #722F37); } +.api-key-save:hover { border-color: var(--success-color); color: var(--success-color); } +.api-key-clear:hover { border-color: var(--error-text); color: var(--error-text); } /* Decoder Language Detection */ .decoder-lang-banner { - background: linear-gradient(135deg, rgba(255, 215, 0, 0.08), rgba(80, 200, 120, 0.06)); - border: 1px solid rgba(255, 215, 0, 0.25); + background: rgba(var(--favorite-color-rgb), 0.07); + border: 1px solid rgba(var(--favorite-color-rgb), 0.25); border-radius: 8px; padding: 10px 14px; margin: 8px 0; @@ -3521,30 +4938,30 @@ body.transform-options-modal-open { display: flex; align-items: center; gap: 8px; - color: var(--accent-color, #FFD700); + color: var(--accent-color); font-size: 0.9rem; } .decoder-lang-info i { font-size: 1.1rem; } -.lang-confidence { color: var(--muted-color, #555566); font-style: italic; } +.lang-confidence { color: var(--text-muted); font-style: italic; } .decoder-translate-btn { - background: var(--bg-darker, #111114); - border: 1px solid var(--accent-color, #FFD700); - color: var(--accent-color, #FFD700); + background: var(--button-bg); + border: 1px solid var(--accent-color); + color: var(--accent-color); padding: 6px 14px; border-radius: 6px; cursor: pointer; - font-family: var(--font-mono, monospace); + font-family: 'Fira Code', monospace; font-size: 0.8rem; transition: all 0.2s; } .decoder-translate-btn:hover:not(:disabled) { - background: rgba(255, 215, 0, 0.12); - box-shadow: 0 0 10px rgba(255, 215, 0, 0.15); + background: var(--accent-tint); + box-shadow: 0 0 10px rgba(var(--accent-color-rgb), 0.15); } .decoder-translate-btn:disabled { opacity: 0.6; cursor: wait; } .decoder-translate-error { width: 100%; - color: var(--danger-color, #722F37); + color: var(--error-text); font-size: 0.8rem; margin-top: 4px; } @@ -3554,18 +4971,172 @@ html { scroll-behavior: smooth; } -/* Tokenade presets: horizontal layout */ +/* Tokenade presets: responsive grid */ .tokenade-presets { - display: flex; - align-items: center; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 108px), 1fr)); gap: 8px; - flex-wrap: wrap; - margin: 8px 0; + margin: 8px 0 12px 0; } + .tokenade-presets > label { margin-right: 4px; } +.tokenade-presets .transform-button { + width: 100%; + min-width: 0; + min-height: 36px; + padding: 6px 8px; + line-height: 1.25; + text-align: center; + white-space: normal; +} + +/* Tokenade structured controls */ +.tokenade-controls { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 8px; +} + +.tokenade-field-text { + display: flex; + flex-direction: column; + gap: 2px; +} + +.tokenade-field-name { + display: block; + font-size: 0.8rem; + font-weight: 600; + letter-spacing: 0.02em; + line-height: 1.25; + color: var(--text-muted); +} + +.tokenade-field-hint { + display: block; + margin: 0; + font-size: 0.74rem; + font-weight: 400; + line-height: 1.3; + color: var(--text-muted); + opacity: 0.85; +} + +.tokenade-sliders.options-grid { + grid-template-columns: 1fr; + gap: 10px; + margin: 0; +} + +.tokenade-sliders .slider-block { + min-width: 0; +} + +.tokenade-toggles.options-grid { + grid-template-columns: 1fr; + gap: 8px; + margin: 0; +} + +.tokenade-toggles .switch { + align-self: stretch; + max-height: none; + min-height: 38px; + margin-left: 0; + width: 100%; +} + +.tokenade-toggles .switch span { + flex: 1; + min-width: 0; + line-height: 1.35; +} + +.tokenade-separator-row .segmented { + width: 100%; + box-sizing: border-box; +} + +.tokenade-separator-row .segmented-buttons { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +@media (min-width: 520px) { + .tokenade-sliders.options-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .tokenade-toggles.options-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (min-width: 720px) { + .tokenade-toggles.options-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +.tokenade-carrier-options { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 8px; +} + +.tokenade-carrier-toggle { + align-self: stretch; + max-height: none; + margin-left: 0; + width: 100%; +} + +.tokenade-carrier-fields.options-grid { + grid-template-columns: 1fr; + gap: 10px; + margin: 0; +} + +.tokenade-carrier-fields label { + min-width: 0; +} + +@media (min-width: 520px) { + .tokenade-carrier-fields.options-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: end; + } +} + +.tokenade-text-options.options-grid { + grid-template-columns: 1fr; + gap: 10px; + margin: 12px 0; +} + +.tokenade-text-options .switch { + align-self: stretch; + max-height: none; + margin-left: 0; + width: 100%; +} + +@media (min-width: 520px) { + .tokenade-text-options.options-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .tokenade-text-options.options-grid > label:first-child { + grid-column: 1 / -1; + } +} + /* Hacker style controls */ .hacker-controls { display: grid; @@ -3607,11 +5178,7 @@ html { .slider-block input[type="range"]::-webkit-slider-runnable-track { height: 6px; border-radius: 4px; - background: linear-gradient( - 90deg, - rgba(var(--accent-color-rgb), 0.42) 0%, - rgba(var(--accent-color-rgb), 0.1) 100% - ); + background: rgba(var(--accent-color-rgb), 0.22); border: 1px solid var(--input-border); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); } @@ -3641,11 +5208,7 @@ html { .slider-block input[type="range"]::-moz-range-track { height: 6px; border-radius: 4px; - background: linear-gradient( - 90deg, - rgba(var(--accent-color-rgb), 0.42) 0%, - rgba(var(--accent-color-rgb), 0.1) 100% - ); + background: rgba(var(--accent-color-rgb), 0.22); border: 1px solid var(--input-border); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); } @@ -3680,7 +5243,7 @@ html { } .segmented { display: flex; - gap: 6px; + gap: 8px; align-items: center; flex-wrap: wrap; padding: 8px; @@ -3688,7 +5251,20 @@ html { border: 1px solid var(--input-border); border-radius: 6px; } -.segmented-label { opacity: .7; margin-right: 6px; } +.segmented-label { + opacity: .7; + margin-right: 0; + flex: 0 0 auto; + font-size: 0.8rem; + font-weight: 600; +} +.segmented-buttons { + display: flex; + flex-wrap: wrap; + gap: 6px; + flex: 1 1 auto; + min-width: 0; +} .segmented button { background: var(--button-bg); border: 1px solid var(--input-border); @@ -3699,32 +5275,74 @@ html { } .segmented button.active { background: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); border-color: var(--accent-color); } -.switch { - display:flex; - gap:8px; - align-items:center; - padding: 6px 8px 6px 8px; - background: var(--main-bg-color); - border:1px solid var(--input-border); - border-radius:6px; +.switch { + display: flex; + gap: 8px; + align-items: center; + padding: 8px 10px; + background: var(--main-bg-color); + border: 1px solid var(--input-border); + border-radius: var(--theme-radius, 6px); margin-left: 6px; + box-sizing: border-box; } -.switch span { opacity:.85; } -.switch.neon { border-color:#1b5e20; box-shadow:0 0 0 1px rgba(76,175,80,.15) inset, 0 0 10px rgba(76,175,80,.15); } + +.switch span { + opacity: 0.92; + color: var(--text-color); +} + +.switch.neon { + border-color: var(--switch-border); + box-shadow: + 0 0 0 1px var(--switch-surface-glow) inset, + 0 0 10px var(--switch-surface-glow); +} + .switch.neon input[type="checkbox"] { appearance: none; - width: 36px; height: 20px; border-radius: 12px; position: relative; - background: #0b2e13; border:1px solid #1b5e20; outline: none; cursor: pointer; transition: background .2s, box-shadow .2s; + width: 36px; + height: 20px; + border-radius: 12px; + position: relative; + flex-shrink: 0; + background: var(--switch-track-bg); + border: 1px solid var(--switch-track-border); + outline: none; + cursor: pointer; + transition: background 0.2s, box-shadow 0.2s; } + .switch.neon input[type="checkbox"]:after { content: ''; - position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background:#a5d6a7; transition: transform .2s; box-shadow: 0 0 6px rgba(165,214,167,.6); + position: absolute; + top: 2px; + left: 2px; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--switch-thumb-bg); + transition: transform 0.2s, background 0.2s, box-shadow 0.2s; + box-shadow: 0 0 6px var(--switch-thumb-glow); +} + +.switch.neon input[type="checkbox"]:checked { + background: var(--switch-track-checked-bg); + box-shadow: 0 0 12px var(--switch-track-checked-glow); +} + +.switch.neon input[type="checkbox"]:checked:after { + transform: translateX(16px); + background: var(--switch-thumb-checked-bg); + box-shadow: 0 0 12px var(--switch-thumb-checked-glow); +} + +.switch.neon input[type="checkbox"]:focus-visible { + box-shadow: var(--focus-shadow); } -.switch.neon input[type="checkbox"]:checked { background:#1b5e20; box-shadow: 0 0 12px rgba(76,175,80,.45); } -.switch.neon input[type="checkbox"]:checked:after { transform: translateX(16px); background:#69f0ae; box-shadow: 0 0 12px rgba(105,240,174,.75); } /* Tokenade section visual parity with transform cards */ .token-bomb-section { @@ -3744,25 +5362,16 @@ html { gap: 8px; margin: 10px 0 12px 0; padding: 10px 12px; - border: 1px solid #1b5e20; - background: rgba(27, 94, 32, 0.15); - color: #69f0ae; + border: 1px solid var(--status-banner-border); + background: var(--status-banner-bg); + color: var(--status-banner-text); border-radius: 6px; font-size: 0.95rem; line-height: 1.35; } -.tokenade-disclaimer i { color: #69f0ae; margin-top: 2px; } +.tokenade-disclaimer i { color: var(--status-banner-text); margin-top: 2px; } -/* Presets row polish */ -.tokenade-presets { - margin: 8px 0 12px 0; -} - -.tokenade-presets .transform-button { - flex: 1 0 auto; - padding: 5px 8px; - width: unset; -} +/* Presets row polish — see grid rules above */ /* Quick picks panel */ .carrier-quick-grid { @@ -3770,9 +5379,8 @@ html { border: 1px solid var(--input-border); border-radius: 8px; padding: 10px; - margin-bottom: 8px; - grid-column: 1 / -1; /* Span full width of the grid */ width: 100%; + box-sizing: border-box; } .carrier-quick-grid .emoji-grid { border: none !important; @@ -3787,12 +5395,11 @@ html { width: 32px; height: 32px; font-size: 18px; } -/* Align tokenade options with transform look */ -.tokenade-carrier-options label, -.hacker-controls .slider-block, -.hacker-controls .segmented, -.hacker-controls .switch { - margin-bottom: 6px; +/* Tokenade slider/toggle spacing */ +.tokenade-sliders .slider-block, +.tokenade-toggles .switch, +.tokenade-separator-row .segmented { + margin-bottom: 0; } /* Reduce extra whitespace at bottom of tab cards */ @@ -3823,23 +5430,23 @@ html { } .tool-toolbar .action-button.copy { - border-color: #1976d2; - color: #90caf9; + border-color: var(--accent-color); + color: var(--action-copy-color); } .tool-toolbar .action-button.copy:hover { - color: #bbdefb; - box-shadow: 0 0 0 1px rgba(144,202,249,.2) inset, 0 0 14px rgba(144,202,249,.18); + color: var(--action-copy-hover); + box-shadow: 0 0 0 1px rgba(var(--accent-color-rgb), 0.2) inset, 0 0 14px rgba(var(--accent-color-rgb), 0.18); } .tool-toolbar .action-button.download { - border-color: #2e7d32; - color: #69f0ae; + border-color: var(--success-color); + color: var(--action-download-color); } .tool-toolbar .action-button.download:hover { - color: #b9f6ca; - box-shadow: 0 0 0 1px rgba(105,240,174,.2) inset, 0 0 14px rgba(105,240,174,.18); + color: var(--action-download-hover); + box-shadow: 0 0 0 1px rgba(var(--success-color), 0.2) inset, 0 0 14px rgba(var(--success-color), 0.18); } /* Primary “generate” actions — not transform-grid category tiles */ @@ -3854,8 +5461,8 @@ html { height: auto; padding: 10px 20px; gap: 10px; - background: linear-gradient(135deg, var(--accent-color), #42a5f5); - color: var(--main-bg-color); + background: var(--accent-color); + color: var(--text-on-accent); border: 1px solid var(--accent-color); font-weight: 600; } @@ -3870,12 +5477,10 @@ html { } .tool-toolbar .transform-button.tool-primary-btn:hover:not(:disabled) { - background: linear-gradient(135deg, var(--accent-color), #42a5f5); - border-color: var(--accent-color); - color: var(--main-bg-color); - filter: brightness(1.08); - box-shadow: 0 4px 12px rgba(var(--accent-color-rgb), 0.35); - transform: translateY(-1px); + background: var(--accent-hover); + border-color: var(--accent-hover); + color: var(--text-on-accent); + box-shadow: none; } .tool-toolbar .transform-button.tool-primary-btn:disabled { @@ -3951,9 +5556,7 @@ html { padding: 14px; border: 1px solid var(--input-border); border-radius: 8px; - background: - linear-gradient(135deg, rgba(25, 118, 210, 0.08), rgba(102, 187, 106, 0.06)), - var(--main-bg-color); + background: var(--main-bg-color); box-shadow: 0 4px 14px rgba(0,0,0,0.08); } @@ -3996,7 +5599,7 @@ html { .lexeme-analysis-item { border: 1px solid var(--input-border); border-radius: 8px; - background: rgba(255,255,255,0.03); + background: var(--surface-hover); padding: 12px; } @@ -4086,11 +5689,139 @@ html { .bijection-section .bij-options.options-grid { margin-top: 0; margin-bottom: 8px; + grid-template-columns: 1fr; + gap: 12px; +} + +.bijection-section .bij-options.options-grid label.bij-field { + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; +} + +.bijection-section .bij-field-text { + display: flex; + flex-direction: column; + gap: 2px; + min-height: 2.35rem; +} + +.bijection-section .bij-field-name { + display: block; + font-size: 0.9rem; + font-weight: 500; + line-height: 1.25; +} + +.bijection-section .bij-field-hint { + display: block; + margin: 0; + font-size: 0.78rem; + font-weight: 400; + line-height: 1.3; + color: var(--text-muted); } .bijection-section .bij-toggles.options-grid { margin-top: 0; margin-bottom: 12px; + grid-template-columns: 1fr; + gap: 8px; +} + +.bijection-section .bij-options.options-grid label, +.bijection-section .bij-toggles.options-grid label { + min-width: 0; +} + +.bijection-section .bij-toggles .switch { + align-self: stretch; + max-height: none; + min-height: 38px; + margin-left: 0; + width: 100%; +} + +.bijection-section .bij-toggles .switch span { + flex: 1; + min-width: 0; + line-height: 1.35; +} + +@media (min-width: 520px) { + .bijection-section .bij-options.options-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: end; + } + + .bijection-section .bij-options.options-grid > .bij-field-type { + grid-column: 1 / -1; + } + + .bijection-section .bij-field-narrow .bij-field-text { + min-height: 2.35rem; + } +} + +@media (min-width: 720px) { + .bijection-section .bij-toggles.options-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 900px) { + .bijection-section .tool-toolbar .transform-button.tool-primary-btn { + flex: 1 1 100%; + min-width: 0; + } +} + +@media (max-width: 768px) { + .bijection-section.transform-section { + padding: 12px; + } + + .bijection-section .section-header h3 { + font-size: 0.95rem; + } + + .bijection-section .bj-lede { + padding-left: 0; + font-size: 0.85rem; + } + + .bijection-section .tool-toolbar { + flex-direction: column; + align-items: stretch; + } + + .bijection-section .tool-toolbar .transform-button.tool-primary-btn, + .bijection-section .tool-toolbar .action-button { + width: 100%; + } + + .bijection-section .bj-mapping-head { + flex-direction: column; + align-items: stretch; + } + + .bijection-section .bj-mapping-head .action-button { + width: 100%; + justify-content: center; + } + + .bijection-section .bj-result-header { + gap: 6px; + } + + .bijection-section .bj-result-meta { + flex: 1 1 100%; + } + + .bijection-section .bj-copy-btn { + margin-left: auto; + } } .bijection-section .bj-count { @@ -4299,14 +6030,14 @@ html { .pc-strategy-btn.active { background-color: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); border-color: var(--accent-color); font-weight: 600; box-shadow: 0 2px 8px rgba(var(--accent-color-rgb), 0.35); } .pc-strategy-btn.active i { - color: var(--main-bg-color); + color: var(--text-on-accent); opacity: 1; } @@ -4363,7 +6094,7 @@ html { border: 1px dashed var(--input-border); border-radius: 8px; margin-top: 12px; - background: rgba(0, 0, 0, 0.15); + background: var(--surface-overlay); } .pc-empty-state i { @@ -4467,15 +6198,15 @@ html { } .remove-field-button:hover { - background-color: #ff6b6b; - border-color: #ff6b6b; - color: white; + background-color: var(--destructive-color); + border-color: var(--destructive-color); + color: var(--text-on-accent); } .add-field-button:hover { background-color: var(--accent-color); border-color: var(--accent-color); - color: var(--button-bg); + color: var(--text-on-accent); } .add-field-button { @@ -4530,7 +6261,7 @@ html { font-size: 0.7em; font-weight: normal; color: var(--text-muted); - background-color: rgba(100, 181, 246, 0.1); + background-color: var(--accent-tint); padding: 2px 8px; border-radius: 4px; margin-left: 8px; @@ -4586,7 +6317,7 @@ html { .copy-button-small:hover { background: var(--accent-color); - color: var(--main-bg-color); + color: var(--text-on-accent); transform: scale(1.05); } @@ -4661,8 +6392,8 @@ html { .custom-tooltip { position: fixed; padding: 8px 12px; - background: rgba(20, 20, 25, 0.98); - color: #fff; + background: var(--tooltip-bg); + color: var(--tooltip-text); font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 0.8rem; line-height: 1.4; @@ -4739,9 +6470,9 @@ html { position: relative; } -/* Transform chain select - only unique override for larger screens */ -.transform-chain-item select { - min-width: min(200px, 100%); /* Never exceed container width */ +/* Transform chain select inherits .transform-select sizing */ +.transform-chain-item .transform-select { + min-width: min(200px, 100%); } @@ -4765,8 +6496,8 @@ html { } .remove-transform-inline-btn:hover { - background: rgba(211, 47, 47, 0.1); - color: #d32f2f; + background: var(--error-bg); + color: var(--destructive-color); opacity: 1; } @@ -4863,21 +6594,110 @@ html { All responsive styles consolidated at end of file ======================================== */ -/* Desktop: 5 columns (only applies above 768px) */ +/* Desktop: auto-fill grid handles column count */ @media (min-width: 769px) { - .transform-buttons .transform-button-group { - flex: 0 0 calc((100% - 32px) / 5); /* 5 columns: account for 4 gaps of 8px */ + .transform-buttons { + grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); + } + + .translate-lang-grid-inline .translate-lang-card { + flex: 0 0 calc((100% - 32px) / 5); min-width: 0; } .translate-lang-grid-inline .translate-lang-btn { - flex: 0 0 calc((100% - 32px) / 5); - min-width: 0; + width: 100%; + } + + .utility-tab-bar { + flex-wrap: wrap; + } + + .utility-tab-btn { + flex: 1 1 50%; + max-width: 50%; + font-size: 0.68rem; + padding: 10px 6px; } } /* Tablets and below (768px) */ @media (max-width: 768px) { + body { + height: auto; + min-height: 100vh; + overflow: auto; + } + + .app-root { + height: auto; + min-height: 100vh; + } + + .app-shell { + grid-template-columns: 1fr; + grid-template-rows: 1fr; + } + + .app-nav { + display: none; + } + + .app-main { + padding: 12px; + min-height: 0; + } + + .topbar-utility-btn, + .utility-backdrop { + display: flex; + } + + .utility-backdrop { + display: block; + } + + .app-utility { + position: fixed; + top: var(--topbar-height); + right: 0; + bottom: 0; + width: min(100%, var(--sidebar-width)); + max-width: 100%; + height: auto; + min-height: 0; + max-height: none; + z-index: 120; + transform: translateX(100%); + transition: transform 0.28s ease; + box-shadow: var(--sidebar-edge-shadow); + border-left: 1px solid var(--input-border); + border-top: none; + pointer-events: none; + visibility: hidden; + isolation: isolate; + container-type: inline-size; + } + + .app-utility.utility-open { + transform: translateX(0); + pointer-events: auto; + visibility: visible; + z-index: 130; + } + + .utility-tab-btn { + font-size: clamp(0.48rem, 10cqi, 0.72rem); + padding: 8px 2px; + } + + body.theme-bt6 .utility-tab-btn, + body.theme-pliny .utility-tab-btn, + body.theme-wildwest .utility-tab-btn { + letter-spacing: 0.04em; + font-size: clamp(0.46rem, 9.5cqi, 0.62rem); + } + .section-header-card { flex-direction: column; gap: 10px; @@ -4903,6 +6723,11 @@ html { .tab-buttons { display: none; } + + /* Legacy horizontal tab bar (if present outside app-nav) */ + .tabs > .tab-buttons { + display: none; + } .tab-tool-select { display: block; @@ -4923,7 +6748,7 @@ html { text-align: left; } - .transform-chain-item select { + .transform-chain-item .transform-select { min-width: 100%; /* Full width on smaller screens */ } @@ -4965,16 +6790,11 @@ html { width: 100%; } - /* Transform buttons - 2 columns on tablets and below */ + /* Transform buttons — tighter min cell on tablets */ .transform-buttons { + grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 8px; } - - .transform-buttons .transform-button-group { - flex: 0 0 calc(50% - 4px) !important; /* 2 columns: simpler calculation */ - min-width: 0; - max-width: calc(50% - 4px); - } /* Translation (AI) language tiles: 2 columns on phones/tablets (grid is more reliable than flex here) */ .translate-lang-grid.translate-lang-grid-inline { @@ -4983,6 +6803,12 @@ html { gap: 8px; } + .translate-lang-grid-inline .translate-lang-card { + width: 100%; + min-width: 0; + max-width: none; + } + .translate-lang-grid-inline .translate-lang-btn { flex: none; width: 100%; @@ -5024,44 +6850,52 @@ html { body { padding: 0; + height: auto; + overflow: auto; } - .container { - padding: var(--spacing-sm); - border-radius: 4px; - } - - header { - flex-direction: column; - gap: var(--spacing-sm); - padding: var(--spacing-sm); - margin-bottom: 20px; - } - - .logo h1 { - font-size: 1.2rem; - padding: var(--spacing-xs); - } - - .actions { - width: 100%; - justify-content: center; - } - - /* Transform buttons flex more compact - 2 columns on mobile */ - .transform-buttons { - gap: var(--spacing-xs); - } - - .transform-buttons .transform-button-group { - flex: 0 0 calc(50% - 2px) !important; /* 2 columns: simpler calculation */ - min-width: 0; - max-width: calc(50% - 2px); + header.app-topbar { + gap: 6px; + padding: 0 8px; } - /* Translation tiles: same 2-col grid as 768px (inherit from above) */ + .app-logo h1, + .logo h1 { + font-size: clamp(0.62rem, 3.5vw, 0.78rem); + padding: 4px 6px; + letter-spacing: 0.08em; + } + + .topbar-actions, + .actions { + gap: 4px; + flex-shrink: 0; + } + + .github-button, + .topbar-utility-btn { + width: 32px; + height: 32px; + min-width: 32px; + padding: 6px; + flex-shrink: 0; + } - /* Options grid single column on very small screens */ + /* Transform buttons — compact cells on small phones */ + .transform-buttons { + grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); + gap: var(--spacing-xs); + } + + .transform-button-name { + font-size: clamp(0.58rem, 6.8cqi, 0.8rem); + padding: 0 12px; + } + + .transform-preview { + font-size: clamp(0.5rem, 6.2cqi, 0.78rem); + -webkit-line-clamp: 3; + } .options-grid { grid-template-columns: 1fr; gap: 10px; @@ -5138,7 +6972,6 @@ html { /* Transform button preview text */ .transform-preview { - font-size: 0.7rem; max-width: 100%; } @@ -5152,10 +6985,7 @@ html { padding-left: var(--spacing-xs); } - /* Tokenade carrier options single column */ - .tokenade-carrier-options { - grid-template-columns: 1fr; - } + /* Tokenade carrier options single column (inherits tokenade-carrier-fields rules) */ /* Carrier quick grid emojis smaller */ .carrier-quick-grid .emoji-grid { @@ -5235,18 +7065,37 @@ html { font-size: 0.85rem; } - /* Legend items wrap better */ + /* Category filter bar */ + .transform-filter-bar { + padding: 12px; + gap: 12px; + } + + .transform-search-wrap { + min-height: 38px; + padding: 0 12px; + } + + .transform-search-wrap input[type="search"] { + height: 36px; + font-size: 0.88rem; + } + + .transform-category-chips { + gap: 5px; + } + + .category-chip { + font-size: 0.75rem; + padding: 5px 10px; + } + .transform-category-legend { gap: var(--spacing-xs); } - .legend-item { - font-size: 0.75rem; - padding: 3px var(--spacing-xs); - } - /* Transform chain items stack vertically */ - .transform-chain-item select { + .transform-chain-item .transform-select { min-width: 100%; width: 100%; } @@ -5276,3 +7125,249 @@ html { margin-left: var(--spacing-md); } } + +/* Custom Spelling Alphabet tool */ +.spelling-alphabet-layout .sa-empty-state { + padding: var(--spacing-md); + border: 1px dashed var(--input-border); + border-radius: 8px; + color: var(--text-muted); +} + +.sa-saved-list { + display: grid; + gap: var(--spacing-md); +} + +.sa-saved-card { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-md); + justify-content: space-between; + align-items: flex-start; + padding: var(--spacing-md); + border: 1px solid var(--input-border); + border-radius: 8px; + background: var(--secondary-bg); +} + +.sa-saved-card h4 { + margin: 0 0 var(--spacing-sm); + color: var(--accent-color); +} + +.sa-saved-preview, +.sa-saved-card p { + margin: 0 0 var(--spacing-xs); + color: var(--text-muted); +} + +.sa-saved-card-actions { + display: flex; + gap: var(--spacing-sm); + flex-wrap: wrap; +} + +.sa-delete-btn { + color: var(--destructive-color); +} + +.sa-meta-grid { + margin-bottom: var(--spacing-md); +} + +.sa-generate-row { + margin-bottom: var(--spacing-md); +} + +.sa-hint { + margin-top: var(--spacing-sm); + color: var(--text-muted); + font-size: 0.95rem; +} + +.sa-error { + margin-bottom: var(--spacing-md); + padding: var(--spacing-sm) var(--spacing-md); + border-radius: 6px; + border: 1px solid var(--error-border); + background: var(--error-bg); + color: var(--error-text-soft); +} + +.sa-progress { + margin-bottom: var(--spacing-md); + color: var(--text-muted); +} + +.sa-letter-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: var(--spacing-sm); +} + +.sa-letter-field { + display: flex; + flex-direction: column; + gap: 4px; +} + +.sa-letter-label { + font-weight: 700; + color: var(--custom-spelling-color); +} + +.sa-letter-field input { + text-transform: uppercase; +} + +.sa-live-preview { + margin-top: var(--spacing-md); + padding: var(--spacing-md); + border-radius: 8px; + background: rgba(var(--custom-spelling-color-rgb), 0.08); + border: 1px solid rgba(var(--custom-spelling-color-rgb), 0.25); +} + +/* Codes tool (QR + barcodes) */ +.codes-layout .codes-mode-tabs { + display: flex; + gap: var(--spacing-sm); + margin-bottom: var(--spacing-md); +} + +.codes-mode-btn { + flex: 1; + max-width: 220px; + padding: 10px 16px; + border: 1px solid var(--border-color); + border-radius: 8px; + background: var(--bg-secondary); + color: var(--text-color); + cursor: pointer; + font-size: 0.95rem; + transition: border-color 0.15s, background 0.15s; +} + +.codes-mode-btn:hover { + border-color: var(--accent-color); +} + +.codes-mode-btn.active { + border-color: var(--accent-color); + background: rgba(var(--accent-color-rgb, 99, 102, 241), 0.12); + font-weight: 600; +} + +.codes-options-grid { + margin-bottom: var(--spacing-md); +} + +.codes-checkbox-label { + display: flex; + align-items: center; + gap: 8px; + align-self: end; +} + +.codes-error { + margin: var(--spacing-sm) 0; + padding: var(--spacing-sm) var(--spacing-md); + border-radius: 8px; + background: rgba(220, 53, 69, 0.12); + border: 1px solid rgba(220, 53, 69, 0.35); + color: #dc3545; +} + +.codes-output-card { + margin-top: var(--spacing-md); + padding: var(--spacing-md); + border: 1px solid var(--border-color); + border-radius: 12px; + background: var(--bg-secondary); +} + +.codes-preview-wrap { + display: flex; + justify-content: center; + align-items: center; + padding: var(--spacing-md); + background: #fff; + border-radius: 8px; + margin-bottom: var(--spacing-md); +} + +.codes-preview-image { + max-width: 100%; + height: auto; +} + +.codes-upload-zone { + margin-bottom: var(--spacing-md); +} + +.codes-upload-label { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); + padding: var(--spacing-lg); + border: 2px dashed var(--border-color); + border-radius: 12px; + cursor: pointer; + text-align: center; + transition: border-color 0.15s, background 0.15s; +} + +.codes-upload-label:hover { + border-color: var(--accent-color); + background: rgba(var(--accent-color-rgb, 99, 102, 241), 0.06); +} + +.codes-upload-label input[type="file"] { + display: none; +} + +.codes-upload-label i { + font-size: 2rem; + color: var(--accent-color); +} + +.codes-hint { + margin-top: var(--spacing-sm); + color: var(--text-muted); + font-size: 0.9rem; +} + +.codes-loading { + padding: var(--spacing-md); + color: var(--text-muted); +} + +.codes-decode-preview { + margin: var(--spacing-md) 0; + text-align: center; +} + +.codes-decode-preview img { + max-width: 100%; + max-height: 280px; + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.codes-decode-result { + margin-top: var(--spacing-md); + padding: var(--spacing-md); + border: 1px solid var(--border-color); + border-radius: 12px; + background: var(--bg-secondary); +} + +.codes-decode-meta { + margin-bottom: var(--spacing-sm); + color: var(--text-muted); + font-size: 0.9rem; +} + diff --git a/css/themes-atmosphere.css b/css/themes-atmosphere.css new file mode 100644 index 0000000..4113023 --- /dev/null +++ b/css/themes-atmosphere.css @@ -0,0 +1,1407 @@ +/** + * Premium atmosphere layers for BT6, Pliny, Cyberpunk, and Wild West themes. + * Relies on --theme-* tokens defined on each body.theme-* block in style.css. + */ + +/* ─── Shared premium theme shell ─── */ + +body[class*="theme-bt6"], +body[class*="theme-pliny"], +body[class*="theme-cyberpunk"], +body[class*="theme-wildwest"] { + position: relative; +} + +body[class*="theme-bt6"] .app-root, +body[class*="theme-pliny"] .app-root, +body[class*="theme-cyberpunk"] .app-root, +body[class*="theme-wildwest"] .app-root { + position: relative; + isolation: isolate; +} + +body[class*="theme-bt6"] .app-root::before, +body[class*="theme-pliny"] .app-root::before, +body[class*="theme-cyberpunk"] .app-root::before, +body[class*="theme-wildwest"] .app-root::before { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 0; +} + +body[class*="theme-bt6"] .app-topbar, +body[class*="theme-pliny"] .app-topbar, +body[class*="theme-cyberpunk"] .app-topbar, +body[class*="theme-wildwest"] .app-topbar, +body[class*="theme-bt6"] .app-shell, +body[class*="theme-pliny"] .app-shell, +body[class*="theme-cyberpunk"] .app-shell, +body[class*="theme-wildwest"] .app-shell { + position: relative; + z-index: 1; +} + +/* Custom select carets */ +body.theme-bt6 select, +body.theme-pliny select, +body.theme-cyberpunk select, +body.theme-wildwest select:not(.settings-theme-select) { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236b5e4f' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; +} + +body.theme-bt6 .settings-theme-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238a8178' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + border-radius: var(--theme-radius); + font-family: var(--theme-ui-font); + letter-spacing: 0.12em; + text-transform: uppercase; + font-size: 0.62rem; +} + +body.theme-bt6 .settings-theme-select:hover, +body.theme-bt6 .settings-theme-select:focus { + border-color: var(--theme-secondary); + color: var(--theme-secondary); +} + +body.theme-bt6 .github-button { + border: 1px solid rgba(var(--theme-secondary-rgb), 0.45); + border-radius: var(--theme-radius); + color: var(--theme-secondary); +} + +body.theme-bt6 .github-button:hover { + background: rgba(var(--theme-secondary-rgb), 0.08); + border-color: var(--theme-secondary); +} + +/* ─── BT6 — bt6.gg: black + blood red, serif headlines, arsenal cards ─── */ + +body.theme-bt6 .app-root::before { + background: + rgba(var(--accent-color-rgb), 0.04), + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='800' height='600' viewBox='0 0 800 600'%3E%3Cg fill='none' stroke='%23962020' stroke-width='0.6' opacity='0.12'%3E%3Cpath d='M120 420 L400 280 L680 420 L620 480 L180 480 Z'/%3E%3Cpath d='M400 280 L400 180 M340 200 L460 200 M320 230 L480 230 M300 260 L500 260'/%3E%3Cpath d='M200 380 Q400 320 600 380'/%3E%3Cpath d='M250 350 Q400 300 550 350'/%3E%3C/g%3E%3C/svg%3E") center 30% / 70% auto no-repeat; + opacity: 1; +} + +body.theme-bt6 .app-root::after { + display: none; +} + +body.theme-bt6 .app-topbar { + background: var(--nav-bg); + border-bottom: 1px solid var(--input-border); + box-shadow: none; +} + +body.theme-bt6 .app-topbar::after { + display: none; +} + +body.theme-bt6 .app-logo h1, +body.theme-bt6 .app-topbar .logo h1 { + font-family: var(--theme-mono-font); + font-size: 0.92rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + border: none; + border-radius: var(--theme-radius); + background: none; + padding: 4px 0; + color: var(--accent-color); + text-shadow: none; + animation: none; +} + +body.theme-bt6 .app-nav { + border-right: 1px solid var(--input-border); + box-shadow: none; +} + +body.theme-bt6 .app-nav .tab-buttons button { + border-radius: var(--theme-radius); + font-family: var(--theme-ui-font); + font-size: 0.68rem; + font-weight: 500; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--text-color); + border-top: 1px solid transparent; + border-left: 2px solid transparent; +} + +body.theme-bt6 .app-nav .tab-buttons button:hover:not(.active) { + background: rgba(255, 255, 255, 0.02); + color: var(--text-color); +} + +body.theme-bt6 .app-nav .tab-buttons button.active { + background: var(--button-bg); + color: var(--accent-color); + border-left-color: var(--accent-color); + border-top-color: var(--accent-color); + box-shadow: none; + font-weight: 600; +} + +body.theme-bt6 .app-main { + background: var(--main-bg-color); + font-family: var(--theme-ui-font); +} + +body.theme-bt6 .app-main h2, +body.theme-bt6 .app-main h3, +body.theme-bt6 .section-title h3, +body.theme-bt6 .section-header h3 { + font-family: var(--theme-mono-font); + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + font-size: 0.95rem; +} + +body.theme-bt6 .section-title small, +body.theme-bt6 .section-header h3 small { + font-family: var(--theme-mono-font); + font-size: 0.58rem; + letter-spacing: 0.18em; + text-transform: uppercase; + background: none; + border-radius: var(--theme-radius); + color: var(--text-muted); + padding: 0; +} + +body.theme-bt6 small, +body.theme-bt6 .hint, +body.theme-bt6 .help-text, +body.theme-bt6 label small { + font-family: var(--theme-display-font); + font-style: italic; + font-size: 0.88rem; + letter-spacing: 0.01em; + color: var(--text-muted); +} + +body.theme-bt6 .app-utility { + border-left: 1px solid var(--input-border); + box-shadow: none; +} + +body.theme-bt6 .utility-tab-btn { + font-family: var(--theme-ui-font); + font-size: 0.62rem; + letter-spacing: 0.12em; + text-transform: uppercase; + border-radius: var(--theme-radius); +} + +body.theme-bt6 .utility-tab-btn.active { + color: var(--accent-color); + border-top: 1px solid var(--accent-color); + border-bottom-color: var(--accent-color); + background: var(--button-bg); + text-shadow: none; + box-shadow: none; +} + +body.theme-bt6 input[type="text"], +body.theme-bt6 input[type="number"], +body.theme-bt6 input[type="email"], +body.theme-bt6 input[type="password"], +body.theme-bt6 select, +body.theme-bt6 textarea { + border-radius: var(--theme-radius); + font-family: var(--theme-ui-font); +} + +body.theme-bt6 button, +body.theme-bt6 .btn { + border-radius: var(--theme-radius); + font-family: var(--theme-ui-font); + font-size: 0.72rem; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +body.theme-bt6 input[type="text"]:focus, +body.theme-bt6 input[type="number"]:focus, +body.theme-bt6 select:focus, +body.theme-bt6 textarea:focus { + box-shadow: var(--focus-shadow); + border-color: rgba(var(--accent-color-rgb), 0.55); +} + +/* Arsenal-card transform buttons */ +body.theme-bt6 .transform-button { + border-radius: var(--theme-radius); + border: 1px solid transparent; + border-top: 1px solid transparent; + border-left: 2px solid transparent; + background: var(--button-bg); + box-shadow: none; + text-align: left; + min-height: 76px; +} + +body.theme-bt6 .transform-button-name { + font-family: var(--theme-mono-font); + font-weight: 700; + font-size: clamp(0.56rem, 6cqi, 0.76rem); + letter-spacing: 0.04em; + text-transform: uppercase; + text-align: left; + color: var(--text-color); + padding: 0 12px; +} + +body.theme-bt6 .transform-button:hover { + background: var(--button-hover-bg); + box-shadow: none; +} + +body.theme-bt6 .transform-button:hover .transform-button-name { + color: var(--text-color); +} + +body.theme-bt6 .transform-button.active { + border-left-color: var(--accent-color); + border-top-color: var(--accent-color); + background: var(--button-bg); + box-shadow: none; +} + +body.theme-bt6 .transform-button.active .transform-button-name { + color: var(--accent-color); +} + +body.theme-bt6 .transform-button.active::after { + content: '→'; + position: absolute; + right: 10px; + bottom: 10px; + font-family: var(--theme-mono-font); + font-size: 0.85rem; + color: var(--accent-color); + opacity: 1; +} + +body.theme-bt6 .transform-button:before, +body.theme-bt6 .transform-button:hover:before, +body.theme-bt6 .transform-button.active:before { + display: none; +} + +body.theme-bt6 [class*="transform-category-"] { + background: var(--button-bg); +} + +body.theme-bt6 [class*="transform-category-"]:hover { + box-shadow: none; +} + +body.theme-bt6 .input-section, +body.theme-bt6 .output-section, +body.theme-bt6 .decode-section { + border: 1px solid var(--input-border); + border-radius: var(--theme-radius); +} + +body.theme-bt6 .status-banner { + border-radius: var(--theme-radius); + font-family: var(--theme-mono-font); + font-size: 0.65rem; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +body.theme-pliny .settings-theme-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2300ff41' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + border-radius: var(--theme-radius); + font-family: var(--theme-ui-font); + letter-spacing: 0.12em; + text-transform: uppercase; + font-size: 0.62rem; +} + +body.theme-pliny .settings-theme-select:hover, +body.theme-pliny .settings-theme-select:focus { + border-color: var(--accent-color); + color: var(--accent-color); + box-shadow: 0 0 14px rgba(var(--accent-color-rgb), 0.25); +} + +body.theme-pliny .github-button { + border: 1px solid rgba(var(--accent-color-rgb), 0.35); + border-radius: var(--theme-radius); + color: var(--text-muted); +} + +body.theme-pliny .github-button:hover { + color: var(--accent-color); + border-color: var(--accent-color); + box-shadow: 0 0 14px rgba(var(--accent-color-rgb), 0.2); +} + +/* ─── Pliny — pliny.gg: black CRT, neon green glow, liberation cards ─── */ + +body.theme-pliny .app-root::before { + background: #000000; +} + +body.theme-pliny .app-root::after { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 0; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Crect width='4' height='2' fill='%23000' fill-opacity='0.28'/%3E%3C/svg%3E"); + background-repeat: repeat; + opacity: 0.45; +} + +body.theme-pliny .app-topbar { + background: var(--nav-bg); + border-bottom: 1px solid var(--input-border); + box-shadow: none; +} + +body.theme-pliny .app-logo h1, +body.theme-pliny .app-topbar .logo h1 { + font-family: var(--theme-ui-font); + font-size: 0.72rem; + font-weight: 500; + letter-spacing: 0.22em; + text-transform: uppercase; + border: none; + border-radius: var(--theme-radius); + background: none; + padding: 0; + color: var(--text-color); + text-shadow: none; + animation: none; +} + +body.theme-pliny .app-nav { + border-right: 1px solid var(--input-border); + box-shadow: none; +} + +body.theme-pliny .app-nav .tab-buttons button { + font-family: var(--theme-ui-font); + font-size: 0.64rem; + font-weight: 500; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text-muted); + border-radius: var(--theme-radius); + border-left: 1px solid transparent; +} + +body.theme-pliny .app-nav .tab-buttons button:hover:not(.active) { + color: var(--text-color); + background: rgba(255, 255, 255, 0.02); +} + +body.theme-pliny .app-nav .tab-buttons button.active { + color: var(--accent-color); + background: rgba(var(--accent-color-rgb), 0.06); + border-left-color: var(--accent-color); + box-shadow: + inset 0 0 0 1px rgba(var(--accent-color-rgb), 0.25), + 0 0 18px rgba(var(--accent-color-rgb), 0.18); + text-shadow: 0 0 10px rgba(var(--accent-color-rgb), 0.35); +} + +body.theme-pliny .app-nav .tab-buttons button.active i { + color: var(--accent-color); +} + +body.theme-pliny .app-main { + background: var(--main-bg-color); + font-family: var(--theme-ui-font); +} + +body.theme-pliny .app-main h2, +body.theme-pliny .app-main h3, +body.theme-pliny .section-title h3, +body.theme-pliny .section-header h3 { + font-family: var(--theme-mono-font); + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; + color: var(--accent-color); + font-size: 0.92rem; +} + +body.theme-pliny .section-title small, +body.theme-pliny .section-header h3 small { + font-family: var(--theme-ui-font); + font-size: 0.62rem; + letter-spacing: 0.14em; + text-transform: uppercase; + background: none; + color: var(--text-muted); + padding: 0; +} + +body.theme-pliny small, +body.theme-pliny .hint, +body.theme-pliny .help-text, +body.theme-pliny label small { + font-family: var(--theme-ui-font); + font-style: normal; + font-size: 0.82rem; + color: var(--text-muted); +} + +body.theme-pliny .app-utility { + border-left: 1px solid var(--input-border); +} + +body.theme-pliny .utility-tab-btn { + font-family: var(--theme-ui-font); + font-size: 0.62rem; + letter-spacing: 0.12em; + text-transform: uppercase; + border-radius: var(--theme-radius); +} + +body.theme-pliny .utility-tab-btn.active { + color: var(--accent-color); + border-bottom-color: var(--accent-color); + background: rgba(var(--accent-color-rgb), 0.06); + box-shadow: 0 0 16px rgba(var(--accent-color-rgb), 0.15); + text-shadow: 0 0 8px rgba(var(--accent-color-rgb), 0.3); +} + +body.theme-pliny input[type="text"], +body.theme-pliny input[type="number"], +body.theme-pliny input[type="email"], +body.theme-pliny input[type="password"], +body.theme-pliny select, +body.theme-pliny textarea, +body.theme-pliny button, +body.theme-pliny .btn { + border-radius: var(--theme-radius); + font-family: var(--theme-ui-font); +} + +body.theme-pliny input[type="text"]:focus, +body.theme-pliny input[type="number"]:focus, +body.theme-pliny select:focus, +body.theme-pliny textarea:focus { + box-shadow: var(--focus-shadow); + border-color: rgba(var(--accent-color-rgb), 0.55); +} + +/* Liberation corpus cards */ +body.theme-pliny .transform-button { + border-radius: var(--theme-radius); + border: 1px solid var(--input-border); + background: var(--button-bg); + box-shadow: none; + text-align: center; + min-height: 76px; +} + +body.theme-pliny .transform-button-name { + font-family: var(--theme-mono-font); + font-weight: 700; + font-size: clamp(0.56rem, 6cqi, 0.78rem); + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--accent-color); + text-align: center; +} + +body.theme-pliny .transform-button:hover { + background: var(--button-hover-bg); + border-color: rgba(var(--accent-color-rgb), 0.25); + box-shadow: none; +} + +body.theme-pliny .transform-button.active { + border-color: var(--accent-color); + background: rgba(var(--accent-color-rgb), 0.08); + box-shadow: none; +} + +body.theme-pliny .transform-button.active .transform-button-name { + color: var(--accent-color); + text-shadow: none; +} + +body.theme-pliny .transform-button:before, +body.theme-pliny .transform-button:hover:before, +body.theme-pliny .transform-button.active:before { + display: none; +} + +body.theme-pliny .transform-button.active:after { + display: none; +} + +body.theme-pliny [class*="transform-category-"] { + background: var(--button-bg); +} + +body.theme-pliny [class*="transform-category-"]:hover { + box-shadow: none; +} + +body.theme-pliny .input-section, +body.theme-pliny .output-section, +body.theme-pliny .decode-section { + border: 1px solid var(--input-border); + border-radius: var(--theme-radius); +} + +body.theme-pliny .status-banner { + border-radius: var(--theme-radius); + font-family: var(--theme-mono-font); + font-size: 0.65rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +body.theme-cyberpunk .settings-theme-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ff2a6d' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); +} + +body.theme-wildwest select.settings-theme-select { + background-color: var(--theme-cream); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%231a1a1a' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; + background-size: 12px 12px; + border-radius: var(--theme-radius); + font-family: var(--theme-ui-font); + letter-spacing: 0.1em; + text-transform: uppercase; + font-size: 0.65rem; + color: var(--theme-charcoal); + border: 1px solid rgba(var(--theme-charcoal-rgb), 0.15); +} + +body.theme-wildwest select.settings-theme-select:hover, +body.theme-wildwest select.settings-theme-select:focus { + background-color: #fff8ea; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23d67c7c' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; + background-size: 12px 12px; + border-color: var(--accent-color); + color: var(--accent-color); + box-shadow: var(--focus-shadow); +} + +body.theme-wildwest .github-button { + border: 1px solid rgba(var(--theme-charcoal-rgb), 0.2); + border-radius: var(--theme-radius); + color: var(--theme-charcoal); + background: rgba(var(--theme-cream-rgb), 0.5); +} + +body.theme-wildwest .github-button:hover { + background: rgba(var(--accent-color-rgb), 0.12); + border-color: var(--accent-color); + color: var(--accent-color); +} + +/* ─── Cyberpunk — neon grid dystopia ─── */ + +body.theme-cyberpunk .app-root::before { + background: + rgba(255, 42, 109, 0.05), + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60' viewBox='0 0 60 60'%3E%3Cpath d='M60 0H0V60' fill='none' stroke='rgba(255,42,109,0.08)' stroke-width='1'/%3E%3C/svg%3E") 0 0 / 60px 60px repeat; +} + +body.theme-cyberpunk .app-root::after { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 0; + background: + rgba(255, 42, 109, 0.06), + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='6' viewBox='0 0 4 6'%3E%3Crect width='4' height='3' fill='%23000' fill-opacity='0.15'/%3E%3C/svg%3E") 0 0 / 4px 6px repeat; + opacity: 0.5; +} + +body.theme-cyberpunk .app-topbar { + background: var(--secondary-bg); + border-bottom: none; + box-shadow: + inset 0 -2px 0 rgba(var(--accent-color-rgb), 0.5), + inset 0 -4px 12px rgba(var(--accent-color-rgb), 0.15), + 0 1px 0 rgba(var(--glitch-color-rgb), 0.3); +} + +body.theme-cyberpunk .app-logo h1, +body.theme-cyberpunk .app-topbar .logo h1 { + font-family: var(--theme-display-font); + font-size: 0.82rem; + font-weight: 700; + letter-spacing: 0.2em; + border: 1px solid rgba(var(--accent-color-rgb), 0.6); + border-radius: var(--theme-radius); + background: rgba(var(--accent-color-rgb), 0.12); + color: #fff; + text-shadow: + -1px 0 rgba(var(--glitch-color-rgb), 0.9), + 1px 0 rgba(var(--accent-color-rgb), 0.9), + 0 0 12px rgba(var(--accent-color-rgb), 0.6); + animation: cyber-chromatic 3.5s ease-in-out infinite; +} + +body.theme-cyberpunk .app-nav { + border-right-color: rgba(var(--accent-color-rgb), 0.25); + box-shadow: inset -2px 0 16px rgba(var(--accent-color-rgb), 0.06); +} + +body.theme-cyberpunk .app-nav .tab-buttons button { + border-radius: var(--theme-radius); + font-family: var(--theme-display-font); + font-size: 0.72rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +body.theme-cyberpunk .app-nav .tab-buttons button.active { + background: rgba(var(--accent-color-rgb), 0.12); + border-left-color: var(--accent-color); + box-shadow: + inset 0 0 20px rgba(var(--accent-color-rgb), 0.12), + 0 0 12px rgba(var(--accent-color-rgb), 0.15); + text-shadow: 0 0 8px rgba(var(--accent-color-rgb), 0.5); +} + +body.theme-cyberpunk .app-main { + background: var(--main-bg-color); +} + +body.theme-cyberpunk .app-utility { + border-left-color: rgba(var(--glitch-color-rgb), 0.2); + box-shadow: inset 2px 0 16px rgba(var(--glitch-color-rgb), 0.04); +} + +body.theme-cyberpunk .utility-tab-btn.active { + text-shadow: 0 0 10px rgba(var(--accent-color-rgb), 0.6); + animation: cyber-neon-pulse 2s ease-in-out infinite; +} + +body.theme-cyberpunk input[type="text"]:focus, +body.theme-cyberpunk select:focus, +body.theme-cyberpunk textarea:focus { + box-shadow: + var(--focus-shadow), + 0 0 16px rgba(var(--glitch-color-rgb), 0.2), + inset 0 0 8px rgba(var(--accent-color-rgb), 0.08); +} + +body.theme-cyberpunk .transform-button:hover { + box-shadow: none; +} + +body.theme-cyberpunk .transform-button.active { + box-shadow: none; +} + +@keyframes cyber-chromatic { + 0%, 85%, 100% { + text-shadow: + -1px 0 rgba(var(--glitch-color-rgb), 0.9), + 1px 0 rgba(var(--accent-color-rgb), 0.9), + 0 0 12px rgba(var(--accent-color-rgb), 0.6); + transform: translateX(0); + } + 88% { + text-shadow: -3px 0 #05d9e8, 3px 0 #ff2a6d, 0 0 16px #ff2a6d; + transform: translateX(2px); + } + 91% { + text-shadow: 3px 0 #05d9e8, -3px 0 #ff2a6d, 0 0 16px #05d9e8; + transform: translateX(-2px); + } +} + +@keyframes cyber-neon-pulse { + 0%, 100% { box-shadow: inset 0 -2px 0 var(--accent-color); } + 50% { box-shadow: inset 0 -2px 0 var(--accent-color), 0 0 12px rgba(var(--accent-color-rgb), 0.3); } +} + +/* ─── Wild West — cream header, sage nav, dusty rose accents ─── */ + +body.theme-wildwest .app-root::before { + background: var(--shell-bg); +} + +body.theme-wildwest .app-root::after { + display: none; +} + +body.theme-wildwest .app-topbar { + background: var(--secondary-bg); + border-bottom: 2px solid var(--accent-color); + box-shadow: none; + color: var(--theme-charcoal); +} + +body.theme-wildwest .app-logo h1, +body.theme-wildwest .app-topbar .logo h1 { + font-family: var(--theme-display-font); + font-size: 1.1rem; + font-weight: 400; + letter-spacing: 0.03em; + text-transform: none; + border: none; + border-radius: var(--theme-radius); + padding: 0; + background: none; + color: var(--theme-charcoal); + text-shadow: 1px 1px 0 rgba(var(--theme-cream-rgb), 0.8); + animation: none; +} + +body.theme-wildwest .app-nav { + background: var(--nav-bg); + border-right: none; + box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.08); +} + +body.theme-wildwest .app-nav .tab-buttons button { + font-family: var(--theme-display-font); + font-size: 0.72rem; + font-weight: 400; + letter-spacing: 0.04em; + text-transform: uppercase; + border-radius: var(--theme-radius); + color: #ffffff; + border-left: 3px solid transparent; +} + +body.theme-wildwest .app-nav .tab-buttons button:hover:not(.active) { + background: rgba(255, 255, 255, 0.12); + color: #ffffff; +} + +body.theme-wildwest .app-nav .tab-buttons button.active { + background: rgba(255, 255, 255, 0.18); + border-left-color: var(--accent-color); + color: #ffffff; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15); +} + +body.theme-wildwest .app-nav .tab-buttons button.active::after { + display: none; +} + +body.theme-wildwest .app-main { + background: var(--main-bg-color); + font-family: var(--theme-body-font); + color: var(--text-color); +} + +body.theme-wildwest .app-main h2, +body.theme-wildwest .section-title h3, +body.theme-wildwest .section-header h3 { + font-family: var(--theme-display-font); + color: var(--accent-color); + letter-spacing: 0.04em; + text-shadow: none; +} + +body.theme-wildwest .section-title { + position: relative; + padding-bottom: 10px; + border-bottom: 1px solid rgba(var(--accent-color-rgb), 0.35); +} + +body.theme-wildwest .section-title::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: -3px; + height: 1px; + background: rgba(var(--theme-secondary-rgb), 0.45); +} + +body.theme-wildwest .section-title small, +body.theme-wildwest .section-header h3 small { + font-family: var(--theme-ui-font); + font-size: 0.62rem; + letter-spacing: 0.14em; + text-transform: uppercase; + background: rgba(var(--accent-color-rgb), 0.2); + color: var(--text-color); + border-radius: var(--theme-radius); + padding: 3px 8px; +} + +body.theme-wildwest small, +body.theme-wildwest .hint, +body.theme-wildwest .help-text, +body.theme-wildwest label small { + font-family: var(--theme-body-font); + font-style: italic; + color: var(--text-muted); +} + +body.theme-wildwest .app-utility { + border-left: 1px solid rgba(var(--theme-charcoal-rgb), 0.1); + background: var(--secondary-bg); + color: var(--theme-charcoal); +} + +body.theme-wildwest .utility-tab-bar { + background: var(--utility-tab-bg); + border-bottom-color: rgba(0, 0, 0, 0.08); +} + +body.theme-wildwest .utility-tab-btn { + font-family: var(--theme-ui-font); + font-size: 0.68rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.85); +} + +body.theme-wildwest .utility-tab-btn:hover { + color: #ffffff; + background: rgba(255, 255, 255, 0.1); +} + +body.theme-wildwest .utility-tab-btn.active { + color: #ffffff; + border-bottom-color: var(--accent-color); + background: rgba(255, 255, 255, 0.14); +} + +body.theme-wildwest .utility-panel-body { + color: var(--theme-charcoal); +} + +body.theme-wildwest .utility-panel-body .hint, +body.theme-wildwest .utility-panel-body small, +body.theme-wildwest .utility-panel-body label { + color: rgba(var(--theme-charcoal-rgb), 0.75); +} + +body.theme-wildwest input[type="text"], +body.theme-wildwest input[type="number"], +body.theme-wildwest input[type="email"], +body.theme-wildwest input[type="password"], +body.theme-wildwest select:not(.settings-theme-select), +body.theme-wildwest textarea { + border-radius: var(--theme-radius); + border-color: var(--input-border); + font-family: var(--theme-body-font); + background-color: var(--input-bg); + color: var(--text-color); +} + +body.theme-wildwest .utility-panel-body input, +body.theme-wildwest .utility-panel-body select, +body.theme-wildwest .utility-panel-body textarea { + background: #fff8ea; + color: var(--theme-charcoal); + border-color: rgba(var(--theme-charcoal-rgb), 0.15); +} + +body.theme-wildwest button, +body.theme-wildwest .btn { + border-radius: var(--theme-radius); + font-family: var(--theme-ui-font); + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +body.theme-wildwest .transform-button { + border-radius: var(--theme-radius); + border: 1px solid var(--input-border); + background: var(--button-bg); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); + min-height: 78px; +} + +body.theme-wildwest .transform-button-name { + font-family: var(--theme-display-font); + font-size: clamp(0.54rem, 5.5cqi, 0.82rem); + color: var(--theme-charcoal); + letter-spacing: 0.02em; +} + +body.theme-wildwest .transform-button:hover { + border-color: rgba(var(--theme-secondary-rgb), 0.55); + background: var(--button-hover-bg); +} + +body.theme-wildwest .transform-button.active { + border-color: var(--accent-color); + border-top: 3px solid var(--accent-color); + background: var(--button-bg); + box-shadow: none; +} + +body.theme-wildwest .transform-button.active .transform-button-name { + color: var(--accent-hover); +} + +body.theme-wildwest .transform-button.active::after { + opacity: 0.75; + width: 30px; + background: var(--accent-color); +} + +body.theme-wildwest .transform-button:before, +body.theme-wildwest .transform-button:hover:before, +body.theme-wildwest .transform-button.active:before { + display: none; +} + +body.theme-wildwest [class*="transform-category-"] { + background: var(--button-bg); +} + +body.theme-wildwest [class*="transform-category-"]:hover { + box-shadow: none; +} + +body.theme-wildwest .input-section, +body.theme-wildwest .output-section, +body.theme-wildwest .decode-section { + border: 1px solid var(--input-border); + border-radius: var(--theme-radius); +} + +body.theme-wildwest .status-banner { + border-radius: var(--theme-radius); + font-family: var(--theme-ui-font); + letter-spacing: 0.06em; + text-transform: uppercase; +} + +/* ─── Accessible (WCAG 2.1 AA) ─── */ + +body.theme-accessible { + font-family: var(--theme-ui-font); + line-height: 1.5; +} + +body.theme-accessible .app-root { + font-size: 1rem; +} + +body.theme-accessible a { + text-decoration: underline; + text-underline-offset: 2px; +} + +body.theme-accessible a.github-button { + text-decoration: none; +} + +body.theme-accessible .app-logo h1, +body.theme-accessible .app-topbar .logo h1, +body.theme-accessible .logo h1 { + font-family: var(--theme-ui-font); + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + animation: none; + text-shadow: none; + background: transparent; + color: var(--text-color); + border: 2px solid var(--text-color); +} + +body.theme-accessible .app-topbar, +body.theme-accessible .app-nav, +body.theme-accessible .app-utility { + border-color: var(--input-border); +} + +body.theme-accessible .app-nav .tab-buttons button, +body.theme-accessible .utility-tab-btn, +body.theme-accessible .transform-button, +body.theme-accessible button, +body.theme-accessible .btn, +body.theme-accessible input, +body.theme-accessible select, +body.theme-accessible textarea { + font-family: var(--theme-ui-font); + border-width: 2px; +} + +body.theme-accessible .app-nav .tab-buttons button { + font-size: 0.9375rem; + font-weight: 600; +} + +body.theme-accessible .app-nav .tab-buttons button.active { + border-left-width: 4px; + border-left-color: var(--accent-color); + background: var(--accent-tint); +} + +body.theme-accessible .utility-tab-btn.active { + border-bottom-width: 4px; + background: var(--accent-tint); +} + +body.theme-accessible .transform-button { + box-shadow: none; +} + +body.theme-accessible .transform-button:before, +body.theme-accessible .transform-button:after { + display: none; +} + +body.theme-accessible .transform-button.active { + outline: 2px solid var(--text-color); + outline-offset: 1px; +} + +body.theme-accessible [class*="transform-category-"].active .transform-button-name, +body.theme-accessible [class*="transform-category-"].active .transform-preview { + font-weight: 600; +} + +body.theme-accessible .transform-button-name { + font-size: clamp(0.75rem, 7.2cqi, 0.9375rem); + padding: 0 6px; + flex-shrink: 0; + min-height: calc(2 * 1.25em + 2px); + max-height: calc(2 * 1.25em + 2px); + -webkit-line-clamp: 2; +} + +body.theme-accessible .transform-button .transform-options-gear, +body.theme-accessible .transform-button .favorite-icon { + display: none; +} + +body.theme-accessible .transform-button-group { + display: flex; + flex-direction: column; + gap: 0; + border: 2px solid var(--input-border); + border-radius: var(--theme-radius); + overflow: hidden; + background: var(--button-bg); + min-width: 0; + height: 100%; +} + +body.theme-accessible .transform-button-group:has(.transform-button.active) { + outline: 2px solid var(--text-color); + outline-offset: 1px; +} + +body.theme-accessible .transform-button { + flex: 1 1 auto; + display: flex; + flex-direction: column; + min-height: 0; + border: none; + border-radius: 0; + border-bottom: 1px solid var(--input-border); + box-shadow: none; +} + +body.theme-accessible [class*="transform-category-"] { + border-left-width: 4px; +} + +body.theme-accessible .transform-button.active { + outline: none; +} + +body.theme-accessible .transform-buttons { + align-items: stretch; +} + +body.theme-accessible .transform-card-actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0; + width: 100%; + flex-shrink: 0; +} + +body.theme-accessible .transform-card-action, +body.theme-accessible .transform-card-action-slot { + min-width: 0; + min-height: 34px; +} + +body.theme-accessible .transform-card-action-slot { + display: block; + background: var(--button-bg); + border-right: 1px solid var(--input-border); +} + +body.theme-accessible .transform-card-action { + padding: 4px 3px; + font-family: var(--theme-ui-font); + font-size: clamp(0.625rem, 5.2cqi, 0.75rem); + font-weight: 600; + line-height: 1.2; + text-align: center; + border: none; + border-right: 1px solid var(--input-border); + border-radius: 0; + background: var(--button-bg); + color: var(--text-color); + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +body.theme-accessible .transform-card-action:last-child { + border-right: none; +} + +body.theme-accessible .transform-card-action-favorite.is-favorited { + background: var(--accent-tint); + font-weight: 700; +} + +body.theme-accessible .transform-card-action:hover { + background: var(--button-hover-bg); +} + +body.theme-accessible .transform-card-action:focus-visible { + outline: 3px solid var(--focus-ring-color); + outline-offset: -3px; + box-shadow: none; + z-index: 1; +} + +body.theme-accessible #category-randomizer .randomizer-button, +body.theme-accessible #category-randomizer .transform-button.transform-category-randomizer.randomizer-button { + padding: 14px 16px; + min-height: 72px; + font-size: 1rem; + background: var(--randomizer-button-bg); + color: var(--randomizer-button-text); + border-color: var(--randomizer-button-border); +} + +body.theme-accessible #category-randomizer .randomizer-button .transform-preview, +body.theme-accessible #category-randomizer .transform-button.randomizer-button .transform-preview { + color: var(--randomizer-button-preview-text); + background-color: var(--randomizer-button-preview-bg); + border-color: var(--randomizer-button-preview-border); + height: auto; + min-height: calc(2 * 1.25em + 8px); + max-height: none; +} + +body.theme-accessible #category-randomizer .transform-button.transform-category-randomizer.randomizer-button.active { + background: var(--randomizer-button-active-bg); + color: var(--randomizer-button-active-text); +} + +body.theme-accessible #category-randomizer .transform-button.transform-category-randomizer.randomizer-button.active .transform-preview { + color: var(--randomizer-button-active-preview-text); + background-color: var(--randomizer-button-active-preview-bg); + border-color: var(--randomizer-button-active-preview-border); +} + +body.theme-accessible #category-randomizer .transform-card-actions { + grid-template-columns: 1fr; +} + +body.theme-accessible #category-randomizer .transform-card-action-slot { + display: none; +} + +body.theme-accessible #category-randomizer .transform-card-action-favorite { + min-height: 40px; + font-size: 0.8125rem; +} + +body.theme-accessible .transform-preview, +body.theme-accessible [class*="transform-category-"]:not(.active) .transform-preview, +body.theme-accessible .transform-button:not(.active):hover .transform-preview { + color: var(--text-color); + background-color: var(--input-bg); + border-color: var(--input-border); + font-family: var(--theme-mono-font); + font-size: clamp(0.75rem, 7cqi, 0.9375rem); + border-width: 2px; + opacity: 1; + text-shadow: none; + flex-shrink: 0; + margin-top: auto; + height: calc(3 * 1.25em + 8px); + min-height: calc(3 * 1.25em + 8px); + max-height: calc(3 * 1.25em + 8px); + -webkit-line-clamp: 3; +} + +body.theme-accessible .transform-category-signwriting .transform-preview { + font-size: clamp(0.75rem, 7cqi, 0.9375rem); + height: calc(3 * 1.25em + 8px); + min-height: calc(3 * 1.25em + 8px); + max-height: calc(3 * 1.25em + 8px); +} + +body.theme-accessible [class*="transform-category-"].active .transform-preview { + color: var(--preview-active-text); + background-color: var(--preview-active-bg); + border-color: var(--preview-active-border); + height: calc(3 * 1.25em + 8px); + min-height: calc(3 * 1.25em + 8px); + max-height: calc(3 * 1.25em + 8px); +} + +body.theme-accessible label, +body.theme-accessible .settings-hint, +body.theme-accessible small, +body.theme-accessible .hint { + font-size: 0.9375rem; + line-height: 1.5; +} + +body.theme-accessible input::placeholder, +body.theme-accessible textarea::placeholder { + color: var(--placeholder-color); + opacity: 1; +} + +body.theme-accessible button:disabled, +body.theme-accessible .btn:disabled, +body.theme-accessible input:disabled, +body.theme-accessible select:disabled, +body.theme-accessible textarea:disabled { + opacity: 1; + color: var(--text-muted); + border-style: dashed; + cursor: not-allowed; +} + +body.theme-accessible *:focus-visible { + outline: 3px solid var(--focus-ring-color); + outline-offset: var(--focus-ring-offset); +} + +body.theme-accessible button:focus-visible, +body.theme-accessible .btn:focus-visible, +body.theme-accessible .transform-button:focus-visible, +body.theme-accessible .app-nav .tab-buttons button:focus-visible, +body.theme-accessible .utility-tab-btn:focus-visible, +body.theme-accessible input:focus-visible, +body.theme-accessible select:focus-visible, +body.theme-accessible textarea:focus-visible, +body.theme-accessible a:focus-visible { + outline: 3px solid var(--focus-ring-color); + outline-offset: var(--focus-ring-offset); + box-shadow: none; +} + +body.theme-accessible .switch.neon input[type="checkbox"]:focus-visible { + outline: 3px solid var(--focus-ring-color); + outline-offset: var(--focus-ring-offset); + box-shadow: none; +} + +body.theme-accessible select, +body.theme-accessible .settings-theme-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23000000' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; +} + +body.theme-accessible .github-button { + border: 2px solid var(--input-border); +} + +body.theme-accessible .topbar-utility-btn { + border-width: 2px; +} + +body.theme-accessible .logo h1 { + animation: none; +} + +body.theme-accessible .translate-lang-grid { + align-items: stretch; +} + +body.theme-accessible .translate-lang-card { + display: flex; + flex-direction: column; + gap: 0; + border: 2px solid var(--input-border); + border-radius: var(--theme-radius); + overflow: hidden; + background: var(--button-bg); + min-width: 0; + height: 100%; +} + +body.theme-accessible .translate-lang-btn { + flex: 1 1 auto; + width: 100%; + min-height: auto; + border: none; + border-radius: 0; + border-bottom: 1px solid var(--input-border); + border-left-width: 4px; + padding: 8px 6px; + font-size: 0.8125rem; + font-weight: 600; +} + +body.theme-accessible .translate-lang-btn .translate-lang-favorite, +body.theme-accessible .translate-lang-btn .translate-remove { + display: none; +} + +body.theme-accessible .translate-lang-custom { + padding-right: 6px; +} + +body.theme-accessible .translate-lang-action-remove { + color: var(--destructive-color); + font-weight: 700; +} + +body.theme-accessible .translate-lang-action-remove:hover { + background: var(--error-bg); +} + +/* ─── Reduced motion ─── */ + +@media (prefers-reduced-motion: reduce) { + body.theme-bt6 .app-logo h1, + body.theme-pliny .app-logo h1, + body.theme-cyberpunk .app-logo h1, + body.theme-wildwest .app-logo h1, + body.theme-accessible .app-logo h1, + body.theme-bt6 .app-topbar .logo h1, + body.theme-pliny .app-topbar .logo h1, + body.theme-cyberpunk .app-topbar .logo h1, + body.theme-wildwest .app-topbar .logo h1, + body.theme-accessible .app-topbar .logo h1 { + animation: none; + } + + body.theme-cyberpunk .app-root::after, + body.theme-cyberpunk .utility-tab-btn.active { + animation: none; + } +} diff --git a/data/alphabets/alchemical.json b/data/alphabets/alchemical.json new file mode 100644 index 0000000..2914262 --- /dev/null +++ b/data/alphabets/alchemical.json @@ -0,0 +1,13 @@ +{ + "name": "Alchemical Symbols", + "priority": 100, + "description": "Classical alchemical symbol alphabet", + "detectorHint": "[🜂🜃🜄🜅🜆🜇🜈🜉]", + "map": { + "A": "🜂", "B": "🜃", "C": "🜄", "D": "🜅", "E": "🜆", "F": "🜇", + "G": "🜈", "H": "🜉", "I": "🜊", "J": "🜋", "K": "🜌", "L": "🜍", + "M": "🜎", "N": "🜏", "O": "🜐", "P": "🜑", "Q": "🜒", "R": "🜓", + "S": "🜔", "T": "🜕", "U": "🜖", "V": "🜗", "W": "🜘", "X": "🜙", + "Y": "🜚", "Z": "🜛" + } +} diff --git a/data/alphabets/celestial.json b/data/alphabets/celestial.json new file mode 100644 index 0000000..9c7d3a1 --- /dev/null +++ b/data/alphabets/celestial.json @@ -0,0 +1,13 @@ +{ + "name": "Celestial Alphabet", + "priority": 100, + "description": "Agrippa's celestial / angelic symbol alphabet", + "detectorHint": "[☉☽☿♀♂♃♄♅♆♇☊☋]", + "map": { + "A": "☉", "B": "☽", "C": "☿", "D": "♀", "E": "♂", "F": "♃", + "G": "♄", "H": "♅", "I": "♆", "J": "♇", "K": "☊", "L": "☋", + "M": "⚳", "N": "⚴", "O": "⚵", "P": "⚶", "Q": "⚷", "R": "⚸", + "S": "⚹", "T": "⚺", "U": "⚻", "V": "⚼", "W": "⚽", "X": "⚾", + "Y": "⛢", "Z": "⛣" + } +} diff --git a/data/alphabets/daedric.json b/data/alphabets/daedric.json new file mode 100644 index 0000000..d64bbd0 --- /dev/null +++ b/data/alphabets/daedric.json @@ -0,0 +1,13 @@ +{ + "name": "Daedric Alphabet", + "priority": 100, + "description": "Elder Scrolls inspired Daedric-style symbols", + "detectorHint": "[ᚠᚡᚢᚣᚤᚥᚦᚧᚨᚩ]", + "map": { + "A": "ᚠ", "B": "ᚡ", "C": "ᚢ", "D": "ᚣ", "E": "ᚤ", "F": "ᚥ", + "G": "ᚦ", "H": "ᚧ", "I": "ᚨ", "J": "ᚩ", "K": "ᚪ", "L": "ᚫ", + "M": "ᚬ", "N": "ᚭ", "O": "ᚮ", "P": "ᚯ", "Q": "ᚰ", "R": "ᚱ", + "S": "ᚲ", "T": "ᚳ", "U": "ᚴ", "V": "ᚵ", "W": "ᚶ", "X": "ᚷ", + "Y": "ᚸ", "Z": "ᚹ" + } +} diff --git a/data/alphabets/dancing-men.json b/data/alphabets/dancing-men.json new file mode 100644 index 0000000..e89e79b --- /dev/null +++ b/data/alphabets/dancing-men.json @@ -0,0 +1,13 @@ +{ + "name": "Dancing Men Cipher", + "priority": 100, + "description": "Sherlock Holmes stick-figure cipher (Unicode approximations)", + "detectorHint": "[┣┫┳┻├┤┬┴╋╞╡╤╧]", + "map": { + "A": "┣", "B": "┫", "C": "┳", "D": "┻", "E": "├", "F": "┤", + "G": "┬", "H": "┴", "I": "╋", "J": "╞", "K": "╡", "L": "╤", + "M": "╧", "N": "╥", "O": "╨", "P": "╪", "Q": "╫", "R": "╬", + "S": "╭", "T": "╮", "U": "╯", "V": "╰", "W": "▸", "X": "◂", + "Y": "▴", "Z": "▾" + } +} diff --git a/data/alphabets/enochian.json b/data/alphabets/enochian.json new file mode 100644 index 0000000..d2f6625 --- /dev/null +++ b/data/alphabets/enochian.json @@ -0,0 +1,13 @@ +{ + "name": "Enochian Alphabet", + "priority": 100, + "description": "Enochian angelic script (Unicode approximations)", + "detectorHint": "[ᛂᛃᛄᛅᛆᛇᛈᛉᛊᛋ]", + "map": { + "A": "ᛂ", "B": "ᛃ", "C": "ᛄ", "D": "ᛅ", "E": "ᛆ", "F": "ᛇ", + "G": "ᛈ", "H": "ᛉ", "I": "ᛊ", "J": "ᛋ", "K": "ᛌ", "L": "ᛍ", + "M": "ᛎ", "N": "ᛏ", "O": "ᛐ", "P": "ᛑ", "Q": "ᛒ", "R": "ᛓ", + "S": "ᛔ", "T": "ᛕ", "U": "ᛖ", "V": "ᛗ", "W": "ᛘ", "X": "ᛙ", + "Y": "ᛚ", "Z": "ᛛ" + } +} diff --git a/data/alphabets/malachim.json b/data/alphabets/malachim.json new file mode 100644 index 0000000..fa2e19e --- /dev/null +++ b/data/alphabets/malachim.json @@ -0,0 +1,13 @@ +{ + "name": "Malachim Alphabet", + "priority": 100, + "description": "Malachim / angel script symbol substitution", + "detectorHint": "[✁✂✃✄✆✇✈✉✊✋✌✍✎✏]", + "map": { + "A": "✁", "B": "✂", "C": "✃", "D": "✄", "E": "✆", "F": "✇", + "G": "✈", "H": "✉", "I": "✊", "J": "✋", "K": "✌", "L": "✍", + "M": "✎", "N": "✏", "O": "✐", "P": "✑", "Q": "✒", "R": "✓", + "S": "✔", "T": "✕", "U": "✖", "V": "✗", "W": "✘", "X": "✙", + "Y": "✚", "Z": "✛" + } +} diff --git a/data/alphabets/mary-stuart.json b/data/alphabets/mary-stuart.json new file mode 100644 index 0000000..e24a618 --- /dev/null +++ b/data/alphabets/mary-stuart.json @@ -0,0 +1,13 @@ +{ + "name": "Mary Stuart Cipher", + "priority": 100, + "description": "Mary Queen of Scots nomenclator-style symbols", + "detectorHint": "[❶❷❸❹❺❻❼❽❾]", + "map": { + "A": "❶", "B": "❷", "C": "❸", "D": "❹", "E": "❺", "F": "❻", + "G": "❼", "H": "❽", "I": "❾", "J": "❿", "K": "➀", "L": "➁", + "M": "➂", "N": "➃", "O": "➄", "P": "➅", "Q": "➆", "R": "➇", + "S": "➈", "T": "➉", "U": "➊", "V": "➋", "W": "➌", "X": "➍", + "Y": "➎", "Z": "➏" + } +} diff --git a/data/alphabets/moon-alphabet.json b/data/alphabets/moon-alphabet.json new file mode 100644 index 0000000..aabb69d --- /dev/null +++ b/data/alphabets/moon-alphabet.json @@ -0,0 +1,13 @@ +{ + "name": "Moon Alphabet", + "priority": 100, + "description": "Moon phase / lunar symbol alphabet", + "detectorHint": "[☾☽☊☋⚸⚹⚺⚻]", + "map": { + "A": "☾", "B": "☽", "C": "☊", "D": "☋", "E": "⚸", "F": "⚹", + "G": "⚺", "H": "⚻", "I": "⚼", "J": "⚽", "K": "⚾", "L": "⛢", + "M": "⛣", "N": "⛤", "O": "⛥", "P": "⛦", "Q": "⛧", "R": "⛨", + "S": "⛩", "T": "⛪", "U": "⛫", "V": "⛬", "W": "⛭", "X": "⛮", + "Y": "⛯", "Z": "⛰" + } +} diff --git a/data/alphabets/passing-the-river.json b/data/alphabets/passing-the-river.json new file mode 100644 index 0000000..bc5fb25 --- /dev/null +++ b/data/alphabets/passing-the-river.json @@ -0,0 +1,13 @@ +{ + "name": "Passing the River Alphabet", + "priority": 100, + "description": "Golden Dawn Passing the River tarot script", + "detectorHint": "[♠♣♥♦♤♧♡♢]", + "map": { + "A": "♠", "B": "♣", "C": "♥", "D": "♦", "E": "♤", "F": "♧", + "G": "♡", "H": "♢", "I": "♩", "J": "♪", "K": "♫", "L": "♬", + "M": "♭", "N": "♮", "O": "♯", "P": "°", "Q": "±", "R": "×", + "S": "÷", "T": "∓", "U": "∔", "V": "∕", "W": "∖", "X": "∗", + "Y": "∘", "Z": "∙" + } +} diff --git a/data/alphabets/rosicrucian.json b/data/alphabets/rosicrucian.json new file mode 100644 index 0000000..a6692b6 --- /dev/null +++ b/data/alphabets/rosicrucian.json @@ -0,0 +1,13 @@ +{ + "name": "Rosicrucian Cipher", + "priority": 100, + "description": "Rosicrucian / Golden Dawn symbol alphabet", + "detectorHint": "[⛤⛥⛦⛧⛨⛩⛪⛫]", + "map": { + "A": "⛤", "B": "⛥", "C": "⛦", "D": "⛧", "E": "⛨", "F": "⛩", + "G": "⛪", "H": "⛫", "I": "⛬", "J": "⛭", "K": "⛮", "L": "⛯", + "M": "⛰", "N": "⛱", "O": "⛲", "P": "⛳", "Q": "⛴", "R": "⛵", + "S": "⛶", "T": "⛷", "U": "⛸", "V": "⛹", "W": "⛺", "X": "⛻", + "Y": "⛼", "Z": "⛽" + } +} diff --git a/data/alphabets/templars.json b/data/alphabets/templars.json new file mode 100644 index 0000000..ff22589 --- /dev/null +++ b/data/alphabets/templars.json @@ -0,0 +1,13 @@ +{ + "name": "Templars Cipher", + "priority": 100, + "description": "Templar / pigpen variant with dot markers", + "detectorHint": "[◧◨◩◪◫◬◭◮◯]", + "map": { + "A": "◧", "B": "◨", "C": "◩", "D": "◪", "E": "◫", "F": "◬", + "G": "◭", "H": "◮", "I": "◯", "J": "◰", "K": "◱", "L": "◲", + "M": "◳", "N": "◴", "O": "◵", "P": "◶", "Q": "◷", "R": "◸", + "S": "◹", "T": "◺", "U": "◻", "V": "◼", "W": "◽", "X": "◾", + "Y": "◿", "Z": "⬒" + } +} diff --git a/data/alphabets/theban.json b/data/alphabets/theban.json new file mode 100644 index 0000000..674c792 --- /dev/null +++ b/data/alphabets/theban.json @@ -0,0 +1,13 @@ +{ + "name": "Theban Alphabet", + "priority": 100, + "description": "Honoric / Theban witch alphabet (symbol substitution)", + "detectorHint": "[∀∁∂∃∄∅∆∇∈∉∊∋∌∍∎∏∐∑−∓∔∕∖∗∘∙√∛∜∝]", + "map": { + "A": "∀", "B": "∁", "C": "∂", "D": "∃", "E": "∄", "F": "∅", + "G": "∆", "H": "∇", "I": "∈", "J": "∉", "K": "∊", "L": "∋", + "M": "∌", "N": "∍", "O": "∎", "P": "∏", "Q": "∐", "R": "∑", + "S": "−", "T": "∓", "U": "∔", "V": "∕", "W": "∖", "X": "∗", + "Y": "∘", "Z": "∙" + } +} diff --git a/docs/THEMES.md b/docs/THEMES.md new file mode 100644 index 0000000..409fef0 --- /dev/null +++ b/docs/THEMES.md @@ -0,0 +1,179 @@ +# Themes + +P4RS3LT0NGV3 uses a **token-based theme system**. Most UI reads CSS custom properties (`--accent-color`, `--button-bg`, etc.) from `body.theme-*` blocks, so new themes are mostly palette + typography work—not per-component rewrites. + +## Built-in themes + +| ID | Name | Notes | +|----|------|--------| +| `dark` | Dark | Default; blue accent | +| `light` | Light | Light surfaces; blue accent | +| `accessible` | Accessible | WCAG 2.1 AA high-contrast; system fonts; strong focus rings | +| `bt6` | BT6 | Black + blood red + gold; mono titles | +| `pliny` | Pliny | CRT green-on-black | +| `cyberpunk` | Cyberpunk | Magenta + cyan neon | +| `wildwest` | Wild West | Cream / sage / dusty rose (light `color-scheme`) | + +Users pick a theme in **Advanced Settings** (utility dock → Settings tab). Press **`D`** to cycle themes. The choice is saved in `localStorage` under the key `theme`. + +## Accessible theme + +The **`accessible`** theme targets **[WCAG 2.1 Level AA](https://www.w3.org/TR/WCAG21/)** for color, contrast, focus, and motion. + +| Requirement | Implementation | +|-------------|------------------| +| **1.4.3 Contrast (AA)** | `#000000` / `#595959` text on `#ffffff`; `#004080` accent; category active colors darkened for white labels | +| **1.4.11 Non-text contrast** | `2px` borders at `#555555` on inputs, buttons, and cards | +| **2.4.7 Focus visible** | `3px` `#004080` outline + `2px` offset on all interactive elements | +| **1.4.12 Text spacing** | `1rem` base size, `1.5` line-height, system UI fonts | +| **1.4.1 Use of color** | Active nav/tabs use underline + weight + border, not color alone | +| **2.3.3 Animation** | Logo glitch and decorative card effects disabled | +| **Transform cards** | Text **Options** / **Favorite** buttons; fixed preview contrast and row heights | +| **Translation picker** | Text **Favorite** (and **Remove** on custom langs); star/× icons hidden | + +Decorative themes (Cyberpunk, Pliny, etc.) are unchanged. For audits, test with the **Accessible** theme selected and keyboard-only navigation. + +## How it works + +1. **`js/utils/theme.js`** — Registry (`themes` array), `applyTheme()`, `cycleTheme()`, persistence. +2. **`css/style.css`** — One `body.theme- { … }` block per theme defining design tokens. +3. **`css/themes-atmosphere.css`** — Optional “premium” overrides for custom themes (nav, transform cards, backgrounds, fonts). Imported at the top of `style.css`. +4. **`index.template.html`** — Inline bootstrap script applies the saved theme **before** Vue mounts (avoids flash of wrong theme). + +When a theme is applied, the body gets: + +- `theme-` — primary selector (e.g. `theme-bt6`) +- `dark-theme` or `light-theme` — legacy aliases still used by a few rules + +Copy an existing `body.theme-*` block as a starting point—`body.theme-dark` and `body.theme-light` are the simplest references; custom themes often follow the BT6 / Pliny pattern. + +## Adding a new theme + +### 1. Register in `js/utils/theme.js` + +```javascript +{ id: 'mytheme', name: 'My Theme', icon: 'fa-star' } +``` + +- **`id`** — lowercase, no spaces; becomes `body.theme-mytheme`. +- **`name`** — label in the dropdown. +- **`icon`** — optional Font Awesome class (not shown in dropdown today, but kept for future use). + +### 2. Add tokens in `css/style.css` + +Add a new block (copy from `body.theme-dark` or an existing custom theme): + +```css +body.theme-mytheme { + --main-bg-color: #…; + --secondary-bg: #…; + --nav-bg: #…; + --utility-tab-bg: #…; + --text-color: #…; + --text-muted: #…; + --accent-color: #…; + --accent-color-rgb: R, G, B; /* comma-separated, no spaces */ + --accent-hover: #…; + /* … accent-tint-*, surface-*, button-*, input-*, error-*, etc. */ + --glitch-color: #…; + --glitch-color-rgb: R, G, B; + --switch-border: #…; + --switch-surface-glow: rgba(…); + --switch-track-bg: #…; + --switch-track-border: #…; + --switch-track-checked-bg: #…; + --switch-track-checked-glow: rgba(…); + --switch-thumb-bg: #…; + --switch-thumb-glow: rgba(…); + --switch-thumb-checked-bg: #…; + --switch-thumb-checked-glow: rgba(…); + color-scheme: dark; /* or light for pale themes */ +} +``` + +**Required tokens** — At minimum, match what `body.theme-dark` defines: surfaces, text, accent (+ `-rgb`), buttons, inputs, focus/error/success, tooltips, switch tokens, and legacy aliases (`--text-primary`, `--border-color`, etc.) at the bottom of the block. + +**Light themes** — Set `color-scheme: light` and ensure `--text-on-accent` contrasts on `--accent-color`. Wild West is the reference for a light custom theme. + +**Optional theme tokens** (used by atmosphere CSS): + +- `--theme-display-font`, `--theme-ui-font`, `--theme-mono-font` +- `--theme-secondary`, `--theme-secondary-rgb` +- `--theme-radius` — corner radius (`0` for sharp, `2px`–`6px` for rounded) +- Wild West also uses `--theme-cream`, `--theme-charcoal`, etc. + +### 3. Optional atmosphere in `css/themes-atmosphere.css` + +For a distinctive look beyond color swaps, add a section: + +```css +body.theme-mytheme .app-root::before { /* background texture / tint */ } +body.theme-mytheme .transform-button { /* card chrome */ } +body.theme-mytheme .app-nav .tab-buttons button.active { /* nav active state */ } +``` + +If you add a custom theme here, also extend the shared selectors at the top of the file (`body[class*="theme-…"]`) so shell layering (`::before` / z-index) stays consistent. + +**Style guidelines** (project convention): + +- Use **flat colors** only—no CSS gradients unless explicitly requested. +- Toggle switches (`.switch.neon`) pick up colors from `--switch-*` tokens automatically. +- Avoid logo `::before` / `::after` decorations. + +### 4. Fonts (if needed) + +Google Fonts are imported at the top of `css/style.css`. Add families there, then reference them in your `--theme-*-font` tokens. + +### 5. Select carets & dropdowns + +Custom themes with non-default accents often need a `select` caret override. See existing blocks: + +```css +body.theme-mytheme select:not(.settings-theme-select) { background-image: url("data:…"); } +body.theme-mytheme .settings-theme-select { … } +``` + +Wild West required extra care so theme dropdown carets do not tile—use `background-repeat: no-repeat` and longhand `background-*` when overriding selects. + +### 6. Build & test + +```bash +npm run build +npm test +``` + +Open `dist/index.html` (or `npm start`) and check: + +- [ ] Advanced Settings theme dropdown lists your theme +- [ ] **`D`** cycles through it without errors +- [ ] Nav, main content, utility dock (desktop) +- [ ] Mobile: utility panel toggle in header; no overlapping FAB +- [ ] Transform grid, toggles, inputs, notifications +- [ ] Advanced Settings / OpenRouter model dropdown (if applicable) +- [ ] Reload persists choice + +No change to `index.template.html` is required unless you rename the bootstrap script flow—the dropdown is driven by `themeOptions` from Vue, which reads `ThemeUtils.getThemes()`. + +## File checklist + +| File | Action | +|------|--------| +| `js/utils/theme.js` | Add `{ id, name, icon }` | +| `css/style.css` | Add `body.theme- { … }` token block | +| `css/themes-atmosphere.css` | Optional component/atmosphere overrides | +| `css/style.css` `@import` | Already imports `themes-atmosphere.css` — no change | +| `README.md` | Add theme to user-facing list (optional) | + +## Updating an existing theme + +1. Edit the `body.theme-` token block in `css/style.css`. +2. Adjust matching rules in `css/themes-atmosphere.css` if the theme has premium styling. +3. Rebuild and spot-check the same checklist above. + +Token changes propagate everywhere that uses `var(--…)`; you rarely need to touch individual tools or templates. + +## Related docs + +- **User-facing overview**: `README.md` → User Experience +- **Project layout**: `CONTRIBUTING.md` → Project Structure +- **UI patterns**: `docs/UI-COMPONENTS.md` diff --git a/index.template.html b/index.template.html index 004bbaa..0e21cde 100644 --- a/index.template.html +++ b/index.template.html @@ -3,7 +3,13 @@ - Parseltongue 2.0 - LLM Payload Crafter + Parseltongue 4.0 — Text Encoder, Decoder & Steganography Tool + + + + + + @@ -13,161 +19,185 @@ -
-
-
- +