npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scrmbl

v0.1.0

Published

Dependency-free scramble/decode text animation for React, Vue, Svelte, and vanilla JS.

Readme

scrmbl

Dependency-free scramble/decode text animation. Characters cycle through random glyphs and resolve into your text.

pnpm add scrmbl

Quick start

React — scrmbl/react

import { Scramble, useScramble } from "scrmbl/react";

// Component: scrambles in on mount, re-scrambles when the text changes.
<Scramble charset="upper" order="random">{title}</Scramble>;

// Hook: attach to any element, trigger whenever you like.
function NavLink({ children }: { children: string }) {
  const { ref, replay } = useScramble<HTMLAnchorElement>({ duration: 450 });
  return <a ref={ref} onMouseEnter={replay}>{children}</a>;
}

Vanilla / TypeScript — scrmbl

import { scramble, ScrambleController } from "scrmbl";

// One-liner: scrambles the element's existing text in.
const ctrl = scramble(document.querySelector("h1")!, { charset: "symbols" });

// Animate to new text; interrupting mid-animation is handled gracefully.
ctrl.update("New headline");
ctrl.replay();  // re-run from nothing
ctrl.finish();  // jump to the end
ctrl.destroy(); // release the element

Vue — scrmbl/vue

<script setup lang="ts">
import { Scramble } from "scrmbl/vue";
</script>

<template>
  <Scramble :text="title" charset="upper" order="random" @complete="onDone" />
</template>

Svelte — scrmbl/svelte

<script lang="ts">
  import { Scramble } from "scrmbl/svelte";
  let { title } = $props();
</script>

<Scramble text={title} charset="upper" order="random" />

Svelte ships as source (.svelte), compiled by your bundler — Svelte 5+.

Options

Every entrypoint accepts the same options (as props in React/Vue/Svelte, as an options object in vanilla).

| Option | Default | Description | | --- | --- | --- | | duration | 800 | Total animation time in ms. Always honored — stagger compresses for long text, so a 40-character headline and a 400-character paragraph both finish on time. | | stagger | 20 | Delay in ms between successive characters beginning to resolve. | | order | "start" | Resolve pattern: "start", "end", "center", "edges", "random". | | charset | "alnum" | Glyph pool: a preset name or any custom string of characters. | | glyphRate | 30 | Glyph swaps per second while a character is scrambling. | | ignore | "" | Characters never scrambled (whitespace never is, regardless). | | scrambleAll | true | Set false to only animate characters that changed between updates — great for live values like prices and counters. | | sweep | false | Wave mode: each character keeps its old glyph until its turn, producing a sweep. Default is the classic everything-scrambles-then-resolves decode. | | seed | random | Deterministic runs: the same seed + text + options produce identical frames. | | respectReducedMotion | true | Snap instantly to the final text when the user prefers reduced motion. | | onStart / onComplete | — | Lifecycle callbacks (events start/complete in Vue, onstart/oncomplete props in Svelte). |

Charset presets

upper · lower · digits · alnum · hex · binary · symbols · blocks (░▒▓█…) · braille (⠿⡿⣷…) · katakana

import { CHARSETS } from "scrmbl";

Any string works too: charset: "01│─┌┐".

Styling

While animating, the library sets data attributes you can hook with CSS:

/* the element */
[data-scrmbl-active] { /* animation in flight */ }

/* per-character spans; [data-glyph] = currently showing a random glyph */
[data-scrmbl-cell][data-glyph] {
  color: var(--accent);
  opacity: 0.55;
}

After the animation settles, the element flattens back to plain text — copy/paste, SEO, and line wrapping behave exactly as if scrmbl was never there.

Accessibility

  • The element's aria-label always carries the real text; per-character spans are aria-hidden, so screen readers never hear glyph soup.
  • prefers-reduced-motion: reduce skips the animation entirely (disable via respectReducedMotion: false if you must).

SSR

Server rendering outputs the final text — no hydration mismatch, no flash of empty content. The scramble-in runs client-side on mount (autoStart={false} opts out in the component adapters).

Notes

  • Fonts: with proportional fonts, widths jitter slightly while glyphs cycle. Monospace fonts, font-variant-numeric: tabular-nums (for digits), or width-stable charsets (blocks, braille) give the steadiest look.
  • Graphemes: text is segmented with Intl.Segmenter where available, so emoji (including ZWJ sequences) scramble as single units.
  • Engine: each frame is a pure function of time — frameAt(plan, t) — which is what makes seeded runs perfectly reproducible. The pure pieces (computePlan, frameAt, segment, CHARSETS) are exported if you want to build your own renderer.

License

MIT © Travis McCormick