GodUIGodUI
129Follow on X

Anatomy of Encrypted Card

How a radial mask and a re-scrambling glyph stream turn a static card into a live ciphertext reveal that follows the pointer.

01/04Stream under, content over
Glyph stream
absolute inset-0 · STREAM_LEN 1500 · aria-hidden
Content
relative z-raised, so children stay on top
tsx
<div className="group relative overflow-hidden …"><div aria-hidden className={STREAM_BASE} style={{ color: streamColor }}>  {stream}</div><div className="relative z-raised">{children}</div></div>
01

Stream under, content over

EncryptedCard keeps a full glyph field painted underneath the content and reveals it through a soft radial mask centered on the pointer. The ciphertext never moves across the surface. The effect is two layers, one CSS variable pair, and an interval that runs only while you hover.

The root is a group relative overflow-hidden card. An absolute, aria-hidden glyph layer fills the inset with STREAM_LEN (1500) characters drawn from DEFAULT_CHARS. Children sit in a sibling at z-raised, so real content never fights the stream for stacking order.

The stream is filled client-side on mount so SSR markup stays deterministic: randomString never runs during the first server render.

02/04The reveal is a mask, not a clip of content

mask-image: radial-gradient(var(--reveal) circle at var(--x) var(--y), #000, transparent)

Stream (full)
always painted · opacity gated by group-hover
Radial mask
#000 inside → transparent outside · reveals, doesn't hide
tsx
el.style.setProperty("--x", `${e.clientX - rect.left}px`);el.style.setProperty("--y", `${e.clientY - rect.top}px`);// STREAM_BASE includes:// [mask-image:radial-gradient(var(--reveal) circle at var(--x,50%) var(--y,50%),#000_0%,transparent_100%)]
02

The reveal is a mask, not a clip of content

Pointer position never moves the glyphs. onPointerMove writes --x and --y as pixel offsets from the card's bounding rect; the stream's mask-image is a radial gradient whose center reads those variables (and --reveal for radius). Defaults are 50% / 50%, so the first paint doesn't flash from a corner.

Opacity is separate from the mask: the stream starts at opacity-0 and fades to --stream-opacity on group-hover over 300ms. The mask only decides where the faded-in stream is visible.

03/04Scramble only while hovered

setInterval(() => setStream(randomString(1500, chars)), speed)

Opacity in
300ms ease · group-hover:[opacity:var(--stream-opacity)]
Xy7#
Re-randomize
setInterval(speed) · default 55ms · glyphs change, opacity steady
tsx
React.useEffect(() => {setStream(randomString(STREAM_LEN, characters));if (reduced || !hovered) return;const id = window.setInterval(() => {  setStream(randomString(STREAM_LEN, characters));}, speed);return () => window.clearInterval(id);}, [characters, speed, hovered, reduced]);
03

Scramble only while hovered

While hovered is true and reduced motion is off, a setInterval re-rolls the entire 1500-character string every speed ms (default 55). Leaving the card clears the interval; entering starts it again. The ciphertext keeps changing under the spotlight, and no timer runs while the card is idle.

04/04Result

Production API key

rotated 3 days ago

Live
sk_live_51Nc••••••••4Rf2
AES-256-GCM at restSOC 2
04

The result

Hover the card and move across it. The spotlight and the scramble are the two mechanisms described above.

One glyph field, one radial mask on --x/--y, and an interval that only lives for the length of the hover.

Reduced motion

Under prefers-reduced-motion, the stream still paints and the mask still tracks, so the ciphertext stays visible. The interval never starts, and motion-reduce:[transition:none] removes the 300ms opacity fade.

tsx
 
const reduced = usePrefersReducedMotion();
// …interval skipped when reduced || !hovered
// STREAM_BASE: motion-reduce:[transition:none]

Motion Score

Encrypted CardSS: Compositor-only
SopacityFade / cross-fade
Each property is graded by how the browser runs it, from S (composited off the main thread) down to F (layout thrashing); the component takes the worst. MotionScore methodology →