diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c2d10e..3a90add 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,104 +4,124 @@ Thank you for your interest in contributing! This guide will help you understand ## πŸ“ Project Structure +Source layout (the runnable app is produced under **`dist/`** by `npm run build`; `dist/` is gitignored). + ``` P4RS3LT0NGV3/ +β”œβ”€β”€ package.json +β”œβ”€β”€ favicon.svg +β”œβ”€β”€ index.template.html # HTML shell; tool *script* tags updated by inject-tool-scripts +β”œβ”€β”€ css/ +β”‚ β”œβ”€β”€ style.css +β”‚ └── notification.css β”œβ”€β”€ js/ -β”‚ β”œβ”€β”€ app.js # Main Vue.js application entry point -β”‚ β”œβ”€β”€ core/ # Core feature modules (shared libraries) -β”‚ β”‚ β”œβ”€β”€ decoder.js # Universal decoder -β”‚ β”‚ β”œβ”€β”€ steganography.js # Steganography encoding/decoding -β”‚ β”‚ └── toolRegistry.js # Tool registry system -β”‚ β”œβ”€β”€ bundles/ # Build-generated files (auto-created) -β”‚ β”‚ └── transforms-bundle.js # Generated bundle from src/transformers/ -β”‚ β”œβ”€β”€ config/ # Configuration constants +β”‚ β”œβ”€β”€ app.js # Vue app entry +β”‚ β”œβ”€β”€ config/ β”‚ β”‚ └── constants.js -β”‚ β”œβ”€β”€ data/ # Generated or static data files (auto-created) -β”‚ β”‚ β”œβ”€β”€ emojiData.js # Generated from Unicode emoji data -β”‚ β”‚ └── emojiCompatibility.js -β”‚ β”œβ”€β”€ utils/ # Utility functions +β”‚ β”œβ”€β”€ core/ # Shared logic (not tab-specific UI) +β”‚ β”‚ β”œβ”€β”€ decoder.js # Universal decode engine +β”‚ β”‚ β”œβ”€β”€ steganography.js # Emoji / invisible carriers +β”‚ β”‚ β”œβ”€β”€ toolRegistry.js # Registers tools, merges Vue data/methods +β”‚ β”‚ └── transformOptions.js +β”‚ β”œβ”€β”€ data/ # Static data shipped with the app (see note below) +β”‚ β”‚ β”œβ”€β”€ anticlassifierPrompt.js +β”‚ β”‚ β”œβ”€β”€ emojiCompatibility.js +β”‚ β”‚ β”œβ”€β”€ endSequences.js +β”‚ β”‚ β”œβ”€β”€ glitchTokens.js +β”‚ β”‚ └── openrouterModels.js +β”‚ β”œβ”€β”€ utils/ β”‚ β”‚ β”œβ”€β”€ clipboard.js -β”‚ β”‚ β”œβ”€β”€ emoji.js +β”‚ β”‚ β”œβ”€β”€ emoji.js # window.EmojiUtils (uses window.emojiData when present) β”‚ β”‚ β”œβ”€β”€ escapeParser.js β”‚ β”‚ β”œβ”€β”€ focus.js +β”‚ β”‚ β”œβ”€β”€ glitchTokens.js β”‚ β”‚ β”œβ”€β”€ history.js β”‚ β”‚ β”œβ”€β”€ notifications.js β”‚ β”‚ └── theme.js -β”‚ └── tools/ # Tool implementations (Vue integration) -β”‚ β”œβ”€β”€ Tool.js # Base class -β”‚ β”œβ”€β”€ TransformTool.js +β”‚ └── tools/ # One *Tool.js per tab (extends Tool.js) +β”‚ β”œβ”€β”€ Tool.js +β”‚ β”œβ”€β”€ AntiClassifierTool.js +β”‚ β”œβ”€β”€ BijectionTool.js β”‚ β”œβ”€β”€ DecodeTool.js β”‚ β”œβ”€β”€ EmojiTool.js -β”‚ β”œβ”€β”€ TokenadeTool.js +β”‚ β”œβ”€β”€ GibberishTool.js β”‚ β”œβ”€β”€ MutationTool.js -β”‚ β”œβ”€β”€ TokenizerTool.js +β”‚ β”œβ”€β”€ PromptCraftTool.js β”‚ β”œβ”€β”€ SplitterTool.js -β”‚ └── GibberishTool.js +β”‚ β”œβ”€β”€ TokenadeTool.js +β”‚ β”œβ”€β”€ TokenizerTool.js +β”‚ β”œβ”€β”€ TransformTool.js +β”‚ └── TranslateTool.js # hidden tab wiring; UI lives on Transform β”œβ”€β”€ src/ -β”‚ β”œβ”€β”€ emojiWordMap.js # Emoji keyword mappings (merged into emojiData.js) -β”‚ └── transformers/ # Transformer modules (source - bundled at build time) +β”‚ β”œβ”€β”€ emojiWordMap.js # Merged into generated emoji data at build time +β”‚ └── transformers/ # Transformer sources β†’ bundled to dist/js/bundles/ β”‚ β”œβ”€β”€ BaseTransformer.js -β”‚ β”œβ”€β”€ ancient/ # Elder Futhark, Hieroglyphics, Ogham, Roman Numerals -β”‚ β”œβ”€β”€ case/ # Camel, Kebab, Snake, Title, etc. -β”‚ β”œβ”€β”€ cipher/ # Caesar, ROT13, VigenΓ¨re, Atbash, etc. -β”‚ β”œβ”€β”€ encoding/ # Base64, Hex, Binary, URL, etc. -β”‚ β”œβ”€β”€ fantasy/ # Quenya, Tengwar, Klingon, Aurebesh, Dovahzul -β”‚ β”œβ”€β”€ format/ # Leetspeak, Pig Latin, Reverse, etc. -β”‚ β”œβ”€β”€ special/ # Randomizer -β”‚ β”œβ”€β”€ technical/ # Morse, NATO, Braille, Brainfuck, etc. -β”‚ β”œβ”€β”€ unicode/ # Upside-down, Fullwidth, Bubble, etc. -β”‚ └── visual/ # Disemvowel, Rovarspraket, Ubbi-dubbi, etc. -β”œβ”€β”€ templates/ # HTML templates for tools (injected at build time) +β”‚ β”œβ”€β”€ index.js # Generated by npm run build:index (gitignored) +β”‚ β”œβ”€β”€ ancient/ +β”‚ β”œβ”€β”€ case/ +β”‚ β”œβ”€β”€ cipher/ +β”‚ β”œβ”€β”€ encoding/ +β”‚ β”œβ”€β”€ fantasy/ +β”‚ β”œβ”€β”€ format/ +β”‚ β”œβ”€β”€ special/ +β”‚ β”œβ”€β”€ technical/ +β”‚ β”œβ”€β”€ unicode/ +β”‚ └── visual/ +β”œβ”€β”€ templates/ # Injected into dist/index.html by inject-tool-templates +β”‚ β”œβ”€β”€ anticlassifier.html +β”‚ β”œβ”€β”€ bijection.html β”‚ β”œβ”€β”€ decoder.html -β”‚ β”œβ”€β”€ steganography.html -β”‚ β”œβ”€β”€ transforms.html -β”‚ β”œβ”€β”€ tokenade.html β”‚ β”œβ”€β”€ fuzzer.html -β”‚ β”œβ”€β”€ tokenizer.html +β”‚ β”œβ”€β”€ gibberish.html +β”‚ β”œβ”€β”€ promptcraft.html β”‚ β”œβ”€β”€ splitter.html -β”‚ └── gibberish.html -β”œβ”€β”€ build/ # Build scripts -β”‚ β”œβ”€β”€ build-index.js # Generates transformer index -β”‚ β”œβ”€β”€ build-transforms.js # Bundles transformers into js/bundles/ -β”‚ β”œβ”€β”€ build-emoji-data.js # Generates emojiData.js from Unicode data -β”‚ β”œβ”€β”€ inject-tool-scripts.js # Auto-discovers and registers tools -β”‚ └── inject-tool-templates.js # Injects templates into index.html -β”œβ”€β”€ tests/ # Test suites +β”‚ β”œβ”€β”€ steganography.html +β”‚ β”œβ”€β”€ tokenade.html +β”‚ β”œβ”€β”€ tokenizer.html +β”‚ β”œβ”€β”€ transforms.html +β”‚ └── README.md +β”œβ”€β”€ build/ +β”‚ β”œβ”€β”€ README.md +β”‚ β”œβ”€β”€ build-index.js # Writes src/transformers/index.js +β”‚ β”œβ”€β”€ build-transforms.js # Writes dist/js/bundles/transforms-bundle.js +β”‚ β”œβ”€β”€ build-emoji-data.js # Writes dist/js/data/emojiData.js +β”‚ β”œβ”€β”€ copy-static.js # Copies css/, js/, favicon β†’ dist/ +β”‚ β”œβ”€β”€ 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 +β”œβ”€β”€ tests/ β”‚ β”œβ”€β”€ test_universal.js β”‚ └── test_steganography_options.js -β”œβ”€β”€ css/ # Stylesheets (edit *.css, then npm run build:css) -β”‚ β”œβ”€β”€ style.css # Source (readable) -β”‚ β”œβ”€β”€ style.min.css # Generated; linked from HTML -β”‚ β”œβ”€β”€ notification.css -β”‚ └── notification.min.css -β”œβ”€β”€ index.template.html # Base HTML template (templates injected here) -β”œβ”€β”€ index.html # Generated file (created by build process) -└── docs/ # Documentation +β”œβ”€β”€ docs/ +β”‚ β”œβ”€β”€ TOOL-SYSTEM.md +β”‚ β”œβ”€β”€ TOOL_ARCHITECTURE.md +β”‚ └── UI-COMPONENTS.md +β”œβ”€β”€ README.md +└── CONTRIBUTING.md + +dist/ # npm run build β€” gitignored +β”œβ”€β”€ index.html # `npm run build:templates` β†’ from index.template.html + templates/ +β”œβ”€β”€ css/ … +β”œβ”€β”€ js/ … +β”‚ β”œβ”€β”€ bundles/transforms-bundle.js +β”‚ └── data/emojiData.js ``` +**Generated / ignored paths (also listed in `.gitignore`):** `dist/`, `src/transformers/index.js`, legacy `js/bundles/transforms-bundle.js`, `js/data/emojiData.js`, root `index.html` if present. The build updates **`dist/index.html`** only; a **root** `index.html` is not produced by the current scripts. + ## 🎯 Key Concepts ### Core vs Tools -- **`js/core/`** - Shared business logic and infrastructure - - These are **NOT** tool-specific - - Examples: - - `decoder.js` (used by DecodeTool + app.js) - - `steganography.js` (used by EmojiTool + decoder.js) - - `emojiLibrary.js` (used by EmojiTool) - - `toolRegistry.js` (ToolRegistry class - infrastructure for the tool system) - -- **Source files** (`src/`) - Source files used by build process - - `emojiWordMap.js` - Emoji keyword mappings (merged into emojiData.js during build) - - `transformers/` - Transformer modules (bundled into transforms-bundle.js) - -- **Generated files** (`js/bundles/`) - - `transforms-bundle.js` - Generated bundle from `src/transformers/` (created by `npm run build:transforms`) - -- **`js/tools/`** - Vue.js integration layer for UI features - - Each tool represents a tab/feature in the UI - - Tools use core modules and utilities for functionality - - Example: `DecodeTool.js` uses `window.universalDecode` from `core/decoder.js` +- **`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/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) +- **`js/tools/`** β€” Vue integration: one `*Tool.js` per tab (plus `TranslateTool.js`, which is hidden and wired from the Transform tab) + - Example: `DecodeTool.js` uses the universal decode API from `core/decoder.js` ### Transformers vs Tools @@ -142,7 +162,7 @@ Transformers are the core text transformation logic. See `src/transformers/READM 1. Create a new file in the appropriate category directory: ```bash - src/transformers/ciphers/my-cipher.js + src/transformers/cipher/my-cipher.js ``` 2. Use the `BaseTransformer` class: @@ -174,7 +194,7 @@ Transformers are the core text transformation logic. See `src/transformers/READM ``` 4. Test it: - - Open `index.html` in a browser + - After `npm run build`, open **`dist/index.html`** in a browser (or use `npm start` β†’ http://localhost:8080 if you prefer) - Your transformer will appear in the Transform tab automatically - Test encoding/decoding - Test with the Universal Decoder @@ -250,7 +270,7 @@ Tools represent UI features/tabs. Examples: Transform tab, Decoder tab, Emoji ta ``` 4. Test it: - - Open `index.html` + - After `npm run build`, open **`dist/index.html`** (or `npm start` at http://localhost:8080) - Your new tab should appear automatically - Test all functionality @@ -273,7 +293,7 @@ Utilities are shared helper functions used across the app. Currently, utility fu }; ``` -2. Add script tag to `index.html` (before `app.js`): +2. Add a script tag to **`index.template.html`** (before `app.js`), then `npm run build:copy` (or `npm run build`) so it lands in **`dist/index.html`**: ```html ``` @@ -289,7 +309,7 @@ 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:** The `js/utils/` directory contains utility functions: clipboard, escapeParser, focus, history, notifications, and theme. The `js/config/` directory contains configuration constants. +**Note:** Prefer `js/utils/` for shared helpers (clipboard, emoji, escapeParser, focus, glitchTokens, history, notifications, theme). Use `js/config/` for constants. ## πŸ§ͺ Testing @@ -328,12 +348,11 @@ npm run test:steg # Steganography options tests ### File Organization -- **Core modules** (`js/core/`) - Shared business logic (e.g., `decoder.js`) -- **Root-level modules** (`js/`) - Feature libraries (e.g., `steganography.js`, `emojiLibrary.js`) -- **Tools** (`js/tools/`) - Vue.js UI integration layer -- **Templates** (`templates/`) - HTML templates for tools (injected at build time) -- **Transformers** (`src/transformers/`) - Text transformation logic -- **Bundles** (`js/bundles/`) - Build-generated files +- **Core modules** (`js/core/`) β€” Shared logic (`decoder.js`, `steganography.js`, `toolRegistry.js`, `transformOptions.js`) +- **App entry** (`js/app.js`) β€” Vue bootstrap and shell behavior +- **Tools** (`js/tools/`) β€” Tab UI layer (`*Tool.js`) +- **Templates** (`templates/`) β€” Tool markup injected into `dist/index.html` +- **Transformers** (`src/transformers/`) β€” Transform implementations; output is `dist/js/bundles/transforms-bundle.js` ### Naming Conventions @@ -351,9 +370,9 @@ npm run build:templates ``` This: -1. Reads all `.html` files from `templates/` directory -2. Injects them into `index.html` at the `#tool-content-container` marker -3. Creates a single static HTML file for fast loading +1. Reads the ordered list of tool templates from `templates/` (see `build/inject-tool-templates.js`) +2. Injects them into **`dist/index.html`** at the `#tool-content-container` marker (from `index.template.html`) +3. Produces the static HTML file served from `dist/` **When to run:** - After editing any template in `templates/` @@ -361,21 +380,21 @@ This: ### Build Script Details -- **Directory Creation**: Build scripts automatically create output directories (`js/bundles/`, `js/data/`) if they don't exist -- **Full Build**: Run `npm run build` to execute all build steps in order -- **Individual Builds**: Each build script can be run independently +- **Directory creation**: Scripts create `dist/` (and subfolders) as needed +- **Full build**: `npm run build` runs tools injection, copy, transformer index, transform bundle, emoji data, template injection (see `package.json` and `build/README.md`) +- **Individual builds**: Each `npm run build:*` step can be run on its own -**Note:** Transformers are loaded from `js/bundles/transforms-bundle.js` which may be pre-built or generated separately. +**Note:** The browser loads transforms from **`dist/js/bundles/transforms-bundle.js`** after a successful `npm run build:transforms` (and `npm run build:copy`). ## πŸ› Debugging ### Common Issues -1. **Template changes not showing**: Run `npm run build:templates` to inject templates into `index.html` +1. **Template changes not showing**: Run `npm run build:templates` to rebuild **`dist/index.html`**, then refresh (or run `npm start` after a full `npm run build`) 2. **Tool not showing**: Check that: - - Tool is registered in `js/core/toolRegistry.js` - - Script tag is in `index.html` before `app.js` - - Template file exists in `templates/` directory + - `npm run build:tools` ran so `toolRegistry.js` and `index.template.html` include the new tool + - Script tag order in **`index.template.html`** (loads before `app.js`) + - Template file exists in `templates/` and is listed in `build/inject-tool-templates.js` when adding a new tab 3. **Tests failing**: Check file paths use `path.join(projectRoot, '...')` ### Browser Console @@ -383,23 +402,24 @@ This: - Open browser DevTools (F12) - Check console for errors - Use `window.transforms` to see all transformers -- Use `window.steganography` to access steganography functions -- Use `window.emojiLibrary` to access emoji functions +- Use `window.steganography` for steganography helpers +- Use `window.emojiData` (after build) and `window.EmojiUtils` (`js/utils/emoji.js`) for emoji lists / splitting ## πŸ“š Documentation -- **Project README**: `README.md` - Overview and user guide -- **Templates**: `templates/README.md` - How to edit tool templates -- **Build Process**: `build/README.md` - Build script documentation -- **Tool System**: `docs/TOOL-SYSTEM.md` - Tool architecture details -- **Code Review**: `docs/CODE-REVIEW.md` - Architecture and code review guidelines +- **Project README**: `README.md` β€” Overview and user guide +- **Templates**: `templates/README.md` β€” Editing tool templates +- **Build process**: `build/README.md` β€” Build scripts +- **Tool system**: `docs/TOOL-SYSTEM.md` β€” Templates, injection, UI vocabulary +- **Tool architecture**: `docs/TOOL_ARCHITECTURE.md` +- **UI components**: `docs/UI-COMPONENTS.md` ## βœ… Checklist Before Submitting - [ ] Code follows existing style - [ ] Tests pass (`npm test`) - [ ] Templates built (`npm run build:templates`) if template files were edited -- [ ] Tested in browser (open `index.html`) +- [ ] Tested in browser (`npm run build`, then open `dist/index.html` or `npm start`) - [ ] No console errors - [ ] Documentation updated (if needed) - [ ] JSDoc comments added (for new functions) @@ -407,7 +427,7 @@ This: ## 🀝 Questions? - Check existing code for examples -- Review `docs/CODE_REVIEW.md` for architecture details +- Review `docs/TOOL_ARCHITECTURE.md` and `docs/TOOL-SYSTEM.md` for architecture details - Look at similar features to understand patterns Thank you for contributing! πŸŽ‰ diff --git a/README.md b/README.md index 7300f94..7672fc0 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,215 @@ # 🐍 P4RS3LT0NGV3 - Universal Text Translator -A powerful web-based text transformation and steganography tool that can encode/decode text in 79+ different languages, scripts, and formats. Think of it as a universal translator for ALL alphabets and writing systems! +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! + +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). ## ✨ Features ### πŸ” **Steganography** -- **Emoji Steganography**: Hide messages within emojis using variation selectors -- **Invisible Text**: Encode text using Unicode Tags block (completely invisible) -- **Image Steganography**: Hide messages in images using LSB techniques +- **Emoji Steganography**: Hide messages within emojis using variation selectors (VS15/VS16 and related options; configurable bit order in Advanced Settings) +- **Invisible Text**: Encode text using Unicode Tags block (visually invisible) +- **Whitespace & zero-width steganography**: Available as transforms for research-style payloads (see transform categories) ### 🌍 **Text Transformations** -#### **Encoding & Decoding** -- **Base64** - Standard base64 encoding/decoding -- **Base32** - RFC 4648 compliant base32 encoding/decoding -- **Base58** - Bitcoin alphabet encoding/decoding -- **Base62** - 0-9A-Za-z compact encoding/decoding -- **Binary** - Convert text to/from binary representation -- **Hexadecimal** - Convert text to/from hex format -- **ASCII85** - Advanced ASCII85 encoding/decoding -- **URL Encode** - URL-safe encoding/decoding -- **HTML Entities** - HTML entity encoding/decoding +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. -#### **Ciphers & Codes** -- **Caesar Cipher** - Classic shift cipher with configurable offset -- **ROT13** - Simple rotation cipher -- **ROT47** - Extended rotation cipher for ASCII 33-126 -- **Morse Code** - International Morse code with proper spacing +#### **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 +- **kebab-case** - kebab-case for slugs and identifiers +- **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 + +#### **Cipher** +- **ADFGX Cipher** - WWI ADFGVX-style polybius + column transposition +- **Affine Cipher** - Affine substitution (ax + b mod 26) +- **Atbash Cipher** - Reverse-alphabet substitution (A↔Z) +- **Autokey Cipher** - Key stream mixed with plaintext (autokey) +- **Baconian Cipher** - Five-letter groups hiding A/B (Bacon biliteral) +- **Beaufort Cipher** - Beaufort key-table polyalphabetic cipher +- **Bifid Cipher** - Polybius square + row/column interleaving +- **Caesar Cipher** - Classic alphabet shift (configurable) +- **Columnar Transposition** - Columnar transposition with a keyword +- **Four-Square Cipher** - Four 5Γ—5 squares; digraph substitution +- **Gronsfeld Cipher** - VigenΓ¨re family with numeric key +- **Hill Cipher** - Matrix-based multi-letter substitution +- **Homophonic Cipher** - Multiple ciphertext symbols per plaintext letter +- **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 +- **Rail Fence** - Zig-zag rail-fence transposition +- **ROT128** - UTF-16 code unit rotation by 128 +- **ROT13** - Rotate Latin letters by 13 places +- **ROT18** - Rotate printable ASCII (33–126) by 18 +- **ROT47** - Rotate printable ASCII (33–126) by 47 +- **ROT5** - Rotate digits 0–9 by 5 +- **ROT8000** - Plane-0 Unicode BMP rotation cipher +- **Scytale Cipher** - Wrap-around strip (scytale) transposition +- **Trifid Cipher** - Three Polybius cubes + trifid grouping +- **Two-Square Cipher** - Digraph cipher with two Playfair squares +- **VigenΓ¨re Cipher** - Polyalphabetic cipher with repeating keyword +- **XOR Cipher** - XOR with a repeating key + +#### **Encoding** +- **ASCII85** - Ascii85 / Adobe-style base-85 encoding +- **Base122** - Binary β†’ 122 printable ASCII characters +- **Base32** - RFC 4648 Base32 +- **Base36** - Base36 (0–9, A–Z) +- **Base45** - Base45 byte encoding +- **Base58** - Bitcoin-style Base58 (no 0/O/I/l) +- **Base62** - Base62 (0–9, A–Z, a–z) +- **Base64** - Standard Base64 +- **Base64 URL** - Base64url (URL-safe alphabet) +- **Base91** - basE91 / Ascii91 encoding +- **Baudot Code (ITA2)** - Five-bit telegraph / ITA2 +- **Binary Coded Decimal** - Decimal digits as BCD nibbles +- **Binary** - Text bytes ↔ binary strings +- **EBCDIC** - EBCDIC byte encoding +- **Emoji Encoding** - Payload encoded with emoji +- **Gray Code** - Binary Gray code +- **Hexadecimal** - Hex encode/decode bytes +- **HTML Entities** - HTML entity escape / unescape +- **Invisible Text** - Unicode Tags / invisible carrier encoding +- **Quoted-Printable** - MIME quoted-printable +- **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 +- **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 +- **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 +- **Remove Extra Spaces** - Collapse runs of spaces +- **Remove HTML Tags** - Strip HTML/XML tags +- **Remove Newlines** - Remove line breaks +- **Remove Numbers** - Remove digit characters +- **Remove Punctuation** - Remove punctuation +- **Remove Tabs** - Remove tab characters +- **Remove Zero Width** - Strip zero-width characters +- **Reverse Words** - Reverse order of words +- **Reverse Text** - Reverse character order +- **Shuffle Characters** - Shuffle characters (random order) +- **Shuffle Words** - Shuffle word order +- **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 +- **Word Wrap** - Break long lines at spaces so each line fits a maximum width +- **Zero-Width Steganography** - Hide data with zero-width characters + +#### **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 +- **Maritime Signal Flags** - International maritime signal flags +- **Morse Code** - International Morse code - **NATO Phonetic** - NATO phonetic alphabet -- **VigenΓ¨re Cipher** - Polyalphabetic cipher (default key "KEY") -- **Rail Fence (3 Rails)** - Zig-zag transposition cipher +- **Semaphore Flags** - Flag semaphore arm positions +- **Tap Code** - Polybius / tap / prison code -#### **Visual Transformations** -- **Upside Down** - Flip text upside down using Unicode characters -- **Full Width** - Convert to full-width Unicode characters -- **Small Caps** - Convert to small capital letters -- **Bubble Text** - Enclose letters in circles -- **Braille** - Convert to Braille patterns -- **Strikethrough** - Add strikethrough using combining characters -- **Underline** - Add underlines using combining characters +#### **Unicode** +- **Bold Italic** - Mathematical sans-serif bold italic +- **Bold** - Mathematical bold +- **Bubble** - Circled / β€œbubble” letters +- **Chemical Symbols** - Chemical element symbols +- **Circled** - Circled Unicode letters +- **Cursive** - Mathematical script / cursive +- **Cyrillic Stylized** - Latin β†’ Cyrillic lookalike letters +- **Dashed Underline** - Combining dashed underline +- **Dotted Underline** - Combining dotted underline +- **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 +- **Monospace** - Mathematical monospace +- **Negative Squared** - Negative circled / squared letters +- **Overline** - Overline combining marks +- **Parenthesized** - Parenthesized Latin letters +- **Regional Indicator Letters** - Regional-indicator flag letters +- **Small Caps** - Small capitals (Unicode) +- **Squared** - Squared / enclosed alphanumeric +- **Strikethrough** - Strikethrough combining characters +- **Subscript** - Unicode subscripts +- **Superscript** - Unicode superscripts +- **Underline** - Underline combining characters +- **Upside Down** - Upside-down Unicode letters +- **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) -#### **Unicode Styles** -- **Medieval** - Gothic/medieval style characters -- **Cursive** - Cursive/script style characters -- **Monospace** - Monospace mathematical characters -- **Double-Struck** - Mathematical double-struck characters -- **Greek Letters** - Greek alphabet characters -- **Wingdings** - Symbol font characters -- **Fraktur** - Mathematical Fraktur alphabet -- **Cyrillic Stylized** - Latin letters mapped to similar Cyrillic glyphs -- **Katakana** - Romaji to Katakana (approximate, reversible) -- **Hiragana** - Romaji to Hiragana (approximate, reversible) -- **Roman Numerals** - Numbers to Roman numerals (reversible) +#### **Visual** +- **Disemvowel** - Remove vowels (speech game) +- **Emoji Speak** - Emoji-heavy β€œspeak” transform +- **RΓΆvarsprΓ₯ket** - Swedish consonant-doubling game +- **Ubbi Dubbi** - Insert β€œub” before vowel sounds -#### **Fantasy Languages** πŸ§™β€β™‚οΈ -- **Quenya (Tolkien Elvish)** - High Elvish language from Lord of the Rings -- **Tengwar Script** - Elvish writing system -- **Klingon** - Star Trek Klingon language -- **Aurebesh (Star Wars)** - Galactic Basic alphabet -- **Dovahzul (Dragon)** - Dragon language from Skyrim +### πŸ› οΈ **Tools** (tabs) -#### **Ancient Scripts** πŸ›οΈ -- **Elder Futhark** - Ancient Germanic runes -- **Hieroglyphics** - Egyptian hieroglyphic symbols -- **Ogham (Celtic)** - Celtic tree alphabet -- **Semaphore Flags** - Flag signaling system +Tabs appear in **UI order** below. **OpenRouter** (optional or required per tool) uses the key in **Advanced Settings** β€” see **OpenRouter API Key Setup** below. -#### **Technical Codes** βš™οΈ -- **Brainfuck** - Esoteric programming language -- **Mathematical Notation** - Mathematical script characters -- **Chemical Symbols** - Chemical element abbreviations +### πŸ”€ **Transform** -#### **Formatting** -- **Pig Latin** - Simple word transformation -- **Leetspeak** - 1337 speak with number substitutions -- **Vaporwave** - Aesthetic spacing -- **Zalgo** - Glitch text with combining marks -- **Mirror Text** - Reversed text - -### πŸ” **Universal Decoder** -- **Smart Detection**: Automatically detects and decodes any supported format -- **Priority Matching**: Prioritizes decoding based on active transform -- **Fallback Methods**: Tries all available decoders if primary fails -- **Real-time Processing**: Instant decoding as you type +- **159 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. +- **Keyboard shortcut**: **T** (shown in the tab title). ### 🌐 **AI Translation** (via OpenRouter) + +*Lives on the **Transform** tab β€” not a separate tab.* + - **20+ Languages**: Major world languages (Spanish, French, Chinese, Japanese, Korean, etc.) - **Dead & Exotic Languages**: Latin, Sanskrit, Ancient Greek, Sumerian, Akkadian, Old English, and more - **Custom Languages**: Add any language on-the-fly @@ -92,23 +217,73 @@ A powerful web-based text transformation and steganography tool that can encode/ - **TranslateGemma Prompt Format**: Uses Google's optimized prompt template for high-quality translation - **Auto-Fallback**: If a model is unavailable, automatically falls back to Gemma 3 27B -### πŸͺ„ **PromptCraft** (AI Prompt Mutation) -- **9 Mutation Strategies**: Rephrase, Obfuscate, Role-Play Wrap, Multi-Language, Expand, Compress, Metaphor, Fragment, and Custom -- **48+ Models**: Frontier (Claude, GPT, Gemini, Grok), Reasoning (o3, o4, DeepSeek R1), Fast (Haiku, Mini), Code-specialized, Open Source (Llama, Qwen), and Search/Research models -- **Parallel Variants**: Generate 1-10 variants simultaneously with diverse temperature settings -- **Copy & Iterate**: Copy any variant or feed it back as input for iterative refinement +### πŸ” **Decoder** (Universal Decoder) -### πŸ› οΈ **Available Tools** -- **Universal Decoder**: Auto-detect and decode any supported format -- **Text Transforms**: 79+ encoding, cipher, and transformation options -- **AI Translation**: Translate to 20+ languages via OpenRouter (built into Transforms tab) -- **PromptCraft**: AI-powered prompt mutation and crafting (dedicated tab) -- **Steganography**: Emoji and invisible text steganography -- **Tokenade Generator**: High-density token payload builder -- **Mutation Lab (Fuzzer)**: Generate diverse text mutations -- **Tokenizer Visualization**: Visualize tokenization for various engines -- **Message Splitter**: Split text into multiple copyable chunks -- **Gibberish Generator**: Create gibberish dictionaries and character removal variants +- **Smart detection**: Runs format detectors and decode paths for supported transforms. +- **Priority matching**: When a transform is active, decoding prefers that format first. +- **Fallback**: Tries other decoders if the primary guess fails. +- **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**. + +### πŸ˜€ **Emoji** (Steganography) + +- **Emoji carriers**: Hide data using variation selectors and supported emoji carriers; pick from the emoji grid. +- **Invisible text**: Switch to Unicode Tags–style invisible encoding where available. +- **Encode & decode**: Separate flows for hiding and recovering text. +- **Advanced Settings**: Bit order, VS choices, and other steganography tuning (sliders icon). +- **Keyboard shortcut**: **H** (shown in the tab title). + +### πŸ’£ **Tokenade** + +- **Token bomb builder**: Depth, breadth, repeats, separators (e.g. ZWSP), variation selectors, noise. +- **Carriers & payloads**: Emoji carriers, text payloads, combining options. +- **Safety**: Warns when estimated output crosses a **danger** token threshold. + +### πŸ§ͺ **Mutation Lab** + +- **Batch mutations**: Generate many variants from one input (count configurable). +- **Seed**: Optional deterministic runs. +- **Toggles**: Random mix, zero-width, Unicode noise, Zalgo, whitespace, casing, encode/shuffle. +- **Random Mix**: Can chain the project’s random transform mixer when enabled. + +### πŸ“Š **Tokenizer** + +- **Engines**: UTF-8 **bytes**, **words**, or **GPT BPE** (**cl100k**, **o200k**, **p50k**, **r50k**) via `gpt-tokenizer` (CDN). +- **Visualization**: Token list with IDs/pieces; **character** and **word** counts. +- **Live updates**: Re-tokenizes when input or engine changes. + +### ↔️ **Bijection** + +- **Custom mappings**: Character-to-number (and related) β€œalphapr”-style maps for research payloads. +- **Controls**: Mapping type, budget, optional examples. +- **Output**: Generated mappings and payloads ready to copy. + +### βœ‚οΈ **Splitter** + +- **Split modes**: By chunk size, **word**, **sentence**, **line**, **regex pattern**, or **token** count (GPT tokenizer). +- **Transform chain**: Optionally run transforms on each piece. +- **Wrapping**: Start/end templates; `{n}` iterator marker; single-line vs multiline copy. + +### πŸ’¬ **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. + +### πŸͺ„ **PromptCraft** (via OpenRouter) + +- **9 Mutation Strategies**: Rephrase, Obfuscate, Role-Play Wrap, Multi-Language, Expand, Compress, Metaphor, Fragment, and Custom +- **48+ Models**: Frontier (Claude, GPT, Gemini, Grok), Reasoning (o3, o4, DeepSeek R1), Fast (Haiku, Mini), Code-specialized, Open Source (Llama, Qwen), and Search/Research +models +- **Parallel Variants**: Generate 1-10 variants simultaneously with diverse temperature settings +- **Copy & iterate**: Copy any variant or feed it back as input for iterative refinement + +### πŸ€– **Anti-Classifier** (via OpenRouter) + +- **Purpose**: Syntactic / paraphrase-style rewrites for research-style prompts. +- **Controls**: Model, temperature, max tokens. +- **Same key**: Uses the same OpenRouter API key as Translation and PromptCraft. ### πŸ“± **User Experience** - **Dark/Light Theme**: Toggle between themes @@ -117,9 +292,12 @@ A powerful web-based text transformation and steganography tool that can encode/ - **Keyboard Shortcuts**: Quick access to features - **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) ### πŸ”‘ **OpenRouter API Key Setup** -Translation and PromptCraft features require an [OpenRouter](https://openrouter.ai/) API key: + +**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. + 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** @@ -130,39 +308,50 @@ Translation and PromptCraft features require an [OpenRouter](https://openrouter. ## πŸš€ **Getting Started** -### **Quick Start (Built Version)** -1. Run the build process (see Development Setup below) -2. Open `dist/index.html` in any modern web browser -3. Type text in the input field -4. Choose a transformation from the categorized buttons -5. Click any transform button to apply and auto-copy -6. Use the Universal Decoder to decode any encoded text +### **Quick Start (local)** +1. `npm install` then `npm run build` (generates the **`dist/`** folder β€” it is **not** checked into git; you must build after clone or source changes) +2. Open **`dist/index.html`** in Chrome, Firefox, Safari, or another browser (double-click the file or use **File β†’ Open**). + +**Alternative β€” run as a local app (npm / npx):** From the project root, after `npm install` and `npm run build`, use **`npm start`** (runs [`serve`](https://github.com/vercel/serve) on port **8080**) or **`npx serve dist -l 8080`**. Then open **http://localhost:8080** β€” same UI, stable URL you can bookmark. **`npm run preview`** runs a full **`npm run build`** and then serves **`dist/`** in one step. ### **Development Setup** ```bash # Install dependencies npm install -# Build all assets (required before use): -# - Copies static files to dist/ -# - Builds transform bundle from source files -# - Generates emoji data -# - Injects tool templates into dist/index.html +# Build all assets (required before use). Order matches package.json: +# build:tools β†’ 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:copy # Copy static files to dist/ -npm run build:transforms # Bundle all transformers to dist/js/bundles/ +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 npm run build:emoji # Generate emoji data to dist/js/data/ -npm run build:templates # Inject tool HTML templates to dist/index.html -npm run build:index # Generate transformer index +npm run build:templates # Inject tool HTML templates into dist/index.html # Run tests npm test # Run universal decoder tests npm run test:universal # Same as above npm run test:steg # Test steganography options +npm run test:all # Universal + steganography tests + +# Optional: serve dist/ over HTTP instead of opening dist/index.html directly +npm start # http://localhost:8080 +npm run preview # npm run build, then serve dist/ ``` +### **Documentation & maintainer notes** + +| Doc | Purpose | +|-----|---------| +| [CONTRIBUTING.md](CONTRIBUTING.md) | Adding transformers, tools, tests | +| [docs/TOOL-SYSTEM.md](docs/TOOL-SYSTEM.md) | Tool templates, build injection, shared UI classes | +| [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)). ## πŸ› οΈ **Technical Details** @@ -171,11 +360,12 @@ npm run test:steg # Test steganography options - **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`) - **Build Process**: - - Transformers are bundled from `src/transformers/` into `js/bundles/transforms-bundle.js` - - Tool templates are injected from `templates/` into `index.html` - - Emoji data is generated from Unicode specifications - - All build steps are required before the app can run + - `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/` ### **Browser Support** - Chrome/Edge 80+ @@ -200,7 +390,7 @@ npm run test:steg # Test steganography options - πŸ†• **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 -- πŸ†• **79+ Transformations**: Added fantasy, ancient, and technical scripts +- πŸ†• **159 Transformations**: Full catalog of encodings, ciphers, Unicode styles, fantasy and ancient scripts, 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 diff --git a/js/README.md b/js/README.md index 77424ab..1c466da 100644 --- a/js/README.md +++ b/js/README.md @@ -1,39 +1,38 @@ -# JavaScript Directory Structure +# JavaScript directory -## Core Modules (`js/core/`) +Source files here are copied to **`dist/js/`** by `npm run build:copy`. Generated artifacts (`dist/js/bundles/transforms-bundle.js`, `dist/js/data/emojiData.js`) are produced by other build steps. -- `decoder.js` - Universal decoder for automatic encoding detection -- `steganography.js` - Emoji and invisible text steganography -- `emojiLibrary.js` - Emoji search, filtering, and library functions -- `toolRegistry.js` - Tool registration and Vue data/method merging +## Core (`js/core/`) + +- `decoder.js` β€” Universal decoder engine +- `steganography.js` β€” Emoji / invisible text steganography +- `toolRegistry.js` β€” Tool registration and Vue data/method merging +- `transformOptions.js` β€” Shared transform UI helpers ## Utilities (`js/utils/`) -- `clipboard.js` - `ClipboardUtils.copy()` - Clipboard API wrapper -- `focus.js` - `FocusUtils.focusWithoutScroll()`, `clearFocusAndSelection()` -- `history.js` - `HistoryUtils` - Copy history management -- `notifications.js` - `NotificationUtils` - Toast notifications -- `theme.js` - `ThemeUtils` - Dark/light theme management -- `escapeParser.js` - Escape sequence parsing +- `clipboard.js`, `focus.js`, `history.js`, `notifications.js`, `theme.js` +- `escapeParser.js` β€” Escape sequence parsing +- `emoji.js` β€” `window.EmojiUtils` (uses `window.emojiData` when loaded) +- `glitchTokens.js` β€” Glitch token panel helpers ## Tools (`js/tools/`) -Tool classes extending `Tool` base class. Auto-discovered by `build/inject-tool-scripts.js`. +Classes extending `Tool`; auto-discovered by `build/inject-tool-scripts.js`. ## Data (`js/data/`) -- `emojiData.js` - Generated emoji data (build output) -- `emojiCompatibility.js` - Emoji compatibility mappings +Committed static data: `anticlassifierPrompt.js`, `emojiCompatibility.js`, `endSequences.js`, `glitchTokens.js`, `openrouterModels.js`. **`emojiData.js`** is generated into **`dist/js/data/`** by `npm run build:emoji`, not edited in-repo. -## Bundles (`js/bundles/`) +## Bundles -- `transforms-bundle.js` - Bundled transformer modules (build output) +Transformer bundle: **`dist/js/bundles/transforms-bundle.js`** (`npm run build:transforms`). A legacy `js/bundles/transforms-bundle.js` path may be gitignored. -## Load Order +## Typical load order (see `index.template.html`) -1. Data files (emojiData, emojiCompatibility) -2. Generated bundles (transforms-bundle) -3. Utilities (escapeParser, focus, notifications, history, clipboard, theme) -4. Core modules (steganography, decoder, emojiLibrary) -5. Tool system (Tool.js, *Tool.js files, toolRegistry) -6. Main app (app.js) +1. Data (`emojiData.js`, `emojiCompatibility.js`, …) β€” `emojiData` from build output in `dist/` +2. Bundles (`transforms-bundle.js`) +3. Utilities (including `emoji.js`) +4. Core (`steganography`, `decoder`, `transformOptions`, …) +5. Tools (`Tool.js`, `*Tool.js`, `toolRegistry.js`) +6. `app.js` diff --git a/js/core/transformOptions.js b/js/core/transformOptions.js index 68f1adf..35c19b9 100644 --- a/js/core/transformOptions.js +++ b/js/core/transformOptions.js @@ -39,10 +39,41 @@ function getMergedTransformOptions(transform) { return Object.assign({}, merged, saved); } +/** + * Resolve merged options for a transform by display name (same prefs as Transform tab). + * @param {string} transformName + * @param {Array} [vueTransforms] - Vue copy of transforms (may omit configurableOptions) + * @returns {Object} + */ +function getMergedTransformOptionsForName(transformName, vueTransforms) { + if (!transformName) { + return {}; + } + let t = null; + if (Array.isArray(vueTransforms) && vueTransforms.length) { + t = vueTransforms.find(function(tr) { + return tr && tr.name === transformName; + }); + } + if ((!t || !t.configurableOptions || !t.configurableOptions.length) && typeof window !== 'undefined' && window.transforms) { + const full = Object.values(window.transforms).find(function(tr) { + return tr && tr.name === transformName; + }); + if (full) { + t = full; + } + } + if (!t || !t.configurableOptions || !t.configurableOptions.length) { + return {}; + } + return getMergedTransformOptions(t); +} + if (typeof window !== 'undefined') { window.getMergedTransformOptions = getMergedTransformOptions; + window.getMergedTransformOptionsForName = getMergedTransformOptionsForName; } if (typeof module !== 'undefined' && module.exports) { - module.exports = { getMergedTransformOptions }; + module.exports = { getMergedTransformOptions, getMergedTransformOptionsForName }; } diff --git a/js/tools/SplitterTool.js b/js/tools/SplitterTool.js index 1c6ac49..cda2038 100644 --- a/js/tools/SplitterTool.js +++ b/js/tools/SplitterTool.js @@ -381,19 +381,44 @@ class SplitterTool extends Tool { const activeTransforms = this.splitterTransforms.filter(t => t && t !== ''); if (activeTransforms.length > 0) { - // Apply each transformation in sequence + const getOpts = typeof window.getMergedTransformOptionsForName === 'function' + ? function(name) { + return window.getMergedTransformOptionsForName(name, this.transforms); + }.bind(this) + : function() { + return {}; + }; + + // Apply each transformation in sequence (same options as Transform tab) for (const transformName of activeTransforms) { const selectedTransform = this.transforms.find(t => t.name === transformName); - if (selectedTransform && selectedTransform.func) { + if (!selectedTransform || !selectedTransform.func) { + continue; + } + + if (transformName === 'Random Mix') { processedChunks = processedChunks.map(chunk => { try { - return selectedTransform.func(chunk); + return window.transforms && window.transforms.randomizer + ? window.transforms.randomizer.func(chunk) + : chunk; } catch (e) { console.error('Transform error:', e); return chunk; } }); + continue; } + + const opts = getOpts(transformName); + processedChunks = processedChunks.map(chunk => { + try { + return selectedTransform.func(chunk, opts); + } catch (e) { + console.error('Transform error:', e); + return chunk; + } + }); } } } diff --git a/js/tools/TransformTool.js b/js/tools/TransformTool.js index 8afd748..39b2b89 100644 --- a/js/tools/TransformTool.js +++ b/js/tools/TransformTool.js @@ -239,20 +239,8 @@ class TransformTool extends Tool { return false; }, getMergedOptionsForTransform: function(transformName) { - let t = this.transforms.find(tr => tr.name === transformName); - if ((!t || !t.configurableOptions || !t.configurableOptions.length) && window.transforms) { - const full = Object.values(window.transforms).find(function(tr) { - return tr && tr.name === transformName; - }); - if (full) { - t = full; - } - } - if (!t || !t.configurableOptions || !t.configurableOptions.length) { - return {}; - } - if (typeof window.getMergedTransformOptions === 'function') { - return window.getMergedTransformOptions(t); + if (typeof window.getMergedTransformOptionsForName === 'function') { + return window.getMergedTransformOptionsForName(transformName, this.transforms); } return {}; }, diff --git a/package-lock.json b/package-lock.json index 6dc7dc3..09c6854 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,1058 @@ { - "name": "P4RS3LT0NGV3", + "name": "p4rs3lt0ngv3", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, - "packages": {} + "packages": { + "": { + "name": "p4rs3lt0ngv3", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "serve": "^14.2.6" + } + }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/serve": { + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.6.tgz", + "integrity": "sha512-QEjUSA+sD4Rotm1znR8s50YqA3kYpRGPmtd5GlFxbaL9n/FdUNbqMhxClqdditSk0LlZyA/dhud6XNRTOC9x2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.18.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.8.1", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.7", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz", + "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.5", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + } + } } diff --git a/package.json b/package.json index c6e2a04..64a1606 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "build:emoji": "node build/build-emoji-data.js", "build:transforms": "node build/build-transforms.js", "build": "npm run build:tools && npm run build:copy && npm run build:index && npm run build:transforms && npm run build:emoji && npm run build:templates", + "start": "serve dist -l 8080", + "preview": "npm run build && serve dist -l 8080", "test": "node tests/test_universal.js", "test:universal": "node tests/test_universal.js", "test:steg": "node tests/test_steganography_options.js", @@ -20,7 +22,15 @@ "type": "git", "url": "." }, - "keywords": ["encoder", "decoder", "steganography", "cipher"], + "keywords": [ + "encoder", + "decoder", + "steganography", + "cipher" + ], "author": "", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "serve": "^14.2.6" + } } diff --git a/src/transformers/README.md b/src/transformers/README.md index 0eeb983..8483883 100644 --- a/src/transformers/README.md +++ b/src/transformers/README.md @@ -134,6 +134,7 @@ Higher priority = more specific pattern (used for decoder result ordering): 3. Test in webapp 4. Add `detector` function if format has distinctive patterns 5. Optionally add test cases to `tests/test_universal.js` +6. Add a one-line description for the transform’s `name` in `DESCRIPTIONS` inside `build/readme-transform-section.js`, then run `node build/readme-transform-section.js` and merge the printed block into the **Text Transformations** section of the root `README.md` (the script exits with an error if a transform is missing from `DESCRIPTIONS`) ## Testing diff --git a/src/transformers/case/alternating-case.js b/src/transformers/case/alternating-case.js index ced61cc..6d57e47 100644 --- a/src/transformers/case/alternating-case.js +++ b/src/transformers/case/alternating-case.js @@ -3,49 +3,66 @@ import BaseTransformer from '../BaseTransformer.js'; export default new BaseTransformer({ - name: 'Alternating Case', - priority: 150, // Higher priority to detect before Base64 - func: function(text) { - let upper = true; - return [...text].map(c => { - if (/[a-zA-Z]/.test(c)) { - const out = upper ? c.toUpperCase() : c.toLowerCase(); - upper = !upper; - return out; - } - return c; - }).join(''); - }, - preview: function(text) { - if (!text) return '[alt case]'; - return this.func(text.slice(0, 6)) + (text.length > 6 ? '...' : ''); - }, - reverse: function(text) { - // Reverse by lowercasing (loses original case pattern) - return text.toLowerCase(); - }, - detector: function(text) { - const cleaned = text.trim(); - if (cleaned.length < 4) return false; - - // Check for alternating pattern in letters only - let lastWasUpper = null; - let alternations = 0; - let letterCount = 0; - - for (const char of cleaned) { - if (/[a-zA-Z]/.test(char)) { - const isUpper = char === char.toUpperCase(); - if (lastWasUpper !== null && isUpper !== lastWasUpper) { - alternations++; - } - lastWasUpper = isUpper; - letterCount++; - } + name: 'Alternating Case', + priority: 150, // Higher priority to detect before Base64 + startWith: 'upper', // 'upper' | 'lower' β€” first alphabetic letter (fallback when options omitted) + configurableOptions: [ + { + id: 'startWith', + label: 'First alphabetic letter', + type: 'select', + default: 'upper', + options: [ + { value: 'upper', label: 'Uppercase' }, + { value: 'lower', label: 'Lowercase' } + ] + } + ], + func: function(text, options) { + options = options || {}; + const sw = options.startWith !== undefined && options.startWith !== '' + ? options.startWith + : this.startWith; + let upper = sw === 'lower' ? false : true; + return [...text].map(c => { + if (/[a-zA-Z]/.test(c)) { + const out = upper ? c.toUpperCase() : c.toLowerCase(); + upper = !upper; + return out; + } + return c; + }).join(''); + }, + preview: function(text, options) { + if (!text) return '[alt case]'; + return this.func(text.slice(0, 6), options) + (text.length > 6 ? '...' : ''); + }, + reverse: function(text) { + // Reverse by lowercasing (loses original case pattern) + return text.toLowerCase(); + }, + detector: function(text) { + const cleaned = text.trim(); + if (cleaned.length < 4) return false; + + // Check for alternating pattern in letters only + let lastWasUpper = null; + let alternations = 0; + let letterCount = 0; + + for (const char of cleaned) { + if (/[a-zA-Z]/.test(char)) { + const isUpper = char === char.toUpperCase(); + if (lastWasUpper !== null && isUpper !== lastWasUpper) { + alternations++; + } + lastWasUpper = isUpper; + letterCount++; } - - // Must have at least 3 alternations and at least 70% alternation rate - return letterCount >= 4 && alternations >= 3 && alternations >= letterCount * 0.7; } + // Must have at least 3 alternations and at least 70% alternation rate + return letterCount >= 4 && alternations >= 3 && alternations >= letterCount * 0.7; + } + }); \ No newline at end of file diff --git a/src/transformers/format/bitwise-not.js b/src/transformers/format/bitwise-not.js index 7ba1a76..ae1f7dd 100644 --- a/src/transformers/format/bitwise-not.js +++ b/src/transformers/format/bitwise-not.js @@ -1,39 +1,69 @@ // bitwise NOT transform +// Encode: UTF-8 bytes β†’ NOT each byte β†’ lossless lowercase hex (invalid UTF-8 after NOT is common). +// Decode: hex β†’ bytes β†’ NOT each byte β†’ UTF-8 decode (exact inverse of encode). +// Helpers must live inside the default export: build-transforms.js concatenates the file body +// into transforms[name] = … so multiple top-level declarations would assign the wrong value. import BaseTransformer from '../BaseTransformer.js'; -export default new BaseTransformer({ +export default (function () { + function utf8BytesBitwiseNot(bytes) { + const out = new Uint8Array(bytes.length); + for (let i = 0; i < bytes.length; i++) { + out[i] = ~bytes[i] & 0xFF; + } + return out; + } + + function bytesToHex(bytes) { + return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join(''); + } + + function hexToBytes(hex) { + const cleaned = hex.replace(/\s+/g, '').replace(/0x/gi, ''); + if (cleaned.length % 2 !== 0) { + return null; + } + if (!/^[0-9a-fA-F]*$/.test(cleaned)) { + return null; + } + const out = new Uint8Array(cleaned.length / 2); + for (let i = 0; i < out.length; i++) { + out[i] = parseInt(cleaned.slice(i * 2, i * 2 + 2), 16); + } + return out; + } + + return new BaseTransformer({ name: 'Bitwise NOT', priority: 100, category: 'format', func: function(text) { - // Invert all bits in each byte const bytes = new TextEncoder().encode(text); - const result = new Uint8Array(bytes.length); - - for (let i = 0; i < bytes.length; i++) { - result[i] = ~bytes[i] & 0xFF; // NOT operation, mask to 8 bits - } - - try { - return new TextDecoder().decode(result); - } catch (e) { - // If decoding fails, return as hex - return Array.from(result).map(b => b.toString(16).padStart(2, '0')).join(''); - } + const inverted = utf8BytesBitwiseNot(bytes); + return bytesToHex(inverted); }, reverse: function(text) { - // Bitwise NOT is self-reciprocal (NOT NOT = original) - return this.func(text); + const inverted = hexToBytes(text); + if (!inverted) { + return '[invalid hex - paste the hex from encode; spaces allowed]'; + } + const bytes = utf8BytesBitwiseNot(inverted); + try { + return new TextDecoder('utf-8', { fatal: true }).decode(bytes); + } catch (e) { + return '[invalid UTF-8 after inverse NOT]'; + } }, preview: function(text) { if (!text) return '[bitwise-not]'; - return this.func(text.slice(0, 5)); + const h = this.func(text.slice(0, 5)); + return h.length > 24 ? `${h.slice(0, 24)}…` : h; }, detector: function(text) { - // Bitwise NOT produces scrambled text, hard to detect - // Check for non-printable characters or unusual patterns - const hasNonPrintable = /[\x00-\x1F\x7F-\x9F]/.test(text); - return hasNonPrintable && text.length >= 5; + const t = text.trim().replace(/\s+/g, ''); + if (t.length < 8 || t.length % 2 !== 0) return false; + return /^[0-9a-fA-F]+$/.test(t); } -}); + }); +})(); diff --git a/src/transformers/format/indent.js b/src/transformers/format/indent.js index 0089fce..3ac53e1 100644 --- a/src/transformers/format/indent.js +++ b/src/transformers/format/indent.js @@ -5,10 +5,27 @@ export default new BaseTransformer({ name: 'Indent', priority: 100, category: 'format', - spaces: 4, // Default indent spaces - func: function(text) { - const spaces = parseInt(this.spaces) || 4; - const indent = ' '.repeat(spaces); + spaces: 4, // Default indent spaces (fallback when options omitted) + configurableOptions: [ + { + id: 'spaces', + label: 'Spaces per indent', + type: 'number', + default: 4, + min: 1, + max: 32, + step: 1 + } + ], + func: function(text, options) { + options = options || {}; + let s = options.spaces !== undefined && options.spaces !== '' + ? parseInt(options.spaces, 10) + : parseInt(this.spaces, 10) || 4; + if (Number.isNaN(s) || s < 1) { + s = 4; + } + const indent = ' '.repeat(s); return text.split('\n').map(line => indent + line).join('\n'); }, @@ -16,9 +33,9 @@ export default new BaseTransformer({ // Remove leading spaces from each line return text.split('\n').map(line => line.replace(/^\s+/, '')).join('\n'); }, - preview: function(text) { + preview: function(text, options) { if (!text) return '[indent]'; - return this.func(text.slice(0, 20)); + return this.func(text.slice(0, 20), options); }, detector: function(text) { // Check if all lines start with same amount of whitespace diff --git a/src/transformers/format/line-numbers.js b/src/transformers/format/line-numbers.js index e3edfe1..debe8b7 100644 --- a/src/transformers/format/line-numbers.js +++ b/src/transformers/format/line-numbers.js @@ -5,15 +5,48 @@ export default new BaseTransformer({ name: 'Line Numbers', priority: 100, category: 'format', - start: 1, // Starting line number - func: function(text) { - const start = parseInt(this.start) || 1; + start: 1, // Starting line number (fallback when options omitted) + gutterWidth: 4, // padStart width for the number column + configurableOptions: [ + { + id: 'start', + label: 'Starting line number', + type: 'number', + default: 1, + min: 0, + max: 9999999, + step: 1 + }, + { + id: 'gutterWidth', + label: 'Number column width', + type: 'number', + default: 4, + min: 1, + max: 12, + step: 1 + } + ], + func: function(text, options) { + options = options || {}; + let start = options.start !== undefined && options.start !== '' + ? parseInt(options.start, 10) + : parseInt(this.start, 10) || 1; + if (Number.isNaN(start)) { + start = 1; + } + let gutterWidth = options.gutterWidth !== undefined && options.gutterWidth !== '' + ? parseInt(options.gutterWidth, 10) + : parseInt(this.gutterWidth, 10) || 4; + if (Number.isNaN(gutterWidth) || gutterWidth < 1) { + gutterWidth = 4; + } const lines = text.split('\n'); let result = ''; for (let i = 0; i < lines.length; i++) { const lineNum = start + i; - result += lineNum.toString().padStart(4, ' ') + ': ' + lines[i] + '\n'; + result += lineNum.toString().padStart(gutterWidth, ' ') + ': ' + lines[i] + '\n'; } return result.trimEnd(); @@ -24,9 +57,9 @@ export default new BaseTransformer({ return line.replace(/^\s*\d+\s*:\s*/, ''); }).join('\n'); }, - preview: function(text) { + preview: function(text, options) { if (!text) return '[line-numbers]'; - return this.func(text.slice(0, 30)); + return this.func(text.slice(0, 30), options); }, detector: function(text) { // Check for line number pattern at start of lines diff --git a/src/transformers/format/text-justify.js b/src/transformers/format/text-justify.js index b221a94..faefde5 100644 --- a/src/transformers/format/text-justify.js +++ b/src/transformers/format/text-justify.js @@ -5,11 +5,42 @@ export default new BaseTransformer({ name: 'Text Justify', priority: 100, category: 'format', - width: 80, // Default width + width: 80, // Default width (fallback when options omitted) align: 'left', // left, right, center - func: function(text) { - const width = parseInt(this.width) || 80; - const align = this.align || 'left'; + configurableOptions: [ + { + id: 'width', + label: 'Line width (characters)', + type: 'number', + default: 80, + min: 8, + max: 200, + step: 1 + }, + { + id: 'align', + label: 'Alignment', + type: 'select', + default: 'left', + options: [ + { value: 'left', label: 'Left (pad on the right)' }, + { value: 'right', label: 'Right (pad on the left)' }, + { value: 'center', label: 'Center' } + ] + } + ], + func: function(text, options) { + options = options || {}; + let w = options.width !== undefined && options.width !== '' + ? parseInt(options.width, 10) + : parseInt(this.width, 10) || 80; + if (Number.isNaN(w) || w < 1) { + w = 80; + } + const width = w; + const align = options.align !== undefined && options.align !== '' + ? options.align + : (this.align || 'left'); const lines = text.split('\n'); let result = ''; @@ -47,9 +78,9 @@ export default new BaseTransformer({ // Remove padding spaces return text.split('\n').map(line => line.trim()).join('\n'); }, - preview: function(text) { + preview: function(text, options) { if (!text) return '[text-justify]'; - return this.func(text.slice(0, 20)); + return this.func(text.slice(0, 20), options); }, detector: function(text) { // Check for consistent line lengths with padding diff --git a/src/transformers/format/word-wrap.js b/src/transformers/format/word-wrap.js index 4a88e2d..6d5bd7f 100644 --- a/src/transformers/format/word-wrap.js +++ b/src/transformers/format/word-wrap.js @@ -5,11 +5,28 @@ export default new BaseTransformer({ name: 'Word Wrap', priority: 100, category: 'format', - width: 80, // Default wrap width - func: function(text) { - const width = parseInt(this.width) || 80; - if (width < 1) return text; - + width: 80, // Default wrap width (fallback when options omitted) + configurableOptions: [ + { + id: 'width', + label: 'Maximum line width (characters)', + type: 'number', + default: 80, + min: 8, + max: 200, + step: 1 + } + ], + func: function(text, options) { + options = options || {}; + let w = options.width !== undefined && options.width !== '' + ? parseInt(options.width, 10) + : parseInt(this.width, 10) || 80; + if (Number.isNaN(w) || w < 1) { + w = 80; + } + const width = w; + const lines = text.split('\n'); let result = ''; @@ -45,9 +62,9 @@ export default new BaseTransformer({ // Remove line breaks (simple approach - may not be perfect) return text.replace(/\n/g, ' '); }, - preview: function(text) { + preview: function(text, options) { if (!text) return '[word-wrap]'; - return this.func(text.slice(0, 50)); + return this.func(text.slice(0, 50), options); }, detector: function(text) { // Check if text has consistent line lengths