scramble-decode
v0.1.0
Published
Deterministic text scramble-decode and glitch-pop effects. Zero dependencies, timeline-friendly, render-safe.
Maintainers
Readme
scramble-decode
Deterministic text scramble-decode and glitch-pop effects, extracted from the KUJO HyperFrames evidence reels.
Two effects:
- Scramble decode — text resolves left-to-right out of a churn of junk
glyphs (
█▓▒░<>/\#[]{}=+*01), like a signal locking in. - Glitch pop — a few discrete horizontal shove + skew frames that snap back to rest, like an analog tracking error.
Why this one instead of the usual scramble libraries: every frame is a pure
function of progress, with seeded randomness. Same inputs, same frame,
every time. That makes it safe for frame-by-frame video renderers
(HyperFrames), scrubbing timelines (GSAP, Web Animations API), tests, and
server-side rendering — while still working as a plain fire-and-forget
animation. Zero dependencies. Respects prefers-reduced-motion.
Install
npm install scramble-decodeOr drop it in a page with no build step:
<script src="https://unpkg.com/scramble-decode"></script>
<!-- exposes window.ScrambleDecode -->Or copy the browser bundle into your project:
npx scramble-decode copy ./publicTry the demo without installing anything:
npx scramble-decode demoQuick start
Declarative (easiest)
<h1 data-scramble data-scramble-glitch>Signal restored.</h1>
<p data-scramble data-scramble-delay="400" data-scramble-duration="1200">
Every run gets a receipt.
</p>
<script src="https://unpkg.com/scramble-decode"></script>
<script>ScrambleDecode.auto();</script>Data attributes:
| Attribute | Meaning |
|---|---|
| data-scramble | opt in; optional value overrides the target text |
| data-scramble-duration | decode duration in ms (default 900) |
| data-scramble-delay | ms to wait before starting |
| data-scramble-pool | custom junk-glyph pool |
| data-scramble-glitch | add the glitch pop after the decode |
Programmatic
import { scramble, glitch, decode } from "scramble-decode";
scramble(el); // decode in place, 900ms
scramble(el, { text: "New headline", duration: 1200, pool: "01" });
glitch(el); // one glitch pop
decode(el); // scramble, then glitch — the full treatment
const fx = decode(el);
await fx.finished; // promise resolves when done
fx.cancel(); // or bail out (snaps to final text)Driven by a timeline (GSAP, HyperFrames, scrubbers)
createScramble / createGlitch return a setProgress(0..1) function and
never touch the clock — you drive them:
import { createScramble, createGlitch } from "scramble-decode";
const seek = createScramble(headline);
const pop = createGlitch(headline, { seed: 7 });
const state = { p: 0 };
gsap.timeline()
.to(state, { p: 1, duration: 0.9, ease: "none", onUpdate: () => seek(state.p) })
.to(state, { p: 2, duration: 0.28, ease: "none",
onUpdate: () => pop(state.p - 1) });Because the output is a pure function of progress, seeking backwards, scrubbing, or re-rendering a frame always produces identical text — which is what frame-by-frame video renderers need.
Pure functions (no DOM at all)
import { scrambleFrame, glitchFrame } from "scramble-decode";
scrambleFrame("RUNLEDGER", 0.5); // "RUNLE▓[=+" (deterministic)
glitchFrame(0.4, { seed: 7 }); // { x: -6, skewX: 1.13 }API
| Export | Kind | Description |
|---|---|---|
| scrambleFrame(text, p, opts?) | pure | scrambled string at progress p |
| glitchFrame(p, opts?) | pure | {x, skewX} transform at progress p |
| createScramble(el, opts?) | adapter | returns setProgress(p) writing into el |
| createGlitch(el, opts?) | adapter | returns setProgress(p) transforming el |
| scramble(el, opts?) | animation | self-driving decode; returns {finished, cancel} |
| glitch(el, opts?) | animation | self-driving pop; returns {finished, cancel} |
| decode(el, opts?) | animation | scramble then glitch |
| auto(root?) | declarative | animate all [data-scramble] elements |
| createRng(seed) | util | the seeded LCG used for glitch offsets |
| DEFAULT_POOL | const | "█▓▒░<>/\#[]{}=+*01" |
Scramble options: text, duration (ms), pool, steps (churn rate, default 40).
Glitch options: duration (ms), pops (default 4), maxShift (px, default 13),
maxSkew (deg, default 2), seed.
Notes
- Text is written with
textContent— no HTML injection surface. - Spaces resolve immediately, which keeps word shapes readable mid-decode.
- With
prefers-reduced-motion: reduce, animations skip straight to the final text. - Works in any evergreen browser; the pure functions also run in Node ≥ 18.
License
MIT © Robert DeVore
