{"_id":"@bunizao/decode-text","name":"@bunizao/decode-text","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@bunizao/decode-text","version":"0.1.0","description":"Dependency-free scramble/decode text reveal. Soulwire-style condensing lines or classic pop-in-place, frame-rate independent, layout-shift free.","type":"module","sideEffects":false,"license":"MIT","author":{"name":"bunizao"},"keywords":["text-animation","scramble","decode","decrypt-effect","typography"],"types":"./src/index.ts","exports":{".":{"types":"./src/index.ts","import":"./src/index.ts","default":"./src/index.ts"}},"scripts":{"build":"bun build src/index.ts --outdir dist --format esm && tsc -p tsconfig.json --emitDeclarationOnly --outDir dist","demo":"bunx vite demo"},"gitHead":"daa356d31c1da2294a4b944c6ffc5d3a9061414b","_id":"@bunizao/decode-text@0.1.0","_nodeVersion":"22.12.0","_npmVersion":"11.14.1","dist":{"integrity":"sha512-WFbdws3DtDlDcStYKJjn9X2eyPVpdColx1ATiS1lBjgWGxzdToRUXdKjqKYOvXgwEynPTA57/PLWL0SbkjHN9w==","shasum":"362504b0db319a37c9680abb06ef72e6de63ae3f","tarball":"https://registry.npmjs.org/@bunizao/decode-text/-/decode-text-0.1.0.tgz","fileCount":3,"unpackedSize":28064,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIAj+2c7o0eTcilb8zcLYlQtbgVNeIQbyoU1hhkkh8aFwAiEAmU+krA5w+HBk3/m9+plcdkakSeug8WQsEh47/7323/I="}]},"_npmUser":{"name":"bunizao","email":"hu@tuu.cat"},"directories":{},"maintainers":[{"name":"bunizao","email":"hu@tuu.cat"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/decode-text_0.1.0_1785263691867_0.9098933144912396"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-28T18:34:51.673Z","0.1.0":"2026-07-28T18:34:51.996Z","modified":"2026-07-28T18:34:52.269Z"},"maintainers":[{"name":"bunizao","email":"hu@tuu.cat"}],"description":"Dependency-free scramble/decode text reveal. Soulwire-style condensing lines or classic pop-in-place, frame-rate independent, layout-shift free.","keywords":["text-animation","scramble","decode","decrypt-effect","typography"],"author":{"name":"bunizao"},"license":"MIT","readme":"# @bunizao/decode-text\n\nDependency-free scramble/decode text reveal. Zero runtime deps, ~2 KB min+gz.\n\nTwo looks:\n\n- **`grow`** — Soulwire-style: the line condenses in from the left while glyphs boil, then settle. Wants a monospace font (scramble and real glyph must share a width).\n- **`static`** — classic decrypt: every character slot is locked to its final width up front and glyphs pop in place. Works in any font.\n\nScheduling keeps Soulwire's fronts but separates the noisy ones from the\nresolve. A `show` front (`p^0.5`) floods cursors in early and a `mash` front\n(`p^2`) graduates them to boiling scramble — both shuffled, both finished\nbefore `settleStart` — and only then does the resolve front sweep left to\nright at constant speed, one glyph at a time, under `easeInOutSine`. The\nscramble pool also absorbs the text's own ASCII glyphs (`scrambleFromText`),\nso the mash reads like the sentence shuffling itself.\n\nWhy it feels right:\n\n- **Frame-rate independent.** Scramble mutation is scheduled in wall time, not per frame — a 120 Hz display boils at the same speed as a 60 Hz one.\n- **No layout shift.** The host's height is locked, per-frame churn is isolated with `contain: layout paint`, and visual lines are measured and re-homed into nowrap blocks so a growing line never re-wraps the paragraph.\n- **Cheap frames.** Settled cells accumulate behind a per-line pointer and are never revisited; in `ltr` order each frame touches only the active window.\n- **Backgrounded tabs resume smoothly** (capped-delta clock) instead of snapping to done.\n- **Accessible.** Screen readers get the full text immediately via a visually-hidden copy; the animated layer is `aria-hidden`. `prefers-reduced-motion` skips the animation entirely by default.\n\n## Usage\n\n```ts\nimport { decodeText } from '@bunizao/decode-text';\n\n// Prepare + start immediately.\nconst controller = await decodeText(document.querySelector('.bio')!, {\n  layout: 'grow',      // 'grow' | 'static'\n  order: 'ltr',        // 'ltr' | 'shuffle'\n});\nawait controller.finished;\n```\n\nTo avoid flashing the full text before the reveal, hide the element with CSS,\nprepare (which blanks every slot), un-hide, then start on your own cue:\n\n```ts\nimport { prepareDecode } from '@bunizao/decode-text';\n\nconst el = document.querySelector('.bio')!; // visibility: hidden in CSS\nconst controller = await prepareDecode(el);\nel.style.visibility = 'visible';            // visible but blank — no flash\nonHeroReady(() => controller.start());\ncontroller.cancel();                        // restore original markup any time\n```\n\nInline markup inside the host is flattened (except `<br>`); color, font-weight\nand font-style that differ from the host are baked onto each character, so\n`<span class=\"highlight\">` / `<b>` emphasis survives.\n\n### Options\n\n| Option | Default | Meaning |\n| --- | --- | --- |\n| `charset` | `` __-—/\\|<> `` | Scramble glyph pool |\n| `cursorChar` | `-` | Glyph a cell shows between the show and mash fronts |\n| `layout` | `grow` | `grow` (condense, monospace) / `static` (pop in place, any font) |\n| `order` | `shuffle` | Show/mash queue: `shuffle` (original) or `ltr` (smooth right-edge growth); final resolution is left to right in both modes |\n| `showPower` | `0.5` | Show front exponent — cells turn visible as `p^showPower` sweeps the queue |\n| `mashPower` | `2` | Mash front exponent — cursor graduates to scramble |\n| `settleStart` | `0.52` | Progress where the left-to-right resolve front starts; show/mash are packed below it |\n| `settleCurve` | `0.8` | Resolve front shape — `1` constant speed, `<1` opens fast and savours the tail, `>1` hesitates then finishes hard |\n| `scrambleFromText` | `true` | Mix the text's own ASCII glyphs into the scramble pool |\n| `durationPerChar` | `0.019` | Seconds per character, clamped to `[minLineDuration, maxLineDuration]` |\n| `minLineDuration` / `maxLineDuration` | `0.42` / `1.25` | Line duration clamp (seconds) |\n| `lineStagger` | `0.2` | Next line starts at this fraction of the summed previous durations |\n| `lineEndGap` | `0.07` | Minimum seconds between two line completions — lines always finish in reading order |\n| `mutationHz` | `18` | Scramble refresh rate per cell (wall time) |\n| `ease` | easeInOutSine | Timeline easing `(t: number) => number` |\n| `fontTimeout` | `400` | Max ms to wait for `document.fonts.ready` before measuring |\n| `respectReducedMotion` | `true` | Skip animation under `prefers-reduced-motion` |\n| `onComplete` | — | Called when the reveal finishes |\n\n### Styling\n\nCells carry `data-state=\"cursor\"` / `data-state=\"scramble\"` while animating and\nget a default inline opacity (0.3 / 0.55). Override with your own CSS:\n\n```css\n.bio [data-state='scramble'] { color: var(--accent); opacity: 1 !important; }\n```\n\n## Demo\n\n```bash\nbun run demo   # vite dev server on the demo/ playground\n```\n\n## Publishing\n\n`bun run build` emits `dist/` (ESM + type declarations). Point `exports` at\n`dist/` before publishing to npm if your consumers do not compile TypeScript\nfrom `node_modules` (this workspace consumes `src/` directly via Vite).\n","readmeFilename":"README.md","_rev":"1-5bc551d7dc5ceb580a078b49256ef68d"}