{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "ns-ui",
  "homepage": "https://design.helpmarq.com",
  "items": [
    {
      "name": "accordion-latch",
      "type": "registry:ui",
      "title": "Accordion Latch",
      "description": "An accordion whose sections latch shut with a small SVG hasp-and-staple on each closed header — opening swings the hasp off its staple, lifts the lid 2px, then unfolds the content; closing reverses and the hasp settles back down with a bounce.",
      "files": [
        {
          "path": "registry/core/accordion-latch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/accordion-latch.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "accordion",
          "disclosure",
          "hasp",
          "latch",
          "aria-expanded",
          "roving-tabindex",
          "svg"
        ],
        "instruction": "Build an accordion (`items: {id, title, content}[]`, `multiple?: boolean` default false for single-open, `defaultOpen?: string[]`) whose sections latch shut with a physical hasp instead of a bare chevron. Each closed header renders a small 16x28 SVG in its top-right corner: a fixed U-shaped staple bracket (a static path, never animates) and a separate hinged hasp group — a hinge dot, a strap line, and a rounded loop rectangle at the strap's free end — whose resting pose (rotate 0deg around the hinge point at the top of the strap) visually hooks the loop down over the staple. All choreography is staged with plain CSS transitions keyed off a single `data-state=\"open\"|\"closed\"` attribute mirrored onto four elements (the hasp SVG group, the header button acting as the 'lid', the content's grid-rows wrapper, and the inner content block) — each stage owns its own transition-duration and transition-delay so one state flip plays the whole sequence in order with no rAF loop and no per-frame React state: OPENING — the hasp swings to rotate(-58deg) immediately (0ms delay, 200ms, `cubic-bezier(0.34,1.56,0.64,1)` — an overshoot 'spring' ease that reads as the loop popping free with a tiny click) → the header lifts `translateY(-2px)` starting around 130ms (as the hasp settles) → the content unfolds via the codebase's `grid-template-rows: 0fr -> 1fr` trick (not `height: auto`, not a scrollHeight measurement) starting around 190ms over 300ms `cubic-bezier(0.16,1,0.3,1)` (ease-out-expo) while the inner content simultaneously fades and slides up from `translateY(4px)` to `0` starting at 220ms. CLOSING reverses the order: content fades/drops back to `opacity:0 translateY(4px)` immediately (120ms, no delay) → the header drops back to `translateY(0)` around 90ms → the grid-rows collapse back to `0fr` around 0ms-220ms total → the hasp drops last, back to `rotate(0deg)`, starting around 180ms over 220ms with a bouncier `cubic-bezier(0.68,-0.55,0.34,1.55)` so the drop reads as a settle rather than a snap. Single-open by default: opening one section closes any other (a `Set<string>` of open ids, cleared before adding unless `multiple` is true); `multiple` lets several stay open independently. Headers are real `<button>` elements wrapped in `<h3>`, each with `aria-expanded` and `aria-controls` pointing at a `role=\"region\" aria-labelledby=\"<header id>\"` panel — the standard disclosure-widget shape, not a custom listbox. Keyboard: roving tabindex across headers (only the currently-relevant header is in the tab sequence, matching the dropdown-drape pattern used elsewhere in this registry) with ArrowUp/ArrowDown moving focus to the previous/next header (wrapping), Home/End jumping to the first/last; Enter/Space toggle via the button's native activation, no extra key handling needed for that part. Hovering a CLOSED header slides its hasp 1.5px sideways via a plain CSS `:hover` rule (no JS) — reads as 'testing the latch' before it's actually opened; this rule is higher-specificity than the resting transform so it cleanly overrides it without fighting the open/close transition. `prefers-reduced-motion: reduce` zeroes every stage's transition-duration and transition-delay to 0ms, so state still changes correctly (nothing gets stuck mid-animation) but the hasp/lid/content all snap between their two end poses instantly rather than sequencing. Zero dependencies, no measured layout, no canvas."
      }
    },
    {
      "name": "approval-inline-diff",
      "type": "registry:ui",
      "title": "Approval Inline Diff",
      "description": "Human-in-the-loop tool-call approval row: every argument is editable inline with an old→new diff, and deciding collapses the row irreversibly into a one-line receipt.",
      "files": [
        {
          "path": "registry/core/approval-inline-diff/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/approval-inline-diff.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "approval",
          "review",
          "agent",
          "form",
          "diff",
          "confirmation",
          "accessibility"
        ],
        "instruction": "Renders one proposed tool call — toolName, the requesting agent's name, and a list of {key, label, value} arguments — as a review row the human must approve or deny before anything runs. Each argument is a real labelled <input>, not a JSON textarea: it starts showing the proposed value and is editable in place. The very first value each field held is captured once at mount and kept for the life of the row; any edit is compared against that original, never against the previous edit, so the row always answers 'what did the human actually change from what the agent asked for'. An optional initialValues map lets a consumer seed a field as already edited before the human ever touches it — useful for showing a queued, pre-adjusted call at rest rather than only after live typing. The moment a field's live value differs from its original, a Geist Mono diff line appears beneath it — the original struck through, an arrow glyph, then the current value — and stays until the row is decided; sr-only 'changed from'/'to' glue text carries the same meaning to assistive tech. Approve and Deny are told apart by fill and weight, not colour: Approve is a solid accent button with a check glyph (the component's one and only use of --accent, reserved for this primary action), Deny is a plain outlined button with an X glyph — no red, no destructive styling, just a lighter, unfilled visual claim so the two outcomes read as equally final but differently weighted. Clicking either calls onDecision once with {outcome, actor: approverName, timestamp} plus the fields as finally edited, then the row collapses: the entire editable body animates its grid-template-rows from 1fr to 0fr (420ms, ease-out-expo-shaped cubic-bezier) and is marked inert so no stray Tab or synthetic replay can reach a control sitting at zero height, while a one-line receipt — outcome glyph, outcome word, tool name, actor, wall-clock time — fades and slides in below it. This state is genuinely terminal: the component holds a decided-once guard that no button, prop, or internal code path ever clears, so there is no way back into edit mode for that call. The receipt line is a native <details>/<summary> disclosure; opening it reveals the full payload exactly as approved or denied, one row per argument, in Geist Mono. A component consumer can pre-seed a row as already decided via initialDecision, for rendering a history/audit feed of past calls next to a live pending one — those seeded rows mount straight into the collapsed receipt (the collapsing editable body is never shown) but still play the same brief fade/slide-in as the receipt itself settles. Fully keyboard operable: Tab reaches every field, then Deny, then Approve, in document order; the decided receipt's summary receives focus automatically after an in-session decision so the outcome — not a vanished button — is what focus and the screen reader land on, backed by an aria-live status announcement of the same text. Under prefers-reduced-motion the collapse and receipt entrance are instant with no transition or keyframe, fully legible and usable either way."
      }
    },
    {
      "name": "autosave-ratchet",
      "type": "registry:ui",
      "title": "Autosave Ratchet",
      "description": "Autosave status as a mechanical ratchet — a 10-tooth gear advances one notch per saved change and tallies the session in its rotation, while a failed save kicks the wheel back half a tooth and holds that broken pose until the next save resolves it.",
      "files": [
        {
          "path": "registry/core/autosave-ratchet/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/autosave-ratchet.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "autosave",
          "status",
          "indicator",
          "tooltip",
          "svg",
          "aria-live",
          "accessibility",
          "editor"
        ],
        "instruction": "Build the Saving / All changes saved indicator for an editor toolbar as a mechanical ratchet, not a spinner or a text swap. A 20px SVG gear (24x24 viewBox) renders 10 radial teeth as stroked lines: 9 in --muted plus one index tooth in --foreground — the index tooth is the only asymmetric feature, so it's the one thing that makes a one-tooth rotation of an otherwise 10-fold-symmetric ring actually visible frame to frame. A fixed pawl triangle sits at 12 o'clock and never rotates with the wheel. Drive it with a `status` prop (idle | saving | saved | error); transitions are what move it, nothing loops or idles on its own. save-start (idle/saved/error -> saving) plays a small anticipation backswing (8deg, 130ms, ease-in) winding the wheel back against the pawl before the outcome is known. ack (saving -> saved) plays a 200ms ease-out-expo spring settle forward past the wind-up to the next tooth (36deg), increments a silent session save counter and stamps the save time, and relaxes the pawl back to its resting --muted/1.4px stroke. Any transition into `error` is the pawl slipping: the wheel kicks back half a tooth (18deg) measured from the last confirmed good seat — not from wherever an in-flight wind-up left it, so repeated failures hold the same broken pose rather than unraveling further — over 160ms, while the pawl itself lifts (a 1.2px translate) and thickens to a 2px --foreground stroke. That kicked, thickened pose holds indefinitely, with zero looping motion, until the next `saved` resolves it. All of this is direct-DOM CSS-transform/stroke writes on two SVG refs (the rotating tooth group and the pawl path) — no React state on the animation hot path. Accessibility: a dedicated sr-only span is the status wrapper (role=status/aria-live=polite/aria-atomic=true) holding only the throttled announcement text — kept separate from the button and tooltip so aria-atomic re-reading it never picks up the button's label or an open tooltip's duplicated text, and so hovering the tooltip (explicitly aria-live=off) can never itself trigger a spurious announcement. 'Saving' and 'Saved' chatter share a 30-second throttle window so a bursty autosave can't spam a screen reader, but 'Save failed' always announces immediately and resets that window so the next resolution is guaranteed to announce too. The gear's SVG is aria-hidden; the only focusable element is a button (accessible name 'Autosave status') that doubles as a tooltip trigger — hover opens it after a 400ms delay, keyboard focus opens it instantly (a keyboard user is never 'hovering past'), and Escape closes it without moving focus. The Geist Mono tooltip duplicates every fact as plain text: current status, how many times the session has saved, and how long ago the last successful save landed (e.g. 'Saved 14 times, last 12s ago'), or 'Save failed. N saved this session, last successful save Ns ago.' while broken. The failed state additionally renders the plain visible words 'Not saved' beside the wheel (aria-hidden, since the live announcement already covers it for assistive tech) so the state is never carried by the graphic alone for a sighted user glancing past it. prefers-reduced-motion drops the wind-up and every eased transition outright: ack and failure both land as an instant discrete step straight to their resolved angle/pose, still fully legible, just not animated. Zero dependencies, no canvas."
      }
    },
    {
      "name": "avatar-stack-flock",
      "type": "registry:ui",
      "title": "Avatar Stack Flock",
      "description": "Avatar stack that mills as a live boids flock and resolves into the classic overlapping row on hover, with the +N badge appearing only once the formation settles.",
      "files": [
        {
          "path": "registry/core/avatar-stack-flock/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/avatar-stack-flock.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "avatar",
          "boids",
          "physics",
          "hover",
          "team",
          "micro-interaction"
        ],
        "instruction": "A team avatar stack that never sits still: 7 DOM avatar circles (28 px, initials, ring-2 ring-background) mill inside a bounded card region as a real boids flock driven by a canvas-free vector sim (separation r34/w1.4, alignment r60/w0.6, cohesion r90/w0.5, max speed 60 px/s, soft-wall 300 px/s^2 ramping inside 24 px of the edge, damping pow(0.98, dt*60)). Hovering or keyboard-focusing the container ramps a per-avatar seek force toward its slot in the classic -8 px overlap right-aligned row from 0 to weight 2.2 over 400 ms, summed into the same three rules with arrival slowdown inside 40 px, so the tidy group photo is itself a settled flocking state. The 60 px/s milling speed cap is deliberately slow for a calm resting drift, so the seek phase ramps in its own, much higher travel-speed ceiling with the same 400 ms curve — idle milling is untouched but a from-scatter hover resolves the row in about a second instead of several. When every agent is within 2 px of its slot at under 1 px/s the +N badge fades in over 200 ms; release ramps the seek off over 400 ms, and the badge only fades out once the row has actually started to leave its slots (not on a momentary pointer-out) as the flock disperses back to milling. Transforms are written per-frame on a refs-only direct-DOM rAF loop that pauses offscreen (IntersectionObserver) and on document.hidden, and genuinely sleeps once resolved and settled below 0.5 px/s until hover exit; ResizeObserver re-derives bounds and slots with zero-size and zero-length-vector guards. Hovering an individual avatar once resolved lifts it 3 px and shows a token-styled name tooltip. All colors are CSS token classes (bg-surface, border-border, ring-background) so both themes self-adapt. prefers-reduced-motion renders the static resolved row with the badge always visible."
      }
    },
    {
      "name": "background-ascii-caustics",
      "type": "registry:ui",
      "title": "Background ASCII Caustics",
      "description": "Ambient water-caustics rendered in ASCII ink density — three rotated wave grids combined multiplicatively and sharpened into thin bright filaments, the way overlapping light wave-fronts trace a caustic web, with the pointer acting as a lens the pattern visibly focuses toward.",
      "files": [
        {
          "path": "registry/core/background-ascii-caustics/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-ascii-caustics.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "ascii",
          "canvas",
          "cursor",
          "caustics",
          "water",
          "light"
        ],
        "instruction": "Build <CausticVeil cellSize? className?> as a full-bleed <canvas>. FIELD: three periodic wave layers, each rotated to its own fixed angle (0, 1.15, 2.3 rad) and drifting at its own phase speed (0.22, -0.17, 0.13 rad/s), sampled as sin(rotatedX * freq + t * speed) per layer and combined MULTIPLICATIVELY — product = wave1 * wave2 * wave3 — never summed (that is background-ascii-plasma's technique). The rendered luminance is pow(max(0, 1 - abs(product)), 5.5): a steep power curve that turns the near-zero-product contours (where the three waves cross in close alignment) into thin bright filaments against a mostly dark field, which is the actual optical mechanism behind a caustic net rather than a cosmetic recolor of a summed field. LENS: the pointer maintains a 0..1 'active' scalar that eases toward 1 while over the canvas and back to 0 once it leaves (time constant ~0.55s), and any grid cell within a 10-cell radius of the pointer has its SAMPLE coordinate pulled INWARD toward the pointer (gaussian falloff, max pull 3.4 cells) before the caustic field is evaluated there — the opposite sign of an outward push, so the web visibly contracts and focuses toward the cursor like light converging through a magnifying lens, and relaxes back to its undisturbed shape once the pointer leaves. Direct-DOM rAF, zero React state on the hot path. Rendering is two-pass for cost control: pass one evaluates every cell into a Uint8Array ramp-index buffer and buckets its index by luminance into one of 6 alpha buckets; pass two sets ctx.globalAlpha once per bucket and draws only that bucket's cells, from the shared ' .:-=+*#%@' density ramp — the same bucketing discipline background-ascii-plasma uses, adapted to this field's multiplicative math. Ink is read once via getComputedStyle(canvas).color and re-derived on a documentElement class MutationObserver for live theme flips. Mono cell measured via an offscreen canvas's measureText. prefers-reduced-motion renders exactly one static frame at t=0 with the lens fully relaxed, and skips the rAF loop and pointer listeners entirely. Loop pauses on document.hidden, resumes on visibilitychange. Props: cellSize (grid cell px, default 12), className."
      }
    },
    {
      "name": "background-ascii-dither",
      "type": "registry:ui",
      "title": "Background ASCII Dither",
      "description": "Luminance-to-glyph canvas renderer: ASCII, Bayer-dither, and dot-matrix modes with cursor-proximity resolve.",
      "files": [
        {
          "path": "registry/core/background-ascii-dither/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-ascii-dither.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "ascii",
          "dither",
          "canvas",
          "cursor"
        ],
        "instruction": "A full-bleed canvas engine that maps a source (animated flowing noise by default, or an image) to a luminance-driven monochrome glyph grid with three modes: ASCII characters in a density ramp, 4x4 Bayer-matrix dithered pixels, and dot-matrix circles sized by brightness. The cursor brightens and resolves nearby cells with a Gaussian falloff and eased trailing. Direct-DOM rAF loop with no React state, theme-aware glyph color, single static frame under prefers-reduced-motion. Window resize is debounced (150ms) so dragging a window edge doesn't spam the grid recompute or (in image mode) refetch/redecode the source on every event; a generation counter discards any image decode superseded by a newer resize before it can write stale-sized data."
      }
    },
    {
      "name": "background-ascii-flow",
      "type": "registry:ui",
      "title": "Background ASCII Flow",
      "description": "An ambient ASCII flow-field background — a fixed set of tracer particles ride a time-evolving 2D curl-noise velocity field, each glyph's direction ('-', '|', '/', '\\\\') and trail alpha encoding its heading and speed, with the pointer stirring a local swirl instead of painting or repelling.",
      "files": [
        {
          "path": "registry/core/background-ascii-flow/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-ascii-flow.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "ascii",
          "canvas",
          "cursor",
          "noise",
          "particles",
          "flow"
        ],
        "instruction": "Build <Slipstream cellSize? className?> as a full-bleed <canvas>. FIELD: a 2D bilinear value-noise potential (hash-based, smoothstepped, two octaves at 0.7/0.3 weight, one drifting in y and one in x-t so the field itself slowly evolves) is turned into a divergence-free velocity via its CURL, taken by central finite differences (vx = dPotential/dy, vy = -dPotential/dx) at a small epsilon — the textbook incompressible-flow trick, never a raw gradient. AMBIENT LAYER: every AMBIENT_STEP-th grid cell (3) samples the curl at t and renders one direction glyph chosen from '-', '|', '/', '\\\\' by quantizing the velocity angle into 4 slope buckets, at a fixed low alpha (0.16) using the --muted token — this alone gives the field a legible static shape even at rest. PARTICLE LAYER: a fixed particle count (scaled by grid area, clamped 90-260) each holds a genuine continuous (x, y) float position (not snapped to a cell) advanced every frame by the curl velocity AT that exact point plus the current time, wrapping at the canvas edges. Each particle keeps an explicit ring buffer of its last 4 positions (Float32Array, no decay-per-cell state anywhere — this is deliberately not background-ascii-wake's persistent heat grid) and every frame redraws all of them at falling alpha (1 down to ~0.05) using the direction glyph derived from the particle's OWN current velocity, in the --foreground token. VORTEX: the pointer's presence over the canvas ramps a 0..1 'active' scalar (eased in/out over ~1/6s) that, once above a small threshold, adds a TANGENTIAL (perpendicular-to-radius) velocity term to any particle within a 120px radius, magnitude falling off linearly with distance — particles visibly swirl around the cursor rather than being pushed away from or pulled toward it, and the swirl relaxes to nothing once the pointer leaves. Direct-DOM rAF, zero React state on the hot path; ink read via getComputedStyle(canvas).color / the --muted custom property at mount and re-derived on a documentElement class MutationObserver. Mono cell measured via an offscreen canvas after document.fonts.ready. prefers-reduced-motion renders exactly one static frame (ambient direction layer only, particles motionless at their seeded positions) and skips the rAF loop and pointer listeners entirely. Loop pauses on document.hidden, resumes on visibilitychange. Props: cellSize (grid cell px, default 14), className."
      }
    },
    {
      "name": "background-ascii-wake",
      "type": "registry:ui",
      "title": "Background ASCII Wake",
      "description": "Full-bleed monospace cursor-trail field: a sparse ambient scatter at rest, with the pointer dragging a decaying character comet whose length and brightness depend on how fast it moves.",
      "files": [
        {
          "path": "registry/core/background-ascii-wake/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-ascii-wake.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "ascii",
          "cursor",
          "canvas",
          "trail"
        ],
        "instruction": "Build <WakeGlyph cellSize? className?> as a full-bleed <canvas> over a persistent per-cell grid, not a stateless proximity glow. STATE: two parallel Float32Arrays sized cols*rows (cols/rows from container size divided by cellSize, default 12px) — `ambient`, a static seeded scatter generated once per resize (~3.5% of cells set to a low 0.08-0.22 value, deterministic per grid size via a small PRNG) representing the sparse, near-silent ground state, and `heat`/`rate`, the live wake: heat decays every frame by `rate * dt` per cell, where `rate` was fixed at the moment that specific cell was last stamped rather than being a single global constant. STAMPING: on pointermove, compute distance and elapsed time since the previous move to get a speed in px/ms, then derive both a stamp radius (in cells) and a decay rate from that speed with inverse relationships — fast movement yields a SMALL radius and a HIGH decay rate (many cells lit briefly and thinly along the path), slow movement yields a LARGE radius and a LOW decay rate (fewer stamps but each one fat and lingering) — this is the \"per-cell decay with velocity dependence\" the trail is built on. Because a fast pointer move can skip several grid cells between two consecutive pointermove events, the path between the previous and current point is sampled in sub-steps (spaced roughly cellSize/2 apart) and each sample stamps its own circular falloff blot, so the wake has no gaps at high speed. A stamp only raises a cell's heat (`Math.max`), never lowers it, and carries its rate along only when it does. RENDER: every frame, each cell's displayed luminance is `Math.max(ambient[i], heat[i])` mapped through the shared \" .:-=+*#%@\" density ramp exactly as background-ascii-dither does, with alpha scaled to luminance; cells at or below the ramp's blank threshold are skipped entirely rather than drawn as an empty glyph, which is what keeps a several-thousand-cell grid affordable to redraw every frame. Glyph ink is read once via getComputedStyle(canvas).color (theme-aware, never a hardcoded hex) and the canvas font uses the live --font-mono custom property rather than a literal family string. Window resize is debounced 150ms and regenerates the whole grid (new dimensions invalidate the old ambient/heat arrays outright — there is no cross-resize cell mapping). REDUCED MOTION: pointer listeners are never attached and no rAF loop starts; a single frame of the ambient scatter alone is painted once, so the component is inert but never blank or crashing."
      }
    },
    {
      "name": "badge-unread-tarnish",
      "type": "registry:ui",
      "title": "Badge Unread Tarnish",
      "description": "Unread badge that tarnishes like brass: solid when fresh, an outline within a day, a muted ring after a week — new activity instantly re-polishes it with a small flare.",
      "files": [
        {
          "path": "registry/core/badge-unread-tarnish/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/badge-unread-tarnish.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "badge",
          "notification",
          "unread-count",
          "navigation",
          "recency",
          "micro-interaction"
        ],
        "instruction": "An unread-count badge for nav items and sidebars whose whole point is a second dimension alongside the count: how stale the newest item is, readable at rest with zero motion. It's a single rounded-full <span role=\"img\"> (not a control — the nav item it decorates is the focusable element) whose data-stage is derived purely from a newestTimestamp prop, never from the count: fresh (age under 24h) renders a solid --foreground fill with background-token digits and semibold weight; waning (under 7 days) drops the fill to transparent and thins to a 1.5px --foreground border at medium weight; dormant (7 days or more) settles to a 1px --muted border with --muted digits at normal weight — fill, border width and text tone all move together so the three stages differ by more than hue and survive monochrome viewing. Ordinary aging (a re-check timer, or a prop simply reflecting more elapsed time) crossfades those CSS properties over a plain 400ms transition; new activity is categorically different, so it's fast-pathed around that transition with a one-frame transitionDuration:0 clamp straight to the fresh stage, topped with a 200ms spring-eased (cubic-bezier back-out) scale(1 to 1.12 back to 1) flare via the Web Animations API, so re-polish always reads as an instant snap rather than an eased fade. Accessibility: the pip's own aria-label spells out both dimensions as text (\"3 unread, newest 2 days ago\"), meant to be pulled in by the decorated nav item's aria-describedby pointing at the pip's id (falls back to an internal useId if none is passed); a separate visually-hidden aria-live=\"polite\" region announces only the re-polish moment itself (\"New unread activity, N unread\"), kept apart from the resting description so routine renders never spam a live region. A count of zero or less renders nothing, matching how unread badges actually get used. Zero dependencies, no canvas — DOM and CSS only, colors entirely from --background, --foreground, --muted and --border. Rendering never touches Date.now() during the initial render (server and the pre-effect client render both compute an age of zero), so there is no hydration mismatch; the real elapsed age is adopted the moment effects can run, and a per-minute interval keeps a badge left open on screen decaying through its stages on its own. prefers-reduced-motion drops the spring flare (the instant stage snap and the 400ms crossfade both stay, since neither is extra motion)."
      }
    },
    {
      "name": "banner-tear-stub",
      "type": "registry:ui",
      "title": "Banner Tear Stub",
      "description": "A dismissible notice with a perforated edge: dismissing rips the panel off along the perforation, leaving a small permanent stub that reopens it.",
      "files": [
        {
          "path": "registry/core/banner-tear-stub/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/banner-tear-stub.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "banner",
          "notice",
          "dismissible",
          "reopenable",
          "alert",
          "perforation",
          "micro-interaction",
          "accessibility"
        ],
        "instruction": "A dismissible notice banner whose 1px --border perforation sits 32px in from the leading edge — a vertical dotted rule punched into a solid line via a CSS mask (5px dash, 3px gap), not the browser's own `dashed` border style, so the gap is exact rather than browser-approximate. Dismissing the panel (a labeled 'Dismiss' button) rips it off along that perforation: the panel translates 24px along X, rotates 1deg, and fades to 0 opacity over 300ms ease-out-expo while it is still occupying its layout space, so nothing else moves until it's actually gone. Once the rip finishes the panel leaves the DOM and the row's height springs (a small critically-damped JS spring driving inline height, not a CSS transition) down to whatever is left: a permanent 32px-wide stub, seated flush at the leading edge, showing the notice's icon above a rotated Geist Mono micro-label — a receipt-book check stub, not a close button, because the record that a notice exists is never destroyed, only shrunk. Clicking the stub (labeled 'Reopen: <title>') reverses both motions: the panel remounts already in its torn pose, the row springs back open, and a frame later the panel animates back to identity, perforation restored between the two pieces. The component doesn't force a width — it's `inline-flex`, sized to its content; pass className=\"w-full\" for a page-width banner, and dismissing it will still shrink it down to just the stub's own small footprint rather than leaving a wide empty strip, because removing the panel from a fit-content flex row is what does the shrinking. The stub is a decorative, non-interactive icon while the panel is attached — there is never a pointless enabled button sitting idle — and only becomes the focusable Reopen control once the panel is actually gone; it gets a 44px touch target via a 6px hit-area overhang on each side of its 32px-wide look, so the slim receipt-stub silhouette never costs tap accuracy. Dismissing moves focus to the stub; reopening moves focus back to the Dismiss button, so keyboard users never lose their place. The outer region is role=status (aria-live=polite) for ordinary notices or role=alert (aria-live=assertive) for the urgent variant, set once at mount and never toggled per interaction, so a tear-then-heal cycle can't turn a one-time 'here's a notice' announcement into repeated noise. Pure DOM/CSS/SVG — no canvas. Colors are --background, --surface, --border, --foreground, --muted and --accent only, so both themes render correctly. prefers-reduced-motion swaps the rip and the reverse rip for a plain opacity crossfade (no translate or rotate) and swaps the height spring for one short 160ms linear resize — still fully dismissible and reopenable, just without the physical motion. Distinct from truncation-word-count: that folds content away in place and the folded object is still the same panel at a smaller size; here dismissal produces a genuinely different, much smaller remainder object (a torn-off stub), and the whole point is that tearing can never destroy the underlying record the way an ordinary close button would."
      }
    },
    {
      "name": "boxplot-ascii-whisker",
      "type": "registry:ui",
      "title": "Boxplot ASCII Whisker",
      "description": "ASCII-textured boxplot family with one shared fence handle: dragging or keying it re-cuts every box's whiskers live against the real sample, and outliers fade in or reclassify back into the whisker as the cut moves.",
      "files": [
        {
          "path": "registry/core/boxplot-ascii-whisker/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/boxplot-ascii-whisker.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "boxplot",
          "data-viz",
          "ascii",
          "canvas",
          "statistics"
        ],
        "instruction": "The registry's first distribution instrument: one box-and-whisker plot per group, quartiles computed by linear-interpolation quantile over the real sample (not a pre-aggregated summary). Box bodies are filled with the family's shared ASCII ramp ' .:-=+*#%@' tiled at a constant mid density purely as texture — box height already encodes the interquartile spread, so this is not a second value channel, matching the family's redundant-density convention elsewhere. The real mechanic is the single shared fence handle below the chart: a real <input type=\"range\"> (k, the IQR multiplier, 0.5 to 3.0 in 0.1 steps), visually replaced by a custom track and thumb the way slider-range-shear carries its accessibility, draggable by pointer or fully operable by the native input's own arrow/Home/End/PageUp/PageDown handling. Moving it recomputes, for every box on the chart at once, the low/high fence (Q1 - k*IQR .. Q3 + k*IQR) against the REAL underlying sample: the farthest sample still inside the fence becomes the new whisker cap (eased into position over roughly 300ms, not snapped), and every sample outside the fence renders as a small outlier ring that fades in — a sample that re-enters the fence as k grows fades back out of view instead of disappearing instantly, so the reclassification itself is visible motion, not a jump cut. Each group also has its own real hit button (roving tabindex, ArrowLeft/ArrowRight moving focus) whose hover or focus tints that box's whiskers and outline to var(--accent) and opens a small tooltip with its median, Q1, Q3 and current outlier count; var(--accent) is otherwise reserved for the fence thumb, matching the family's convention of accent for interaction only, never as a value channel. Tokens are read via getComputedStyle at mount and re-read through a MutationObserver on the document root's class attribute, so both themes repaint correctly on toggle. prefers-reduced-motion snaps whisker and outlier changes to their final state in one paint instead of lerping; the fence handle remains fully operable either way. Zero dependencies."
      }
    },
    {
      "name": "breadcrumb-fold",
      "type": "registry:ui",
      "title": "Breadcrumb Fold",
      "description": "A breadcrumb that folds like a camera bellows — ancestor segments rest pleated into narrow slivers and the current page sits fully extended, with hover or focus inflating any sliver back to natural width as its neighbours redistribute the fixed total width.",
      "files": [
        {
          "path": "registry/core/breadcrumb-fold/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/breadcrumb-fold.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "breadcrumb",
          "navigation",
          "hierarchy",
          "overflow",
          "accessibility"
        ],
        "instruction": "A breadcrumb for deep hierarchies (file paths, org trees, nested categories) that fits an unbounded number of levels into a fixed horizontal budget without ever hiding one behind an ellipsis menu. At rest, every ancestor segment is compressed to a narrow 20px pleat — its label clipped by overflow plus a mask-image fade on the trailing edge, three 1px vertical fold lines in --border marking the pleat — while the current (last) segment sits at its full measured width. Hovering or focusing any segment, ancestor or current, retargets it to its natural width; every other segment redistributes the remaining budget proportionally to its own natural width, floored at the 20px pleat. The redistribution is exact and jitter-free without a physics loop: every segment's flex-basis transitions on the identical CSS duration and easing curve, so at any instant during the spring all segments sit at start_i + (end_i − start_i) × the same eased fraction — since the start widths and the end widths both sum to the container's available width by construction, the row's total width is a mathematical invariant of that shared fraction and never wobbles mid-motion, overshoot included. Natural widths are read once from a hidden, absolutely positioned ghost copy of the full trail (out of flow, nowrap, same padding as the live row) so measuring never feeds back on the already-clipped visible list, and widths are re-measured on ResizeObserver and once on document.fonts.ready. Separator chevrons are small aria-hidden SVGs in --muted, rotating about 15 degrees toward whichever side currently holds the expanded segment. Unlike a folding tree (which articulates each branch open around a hinge) or an ellipsis-menu breadcrumb (which deletes the middle of the trail into a popover), no level is ever removed from the DOM or from view — constant-total-width redistribution among always-visible pleats is the mechanism itself, not a fallback for when truncation fails. Markup is nav[aria-label=Breadcrumb] > ol > li > a or button, aria-current=\"page\" on the last segment; every compressed segment keeps its full label as real text content — only a CSS clip, never a truncated string — so its accessible name is always complete. Tab order is plain document order and keyboard focus expands a pleat exactly like hover does, so every level is readable without a mouse. prefers-reduced-motion snaps widths directly to their target instead of springing."
      }
    },
    {
      "name": "breadcrumb-overflow-menu",
      "type": "registry:ui",
      "title": "Breadcrumb Overflow Menu",
      "description": "A breadcrumb trail that collapses from the middle into a menu of the hidden levels, with an accent rule that sweeps under the current level.",
      "files": [
        {
          "path": "registry/core/breadcrumb-overflow-menu/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/breadcrumb-overflow-menu.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "breadcrumb",
          "navigation",
          "overflow",
          "menu",
          "accessibility"
        ],
        "instruction": "A breadcrumb navigation trail for paths deep enough to outgrow their container. When the trail cannot fit, it gives way from the middle rather than the tail: the first crumb and the last two always survive (both counts are props) and everything between them folds into a single ellipsis button that opens a menu of the hidden levels, so no level is ever lost to keyboard or assistive tech — it just moves. Every width in the fit calculation is read from a second, hidden copy of the full uncollapsed trail rendered out of flow, never from the visible list; measuring the visible list is what turns this pattern into a feedback loop where collapsing shrinks the content, which says it fits, which expands, which overflows, and shows up as permanent jitter at any container width near the threshold. The ellipsis chip's own width is part of the budget, candidates are dropped from the centre outward until the row fits, re-expanding demands 24px of extra room so a container resting on the threshold settles, an identical result never calls setState, and everything is remeasured once on document.fonts.ready because a webfont swap invalidates every number. The current level is a plain span with aria-current=\"page\" carrying a 2px accent rule that sweeps from zero to the exact text width whenever the last crumb's id changes, instead of a static bold treatment. Markup is nav > ol > li with real links or buttons in natural tab order; the ellipsis is aria-haspopup=\"menu\" with a labelled count, and its role=\"menu\" popover has roving tabindex, wrapping Arrow Up/Down, Home/End, Escape to close and restore focus, and Tab to close and move on. Zero dependencies, no canvas, colors entirely from --accent, --muted, --foreground, --surface and --border so both themes read correctly, and prefers-reduced-motion renders the rule at full width with no sweep and mounts the menu with no fade."
      }
    },
    {
      "name": "button-cooldown-heat",
      "type": "registry:ui",
      "title": "Button Cooldown Heat",
      "description": "A rate-limited button that heat-soaks: each press deposits heat into a visible bottom-up fill, dilates its letter-spacing, swells its surface and brightens its border, growing a heat-haze shimmer as it nears the limit and decaying exponentially (fill draining, haze fading) when idle — hammered past its duty cycle it soaks into a distinct hazard-hatched dead state, going dead on a flat 1px dip until it visibly cools back below the re-arm mark.",
      "files": [
        {
          "path": "registry/core/button-cooldown-heat/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/button-cooldown-heat.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "button",
          "rate-limit",
          "cooldown",
          "duty-cycle",
          "hysteresis",
          "heat-haze",
          "micro-interaction",
          "accessibility"
        ],
        "instruction": "A button (`children` is its visible label and accessible name, `onPress` fires on every press that isn't currently soaked) whose rate limit is rendered entirely on its own body — no progress bar, no digit readout bolted on. A scalar `h` (\"heat\") starts at 0, gains +0.34 on every non-soaked press, and decays continuously toward 0 with an exponential half-life of 2.5s, computed in a single requestAnimationFrame loop (delta-time based, sleeps once h, the dip, and the soak flag all settle, wakes again on the next press) rather than a CSS transition — the decay itself has to be watchable, not snapped. Each frame writes the live value straight onto the button element as a `--heat` CSS custom property (plus a `--dip` scalar, see below); the button derives two further custom properties from it in CSS — `--h: min(var(--heat), 1)` (the same clamp reused everywhere) and `--warm: max(0, min(1, calc((var(--heat) - 0.7) * 3.3333)))`, a ramp that's 0 below h=0.7 and climbs 0 to 1 across 0.7-1.0, held at 1 through the hottest part of the soak but fading out again as h keeps decaying past 0.7 toward the (much lower) re-arm point — the hazard hatch and dimmed label, gated on the soaked flag rather than `--warm`, are what stay on for the full lockout. Several `calc()`/`color-mix()` expressions consume those: `letter-spacing: calc(var(--h) * 0.06em)` on the label span (0 to 0.06em), `transform: scale(calc(1 + 0.02 * var(--h)))` on the button itself (1 to 1.02, thermal expansion of the surface), `border-color: color-mix(in srgb, var(--border), var(--foreground) calc(var(--h) * 100%))` (brightening the border from --border toward --foreground as heat rises — never toward --accent, this is thermal state, not an interactive affordance), a bottom-up fill layer (`linear-gradient(to top, ...)` hard-stopped at `calc(var(--h) * 100%)`, tone mixed from --muted toward --foreground by `--warm`, opacity `calc(0.16 + var(--warm) * 0.16)`) giving the accumulated heat an actual gauge instead of leaving it to the border and scale alone, and a haze layer (a soft diagonal band on a `220% 100%` background sweeping via an animated `background-position`, opacity gated to `calc(var(--warm) * 0.35)` so it is invisible at rest and only appears once the button is genuinely close to, or still cooling from, the limit). Both the fill and the haze double as the cooldown display without any separate state: nothing resets when soak ends, `--h`/`--warm` simply keep draining as `h` decays, so the same gauge that filled on the way up visibly empties back out afterward. At h >= 1.0 the press handler flips a `soaked` flag true and stops adding heat or calling `onPress` at all; further presses instead set a second, independent scalar `--dip` to 1, which the same rAF loop decays linearly (not exponentially, and with no spring/bounce — genuinely overdamped) to 0 over about 180ms, composed into the button's transform as `translateY(calc(var(--dip) * 1px))` alongside the ambient scale — a flat, dead 1px sink with no return energy, the tactile equivalent of the button silently swallowing a click. The soak flag only re-arms once `h` decays back down to <= 0.05 — genuinely cooled, not merely below 1.0 again. A tighter gap (the button previously re-armed at 0.7) let a single subsequent press — 0.7 + 0.34 = 1.04 — instantly punch back over the limit, so the lockout was only ever honored for a sliver of the real decay and a press mid-cooldown looked like it simply refilled the button; the wide gap down to 0.05 forces the cooldown to run to completion before any press can do anything again, and keeps the state from flickering at a boundary the live value happens to be sitting on. While soaked, the fill layer additionally grows a diagonal hazard hatch (a `repeating-linear-gradient` of --foreground mixed toward transparent) and breathes gently between 60% and 100% opacity on a 1.6s loop, and the label dims toward --muted — a distinct, unmissable \"dead\" read rather than just the same warm tone held in place, and a separate ambient animation from the dip's own deliberately un-animated overdamped feedback. Accessibility: the button carries `aria-disabled=\"true\"` only while soaked (never the native `disabled` attribute, so it never leaves the tab order and stays clickable — clicking while soaked is what produces the dead-dip feedback, not a no-op DOM), and `aria-describedby` a permanently visible Geist Mono caption below the button that duplicates every thermal cue in words rather than leaving any of it to motion alone: \"ready\" at rest, \"heat NN%\" while warm-but-armed (NN = the same clamped h driving the visual dilation, as a percentage), and \"cooling down, ready in about Ns\" while soaked, where N is solved directly from the decay math (t = ln(h / 0.05) / k, k = ln(2)/2.5) rather than a separately-drifting counter. That countdown text recomputes every frame internally but only commits a state update (and thus only repaints/re-announces) at most once per second, exactly as specified, while the underlying `--heat` custom property keeps updating every rAF frame underneath it so the visual swelling itself stays smooth. A separate visually-hidden `role=\"status\" aria-live=\"polite\"` span announces only the two discrete edge transitions — entering soak and re-arming — rather than re-reading the throttled countdown on every tick, which would be noisy. `prefers-reduced-motion: reduce` is handled as a synchronous CSS override (`transform: none !important`, `letter-spacing: 0 !important`, the haze layer `display: none !important`, and the soaked hazard hatch pinned to a static `opacity: 1` with its breathing animation cancelled, all inside the media query, no JS matchMedia race to lose on first paint): the scale and letter-spacing dilation, the dip, the sweeping haze and the hazard breathing all disappear entirely, while the fill's height, the border-color brightening and the caption text are left alone since they're color/size reads rather than motion — reduced motion loses nothing informationally, only the animated half of the redundancy. Every color is `var(--border)`, `var(--foreground)` and `var(--muted)` combined with `color-mix()`, plus `var(--background)` at rest and `var(--accent)` only on the keyboard focus ring — no hex, no canvas, DOM+CSS only, zero dependencies. Deliberately does not declare a `gate` descriptor: reaching soak from a cold mount takes three real presses (0.34 x 3 = 1.02), and the verifier's gate mechanism is a single click of `openBy` — structurally short of what's needed, and pre-warming the mount to fake it would corrupt the default resting screenshot the owner judges first. The autoplay descriptor still demonstrates the full cycle live on the landing-page card: at a 900ms press period the heat fixed-point per cycle exceeds 1.0, so repeated autoplay presses climb into soak, dip a few times, decay back below the re-arm mark, and climb again — the whole duty cycle, looping."
      }
    },
    {
      "name": "button-glass",
      "type": "registry:ui",
      "title": "Button Glass",
      "description": "Liquid-glass button with translucent blurred surface and press states.",
      "files": [
        {
          "path": "registry/core/button-glass/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/button-glass.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "button",
          "glass",
          "micro-interaction"
        ],
        "instruction": "A liquid-glass button: translucent blurred surface, thin light border, subtle hover lift and press scale, visible keyboard focus ring, for a dark minimal (Geist-style) design system."
      }
    },
    {
      "name": "button-retry-backoff",
      "type": "registry:ui",
      "title": "Button Retry Backoff",
      "description": "A retry button that visibly winds a torsion spring through its backoff — disabled and charging while a --foreground arc grows on real timing, notching every failure, pressable only once fully wound.",
      "files": [
        {
          "path": "registry/core/button-retry-backoff/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/button-retry-backoff.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "button",
          "retry",
          "error-handling",
          "rate-limit",
          "backoff",
          "network"
        ],
        "instruction": "A retry button that refuses to be the dishonest always-enabled kind: it is a real <button>, genuinely disabled while backing off, wired to an onRetry callback that returns/resolves false (or throws) to report failure. On failure a 1.5px --foreground arc grows from 0 to 360 degrees around an inner icon's --border track on LINEAR timing matched exactly to the real computed backoff delay (baseDelayMs * factor ^ (attempt-1), capped at maxDelayMs) — the animation duration IS the enforced cooldown, not a decorative approximation of it — while the refresh glyph inside rotates slowly backward as if winding under tension. The button only becomes pressable again once the wind completes: at that instant the icon pops with one damped-spring overshoot and the glyph un-tenses forward by a permanent 5 degrees (a correction, not a bounce back to where it started), and the button gains --accent text and border as its one legitimate accent use, signaling 'ready.' Every failure also drops a permanent 2px --muted tick at a fixed angular slot on the ring — the 1st failure always the same slot, the 2nd always the next — so the ring reads as a history of the current error episode, not just a countdown; the ring saturates at maxNotches (default 8) but the textual attempt count never does. A successful retry resolves the episode: notches and attempt count reset to zero and the ring goes fully quiet. Accessibility: aria-describedby points at a polite, atomic live region that is also rendered as visible text (all ring state exists as text, not just pixels) and only updates at coarse boundaries — once per second while winding ('Retry available in N seconds, attempt K'), once when checking ('Retrying…'), and once, distinctly, the instant it charges ('Retry available now') so the enabled state is announced exactly once rather than on every frame; focus is never stolen programmatically. Hot-path values (arc offset, glyph rotation, the settle spring) are written directly to refs every animation frame; React state only carries status/attempt/caption so re-renders stay coarse. Under prefers-reduced-motion the arc advances in discrete steps with no glyph rotation and no overshoot, but the backoff and notch history stay fully legible as text. Differs from a countdown readout by charging a mechanism toward readiness (an increasing arc, a state the button gates on) rather than depleting a displayed number, and by being an interactive control that accumulates failure history rather than a passive readout of remaining time. Zero dependencies, DOM + SVG + CSS only — no canvas."
      }
    },
    {
      "name": "card-number-emboss",
      "type": "registry:ui",
      "title": "Card Number Emboss",
      "description": "A card-number input rendered as an embossing machine — typed digits raise as bevel-shadowed metal, a stamping-head caret dips on every keystroke, and a brand watermark fades in once enough digits exist.",
      "files": [
        {
          "path": "registry/core/card-number-emboss/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/card-number-emboss.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "payment",
          "card-number",
          "form",
          "input",
          "emboss",
          "luhn",
          "accessibility"
        ],
        "instruction": "Build a card-number input styled as a physical embossing plate, not a flat text field. Container: a rounded-[16px] bordered plate using the repo's `bg-surface` token (so it still adapts with the theme toggle like every other component) with a subtle monochrome grain overlay — generate it via an inline SVG `feTurbulence` filter data-URI set as `backgroundImage` (never a CSS `linear-gradient`/`radial-gradient`, which would read as a banned gradient wash; a grayscale noise texture is a different thing and is fine). Never use a real payment-brand logo asset — detect a text wordmark ('VISA' / 'MASTERCARD' / 'AMEX' by IIN prefix, else generic 'BANK') and render it as plain Geist Mono uppercase text, not an image.\n\nReal input semantics matter more than the visual trick here: use one native `<input inputMode=\"numeric\">` per logical field (card number, expiry, CVC) with correct `autoComplete` (`cc-number` / `cc-exp` / `cc-csc`) and a real associated `<label>`. Each input is made fully transparent (`color: transparent; caret-color: transparent; background: transparent`) and absolutely positioned to EXACTLY overlay its own decorative, `aria-hidden` formatted display underneath (`z-index` above the display) — the visible plate area IS the actual click/tap/focus target, this is not a tiny sr-only proxy hidden in a corner. Because the real input's own caret and outline are invisible, drive focus visibility through a sibling CSS rule instead: mark the decorative display div with a plain attribute (`data-ep-display`) and write `.input:focus-visible ~ [data-ep-display] { outline: ...}` so focusing the real (invisible) input rings its visible display sibling.\n\nEmboss look: digit characters get a light-colored fill plus a DUAL text-shadow bevel — a light offset (e.g. `-1px -1px 0 rgba(255,255,255,0.22)`) and a dark offset (`1px 1px 1px rgba(0,0,0,0.6)`) — grouped 4-4-4-4 with empty slots shown as a muted middle-dot placeholder rather than blank space, so the card always visually reads as 16 slots. A small caret element (a short vertical bar standing in for a 'stamping head') sits after the last typed group; on every keystroke (onChange, not per animation frame) it dips — a quick `translateY(2px) scaleY(0.85)` for ~90ms then eases back — via a direct ref style write, not React state, since this fires on every keystroke and must stay cheap. The watermark wordmark fades in (opacity transition) once the card number reaches 4+ digits.\n\nExpiry and CVC are smaller fields in the same visual treatment (same digit/bevel styling, smaller type), positioned at the bottom of the plate alongside the brand wordmark. CVC's decorative display shows bullet placeholders matching its typed length rather than the literal digits (a CVC is usually masked even where the surrounding UI is otherwise showing plaintext digits, since it's the one field meant to not linger visibly).\n\nLuhn validation fires on the card-number input's onBlur only (never while still typing): compute the Luhn checksum over the entered digits; if invalid, flatten JUST the last group's emboss (`text-shadow: none`, muted color) and draw a `var(--error)` hairline underline beneath that specific group — the other groups keep their embossed look. Clearing/re-editing the number resets the invalid state until the next blur re-validates. A dedicated sr-only `role=status aria-live=polite` span announces the validity result on blur, separate from any field's own label.\n\nHover on the plate raises a very faint sheen (a subtle white-at-low-opacity overlay fading in via CSS `:hover`, not a colored gradient). Reduced motion: the caret-dip transition and the hover-sheen transition are both suppressed entirely (state changes land instantly, no animated step). No dependencies."
      }
    },
    {
      "name": "carousel-card-riffle",
      "type": "registry:ui",
      "title": "Carousel Card Riffle",
      "description": "A card-stack navigator that shows its actual card edges as a scrubbable stripe of thin lines — dragging it riffles cards past with quick flip-past kicks, and the same stripe doubles as the pagination readout.",
      "files": [
        {
          "path": "registry/core/carousel-card-riffle/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/carousel-card-riffle.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "stack",
          "cards",
          "carousel",
          "stepper",
          "pagination",
          "scrub",
          "slider",
          "onboarding",
          "queue"
        ],
        "instruction": "A card-stack navigator for stepping through a deck (onboarding steps, image sets, stacked notifications, a review queue) where the pagination indicator and the scrub control are the same element: an edge stripe running down the right side of the single visible top card, rendered as one 1px --border line per item spaced by a natural 3px pitch (1px line + 2px gap) that compresses down to a 1.5px floor when the deck is taller than the stripe's measured height — so the stripe's own length is literally the deck's physical thickness, and a 4-card deck reads visibly thinner than a 40-card one. The line at the current index is recolored --foreground (2px tall vs 1px for the rest) and transition-colors over 200ms, so scrubbing through cards is seen as that highlight relocating rather than a separate progress bar. RENDERING: only the top card is ever mounted — no off-stage card DOM — behind it two decorative translate-offset border/surface rectangles (opacity 60%/35%, aria-hidden) read as stacked depth. Those two depth layers shuffle along with every committed step at a subtler, proportionally-scaled amplitude: each kicks a smaller, opposite-leaning translate+rotate than the top card's own flip (reduced amplitude with depth, second layer smaller than the first) and eases back to its resting offset a beat after the layer in front of it, so a step reads as the whole deck visibly cascading and restacking, cleanly, not just the top card swapping out. INTERACTION: the stripe is role=\"slider\" (vertical, aria-valuemin 0, aria-valuemax count-1, aria-valuenow the current index, aria-valuetext \"Card N of total\"), pointer-draggable — clientY mapped linearly to a raw fractional index across the stripe's own measured height, rounded to the nearest card on every pointermove. Crossing to a new card kicks the top card: a synchronous double transform write (snap to perspective(720px) rotateY(5deg) translateX(∓11px) translateY(deterministic per-index jitter, ±2px via a sine hash so no two cards' kicks look identical), then next-frame transition back to identity over 220ms on a smooth ease-out cubic-bezier(0.22,1,0.36,1) with no overshoot) — a clean, deliberate flip-past, not a subtle nudge and not a bouncy glitch. Velocity gates which behavior plays: below 14 idx/s the kick fires per card (discrete stepping); at or above it, kicks are suppressed and a CSS filter blur (0–5px, ramped by velocity, capped at 46 idx/s) plays on the top card instead via a 140ms filter transition — fast scrubs blur the deck past, slow scrubs step it card by card. Release always settles: blur eases back to zero, and if the pointer let go mid-fast-pass without a kick ever having marked the final card, one settle kick fires so every release reads as an arrival, not a value glitching into place. A tap on the stripe (no drag) jumps straight to that position. The stripe's hit area and its drag-to-index mapping are sized to the card's own measured height (a full-height rail beside it), independent of how compressed the drawn line-pitch gets for a large deck — so precision doesn't collapse as more cards are added. Keyboard, once the stripe is focused: ArrowLeft/PageUp step back one card, ArrowRight/PageDown step forward one, Home/End jump to the first/last — every keyboard step plays the same kick as a slow drag step. Mouse wheel or trackpad scroll over the stripe also steps one card per tick (deltaY accumulated, cooldown-gated so a fast fling can't chain through several kicks at once). REDUCED MOTION: no perspective, no rotateY, no back-layer shuffle, no blur, ever — a card change instead snaps the top card's opacity to 0.4 and eases it back to 1 over 120ms, so the deck is always readable as a state change without vestibular motion. A11Y: the whole thing sits in one role=\"group\" (aria-label describing the deck) with a visually-hidden aria-live=\"polite\" span that announces \"Card N of total\" on every committed index change, independent of the stripe's own aria-valuetext. Differs from gallery-coverflow-caustic, which browses large cover-art cards laterally with drag momentum and a chromatic-aberration flourish on the focused card at rest in a wide gallery layout — carousel-card-riffle has exactly one visible card at a time and no momentum/coverflow geometry at all. Differs from drill-down-spines, whose collapsed levels are permanently visible, individually clickable, read-at-rest spines standing for navigation *history* you can jump back into at any depth — carousel-card-riffle's edge lines are not independently interactive targets and carry no history semantics, they're a single continuous scrub surface over a linear, ephemeral position in a flat deck. Zero dependencies, DOM+CSS only, no canvas — every color from --background/--foreground/--muted/--border/--surface/--accent tokens."
      }
    },
    {
      "name": "chart-bar-dither",
      "type": "registry:ui",
      "title": "Chart Bar Dither",
      "description": "Canvas bar chart in ordered-dither ink density, where hovering or focusing a bar pulls it into sharper resolution while the rest ease back to a coarse print.",
      "files": [
        {
          "path": "registry/core/chart-bar-dither/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chart-bar-dither.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "bar",
          "data-viz",
          "dither",
          "canvas",
          "ink"
        ],
        "instruction": "A dithered-chart-family bar chart, and the family's first CANVAS member: chart-bar-halftone and chart-donut-halftone render SVG patterns with a fixed set of 17 discrete ink levels, which is cheap but can only ever swap between those levels instantly; this component paints raw pixels every frame specifically so ink resolution itself can animate continuously, which is the real mechanic. Bars rest at a deliberately loose 7px ordered-dither cell (the family's shared 4x4 Bayer matrix, 17 levels 0-16, level tracking each bar's value exactly like chart-bar-halftone) — a rough proof print. Hovering or focusing a bar eases its own cell size down to 2px over roughly 360ms (an exponential lerp redrawn every frame), a literal focus-pull from rough print to resolved print, while every other bar simultaneously eases back up to the coarse 7px cell if it had been focused before. Density is still the only value channel: pure var(--foreground) ink on var(--background) paper, var(--accent) reserved for keyboard focus only, matching heatmap-year-stipple's precedent and chart-bar-halftone's own colour rule — the focus-pull changes resolution, never hue, which is what keeps this a dither-family member rather than a different chart entirely. Canvas tokens are read via getComputedStyle on the document root at mount and re-read through a MutationObserver on its class attribute, so both themes repaint correctly on toggle with no remount. Each bar has a real, transparent <button> hit target sized to its full slot (wider than the painted bar, per the >=24px hit-area rule) with an aria-label of \"label: value\" and roving tabindex — ArrowLeft/ArrowRight move focus between bars — positioned over the canvas so the interaction is genuinely keyboard-reachable, not just pointer-hover. Hover or focus also raises a small value+label tooltip above the bar. A VIEW TABLE toggle swaps the chart for a real HTML table with the same data, the accessibility twin for a continuous value scale. On mount, bars grow from the baseline over 480ms with a 45ms per-bar stagger, driven by the same rAF loop as the resolution lerp; prefers-reduced-motion renders every bar at full height and its resting coarse cell size in one static paint, and hover/focus still snaps the cell size directly (no animated lerp, no persistent rAF loop) so the interaction stays available without motion. Zero dependencies."
      }
    },
    {
      "name": "chart-bar-halftone",
      "type": "registry:ui",
      "title": "Chart Bar Halftone",
      "description": "Bar chart whose fills are an ordered-dither halftone instead of a flat color — ink density tracks value as a second, redundant channel to height.",
      "files": [
        {
          "path": "registry/core/chart-bar-halftone/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chart-bar-halftone.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "bar",
          "data-viz",
          "dither",
          "halftone",
          "svg",
          "ink"
        ],
        "instruction": "A dithered-chart-family bar chart: the first member alongside chart-donut-halftone, both stamped from the same 4x4 Bayer matrix already used by background-ascii-dither and ascii-engraving-contour elsewhere in this suite, so the aesthetic those two components established stays a single, shared constant rather than three independent reimplementations. Bars are thin (22px, under the 24px mark-spec cap), 4px-rounded at the data end and square at the baseline, and instead of a flat fill each bar is an SVG path filled with one of 17 precomputed patterns (levels 0-16): pattern N tiles the Bayer matrix's 16 cells at 4px each and inks every cell whose Bayer value is below N, so a bar's own ink density is a second, independently-legible encoding of its value — cover the bar's height and the halftone alone still tells you roughly how full it is. This is the family's colour decision made explicit: density is the only value channel here, pure var(--foreground) ink on var(--background) paper exactly like heatmap-year-stipple's precedent, with var(--accent) reserved for keyboard focus only, never for data. Every fill, gridline, and border is a CSS custom property referenced directly as an SVG presentation-attribute value (fill=\"var(--foreground)\"), so both themes repaint correctly on toggle with no getComputedStyle call and no MutationObserver — unlike the canvas-based components in this family, plain SVG lets the browser's own cascade do that work. Hairline gridlines mark 0/25/50/75/100% with no tick labels, because every bar already carries a direct value label at its tip (a rounded compact figure, 1.2K style) per the mark spec's own rule that axis ticks are dropped once every value is labeled; a category label sits below the baseline. Each bar has an invisible hit rectangle sized to its full slot (wider than the painted bar, per the >=24px hit-area rule) carrying role=button, an aria-label of \"label: value\", and roving tabindex — ArrowLeft/ArrowRight move focus between bars, and hover or focus both raise a small value+label tooltip positioned above the bar without gating any information (the same fact is already in the aria-label and in the table view). A VIEW TABLE toggle swaps the chart for a real HTML table with the same data, the accessibility twin required for a continuous value scale. On mount, bars grow from the baseline on a 480ms ease-out transform, staggered 45ms per bar; prefers-reduced-motion skips the stagger and renders bars at full height immediately. Zero dependencies, pure SVG."
      }
    },
    {
      "name": "chart-donut-halftone",
      "type": "registry:ui",
      "title": "Chart Donut Halftone",
      "description": "Donut chart for an ordinal size scale where wedge angle carries share and ordered-dither density carries tier order, instead of a second colour.",
      "files": [
        {
          "path": "registry/core/chart-donut-halftone/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chart-donut-halftone.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "donut",
          "pie",
          "data-viz",
          "dither",
          "halftone",
          "svg",
          "ink"
        ],
        "instruction": "The dithered-chart family's part-to-whole member, built deliberately as an exception to the family's own bar-first bias: the dataviz heuristic used across this registry says bar beats pie for almost every job, so this donut only exists because its segments are ORDINAL (a small fixed size-tier scale, S through XL) rather than nominal categories, and the two channels it uses encode two different facts. Wedge angle is the only magnitude channel, exactly as in any donut — it shows each tier's share of the whole. Wedge fill density is not a second copy of that share (which would be the textbook anti-pattern: a value-ramp re-encoding what the angle already shows); it encodes the tier's POSITION in the ordinal scale, sparsest ink at the low tier and solid ink at the high tier, the same 'one hue, monotone lightness steps' idea the dataviz skill prescribes for an ordinal ramp, translated into ink density. Density comes from the same 17-level Bayer-matrix pattern set as chart-bar-halftone — the 4x4 matrix already used by background-ascii-dither and ascii-engraving-contour, duplicated verbatim so both family members produce byte-identical density at a given level. Pure var(--foreground) ink on var(--background) paper; var(--accent) is reserved for keyboard focus only, matching heatmap-year-stipple's precedent and never appearing in the data itself. Each wedge is drawn as an SVG annular-sector path (96px outer radius, 56px inner) with a small angular inset between neighbors standing in for the family's usual 2px surface gap, is a real role=button element with roving tabindex (arrow keys cycle tiers) and an aria-label carrying its label, value and share percentage, and lifts 6px along its own bisector on hover or focus. The donut's center holds a live readout: the grand total and chart title at rest, swapping to the hovered or focused tier's own value and label — a hero-figure-style number in the sans face, never tabular-nums, per the mark spec. A legend beside the donut lists every tier with its own density swatch and share, present unconditionally since two or more series always carry one. A VIEW TABLE toggle swaps the donut for a real HTML table (tier, value, share) — the required accessibility twin, and the only place a reader needs the exact numbers rather than the visual read. Entrance is a 220ms scale/opacity settle staggered per wedge; prefers-reduced-motion drops the stagger and transitions outright. Zero dependencies, pure SVG, no getComputedStyle or theme observer needed since every fill is a CSS custom property referenced directly as a presentation-attribute value."
      }
    },
    {
      "name": "chart-funnel-stage-drop",
      "type": "registry:ui",
      "title": "Chart Funnel Stage Drop",
      "description": "Canvas funnel chart in ordered-dither ink density, where hovering or focusing a stage animates its own drop-off as ink particles falling into the next stage.",
      "files": [
        {
          "path": "registry/core/chart-funnel-stage-drop/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chart-funnel-stage-drop.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "funnel",
          "data-viz",
          "dither",
          "canvas",
          "ink"
        ],
        "instruction": "The dithered-chart family's funnel/stage-drop chart (backlog queue #13), a CANVAS member alongside chart-bar-dither and chart-line-dither. Each stage renders as a trapezoid tapering from its own share-of-top width to the next stage's width, filled with the family's shared ordered-dither ramp (4x4 Bayer matrix, 17 ink levels) at a density tracking that stage's own fraction of the top of the funnel — the usual redundant density channel, pure var(--foreground) ink on var(--background) paper, var(--accent) reserved for the hovered stage's outline and keyboard focus only. The mechanic unique to this family member: hovering or focusing a stage animates its own drop-off — the count lost before the next stage — as a seeded rain of ink particles trickling out of the stage's bottom edge, falling with a sinusoidal horizontal jitter through the gap toward the next stage's top edge, fading in over the first 12% of the fall and out over the last 25%. Particle count is proportional to the actual drop-off as a fraction of the funnel's top value (capped at 34), and the fall is a continuous, looping, gravity-style animation only while that stage is hovered or focused — nothing else in the registry visualizes a delta as literally falling ink. Both the particle offsets and jitter come from a mulberry32 PRNG seeded on the stage's own label, so hovering the same stage twice always reproduces the identical rain, never Math.random(). Each stage has a real, full-width <button> hit target spanning its row plus the gap beneath it, with an aria-label carrying its value, share of top, and (for every stage but the last) its drop-off count, and roving tabindex — ArrowUp/ArrowDown move focus between stages. Tokens are read via getComputedStyle on the document root at mount and re-read through a MutationObserver on its class attribute. On mount, stages unfurl from zero width over 420ms with a 60ms per-stage stagger; prefers-reduced-motion skips the stagger and, on hover or focus, renders one static frame of evenly spaced particles along the fall path instead of animating them, so the drop-off is still communicated without a persistent rAF loop. A VIEW TABLE toggle swaps the chart for a real HTML table listing each stage's value and drop-off. Zero dependencies."
      }
    },
    {
      "name": "chart-line-dither",
      "type": "registry:ui",
      "title": "Chart Line Dither",
      "description": "Canvas line/area chart in ordered-dither ink density, where a scrub cursor drags a local higher-resolution loupe through the fill as it moves.",
      "files": [
        {
          "path": "registry/core/chart-line-dither/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chart-line-dither.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "line",
          "area",
          "data-viz",
          "dither",
          "canvas",
          "ink"
        ],
        "instruction": "The dithered-chart family's line/area chart, a CANVAS sibling to chart-bar-dither. The area under the line rests at a uniform coarse 7px ordered-dither cell (the family's shared 4x4 Bayer matrix), each column's ink level tracking that column's own interpolated value between its two nearest data points — a second, redundant encoding of height exactly like chart-bar-dither's bars. The real mechanic is the scrub reticle: moving the pointer over the canvas (or moving keyboard focus between the data points, each a real role=option button inside a role=listbox with roving tabindex and Home/End support) drags a resolution field along with it — columns within about 70px of the reticle ease their own cell size down toward a fine 2px in a soft falloff, so a visibly sharper 'developed' band of print trails the cursor through an otherwise coarse plate, and eases back to coarse as the reticle moves away or the pointer leaves. The eased cursor position lives in a ref and the rAF loop is entirely self-sustaining while the target keeps moving, so continuous pointer motion never needs a React re-render to keep animating. The line itself is stroked as solid ink on top of its own dithered area for legibility. Resolution is the only thing the reticle changes — density still never encodes anything but each column's own value, and colour never encodes data at all: pure var(--foreground) ink on var(--background) paper, var(--accent) reserved for the active point marker and keyboard focus only, matching the rest of the family. Tokens are read via getComputedStyle on the document root at mount and re-read through a MutationObserver on its class attribute. On mount the chart reveals left-to-right via a growing clip over ~620ms; prefers-reduced-motion skips that reveal and renders the full plate immediately, and the scrub reticle snaps directly to its target with no eased trail and no persistent rAF loop, so the interaction stays available without motion. A live readout above the chart shows the scrubbed (or, at rest, most recent) point's value and label. A VIEW TABLE toggle swaps the chart for a real HTML table with the same data. Zero dependencies."
      }
    },
    {
      "name": "chart-radar-dither",
      "type": "registry:ui",
      "title": "Chart Radar Dither",
      "description": "Canvas radar/spider chart in ordered-dither ink density, swept by a continuously rotating radar line that locally resolves each axis wedge to sharper ink as it passes.",
      "files": [
        {
          "path": "registry/core/chart-radar-dither/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chart-radar-dither.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "radar",
          "spider",
          "data-viz",
          "dither",
          "canvas",
          "ink"
        ],
        "instruction": "The dithered-chart family's radar/spider chart, its fourth CANVAS member. The polygon's interior is tiled into one triangular wedge per axis (center to each pair of adjacent vertices), each filled with the family's shared ordered-dither ramp (4x4 Bayer matrix, 17 ink levels) at a density averaging that wedge's two adjacent axis fractions — the usual redundant channel alongside the polygon's own radius, which remains the chart's only true magnitude encoding. The mechanic unique to this family member: a literal radar sweep line rotates continuously around the center (one revolution per 6s) and, as it crosses a wedge, that wedge's ink cell eases from a coarse 6px down to a fine 2px, then decays back to coarse over a trailing 650ms echo once the sweep has moved on — every axis is visited and briefly sharpened in turn, entirely ambient. Hovering or focusing an axis (a real, circular <button> at its vertex, role=option inside a role=listbox with roving tabindex — ArrowLeft/Right or ArrowUp/Down cycle axes) independently pins that axis's two adjacent wedges to fine resolution regardless of where the sweep currently is, and raises a live value readout above the chart. Resolution is the only channel either mechanic touches; colour never encodes data — pure var(--foreground) ink on var(--background) paper, var(--accent) reserved for the sweep line itself, the hovered vertex, and keyboard focus, matching the rest of the family. Tokens are read via getComputedStyle on the document root at mount and re-read through a MutationObserver on its class attribute. On mount the polygon scales in from the center over 460ms; prefers-reduced-motion freezes the sweep line at a fixed bearing with no rotation and no persistent rAF loop, while hover/focus still snaps the touched wedges to fine resolution instantly, so the interaction stays available without motion. A VIEW TABLE toggle swaps the chart for a real HTML table listing each axis's value against its own max. Requires at least 3 axes. Zero dependencies."
      }
    },
    {
      "name": "chart-ridgeline-terrain",
      "type": "registry:ui",
      "title": "Chart Ridgeline Terrain",
      "description": "Unknown-Pleasures ridgeline chart whose live history recedes into scrolling ambient noise terrain, dented gravitationally by the cursor.",
      "files": [
        {
          "path": "registry/core/chart-ridgeline-terrain/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chart-ridgeline-terrain.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "data-viz",
          "ridgeline",
          "noise",
          "cursor",
          "ambient",
          "chart"
        ],
        "instruction": "An Unknown-Pleasures wireframe landscape on a DPR-aware Canvas 2D: ~96 columns by 40 rows of ridgeline polylines drawn back to front, each row stroked then filled below with the theme's surface/background token so nearer ridges occlude farther ones (painter's-algorithm ridgeline trick); row y-spacing eases quadratically so rows compress at the horizon and row width narrows ~35% toward the back. Height per vertex = ambient + data: ambient is 2-octave value noise scrolling toward the viewer at 0.06 u/s with 18px amplitude scaled up toward the front; data is a series prop (number[]) where sample age maps to row depth, so each new sample enters at the front row and the whole history glides one row back over 600ms with cubic-bezier(0.22,1,0.36,1) interpolation between fractional row offsets, the chart's history literally receding into ambient terrain. The cursor dents the mesh with a screen-space gaussian (sigma 90px, max depth 26px), plus a soft foreground-token glow bloomed at the dent center so the interaction reads clearly: dent amount lerps toward full at 0.12/frame while hovered and releases through an underdamped spring (k=70 s^-2, zeta=0.6) for one visible rebound. Stroke fades from 1.5px foreground-token color at the front row to 1px muted-token color at ~25% opacity at the horizon. Fill and stroke colors are resolved from CSS custom properties (--surface/--background, --foreground, --muted) at mount and re-derived via a MutationObserver on the document root's class attribute, so the terrain repaints correctly on theme toggle without a remount. Data and pointer live in refs; a single rAF loop is the only writer and pauses when the element leaves the viewport. Under prefers-reduced-motion: no noise scroll, no dent, a static render of the current series redrawn instantly on data change or theme change. With an empty series it idles as pure atmosphere; with no noise it reads as a strict chart."
      }
    },
    {
      "name": "chart-scatter-ascii-bin",
      "type": "registry:ui",
      "title": "Chart Scatter ASCII Bin",
      "description": "Two-dimensional scatter plot rendered as a glyph-density grid, with a pointer- or keyboard-driven brush that live re-bins the exact points under it and reads out the selection.",
      "files": [
        {
          "path": "registry/core/chart-scatter-ascii-bin/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chart-scatter-ascii-bin.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "scatter",
          "data-viz",
          "ascii",
          "canvas",
          "brush"
        ],
        "instruction": "A scatter plot rendered entirely as a glyph-density grid: every raw (x, y) sample is binned into a fixed 42x20 cell grid and each cell's character comes from the shared 10-step ASCII ramp ' .:-=+*#%@', scaled to that cell's point count relative to the densest cell — a static joint-distribution print, redundant with nothing else in the registry (histogram-live-grain tallies one dimension, stem-and-leaf-live keeps every digit, neither bins two continuous axes at once). The real mechanic is the brush: a circular region tracks the pointer over the plot, or an arrow-key-steerable focus target for keyboard users (Shift moves it in larger steps, Escape clears it), and on every move it re-bins the RAW points — not the grid cells — by exact Euclidean distance to the brush centre, so the live readout above the plot ('N pts · mean (x, y)') is an exact statistic over the selection, not a cell-count approximation. Every glyph currently touched by the brush relinks from var(--foreground) to var(--accent) ink in place, and a thin accent ring traces the brush radius — the only colour anywhere in the piece, reserved for the live interaction exactly as chart-bar-dither reserves accent for keyboard focus. Losing focus or pointer-leave clears the brush and every glyph returns to plain ink. Tokens are read via getComputedStyle at mount and re-read through a MutationObserver on the document root's class attribute, so both themes repaint correctly on toggle. On mount, the whole grid fades in over 320ms; prefers-reduced-motion renders it at full opacity immediately and the brush still updates instantly on interaction (no animated lerp, no persistent rAF loop once settled). Zero dependencies."
      }
    },
    {
      "name": "chart-waterfall-ascii-step",
      "type": "registry:ui",
      "title": "Chart Waterfall ASCII Step",
      "description": "ASCII-ramp waterfall chart where toggling a step excludes or restores its delta and every bar after it — plus the running total — visibly recomputes and re-animates, not just a hover tooltip.",
      "files": [
        {
          "path": "registry/core/chart-waterfall-ascii-step/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chart-waterfall-ascii-step.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "waterfall",
          "data-viz",
          "ascii",
          "canvas"
        ],
        "instruction": "The registry's first waterfall chart: a start bar, a chain of floating delta bars (each spanning the running cumulative before and after it), and a trailing running-total bar. Every bar fill uses the family's shared 10-step ASCII ramp ' .:-=+*#%@' tiled at a small cell size, with density tracking |delta| relative to the largest bar on the chart — redundant with bar height, exactly like chart-bar-dither's own colour rule (pure var(--foreground) ink, var(--accent) reserved for the hovered or keyboard-focused step). The real mechanic: every intermediate step (not the start, not the total) has a genuine toggle — a transparent hit button sized to its full slot, roving tabindex with ArrowLeft/ArrowRight moving focus between steps, aria-pressed reflecting whether the step is currently included. Activating it (click, or Enter/Space via keyboard) excludes that step's delta from the chain or restores it, and the recompute is rendered as real motion: every bar from that step onward, and the trailing total, eases its floating top/bottom to the newly recomputed cumulative over roughly 420ms, with the displayed numeric label tracking the same lerp — this is what makes it a genuine recompute rather than a tooltip. Hovering or focusing without activating shows a small tooltip with that step's exact contribution and its before/after cumulative, so the numbers are readable before committing to a change. A dashed connector line links each bar's resolved edge to the next bar's start so the chain reads as one continuous bridge even mid-animation. Tokens are read via getComputedStyle at mount and re-read through a MutationObserver on the document root's class attribute, so both themes repaint correctly on toggle with no remount. prefers-reduced-motion snaps every recompute to its final position/value in one paint instead of lerping, and the toggle itself remains fully functional. Zero dependencies."
      }
    },
    {
      "name": "checkbox-domino-run",
      "type": "registry:ui",
      "title": "Checkbox Domino Run",
      "description": "Select-all where the change propagates like a domino run: flip the master and a wavefront tips down the list, each row's checkbox flipping and shoving the next — click any row mid-run to halt the wave there, leaving the rest untouched.",
      "files": [
        {
          "path": "registry/core/checkbox-domino-run/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/checkbox-domino-run.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "checkbox",
          "select-all",
          "bulk-action",
          "list",
          "form",
          "micro-interaction"
        ],
        "instruction": "A select-all/toggle-all whose change propagates like a domino run rather than a cosmetic stagger: flipping the master checkbox (real input[type=checkbox], aria-controls pointing at the row list's id) releases an rAF-driven wavefront index that advances at a fixed 14 rows/sec. As the front reaches row i, that row's own checkbox commits its new checked state (a 120ms CSS transition on fill/border with a slight spring overshoot, cubic-bezier(0.34,1.56,0.64,1)) and the row's <li> takes a transient translateY(2px) lean toward row i+1, held for roughly one row-interval plus a 40ms overlap so consecutive leans read as continuous handed-off contact rather than independent bumps. A thin 2px --foreground tick travels down the list's left edge on the same rAF loop, marking the front's live position. State commits per row the instant the front passes it — nothing is staged or rolled back, so stopping the run costs nothing. INTERRUPTION IS FIRST-CLASS: every row stays a fully operable native checkbox for the run's entire duration; activating ANY row, by pointer or keyboard, or pressing Escape, halts the wave immediately. Rows the front already passed keep their committed state; rows beyond the front are simply never touched, standing exactly as they were — this is the 'all except a few' path, no undo needed because nothing past that point was ever touched. A11Y: a polite live region announces once at the start ('Enabling all…'/'Disabling all…') and once at the end or halt ('Enabled 34 of 40, stopped at row 35.'), throttled to those two moments only — not per row. Every row and the master are real input[type=checkbox] elements with native label association for their accessible name; the master's own live counter is aria-hidden decoration, not part of its name. REDUCED MOTION: there is no wave to catch, so interruption is replaced by a plain safety net — the master flips every row's state in a single frame (no wavefront, no lean, no tick) and a 5s Undo affordance appears beneath the list, reverting the whole batch if pressed in time. TOKENS: --foreground for the checkbox fill and the traveling tick, --border for hairlines between rows, --muted for secondary row text and the counter, --accent for the focus ring only. Pure DOM/CSS — no canvas, no SVG filters."
      }
    },
    {
      "name": "checkbox-ink-stroke",
      "type": "registry:ui",
      "title": "Checkbox Ink Stroke",
      "description": "A checkbox and its tri-state select-all cousin whose mark is inked on by a variable-width calligraphic pen stroke — thin entry, thick corner flick, a hair of overshoot at the tip — so checked, unchecked and indeterminate read as three distinct pen gestures.",
      "files": [
        {
          "path": "registry/core/checkbox-ink-stroke/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/checkbox-ink-stroke.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "checkbox",
          "tri-state",
          "indeterminate",
          "select-all",
          "form",
          "svg",
          "micro-interaction",
          "calligraphy"
        ],
        "instruction": "A checkbox primitive and its tri-state select-all/nested-settings cousin, built around one signature idea: the checkmark is not a static glyph, it is ink laid down by a pen with pressure. GEOMETRY: the check is a single filled ribbon path, built once at module load by offsetting a hand-placed spine polyline (entry -> corner -> tip, with a small geometric overshoot past the corner's natural stopping point baked into the tip coordinate) with a per-point half-width that starts thin at the entry, peaks widest right at the corner, and tapers back down through the flick to a hairline at the overshoot tip — a small offset-polyline helper (buildRibbon) averages each interior point's two adjacent segment normals so the taper reads smooth, and the sharp direction reversal at the corner naturally bulges on the convex side and pinches on the concave one, exactly like ink pooling as a pen changes direction. Indeterminate does not reuse or shrink this path — it draws a separate, simpler fat rounded dash, so a settings list scans as genuinely three different marks, not a check, a smaller check, and a minus. REVEAL: both ink shapes sit under a CSS clip-path (inset, fill-box, left-anchored) that transitions from fully clipped to fully revealed over ~220ms ease-out-expo, so the mark appears to draw itself left-to-right — entry, corner, tip — rather than fade or pop in. Unchecking transitions the same clip-path back to its clipped state, which reads as the ink retracting toward the entry edge; once retraction finishes, a small dot pops in at the entry point and fades over ~320ms, like the very last bead of ink lifting off the page. The box's own corner radius relaxes from 4px to 6px on check (so a list of checked settings scans faster as a group), with a brief ~4% scale settle-pop standing in for the flick's overshoot — restarted via an imperative class toggle rather than a remount, so an already-focused input never loses focus mid-interaction. STATE MODEL: value is `boolean | \"indeterminate\"`, controlled or uncontrolled (checked/defaultChecked/onCheckedChange), matching the common tri-state-checkbox convention — the underlying DOM value is always a real boolean; \"indeterminate\" is applied imperatively as the native `.indeterminate` DOM property (never an HTML attribute) each time the resolved state changes, which is what gives it real native AT announcement as a mixed state rather than a custom aria hack. A demo composes one plain labelled checkbox and one select-all header wired to three child checkboxes via ordinary parent state (all-on -> checked, all-off -> unchecked, mixed -> indeterminate), showing both the everyday control and the nested-settings pattern from the same primitive. A11Y: a real `input[type=checkbox]` carries checked, the imperative indeterminate property, and disabled, and owns every native keyboard and screen-reader interaction; it is visually hidden (opacity 0, sized to exactly cover the box) but stays in the tab order, keeps real focus, and is never aria-hidden or tabIndex -1. Passing `label` wraps the input in a native `<label>` for a real accessible name; omitting it falls back to an `aria-label` (default \"Checkbox\") so the control is never nameless. The SVG ink layer is aria-hidden throughout. Colors are token-only: ink is --foreground, the box border is --border in every state (states are told apart by the ink's shape, never by tinting the box), and --accent appears nowhere but the peer-focus-visible ring — no fill/background color ever marks a checked state. Both themes render identically in structure since nothing but token references are used. REDUCED MOTION: every transition and animation here (clip-path reveal/retraction, radius relax, settle-pop, entry dot) is disabled under prefers-reduced-motion, so the ink for the current state is simply present or absent immediately, still fully legible and usable — the dot is skipped entirely rather than left stranded on screen. Zero dependencies beyond React; no canvas, no requestAnimationFrame, no timers."
      }
    },
    {
      "name": "checkbox-tally-notch",
      "type": "registry:ui",
      "title": "Checkbox Tally Notch",
      "description": "Checkbox group rendered as a carpenter's tally: each check carves a notch stroke into its row and adds a stroke to an aggregate tally cluster in the header, where every fifth stroke slashes diagonally across its group of four. Strokes draw on with a dashoffset sweep and retract on uncheck.",
      "files": [
        {
          "path": "registry/core/checkbox-tally-notch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/checkbox-tally-notch.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "checkbox",
          "checklist",
          "form",
          "svg",
          "micro-interaction"
        ],
        "instruction": "Build a checkbox group styled as a carved tally board. STRUCTURE: a bordered bg-surface card (role=group with aria-label from the label prop) whose header carries the group label, an SVG tally cluster, and an aria-live 'checked/total' mono counter; below, one full-width row per item, each a real <button role=checkbox aria-checked> containing a 20px SVG notch mark, the label, and an optional mono hint line. STROKES: every stroke is an SVG <path pathLength={1}> with strokeDasharray 1, drawn by transitioning strokeDashoffset 1 -> 0 over 260ms (ease-out-back-ish cubic-bezier(0.22,1,0.36,1)) and retracted on uncheck with an ease-in curve; the row's notch is two cuts, a short down-stroke then the long up-stroke delayed 120ms so the carve reads as two motions. TALLY CLUSTER: render items.length strokes grouped in fives, four leaning verticals (6px pitch) then a diagonal slash across the group; stroke k is drawn when checkedCount > k, with a 30ms per-position stagger so a batch (e.g. defaultChecked at mount) carves in sequence. Jitter every stroke's lean, length, and endpoints with a deterministic mulberry32-style hash of its index so the carving looks hand-cut but renders byte-identical every mount (stable screenshots). INK: strokes stroke='var(--foreground)', unchecked notch boxes stroke='var(--border)', all chrome from the border/surface/muted tokens — zero hex in markup, both themes render. STATE: uncontrolled Set<string> seeded from defaultChecked, onChange fires with checked ids in items order; checked rows dim their label to text-muted. INTERACTION: rows are native buttons so Space/Enter toggle and Tab reaches every row; hover tints the row bg-border/40; focus-visible draws an inset 2px accent outline. Reduced motion: strokes and notches snap with transition:none. No canvas, no timers, no observers — the only state is the Set, everything else is CSS transitions on SVG attributes."
      }
    },
    {
      "name": "citation-grounding-gap",
      "type": "registry:ui",
      "title": "Citation Grounding Gap",
      "description": "A single SVG baseline under a RAG answer's prose — a solid 1px plank under every sourced sentence, a fading 3-dash gap wherever a claim has none — so grounding coverage reads at a glance instead of hover-hunting citation pills.",
      "files": [
        {
          "path": "registry/core/citation-grounding-gap/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/citation-grounding-gap.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "rag",
          "citation",
          "grounding",
          "trust",
          "svg",
          "dialog",
          "accessibility",
          "ai"
        ],
        "instruction": "A grounding-coverage primitive for a RAG (retrieval-augmented generation) answer, taking `sentences: { id, text, source?: { title, excerpt, url? } | null }[]` and rendering the prose as a run of real, focusable `<button>`s (one per sentence, unstyled — plain text in the flow) with a single absolutely-positioned SVG layer beneath them. Each sentence's own line box(es) are measured with `Range.getClientRects()` (a Range over the button's contents, not a synthetic straight strip), so a sentence that wraps across two lines gets a mark under each visual line, and the layer redraws on ResizeObserver, window resize and `document.fonts.ready`. A sourced sentence gets one solid 1px stroke (`--border`, `stroke=currentColor`) running the width of its line; an unsourced sentence gets three dashes fading out from each edge toward an open middle instead of one continuous line — the absence of grounding is drawn as loudly as the presence of it, which is the whole inversion this component is built around: coverage of the *entire* answer at a glance, not a pill that only ever marks where a citation happened to be attached. Clicking a solid plank (or pressing Enter on its sentence, since it's a real button) opens a bottom sheet — a native `<dialog>` via `showModal()`, so the focus trap, background inertness and Escape-to-close are the platform's, not hand-rolled — sliding up with a 420ms ease-out-expo transform and 16px top corner radius, showing that source's title, excerpt and hostname link; words in the excerpt that also appear in the claim are underlined in the exact same stroke style as the plank (`--border`, measured with a second `Range.getClientRects()` pass over the excerpt's own text node, so resizing the sheet keeps the underline glued to the words it marks, and merges adjacent matched words into one continuous underline rather than one dash per word). Clicking an open gap (or focusing an ungrounded sentence and pressing 'f') fires `findSupport(sentence)` — an injectable async resolver, defaulting to a small id-hashed demo stub — and announces the attempt and its outcome through a `role=status aria-live=polite` region ('Searching for support: \"...\"', then 'Support found: <title>.' or 'No source found for this claim.'); on success the gap's plank draws itself in left-to-right via `stroke-dashoffset` (a CSS `animation`, so it plays on mount with no rAF-timing race) rather than popping solid, then settles to the same resting stroke as any other grounded plank. Every sentence carries `aria-description` ('supported by <source title>' / 'no source found' / 'searching for support') so the coverage state is announced without a screen reader user needing to discover the drawing underneath — the SVG marks are `aria-hidden`, semantics live entirely on the text. The dialog restores focus to the sentence that opened it on any close path (Escape, backdrop click, close button), and locks body scroll for the duration since `showModal()` blocks pointer/focus reaching the page but not its scroll. Zero dependencies, DOM+SVG+CSS only, no canvas; every stroke and surface is a token (`--background --foreground --muted --border --accent`) with `--accent` appearing only on focus rings, never in a plank or gap. `prefers-reduced-motion` drops the sheet's slide-in, the plank's draw-in animation and the searching pulse — final state is identical, just not eased into. Distinct from citation-grounding-hatch: citation-grounding-hatch is a separate three-state (grounded/unsupported/contradicted) instrument strip below the prose with non-interactive sentence text; citation-grounding-gap is binary (grounded or gap), the mark lives directly under each sentence's own wrapped line rather than in a separate strip, the sentences themselves are the interactive controls, and an ungrounded gap can actively search for support rather than only explaining its absence. Distinct from citation-inline-card: citation-inline-card is a citation pill beside one claim opening a per-source stepper card; citation-grounding-gap makes the coverage of the whole answer the subject, with the gaps as loud as the citations."
      }
    },
    {
      "name": "citation-grounding-hatch",
      "type": "registry:ui",
      "title": "Citation Grounding Hatch",
      "description": "A per-sentence grounding trace under an AI answer: solid where a source backs the claim, a bare hairline where the model is inferring alone, diagonal hatch where a source contradicts it — shape-encoded, never color, with a static legend keying each mark to its meaning and a click-to-raise source panel.",
      "files": [
        {
          "path": "registry/core/citation-grounding-hatch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/citation-grounding-hatch.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "rag",
          "trust",
          "citation",
          "grounding",
          "trace",
          "chart",
          "accessibility",
          "ai"
        ],
        "instruction": "A grounding-coverage instrument for a RAG (retrieval-augmented generation) answer, answering 'how much of this is actually supported' at a glance instead of leaving that question to per-claim citation pills, which only ever show where a source WAS attached and silently skip the unsupported middle. Takes `sentences: { id, text, status: 'grounded'|'unsupported'|'contradicted', sourceId? }[]` and `sources: { id, label, excerpt, match?: [start,end] }[]`. Renders the answer prose with each sentence in a plain (non-interactive) span, and below it a single 3px core-sample track cut into one segment per sentence, each a real `<button>`: a thick solid bar for `grounded`, a bare 1px hairline for `unsupported` (the absence of grounding is drawn, not omitted — the track never just goes blank), and a diagonal 5-tooth hatch for `contradicted`. The three states are shape-and-pattern encoded — solid / absent / hatched — deliberately never color-coded, so the map still reads under grayscale or color-blindness; every color in the component is a token (`--background --foreground --muted --border --accent`), with `--accent` appearing only on focus rings and the two click-triggered highlights below, never as decoration. A static text legend ('Grounded' / 'Unsupported' / 'Contradicted', each beside its own always-rendered swatch of that exact mark) sits directly above the track so the solid/absent/hatched encoding is self-explanatory at rest, with no hover or click required to learn what a mark means. Segments draw in left-to-right via SVG `stroke-dashoffset` (each line carries `pathLength={1}`, so no pixel-length measurement is needed), starting ~300ms after a given sentence id first appears in the `sentences` array and staggering across a same-render batch when `streaming` is true — the trace visibly catches up to prose that's still arriving rather than snapping in whole. Hovering OR focusing a segment brightens its matching sentence in the prose above via a shared `data-sentence-id` (and vice versa — hovering the sentence text brightens its segment), so the sentence<->evidence link works for keyboard users exactly as it does for mouse users, not just on :hover. Clicking a `grounded` or `contradicted` segment raises an inline glass detail panel (a CSS `grid-template-rows` 0fr->1fr expand, not a portal — nothing to clip) showing that source's label, a Supports/Contradicts tag, and its excerpt with the matching span (`source.match`) underlined in accent; the same accent underline lands on the sentence itself at the same time, so the claim and its evidence highlight together, both ways. Clicking an `unsupported` segment does not raise the panel (there is nothing to show) but still selects it, surfacing a one-line explanation ('Unsupported — the model is inferring, no retrieved source backs this claim.') below the track. Every segment button carries a descriptive `aria-label` ('Sentence 3: supported by Source B' / 'Sentence 4: no source' / 'Sentence 5: contradicted by Source C'), and a visible summary line above the panel ('4 of 6 sentences grounded, 1 contradiction') is `aria-live=polite` and IS the sighted digest, not a separate hidden echo of it, so streamed updates announce themselves through the exact text on screen. Zero dependencies; DOM+SVG+CSS only, no canvas; `prefers-reduced-motion` drops every transition (the dash reveal, the highlight underline, the panel expand) via a scoped CSS media query — the final state is identical, just not eased into. Distinct from citation-inline-card: citation-inline-card attaches a citation pill beside one claim and opens a per-source stepper card; citation-grounding-hatch makes the coverage map itself the subject, one continuous instrument strip across the whole answer that foregrounds the unsupported gaps and contradictions citation pills never draw at all — reach for citation-inline-card to cite a specific claim, reach for citation-grounding-hatch to audit an entire answer's trustworthiness at a glance."
      }
    },
    {
      "name": "citation-inline-card",
      "type": "registry:ui",
      "title": "Citation Inline Card",
      "description": "An inline citation pill (hostname + source count, Geist Mono) that opens one bordered card with a title, excerpt and a 1/N stepper, instead of a popover per source.",
      "files": [
        {
          "path": "registry/core/citation-inline-card/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/citation-inline-card.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "citation",
          "footnote",
          "popover",
          "stepper",
          "reference",
          "accessibility",
          "typography"
        ],
        "instruction": "An inline citation primitive for attributing a claim to one or more sources, taking a single `sources` array prop (`{ id, title, excerpt, url }[]`). The trigger is one monochrome superscript pill sitting in the text flow — the hostname of the first source in Geist Mono, raised and shrunk like a footnote marker, with a small `·N` suffix appended when more than one source backs the claim. Activating it (click, or Enter/Space since it's a real button) opens a single bordered card rather than one popover per source: a header row shows a `1 / N` mono readout plus Previous/Next chevron buttons (only rendered when N>1) and a close button, and the body shows the current source's title, a 3-line-clamped excerpt, and a hostname link that opens the source in a new tab. Paging through sources updates a visually-hidden `role=status aria-live=polite` region with 'Source 2 of 3' on every step, so the position change is announced to assistive tech exactly like the visible '2 / 3' readout is to sighted users. The card is rendered through a portal into `document.body` and positioned with `position: fixed`, coordinates read from the trigger's `getBoundingClientRect()` on open, on every step (the excerpt's length can change the card's height) and on scroll/resize — not laid out as a plain `absolute` child of the trigger, which is what lets an ordinary ancestor with `overflow-hidden` (a bordered content panel, a card, a table cell) silently clip it. It also flips above the trigger when there isn't room below and clamps horizontally to stay inside the viewport. Fully keyboard-operable: opening moves focus into the card so Tab reaches Previous, Next and the source link in visible order; Left/Right arrow keys step through sources without leaving the keyboard; Escape closes the card and returns focus to the trigger from anywhere on the page, not only while focus happens to sit inside the card — a non-modal popover that survives Escape because focus drifted is a real keyboard trap. An outside pointerdown closes it, and so does focus leaving the card entirely (moving *within* the card, between the stepper buttons and the source link, is not a leave), so the card never sits floating over the prose while the reader is somewhere else. The card is `role=\"dialog\"` with `aria-labelledby` pointing at the title, so it always carries an accessible name. Hostnames are derived from each source's `url` via the `URL` constructor with a `www.` strip, falling back to a best-effort string strip for a non-absolute URL rather than throwing. Zero dependencies beyond react-dom's `createPortal`; no canvas; every color is a token (`--background --foreground --muted --border --accent`) so both themes render correctly, and `--accent` only shows up on hover/focus states, never as decoration. Under `prefers-reduced-motion` the card's entrance animation is dropped via a CSS media query — it still opens instantly and is fully operable, nothing is lost."
      }
    },
    {
      "name": "compare-crack-seam",
      "type": "registry:ui",
      "title": "Compare Crack Seam",
      "description": "Before/after comparison slider whose divider is a living Voronoi crack seam — fast drags spawn branching micro-fissures, slow drags heal them shut, and release settles with a 1px specular glint traveling the fracture.",
      "files": [
        {
          "path": "registry/core/compare-crack-seam/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/compare-crack-seam.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "compare",
          "slider",
          "before-after",
          "voronoi",
          "crack",
          "canvas",
          "clip-path",
          "physics",
          "image-diff"
        ],
        "instruction": "A before/after comparison slider whose divider is a living crack seam instead of a straight line. Two absolutely-stacked DOM layers (arbitrary before/after children) sit in a rounded-md border-border frame; the after layer is clipped by a CSS clip-path polygon that follows a jagged seam polyline, and a single DPR-aware canvas overlay draws the hairline crack strokes on top. Seam geometry: seed ~140 Poisson-disc points across the pane; at sample rows spaced ~0.55 cell-heights apart, find the nearest and second-nearest seeds to (handleX, y) and solve the point on their perpendicular bisector — an exact Voronoi cell wall from half-plane math — clamped to ±18px of the handle, giving x-offsets stored relative to the handle so the seam translates with it and only re-jags when the handle crosses into a new cell (nearest-seed id change), with new offsets eased in exponentially. Motion: handle x follows the pointer through a taut spring (k=170 s⁻², zeta=0.85, no wobble) integrated semi-implicitly in one direct-DOM rAF loop that writes the clip-path polygon string, the handle transform, and all canvas strokes with zero React state on the hot path and sleeps whenever settled. While dragging with |vx| > 600 px/s, spawn 2–4 micro-fissure branches (30–70px two-segment polylines, stroke alpha mapped to velocity, capped at 28) anchored to seam offsets; once speed drops below threshold each fissure retracts tip-first over 300ms ease-out. On pointer release the seam settles on the same spring, then a 1px specular dash ~56px long travels the full seam arc-length over 450ms. Seam strokes are token-derived, not hardcoded: the RGB is read off the resolved --foreground CSS custom property on mount and re-sampled via a MutationObserver on documentElement class changes, so the crack, its ghost pass, and the glint stay visible against both light and dark before/after content — 1px 0.32-alpha hairline plus a 0.75px-offset 0.5px 0.10-alpha ghost pass. Interaction: pointer capture on a 40px-wide full-height handle strip with a 28px glass grip chip (rounded-sm, border/background/shadow all color-mix'd off --foreground so the glass affordance reads on light and dark alike, backdrop-blur), a hidden native range input (sr-only, step 2) so arrow keys move 2% per press with a focus-visible accent outline echoed onto the grip, onChange fired on release/keyboard only, and a ResizeObserver rebuild preserving the split fraction. prefers-reduced-motion drops the canvas and spring entirely for a straight 1px bg-border divider with standard instant compare behavior. Zero dependencies."
      }
    },
    {
      "name": "confidence-logprob-hatch",
      "type": "registry:ui",
      "title": "Confidence Logprob Hatch",
      "description": "Hand-hatched underlines under low-confidence tokens, stroke density mapping straight to logprob doubt in three buckets, with a hover/focus popover naming what the model almost said instead.",
      "files": [
        {
          "path": "registry/core/confidence-logprob-hatch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/confidence-logprob-hatch.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "confidence",
          "uncertainty",
          "logprobs",
          "annotation",
          "popover",
          "typography",
          "accessibility",
          "chat"
        ],
        "instruction": "Render token-level model uncertainty as pencil hesitation rather than as blur, opacity or color coding on the words themselves. Takes one prop, `segments`: an ordered array mixing plain strings (confident prose, rendered completely as-is, no wrapper, no marks) with `DoubtToken` objects (`{ id, text, bucket, said, alternatives }`) for the spans the generation's own logprobs flagged as uncertain — from a single generation's logprobs, no resampling, cheap enough to run on every message. `bucket` is one of exactly three values — 'sparse', 'medium', 'cross-hatch' — each mapped to a diagonal-stroke SVG `<pattern>` of increasing density (a widely-spaced single pass, a tighter single pass, then two crossing passes), like a draftsman going over a line once, again, then crossed. All three patterns live in one shared `<defs>` block rendered once per `PencilHedge` instance (ids namespaced off `useId` so multiple instances on a page never collide), and each hatched span fills a 4px-tall `<rect>` underline with `url(#...)`, so density is the only encoding — a shape channel, not a hue, which is why it survives every theme and every color-vision deficiency untouched. The token text itself is always full `--foreground`, full weight, never faded or blurred, in every bucket including 'confident' (a plain string, not wrapped at all) — that full-opacity floor is the whole point: this differs from an ink-reveal/redaction-hold-reveal treatment, which fades or weights the text itself as a stream-arrival cue; here the words never move, only a thin mark underneath them does. Hovering or focusing a hatched span opens a small popover (rounded-md, the registry's 12px radius) anchored directly below it (flips above when the viewport doesn't have ~150px of room below) listing the model's own pick and its runner-ups as monospace rows — a 'said' row showing the emitted text and its probability to two decimals, then each alternative as a real `<button>` labeled 'almost' on the first row, showing its own text and probability. Clicking an alternative rewrites the span to that text in place with a brief settle transition (~220ms, eased, skipped outright under `prefers-reduced-motion` — the rewrite still happens, just instantly) and the span drops its hatching and role entirely, becoming indistinguishable from ordinary confident text — a resolved token carries no residual mark. The popover is a plain DOM child of the hatched span, not a portal: because `mouseenter`/`mouseleave` never fire when the pointer crosses from an element into its own descendant, moving the pointer from the word down into the popover's buttons never flickers it shut, and Tab reaches the buttons in ordinary document order with no focus-stealing needed to compensate for portal placement. Accessibility: each hatched span is `role=\"note\"` with `tabIndex=0` and an `aria-label` carrying the doubt — by default a terse 'Low confidence: <text>' so a message's default reading flow stays quiet, expanding on the caller's opt-in `verbose` prop to name every alternative with a rounded percentage ('Low confidence: March 12, alternatives March 21 24%, March 2 9%') — screen-reader verbosity is opt-in per message, not global. The popover itself is `role=\"dialog\"` with an `aria-label` naming the token it lists alternatives for, so it always has an accessible name whether it was opened by mouse or keyboard. Escape closes it from anywhere — span focused, a button inside it focused, or neither (a pure hover-open) — and returns focus to the triggering span, so it can never trap. An optional `daggerOnHighestDoubt` prop appends a decorative, `aria-hidden` superscript dagger to spans in the 'cross-hatch' bucket only, for sighted readers who want the single highest-doubt bucket flagged inline without re-reading hatch density. Every color is one of the five registry tokens (`--background --foreground --muted --border --accent`) referenced directly as CSS custom properties in the SVG pattern strokes and the DOM markup — no canvas anywhere in this component, so no `getComputedStyle` read is needed, and `--accent` only appears on focus rings, never as decoration."
      }
    },
    {
      "name": "confirm-dial-align",
      "type": "registry:ui",
      "title": "Confirm Dial Align",
      "description": "Destructive-action confirm gated on precision, not patience: rotate an SVG dial's notch to within 3deg of a fixed index, hold it there 400ms, and the dial snaps home and arms the confirm button — overshoot and it springs back like a slipping safe tumbler.",
      "files": [
        {
          "path": "registry/core/confirm-dial-align/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/confirm-dial-align.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "dial",
          "confirm",
          "destructive",
          "slider",
          "physics",
          "accessibility",
          "confirmation"
        ],
        "instruction": "A destructive-action confirm built on precision rather than patience or distance: a 176px SVG dial (an outer ring in --border, a rotating notch group, and a fixed index tick at 12 o'clock) must be turned by pointer drag, mouse wheel, or arrow keys until the notch sits within 3deg of the index and is HELD there for a 400ms dwell. Drag maps pointer-angle delta around the dial's center to rotation damped by a 0.6 friction factor (a raw 1:1 turn would feel twitchy at this tolerance); wheel and arrow keys step exactly 2deg via a visually-hidden native input[type=range] (role=slider, min -180 max 180, step 2) that also carries a live aria-valuetext like '12 degrees from unlocked' (or 'Unlocked' once armed) so screen-reader users get the same continuous feedback sighted users get from the notch's position. Leaving the 3deg band before the 400ms dwell completes is a failed catch: the dial springs out to a fixed +8deg stop under a stiff, lightly underdamped spring (k=380, zeta=0.55) — a deliberate 'give' read as a safe tumbler that almost caught and slipped, visually and mechanically distinct from a plain rubber-band snap-back. Completing the dwell cancels the timer, spring-snaps the notch to exact zero (k=260, zeta=0.85, one small overshoot), thickens both the notch and index ticks from --border to --foreground, and arms the destructive button, which is the component's one legitimate use of --accent (a border that appears only once the precision test is actually passed). Because a fine-motor precision gate is inherently hostile to motor-impaired users, an always-visible 'Type to confirm instead' native <details>/<summary> disclosure offers an equal alternate path — typing the configured word (default 'delete', case-insensitive) arms the button exactly like a successful dwell; after two failed dial catches that disclosure auto-expands (aria-live announces it) so the escape hatch is offered, not just available. Every alignment success or fallback match announces politely via aria-live ('Aligned, delete enabled.' / 'Confirmed, delete enabled.'). All physics run on direct-DOM refs (dial transform, slider value/aria-valuetext, spring loops) with React state reserved for the rare armed/confirmed/failed-count/fallback-open transitions. Once armed the dial locks (further drag/wheel/keys are ignored) since a caught tumbler has nothing left to prove. Under prefers-reduced-motion the slip and snap springs are replaced by instant jumps to the same end angles — no oscillation — while drag, wheel, and keyboard stepping behave identically either way."
      }
    },
    {
      "name": "confirm-hold-ink",
      "type": "registry:ui",
      "title": "Confirm Hold Ink",
      "description": "Press-and-hold destructive action — monochrome ink pours up from the press point, release early and it recoils.",
      "files": [
        {
          "path": "registry/core/confirm-hold-ink/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/confirm-hold-ink.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "button",
          "destructive",
          "micro-interaction",
          "confirmation",
          "canvas"
        ],
        "instruction": "A destructive-action button requiring press-and-hold, rendered as a canvas ink fill: holding pours monochrome ink (foreground token) up from the press point with a live rippling meniscus edge, subtle grain, and a pressure microshake as it nears the top; the label inverts over the ink via a difference blend. Releasing early (including pointercancel or blur) recoils the ink with an elastic damped spring; when the fill completes, the button pops with one spring overshoot and the label swaps. Works with pointer and keyboard (hold Space/Enter). Canvas colors derive from computed CSS tokens and re-derive on theme change; the rAF loop sleeps when settled and pauses offscreen; prefers-reduced-motion still holds-to-confirm but renders a plain clean fill with no waves, grain, shake, or pop."
      }
    },
    {
      "name": "confirm-slide-shatter",
      "type": "registry:ui",
      "title": "Confirm Slide Shatter",
      "description": "Frosted-glass confirm slider where drag distance drives Voronoi crack density — release early and the cracks heal on a spring; complete the travel and the pane explodes into tumbling glass shards revealing the confirmed state.",
      "files": [
        {
          "path": "registry/core/confirm-slide-shatter/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/confirm-slide-shatter.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "slider",
          "confirm",
          "destructive",
          "glass",
          "canvas",
          "voronoi",
          "shatter",
          "physics"
        ],
        "instruction": "A frosted-glass confirm slider where destruction IS the progress indicator: a 320×56 DOM track in the house glass recipe (light: bg-white/60 with a black/15 border; dark: bg-white/[0.06] with a white/10 border; backdrop-blur-xl, inset top specular, rounded-md) carries a DPR-aware canvas overlay and a 48px grabbable thumb, itself theme-split (black-tinted border/fill on light, white-tinted on dark) so it reads against the pane in both modes. On mount, seed ~48 Poisson-disc points across the track and compute exact Voronoi cells once via half-plane bisector clipping; deduped cell walls become crack polylines, each assigned a reveal threshold t from its distance to the thumb origin, and the same cells are reused later as shard polygons. Dragging (pointer capture, progress in a ref, thumb transform set in a rAF loop, zero React state on the hot path) strokes every polyline with t<p segment-by-segment from its near end — 1px rgba(255,255,255,0.35) hairline plus a 0.75px-offset 0.5px rgba(255,255,255,0.12) ghost for glass depth — and past p=0.6 the whole track shudders ±0.5px per frame. Release early and p springs back to 0 (stiffness 220, damping 26) so the cracks retract along the same t-mapping, healing for free. The thumb is a full role=\"slider\": Arrow/Up/Down keys nudge progress by 0.08, Home/End jump to the ends, and Enter/Space confirm outright, each keyboard step rendering once without waking the rAF loop. At p≥0.98 (by drag or key) the DOM glass hides instantly and the canvas flips to shard mode: each Voronoi cell tumbles outward from the thumb at 120–420px/s under 1800px/s² gravity with ±3rad/s spin, fading over 700ms; after ~900ms the canvas clears, revealing a bg-surface row with a check icon and mono CONFIRMED. The rAF loop sleeps whenever settled, and prefers-reduced-motion drops the canvas entirely for a plain slide with an instant confirmed swap."
      }
    },
    {
      "name": "consent-scope-redact",
      "type": "registry:ui",
      "title": "Consent Scope Redact",
      "description": "A consent surface that shows instead of tells: a live sentence templated from the user's own record, where each sharing scope is a real switch and toggling it sweeps a felt-pen redaction bar over exactly the tokens that scope withholds.",
      "files": [
        {
          "path": "registry/core/consent-scope-redact/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/consent-scope-redact.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "consent",
          "privacy",
          "redaction",
          "switch",
          "toggle",
          "scope",
          "form",
          "accessibility"
        ],
        "instruction": "`<LampBlack scopes={LampBlackScope[]} record={LampBlackPart[]} />` renders one consent card that is simultaneously the consent document and the UI: `record` is an ordered array of literal strings and scope-tagged tokens (`{ text, scope, kind? }`) templated into a running sentence — the user's own data, not placeholder copy — and `scopes` is the list of sharing switches that control it, each `{ id, label, hint?, defaultShared? }`. Every token whose `scope` matches a switch's `id` is wrapped in an inline-block span sized by its own text (never a fixed width, so the redaction can't lie about length); the wrapper stays in flow so the sentence never reflows regardless of which scopes are on or off. MECHANISM: on scope-off, an aria-hidden bar (background --foreground, 2px radius) scales X from 0 to 1 with transform-origin left over 260ms on a felt-pen ease (`cubic-bezier(0.16, 1, 0.3, 1)`, fast-start/dry-out-finish — quick to lay down ink, slow to finish the stroke) while the token's own text color transitions to transparent underneath on the same timing, keeping its box width so the line holds still; scope-on reverses the same transform back to scaleX(0). Each bar carries a small deterministic +/-0.3deg rotation (derived from its text and stagger index, not Math.random, so it's stable across renders) for a hand-laid, not-quite-ruler-straight feel. Tokens sharing one scope (e.g. an email and a phone number both gated by 'Contact info') stagger their bars 40ms apart in document order, so a multi-token scope reads as one dip of a pen crossing the line rather than a single instant mask. ACCESSIBILITY IS THE CONTRACT, not a coat of paint: each scope is a real `role=switch` button (`aria-checked` tracks shared/withheld, `aria-labelledby` points at its visible label — no separate aria-label duplicating it) so a screen reader user drives the exact same consent as a sighted one. A withheld token's real text is `aria-hidden` and is replaced in the accessibility tree by a visually-hidden `[withheld: kind]` string (kind defaults to the scope id, e.g. 'email', 'city', 'amount') — the content is never silently removed and never left readable through hidden pixels, it is explicitly announced as withheld. A plain-text summary line beneath the switches ('Sharing Contact info. Withholding Location, Payment amount.') states the same fact in prose and sits in an `aria-live=polite` region so toggling a switch is announced without the user having to re-read the whole card. REDUCED MOTION drops all four transitions (bar sweep, glyph color, switch thumb, switch track) to instant swaps via `prefers-reduced-motion: reduce` — every state is still reachable, nothing is stuck mid-animation. Colors are tokens only (`--background --foreground --muted --border --accent`), --accent appears solely as the switch's on-state track fill and the focus ring, never as decoration; DOM+CSS only, zero dependencies, no canvas. Differs from a chaff/noise-separation pattern: consent-scope-redact doesn't sort content from clutter, it binds each control to token-level redaction of one real record with the bar itself as the only feedback the user needs — the redacted sentence is the permission grant, not a description of one. Also distinct from redaction-hold-reveal (a single hold-to-peek redaction primitive with no switch behind it, meant for one inline reveal in running prose): consent-scope-redact is a multi-scope consent surface where several real switches each own a named group of tokens across one shared sentence."
      }
    },
    {
      "name": "contact-form-teletype",
      "type": "registry:ui",
      "title": "Contact Form Teletype",
      "description": "A contact form whose validation prints as a teletype receipt below it — each field's check accretes as a new line, and correcting a field strikes through the old line and reprints rather than clearing it.",
      "files": [
        {
          "path": "registry/core/contact-form-teletype/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/contact-form-teletype.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "form",
          "contact",
          "validation",
          "mono",
          "log",
          "accessibility",
          "teletype"
        ],
        "instruction": "<ContactFormTeletype fields? onSubmit? debounceMs? className?> renders a real <form> (Name, Email, Message by default, each with its own validate(value) returning an error string or null) and, below it, a role=log aria-live=polite receipt panel. 260ms after the last keystroke in a field (debounceMs), that field's validate runs and a new dot-leader line prints into the receipt — 'NAME .......... OK' or 'EMAIL .......... FAIL — not a valid address' — via a width transition stepped one character at a time (steps(N, end)) so it reads as typed rather than faded in. Submitting also validates and prints a line for every field regardless of debounce state. Nothing is ever erased: when a field is corrected and revalidated, its previous receipt line gets a strike-through and stays exactly where it was, and the new result prints as a fresh line below it — the receipt is a real cumulative log of the fill, not a status that overwrites itself. Colors: --foreground for a passing line, --error for a failing one; the form fields themselves use --border/--muted/--accent for resting/hover/focus, no hardcoded hex anywhere. Under prefers-reduced-motion every line renders at full width instantly with no step transition. Zero dependencies, no canvas."
      }
    },
    {
      "name": "container-box-drawing",
      "type": "registry:ui",
      "title": "Container Box Drawing",
      "description": "A container with real box-drawing chrome — an inline title breaking the top rule, upgrading to a double-line border character by character on hover or focus.",
      "files": [
        {
          "path": "registry/core/container-box-drawing/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/container-box-drawing.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "container",
          "ascii",
          "chrome",
          "hover",
          "box-drawing"
        ],
        "instruction": "A reusable panel whose border is composed of actual box-drawing characters (┌ ─ ┐ │ └ ┘) laid out in a monospace character grid, not a CSS border. It measures its own content box via ResizeObserver plus a hidden 1ch/1lh probe span (same font-mono, same explicit line-height) to convert pixel dimensions into an exact character count, so the border always tiles in whole characters regardless of what the children measure out to — arbitrary child height and width both just work. An optional `title` breaks the top rule like a fieldset legend, centered with the remaining rule characters split evenly left and right (the split shrinks rather than overflows the frame if the title is wider than the measured column count allows). On hover or focus-within (tracked via bubbling focus/blur, not a CSS :focus-within selector, so the double-line color transition and the character sweep stay driven by the same state) the border upgrades from the single-line glyph set to the double-line set (╔ ═ ╗ ║ ╚ ╝), one character at a time, sweeping clockwise starting at the top-left corner; leaving reverses the same sweep, starting the reversion at the same top-left corner rather than reversing direction. The perimeter's traversal order is built once by generating each side's DOM nodes directly in clockwise sequence — the bottom and left sides are generated right-to-left and bottom-to-top respectively and then visually restored to normal reading order with `flex-row-reverse`/`flex-col-reverse`, so the ref collection order (which the sweep indexes into) never has to be computed separately from the render order. A parallel color transition (border token to foreground token) rides alongside the glyph sweep. Direct-DOM: the sweep and the initial per-character paint both write `textContent` on refs inside a rAF loop, never React state per frame. Under prefers-reduced-motion the border snaps straight to its target single/double state with no sweep. Shortcut taken: a resize that lands mid-sweep replays the full sweep from its current hover/focus state rather than resuming progress, since font/content resizes mid-hover are a rare interaction."
      }
    },
    {
      "name": "context-compaction-river",
      "type": "registry:ui",
      "title": "Context Compaction River",
      "description": "Context compaction drawn as a meandering river: live turns are points on one curving channel, and a compacted run of turns necks off into a reopenable, re-injectable oxbow lake sitting 12px off the channel.",
      "files": [
        {
          "path": "registry/core/context-compaction-river/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/context-compaction-river.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "context-window",
          "compaction",
          "agent",
          "llm",
          "svg",
          "diagram",
          "history",
          "mono",
          "menu",
          "undo",
          "accessibility"
        ],
        "instruction": "OxbowTurn draws the live conversation as one gently meandering channel in a side rail: a controlled `items` prop is a single ordered sequence of turn entries and compaction entries, and every entry — of either kind — gets one row slot, spaced evenly down the rail. Turn entries are plotted as points on the channel (x from a deterministic two-harmonic sine so the curve reads as organic without any randomness or physics sim); a fresh Catmull-Rom-to-bezier pass through only the turn points draws the channel path. Compaction entries do not contribute a channel point at all, so when a run of turns gets folded into one compaction, the channel simply stops routing through that stretch — the surviving points ease toward their new, tighter spacing over roughly 300ms (a direct rAF loop writing cx/cy/d via setAttribute, no React state on the hot path, sleeping once settled, in the same idiom as this registry's other spring-driven diagrams), which is what 'compaction shortens the river' looks like here. The folded turns settle as a closed oxbow-lake shape — a small filled loop (`color-mix(in srgb, var(--border) 6%, transparent)` fill, `--border` stroke) plus a short connecting stub, both purely decorative and `aria-hidden` — 12px off the channel, animated in on a 300ms cubic-bezier(0.16,1,0.3,1) transform+opacity transition (ease-out-expo) the instant the compaction first appears in `items`. Carried on the lake is a real, always-focusable Geist Mono token-count chip (`{turnsFolded}↩ {tokenCount} tok`, never `aria-hidden`) — clicking it opens a small popover with the plain-language summary, a turns/summarized-at/token-count `<dl>`, and a `role=menu` containing one `role=menuitem`: 're-inject into live context'. Choosing it drifts the lake back onto the channel (reverse transition, ~260ms) before the `onReinject(id)` callback actually fires and the consumer's state update splices the folded turns back into `items` as live turns again — nothing is removed from view until the return trip has finished playing, and a brief stroke-width pulse on the channel marks the splice. The chip trigger is deliberately open-only: a second click while already open is a no-op (only Escape, an outside click, or choosing the menu item closes it), so a scripted 'click the first control' pass and a later 'now click this same control and expect it open' check never fight over the same toggle. Hovering or focusing the chip (without clicking) also reveals a small, token-styled tooltip above it — 'turns {a}–{b} pinched off the channel · re-injectable' — so a user can preview a branch point before committing to opening the full menu; it's wired via `aria-describedby` and hides once the menu is open. The drawing (channel, dots, lake shapes) is entirely `aria-hidden` and carries zero information a screen reader needs beyond redundancy — the actual facts live as plain, always-visible text in a real list right below it: 'Live context, {n} turns' plus one 'Compacted: turns {a}–{b}, summarized {when}' line per oxbow, and every compaction/re-injection additionally announces itself through an `aria-live=polite` region ('Compacted turns 3–9 into a 1,840-token summary.' / re-inject's own announcement). Each turn dot also gets a real, invisible interactive target overlaid on top of it (an unstyled focusable `<button>` positioned by the same rAF loop that eases the decorative circle, so the hit target tracks the dot through every re-layout instead of snapping ahead of it) — hovering or Tab-focusing it reveals a token-styled tooltip with that turn's preview (`aria-describedby`), and its accessible name is 'Turn {label}: {preview}' (or just 'Turn {label}' when no preview was supplied); the newest turn renders slightly larger and gently breathes to mark 'currently live'. `prefers-reduced-motion` replaces every transition with an instant fade-and-move (channel positions snap directly to target, no rAF loop runs at all, lake enter/exit is a 160ms opacity-only cross-fade already sitting at its settled 12px offset) — every state stays fully legible and functional, just static. Props: `items` (`OxbowTurnItem[]`, a union of `{kind:'turn', id, label, preview?}` and `{kind:'compaction', id, turns, summary, tokenCount, compactedAgo}` — fully controlled, the component holds no business state, only UI state for which popover is open and the enter/exit animation phase), `onReinject`, `ariaLabel`, `className`. Dragging an oxbow back onto the channel was considered as an enhancement on top of the menu path but deliberately not built for v1 — the menu's re-inject item is the only path, already fully keyboard operable, and the brief is explicit that drag must never be the *only* way in. Demo: an agent-session card seeded with 10 live turns and two already-settled oxbows (turns 3–9 and turns 14–16) so the resting screenshot already shows the channel visibly shortened at two separate folds, each carrying its own independently-openable chip — legible as multiple branches, not a one-off — plus ADD TURN (streams one more live turn), COMPACT OLDEST (folds the next 2–4 oldest live turns into a fresh oxbow and plays the pinch), and RESET SESSION controls."
      }
    },
    {
      "name": "context-menu-unfold",
      "type": "registry:ui",
      "title": "Context Menu Unfold",
      "description": "A context menu that unfolds like a pocket knife — items swing out from a hinged spine as slim blades, staggered from folded to open, with a hairline edge highlight while swinging; submenus unfold as a second, smaller knife from a blade's tip.",
      "files": [
        {
          "path": "registry/core/context-menu-unfold/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/context-menu-unfold.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "context-menu",
          "menu",
          "dropdown",
          "right-click",
          "keyboard-navigation",
          "submenu",
          "accessibility"
        ],
        "instruction": "Build a context menu whose open/close animation reads as a pocket knife unfolding, usable both via right-click on a wrapped region and via a visible, accessible trigger button (both open the identical menu — the button exists because a right-click affordance alone is unreachable by keyboard, and it is also what any automated or assistive-tech interaction should use). On open, compute an anchor point (the pointer's clientX/clientY for a right-click, or the trigger button's own bounding rect for a button click) and decide a fold 'side' (right or left) by checking whether the menu's fixed width would overflow the right edge of the viewport from that anchor — if it would, anchor the panel so its right edge sits at the pointer instead of its left edge, and mirror the fold direction. The menu panel itself is position:fixed, its top clamped so it never overflows the bottom of the viewport either. Each menu item is a real <button role=\"menuitem\"> (transform-box: fill-box; transform-origin: left) that starts, on mount, folded flat (`rotate(80deg)` or `rotate(-80deg)` depending on fold side, so it always rotates 'away from' the anchor edge like a real blade tucked behind the spine) and opacity 0. Immediately after mount (post-reflow, so the folded starting pose actually commits as a distinct frame), transition every item to `rotate(0deg)` / opacity 1 over ~260ms with an ease-out-expo curve, but stagger each item's transition-delay by its index × ~38ms so they swing open one after another rather than all at once — a real fan-of-blades opening. Simultaneously transition the item's border-color from --border to --foreground over the same timing (the 'hairline edge highlight while swinging'), then — once that item's own swing finishes — transition the border back down to --border over a short ~120ms settle, so the highlight is specifically tied to motion, not a resting state. Closing reverses this: stagger from the LAST item to the first (reverse order reads as 'folding back up'), shorter duration (~150ms) and tighter stagger (~22ms), transform back to the folded rotation and opacity 0 — actually unmount the panel only after the full staggered close duration elapses, never abruptly. All of this is one-shot ref-driven inline style writes per open/close event (computed once, CSS interpolates) — never a continuous rAF loop, since nothing here needs per-frame updates. A submenu (items with a `submenu` array) is the exact same component rendered recursively: hovering or pressing ArrowRight/Enter on an item that has one computes an anchor at that blade's own tip (its bounding rect) and mounts a second, smaller-feeling knife there with the identical fold-in/fold-out mechanic — closing it (Escape, ArrowLeft context, or selecting one of its items) returns focus to the parent blade that opened it. Full menu keyboard semantics on the top-level panel (role=menu, aria-label): ArrowDown/ArrowUp move a roving focus among enabled (non-disabled) items only, wrapping at both ends; Home/End jump to the first/last enabled item; typeahead buffers printable keystrokes (reset after ~700ms of inactivity) and jumps focus to the first item whose label starts with the buffered string, case-insensitively; Escape folds the whole menu closed and returns focus to whatever opened it (the right-click surface conceptually, or literally the trigger button); Tab also closes the menu (without stealing focus) since a menu should never trap Tab out of the document flow. A pointerdown anywhere outside any open menu panel (top-level or submenu) closes the whole stack. Hovering an item (mouse only) slides it 2px further out along its own resting angle (0deg, so a plain translateX, signed by fold side) as a lightweight 'lean into it' cue, separate from the open/close swing. prefers-reduced-motion: items appear directly in their open pose (opacity 1, rotate(0deg)) with no folded starting frame, no stagger, no swing — closing likewise just disappears, both still fully functional and instant rather than absent. Distinct from menu-nested-trays (telescoping horizontal trays) and dropdown-drape (a cloth/verlet-simulated dropdown panel): this is a radial-fold context-menu primitive, not a scrollable tray or a physics panel. Zero dependencies, no canvas."
      }
    },
    {
      "name": "context-prompt-shims",
      "type": "registry:ui",
      "title": "Context Prompt Shims",
      "description": "Prompt composition rendered as machinist shims fitted into a fixed-height gap: each section's row height maps to its real token count, order is drag/keyboard reorderable with FLIP transforms, and an over-budget insert compresses the stack, bounces back to the tray, and surfaces a truncation remedy instead of an error string.",
      "files": [
        {
          "path": "registry/core/context-prompt-shims/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/context-prompt-shims.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "prompt-engineering",
          "context-window",
          "tokens",
          "llm",
          "agent",
          "listbox",
          "drag-reorder",
          "keyboard",
          "mono",
          "dashboard",
          "accessibility"
        ],
        "instruction": "A prompt-composition tool built on the conceit of fitting machinist shims into a gap of fixed height (the context window). PROPS: budget (total token capacity, default 8000), sections (initial stack, top-to-bottom = prompt order, each { id, label, tokens, truncatable?, minTokens? }), candidates (tray items not yet part of the prompt, same shape), scale (px per token, default 0.06), className, aria-label. The component owns its own stack/tray state internally (uncontrolled). RENDERING: a bordered frame of height budget*scale contains, from top, a dashed-bottom clearance region whose height is the literal unclaimed px (budget minus the sum of committed row heights) — an honest empty space, never a percentage bar — followed by the stack itself: one row per section, height = max(8px, tokens*scale) so a genuinely small section renders as a true hairline (sub-16px rows drop to a smaller type size so the label still reads instead of clipping), bg-muted/8 fill, a 1px border-border top edge, and a font-mono tabular-nums token count right-aligned. Stack order IS prompt order. REORDER: each row is a role=option inside a role=listbox with aria-roledescription 'prompt section'; a decorative aria-hidden grip drives pointer drag (live-swap by comparing the dragged row's center against the other rows' rects captured at drag start), and every reorder — drag or keyboard — plays a FLIP transform on the affected rows (measure before, invert, animate to identity over 320ms cubic-bezier(0.22,1,0.36,1)). KEYBOARD: Tab reaches the active option (roving tabindex); arrows alone move focus between options; Space grabs the focused option (announced 'label grabbed, position X of N, N tokens'), arrows then reorder it in place (announced 'label, position X of N, N tokens, N remaining' after every move), Space again drops it, Escape cancels a grab without moving it. INSERT: tray items render with their token count and an Insert button; inserting a candidate that fits plays a FLIP push-down of everything below its insertion point (just ahead of the final turn) plus a brief fade/slide-in for the new row, and clearance shrinks honestly. THE SIGNATURE MOMENT — an over-budget insert: the whole stack elastically compresses (scaleY 0.98, transform-origin bottom, 120ms) and un-compresses, the tray candidate that failed plays a spring-back bounce in place (it never left the tray), the largest truncatable row's top edge pulses once in --foreground, and a persistent 'Truncation suggestion' panel opens below the frame naming the exact overflow and the suggested section, with 'Trim to fit' (reduces that section by precisely the overflow amount) and 'Trim 30%' actions plus a dismiss control — 'context length exceeded' becomes a visible non-fit with a concrete remedy, not an error string. A role=alert live region announces the same overflow amount and suggested section the instant it happens, matching the visual. ACCESSIBILITY: every move (keyboard or drag-drop) announces position and running total through an aria-live=polite region; the over-budget rejection additionally fires role=alert; the truncation panel is a labeled role=group with real buttons. REDUCED MOTION: the FLIP transforms, compress/bounce/pulse animations, and insert fade all drop entirely; the truncation panel still opens (a static, unanimated badge marks the suggested row) and the rejection is still announced — every state stays reachable and legible, just without motion. No canvas, no SVG paint loop — plain DOM/CSS, tokens only."
      }
    },
    {
      "name": "copy-button-travel",
      "type": "registry:ui",
      "title": "Copy Button Travel",
      "description": "Copy-to-clipboard button whose confirmation shows what got copied: a duplicate of the source text visibly peels off the line and travels the real distance to the clipboard glyph before fading, arriving as the glyph gives a small catch-bounce and the label ticks to \"Copied\".",
      "files": [
        {
          "path": "registry/core/copy-button-travel/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/copy-button-travel.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "button",
          "copy",
          "clipboard",
          "micro-interaction",
          "form"
        ],
        "instruction": "A copy-to-clipboard row: a monospace source string (truncated with an ellipsis if it overflows) next to a real <button> that copies it. On click, writeText runs (with a hidden-textarea/execCommand fallback for insecure or sandboxed contexts, wrapped so it never throws), the button's aria-label already names exactly what it copies (\"Copy {description}\", or a truncated preview of the value if no description is given), and the visible label ticks from \"Copy\" to \"Copied\" in Geist Mono. Simultaneously — unless prefers-reduced-motion or the source is scrolled off-screen, in which case it's a plain label swap only — a clone of the source text (its first ~24 characters, same font, --foreground at 85% opacity so it visibly separates from the still-present original) is measured via getBoundingClientRect and absolutely positioned exactly over the source, then animated over 720ms ease-out-expo along the actual measured distance to the button's clipboard glyph (82% of the real gap, plus a slight upward peel) rather than a fixed token nudge — a duplicate spawned 250px from the glyph needs to visibly cover that ground, not crawl 12px and vanish. It holds near-full opacity through the first half of the flight, then fades to 0 and tightens its letter-spacing over the second half as it nears the glyph, reading as a carbon copy lifting off the page and traveling into the button. The clipboard glyph itself gives one small spring-eased settle bounce (scale 1 -> 1.06 -> 1), timed via its own delay to fire exactly as the duplicate arrives, as if it just received it. Success is also announced through a visually-hidden aria-live=polite status region (\"Copied to clipboard\") so screen reader users get the confirmation without seeing the animation; the flying duplicate and the icon are aria-hidden, and nothing about the outcome exists only in the animation — the label swap and the live region both carry it on their own. The button reverts to its resting label and icon after 2 seconds. It is a single native <button>: Enter and Space behave exactly like a click, with no custom key handling required, and a visible focus ring in --accent. This is deliberately the inverse of a stamp-style confirmation: nothing presses down or leaves an impression here, a duplicate of the real content visibly rises up and travels the real distance to where it landed."
      }
    },
    {
      "name": "copy-field-crimp",
      "type": "registry:ui",
      "title": "Copy Field Crimp",
      "description": "A copyable value field whose confirmation is typographic — letter-spacing crimps shut in a wave from the clicked character, then a check scales into the icon slot and holds. No toast, no green flash.",
      "files": [
        {
          "path": "registry/core/copy-field-crimp/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/copy-field-crimp.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "copy",
          "clipboard",
          "api-key",
          "field",
          "micro-interaction",
          "typography",
          "confirmation"
        ],
        "instruction": "A copyable value field rendered in Geist Mono inside a 6px-radius bordered button that fills as the whole control: clicking anywhere on the value (or activating it by keyboard) copies the real value via navigator.clipboard with an execCommand fallback for insecure contexts, and the confirmation happens inside the type itself. Each character is its own span carrying a `--cc-delay` custom property; on copy, an origin character index is computed — nearest to the pointer's clientX for a mouse click, or the string's start for keyboard activation (detected via the native click event's `detail === 0`) — and every character's delay becomes its distance from that origin times ~18ms. A CSS keyframe then plays per character at its own delay: letter-spacing animates 0 to -0.06em and back to 0 with a hair of positive overshoot on the way out, so the string visibly crimps shut in a wave radiating from the touched point and springs back open, like a wire getting a ferrule. Characters remount on every activation (keyed by an incrementing run id) so the wave always replays from its start on repeat clicks. Once the wave finishes sweeping outward, a small inline SVG check stroked in --foreground scales into the icon slot (which idles as a muted copy glyph) with an ease-out-expo curve, holds for 1.2s, then fades back to the idle glyph — the icon slot is reserved space, so nothing shifts layout and there is no toast or color flash anywhere else on screen. A visually-hidden aria-live=polite region announces \"Copied\" on each successful copy (reset and re-set per activation so repeat copies re-announce); the button's accessible name is an explicit aria-label derived from the optional `label` prop (default \"Copy value\"), never from the value itself, and both the character spans and icons are aria-hidden, so a `masked` field (bullets in place of characters, real value still copied) never leaks the secret into the accessible name either way. prefers-reduced-motion drops the letter-spacing wave and shortens the icon's fade/scale transitions to near-instant, but the check still appears and holds — the confirmation still reads, it just isn't animated."
      }
    },
    {
      "name": "countdown-vapor-digits",
      "type": "registry:ui",
      "title": "Countdown Vapor Digits",
      "description": "Live countdown where each digit change is a phase transition: the outgoing digit sublimates into grains on curl-noise wind while the incoming digit condenses from the same cloud.",
      "files": [
        {
          "path": "registry/core/countdown-vapor-digits/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/countdown-vapor-digits.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "particles",
          "countdown",
          "typography",
          "noise",
          "spring",
          "time"
        ],
        "instruction": "A live HH MM SS countdown rendered as monochrome grains on a DPR-aware Canvas 2D over a real <time> element (aria-live=off, tabular Geist Sans 600, visually transparent) so screen readers get truth and reduced motion gets a static visible countdown with the canvas hidden. After document.fonts.ready, rasterize digits 0-9 once on an offscreen canvas sized to one tabular digit cell, sampling alpha > 128 at a 3px stride, capped at 2500 grains per digit, stored in Float32Array pools (x, y, vx, vy, hx, hy). Each second boundary a setTimeout tick updates the DOM digits and, for every changed column, runs a phase transition: outgoing grains move to a vapor pool with a 600-900ms per-grain lifespan, driven by wind equal to the curl of a 2-octave value-noise field (field scale 0.008, per-grain speed 40-90 px/s, biased upward) with alpha fading over life; incoming grains spawn from the departing cloud region with slight jitter and spring to their new glyph homes with k=90 s^-2, zeta=0.55, per-frame drag 0.92, dt clamped to 32ms, so a full swap reads settled in ~800ms. Grains draw as 2x2 fillRect in the live computed foreground color (read via getComputedStyle on a digit element, re-read on documentElement class/data-theme mutation and OS color-scheme change so a live theme toggle repaints correctly with one forced wake) with alpha 0.5 + 0.5*min(1, speed/500); vapor multiplies in its life fade. The rAF loop wakes on each tick and sleeps when every column has grains within 0.5px of home with |v| < 2 and no live vapor; settled columns (hours, minutes) skip physics entirely and draw one flat pass, so seconds churn constantly while hours stay typographically calm. Canvas overdraws the layout box (110px headroom) so rising vapor never clips. Props: targetDate (Date | string | epoch ms, default 24h out), labels row in font-mono text-muted beneath the groups. Zero dependencies."
      }
    },
    {
      "name": "counter-carry-ripple",
      "type": "registry:ui",
      "title": "Counter Carry Ripple",
      "description": "Display-only numeric readout where change animates the way arithmetic works: only the digits that differ move, and a carry (199 -> 200) visibly ripples right-to-left across the columns it touches.",
      "files": [
        {
          "path": "registry/core/counter-carry-ripple/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/counter-carry-ripple.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "counter",
          "readout",
          "typography",
          "data-viz",
          "spring",
          "accessibility",
          "live"
        ],
        "instruction": "A live numeric readout for `value` (request counts, balances, queue depth, follower counts) whose motion encodes the STRUCTURE of the change, not just that a change happened. Digits are diffed by place value (ones, tens, hundreds, ...) against the previous value, never by string index or position — that's what keeps a value that grows a digit (999 -> 1000) lining every existing column up with its own place instead of shoving the whole row sideways. Each column that differs plays a two-row vertical flip (old glyph slides out, new glyph slides in from the opposite side, direction set once per update by whether the value went up or down) inside an overflow-hidden cell; columns that don't differ never move at all. When several columns change together in a carry chain, they don't fire at once: delay is computed per column as 40ms * (its place distance from the rightmost changed place), so the flip visibly travels leftward across exactly the columns the carry touched — a lone digit change (no carry) still fires at zero delay, only a real chain ripples. A brand-new leading column (999 -> 1000) grows its own width in from 0 rather than appearing in one frame; a column that stops existing (1000 -> 999) shrinks the same way before it leaves the DOM — both are FLIP-style width/opacity transitions local to that one cell, so the container never jump-resizes. MOTION: translateY/width/opacity transitions on cubic-bezier(0.34,1.56,0.64,1) over 380ms, chosen to approximate the requested spring (mass 1, stiffness 300, zeta~0.6): that pair's 2nd-order step response overshoots ~9.5% and settles in ~380ms, which on a ~1.15em row reads as the ~2px hop. Staggering rides the browser's own transition-delay per column — no per-column JS timers, just one batch timeout that flips every settled column back to a plain static span once the slowest one finishes. Optional `decimals` prop fixes a count of fractional digits (for balances); the decimal point is a static separator, never diffed or animated itself. Negative values get a static leading minus sign, also outside the diff. ACCESSIBILITY: the digit row is aria-hidden (it's a decorative rendering of the value); the actual accessible content is a visually-hidden text node inside a role=status aria-live=polite aria-atomic=true wrapper, holding a locale-formatted string of the CURRENT value — updates to it are debounced 500ms so a burst of rapid ticks announces only the settled result once, not every intermediate tick. REDUCED MOTION: prefers-reduced-motion swaps straight to the new digits with no flip, no ripple, no width transition — the value is still fully correct and readable. Zero dependencies, pure DOM + CSS transforms, no canvas."
      }
    },
    {
      "name": "date-picker-moon",
      "type": "registry:ui",
      "title": "Date Picker Moon",
      "description": "Masked date input whose popover calendar doubles as a moon-phase almanac: every day cell carries a canvas moon with a real synodic terminator, the field icon tracks the focused date's phase live, and committing runs an eclipse transit across the chosen numeral.",
      "files": [
        {
          "path": "registry/core/date-picker-moon/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/date-picker-moon.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "date-picker",
          "input",
          "calendar",
          "canvas",
          "form",
          "moon",
          "keyboard",
          "micro-interaction"
        ],
        "instruction": "Build a date input whose popover calendar doubles as a moon-phase almanac. RENDER: masked text input (MM/DD/YYYY, digits auto-slashed, full validation with revert-on-blur) plus a popover role=grid month calendar of 42 real button cells (role=gridcell, roving tabindex); ONE canvas absolutely positioned behind the whole grid draws all 42 moon glyphs (14px discs) at cell centers measured via getBoundingClientRect after a double-rAF layout settle, plus a 20px canvas in the input's trailing icon slot; both canvases get EXPLICIT style.width/height and dpr-clamped backing stores (a canvas is a replaced element — inset never sizes it). PHASE MATH: mean synodic month 29.530588853 d, reference new moon epoch 2000-01-06 18:14 UTC, sampled at local noon; f = ((days since epoch) mod syn)/syn; illuminated fraction k = (1 - cos(2*pi*f))/2; disc stroked --border, lit region --foreground at 85% alpha, terminator drawn as a semi-ellipse with x-radius r*|cos(2*pi*f)|, lit limb right while waxing (f < 0.5) and left while waning, ellipse sweep direction flipping crescent vs gibbous. MOTION: trailing icon crossfades 150ms (dual-alpha draw, cleared per frame) whenever focus movement or typing changes the previewed date; committing from the grid fires an eclipse transit — a foreground corona behind the chosen numeral occluded by an opaque background-ink shadow disc sweeping left-to-right over 420ms ease-out-expo, clipped to the cell, with the entire grid canvas cleared and all 42 moons redrawn every frame (zero accumulation), then the popover closes and focus returns to the input; month changes re-measure and re-lay glyphs after cell layout settles (double rAF + ResizeObserver). rAF runs only during crossfade/transit and is parked offscreen (IntersectionObserver) and on document.hidden — the static almanac costs zero frames. INTERACTION: Arrows move a day/week, Home/End week edges, PageUp/Down month, Shift+PageUp/Down year (view follows focus), Enter/click commits, Esc or outside pointerdown closes (Esc returns focus), ArrowDown in the input opens; aria-selected and aria-current=date on cells; phase name (e.g. Waxing gibbous) in every cell's accessible label and as visible helper text with percent illumination under the field. REDUCED MOTION: no transit or crossfade, instant selection and close; static moons stay as information. TOKENS: all canvas ink parsed from getComputedStyle(--foreground/--border/--background) at mount and re-derived via MutationObserver on documentElement class; hover/focus affordances token-relative (foreground-alpha tints, accent focus rings). Guard zero-size grids before measuring, and tear down every rAF, timer, observer, and document listener on unmount."
      }
    },
    {
      "name": "date-range-tape",
      "type": "registry:ui",
      "title": "Date Range Tape",
      "description": "Date-range calendar where the selection is a tape measure: clicking anchors the hook, moving extends a mono-ticked strip across the grid with a live night count on its free end, and confirming locks it in place or Escape recoils it to zero.",
      "files": [
        {
          "path": "registry/core/date-range-tape/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/date-range-tape.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "date-picker",
          "calendar",
          "range",
          "grid",
          "keyboard",
          "measurement",
          "form",
          "micro-interaction"
        ],
        "instruction": "Build an inline (non-popover) month calendar, role=grid of 42 real button gridcells, where selecting a range renders as a physical tape measure pulled across the cells rather than a highlighted region. MECHANISM: pointerdown or Enter on a cell drops the hook and anchors the range (status 'anchored'); pointer move, drag, hover, or arrow-key focus movement extends a segmented strip toward the current cell. Segments are computed from real measured cell rects (getBoundingClientRect), not index math, so the strip wraps cleanly into one run per week row when the range spans multiple weeks — each row's run stops at that row's rightmost/leftmost matched cell and a new run picks up on the next row. Tick marks are a repeating-linear-gradient in var(--border) sized to the measured cell pitch (not a fixed px value) so it stays aligned to the grid at any width. A chip on the strip's free end prints the live count ('N nights'), font Geist Mono, tabular-nums; every time the count changes it replays a 60ms number-settle (slide + fade from a removed/re-added CSS class, restarted via a forced reflow so rapid crossings don't stack). A second pointerup/Enter on a different cell confirms and locks the tape (status 'confirmed', strip switches to accent-tinted styling); Escape, or a second press on the anchor cell itself, retracts the tape to zero width on a 260ms ease-out-expo scaleX(0) (transform-origin at the anchor end) and clears the selection. GEOMETRY: one ResizeObserver on the grid wrapper recomputes segments on any layout change (font load, container resize); the tape overlay is a single aria-hidden, pointer-events-none absolutely-positioned layer above the grid so it never intercepts clicks meant for cells beneath it — except the free-end count chip, which re-enables pointer-events on itself alone since it never overlaps a cell. INTERACTION: pointerdown on any cell starts or moves the sequence; a document-level pointerup listener (via refs mirroring latest state, so it never closes over stale closures) resolves drag-release anywhere on screen against the cell under the pointer. Arrow keys move grid focus (roving tabindex, one tabbable cell); Home/End jump to week edges, PageUp/PageDown change month, Shift+PageUp/PageDown change year; while anchored, arrow movement also extends the tape and updates an aria-live=polite region with 'Through July 30, 14 nights'; Enter sets the anchor or confirms; Escape retracts. Cells carry aria-selected for the current range and aria-current=date for today; the grid's aria-describedby points at a persistent readout line above it that mirrors state in plain text (idle / anchored-choose-end / confirmed-with-count) for anyone not watching the tape visually. REDUCED MOTION: the retraction scaleX transition and the number-settle animation are both dropped via prefers-reduced-motion, but the count and locked state still update instantly and correctly — nothing is lost, only the motion. TOKENS: strip fill/border, ticks, and hook use var(--border) and var(--foreground) at low alpha while armed, switching to var(--accent) at low alpha only once confirmed (interaction-only accent use, never the resting/armed color). No canvas — DOM divs plus a CSS repeating-linear-gradient background for the ticks. Controlled (value + onValueChange, value: {start, end} | null) or uncontrolled API; re-anchoring from a confirmed state clears the prior range first. DEMO: a cabin-booking card whose footer price and 'Reserve' button (disabled until a range is confirmed) derive live from the selected range."
      }
    },
    {
      "name": "device-mockup-ascii-screen",
      "type": "registry:ui",
      "title": "Device Mockup ASCII Screen",
      "description": "A phone-frame device mockup whose screen is a live ASCII/scanline raster; dragging the handle skews the frame while the raster independently resamples its glyph rows to the same tilt.",
      "files": [
        {
          "path": "registry/core/device-mockup-ascii-screen/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/device-mockup-ascii-screen.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "device-mockup",
          "phone",
          "ascii",
          "canvas",
          "drag",
          "scanline"
        ],
        "instruction": "Build <DeviceMockupAsciiScreen className?>: a phone-shaped frame (rounded-[28px], thick border, a small pill notch) sized 220x440, its screen a <canvas> rendering a fake app UI (header bar, a row of stat blocks, ragged paragraph lines) as literal ASCII glyphs (# = - . by density) at an 8px cell pitch, plus a slow scanline sweep (one bright accent-colored row cycling top to bottom every ~3.2s, paused at its mid-position under prefers-reduced-motion). THE MECHANIC: a real drag handle below the frame (role=button, tabIndex 0, aria-label 'Drag or use arrow keys to tilt the device') applies a CSS `skew(Xdeg, Ydeg)` transform DIRECTLY to the frame element as the pointer moves (drag range +-120px maps to +-14deg on each axis, arrow keys nudge 3deg per press as a keyboard-accessible equivalent) — that transform tilts the bezel, the notch, everything. Independently, the canvas draw pass reads the CURRENT live skewX angle every frame and resamples its OWN glyph raster to match: each text row's horizontal draw position is offset by `tan(skewX) * cellHeight * (row - centerRow)`, recomputed on every paint while dragging, so the screen's ASCII content visibly shears in sync with the frame's tilt through its own procedural redraw rather than being a flat texture merely wrapped inside the frame's CSS transform (drop it and the content would never re-align with a tilted bezel). On release the skew eases back to 0 over ~420ms (eased, one rAF loop) unless reduced motion is on, in which case it snaps directly to 0. This stays a deliberate 2D skew — no perspective/projection math, no three, no WebGL. Colors are token-only: canvas ink var(--foreground), scanline var(--accent), background var(--background)/var(--border) for the bezel, read via getComputedStyle and re-read on a MutationObserver watching the root's class/style attributes. The drag handle is the only interactive control — a real role=button with a visible hover and focus-visible state distinct from rest — everything else (frame, canvas) is aria-hidden decoration. Zero dependencies."
      }
    },
    {
      "name": "diagram-ascii-flow",
      "type": "registry:ui",
      "title": "Diagram ASCII Flow",
      "description": "A box-drawing flowchart whose connectors are a real orthogonal router: dragging a node re-routes every edge touching it live, cell by cell, recomputing junction glyphs (┼ ├ ┤ ┬ ┴) from scratch as paths cross. Clicking a node selects it and reports its live connection count; arrow keys move the focused node without a pointer.",
      "files": [
        {
          "path": "registry/core/diagram-ascii-flow/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/diagram-ascii-flow.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "diagram",
          "flowchart",
          "graph",
          "ascii",
          "box-drawing",
          "drag",
          "keyboard-navigation"
        ],
        "instruction": "Build a flowchart from `nodes` (FlowNode[] — `{id, label}`) and `edges` (FlowEdge[] — `{from, to}` referencing node ids), laid out on a fixed 26x13 monospace character grid. Every node keeps a live `{col, row}` position in React state (seeded from a small default layout for the demo's 6-node/7-edge dataset) and is rendered as an 8x3-cell bordered box (`border-border` at rest) with a real `<button data-diagram-node={id}>` overlay covering it — never a div with a click handler. THE ROUTER (the actual mechanic): for every edge, `edgePath(src, dst)` decides orientation by whether the two node rectangles overlap in column range — if they don't, it's a side-by-side route exiting the source's right or left mid-edge and entering the destination's opposite side; if they do (one stacked over the other), it's a top/bottom route off the vertical mid-edge instead. Either way the path is a single-bend Manhattan Z (straight through if the exit and entry already share a row/column) built as an explicit polyline of grid points. `tracePolyline` walks every edge's polyline one grid cell at a time and OR-accumulates a 4-bit direction mask (N/E/S/W) per cell into one shared `Map<'x,y', mask>` covering ALL edges at once — so where two edges' paths cross or merge, that cell's mask naturally picks up bits from both. A 16-entry lookup table turns every possible mask into its box-drawing character: pure straight runs (`│`/`─`), the four corners (`┌┐└┘`), the three-way junctions (`├┤┬┴`), and the four-way crossing (`┼`) — recomputed as one `useMemo` over ALL node positions, so any node move re-derives the entire glyph grid from a blank map rather than patching stale cells. Grid glyphs render as `aria-hidden` monospace text rows UNDER the node boxes (edges are routed to stop one gap-cell short of every node's border, so they never draw through a node's interior). DRAG: pointerdown on a node's button captures the pointer and records the start client position plus the node's start cell; pointermove converts client-pixel delta into a whole-cell delta (`round(dx/CELL_W)`, `round(dy/CELL_H)`) and, if the resulting cell is inside grid bounds and does not overlap another node's rectangle (a 1-cell buffer, simple AABB check — colliding moves are silently rejected, keeping the last valid position), commits the new `{col, row}`, which re-triggers the router memo so every touched connector visibly re-routes on the same frame — this live re-route during the drag, not a static diagram, is the entire point of the component. SELECT: a pointerup that never moved the node (or a keyboard Enter/Space, which never touches the pointer handlers) fires the node's `onClick`, which toggles a `selectedId` and renders a `data-diagram-selection` readout below the canvas naming the node and its live edge count (`edges.filter(e => e.from === id || e.to === id).length`); a genuine drag sets a ref flag that suppresses the trailing synthetic click browsers fire after a captured pointerup, so dragging a node never also toggles its selection. Clicking empty canvas space, or Escape, clears the selection. KEYBOARD: every node button is a real, always-tabbable, individually-labeled control (`aria-label` states the node name, its connection count, and both available actions) — arrow keys nudge the focused node by exactly one grid cell through the same clamp+collision path the pointer drag uses, so keyboard users reach the identical re-routing mechanic with no pointer at all. Selected node's border and label switch to `--accent`; hover alone (unselected) brightens border toward `--accent` at 40% mix and label to `--foreground` — visibly distinct from both rest and selection. Tokens only (`--background --foreground --muted --border --accent`, read via `getComputedStyle` on the document root, re-read on a `MutationObserver` watching its class attribute) — no hardcoded hex, correct in both themes. No rAF loop: every recompute is a direct response to a pointer or keyboard event, so there is nothing to gate behind `prefers-reduced-motion` beyond the interaction itself, which stays fully available. Pure DOM + CSS, zero dependencies."
      }
    },
    {
      "name": "dial-moire",
      "type": "registry:ui",
      "title": "Dial Moire",
      "description": "Weighted rotary knob tuned like a radio: counter-rotating line gratings shimmer with moire interference, and a hidden word only resolves at the detent.",
      "files": [
        {
          "path": "registry/core/dial-moire/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/dial-moire.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "moire",
          "dial",
          "knob",
          "physics",
          "interference",
          "hidden-message",
          "detent"
        ],
        "instruction": "A weighted rotary knob whose feedback channel is optical interference, built on a DPR-clamped (max 2) Canvas 2D field. Two fine-line gratings (1px strokes at ~0.5 alpha derived live from the --foreground token, 6px pitch, re-derived via a MutationObserver on theme-class changes so both themes stay legible): grating A is fixed and carries a hidden message as a locally phase-inverted region — the message glyphs are rasterized offscreen to an alpha mask (hero-gravity-well sampling pattern) and inside the mask A's line phase is offset by half a pitch, so the word only gains contrast when the overlay aligns; grating B is an oversized pre-rendered square that rotates with the dial angle through a radio-vernier gear ratio (~0.15), its phase chosen so its lines coincide with A's background lines at zero rotation. Both gratings are pre-rendered to offscreen canvases and composited per frame. Knob: 160px disc, bg-surface with border-border ring, a 24-tick ring layer rotating rigidly with the dial, a needle that lags the ring through a second-order spring (k=120 s^-2, zeta=0.75, weighted Inertial-Dial feel), and a font-mono ALIGNMENT percent readout below. Interaction: pointer-capture drag maps pointer angle about the knob center to ring angle with smoothed angular velocity; on release omega integrates under friction decay 3.5 s^-1 (flick spins, coasts, settles). Detent: within +-4 deg of alignment a snap spring (k=200 s^-2, zeta=0.9) pulls to exact zero with one tiny overshoot and the readout ticks to 100%. A single direct-DOM rAF loop is the sole writer (canvas draw, ring/needle transforms, readout text, slider value — no React state on the hot path) and sleeps when |omega| < 0.02 rad/s and needle error < 0.05 deg. Keyboard a11y via a visually hidden range input (arrows step 2 deg, aria-valuetext reports percent aligned, short detent grace so steps can escape the band). Under prefers-reduced-motion the dial renders the aligned state statically with the word legible and steps instantly with no loop. Zero dependencies."
      }
    },
    {
      "name": "dialog-emerge",
      "type": "registry:ui",
      "title": "Dialog Emerge",
      "description": "A native <dialog> modal that grows out of the control that opened it and returns into it on close.",
      "files": [
        {
          "path": "registry/core/dialog-emerge/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/dialog-emerge.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "dialog",
          "modal",
          "overlay",
          "flip",
          "accessibility"
        ],
        "instruction": "A controlled modal built on the native <dialog> element opened with showModal(), so the focus trap, background inertness to pointer and focus, Escape-to-close, top-layer stacking and ::backdrop come from the platform rather than a hand-rolled implementation. One piece of background inertness the platform does not provide is scroll: showModal() leaves the page behind the dialog free to scroll, so that is hand-rolled — body scroll is locked for as long as the dialog is open (captured/restored against whatever inline style was already on the body, not clobbered) and released on close and on unmount even mid-close-animation, with the vanishing scrollbar's width compensated as body padding so the page doesn't jump width when the lock engages. The entrance is a FLIP morph: the trigger element's bounding rect is measured at open time (never at mount — it moves on scroll and resize), inverted into a single translate+scale transform on the dialog, and played out to identity, with the corner radius counter-scaled so it reads constant and the panel content fading in slightly late so real text never squashes through a small scale. Closing runs the same transform backwards so the panel returns into the trigger; the close is timer-driven rather than transitionend-driven, so a backgrounded tab can never strand an un-closed dialog, and a re-open landing mid-close cancels the pending timer and resets styles first. With no triggerRef (opened programmatically) it falls back to a centered scale-in. Escape's `cancel` event is intercepted so the return-to-origin still plays, backdrop clicks dismiss (opt-out via dismissOnBackdrop), and any close the component didn't originate is reported back through onOpenChange. The backdrop is a token-derived dim with no blur — ink-over-paper in light, the background token's own near-black in dark — set via a custom property that ::backdrop inherits from the dialog. prefers-reduced-motion skips the morph and the scrim fade entirely and opens instantly, fully functional."
      }
    },
    {
      "name": "diff-unified-viewer",
      "type": "registry:ui",
      "title": "Diff Unified Viewer",
      "description": "A line-addressable unified-diff viewer, split or unified, that swaps red/green blocks for a thin left-rail marker and a muted gutter glyph — colorblind-safe by construction — plus a widget slot for attaching an AI annotation or review comment to any line address.",
      "files": [
        {
          "path": "registry/core/diff-unified-viewer/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/diff-unified-viewer.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "diff",
          "code-review",
          "developer-tools",
          "typography",
          "accessibility",
          "split-view",
          "unified-diff"
        ],
        "instruction": "Renders a unified-diff string (as produced by `git diff` / `diff -u`) as either a single-column unified view or a two-pane split view, toggled by an accessible role=radiogroup labeled 'Diff view' with role=radio Unified/Split buttons (arrow keys move focus and selection, aria-checked reflects state, the change is also announced through a visually-hidden aria-live=polite region). PARSING: a hand-written state machine reads file headers (`--- a/path`, `+++ b/path`, with git's `a/`/`b/` prefixes and any trailing tab-separated timestamp stripped, `/dev/null` recognized for adds/deletes), `diff --git`/`index`/`rename from|to`/`similarity index`/`old mode`/`new mode`/`Binary files` as meta lines, hunk headers matched by `/^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@(.*)$/` seeding running old/new line counters, `+`/`-`/` ` markers for add/del/context, and `\\ No newline at end of file`. It never throws: an unparsable `@@` line is flagged in place with a warning icon and the following lines still render (numberless) rather than aborting; input with no recognizable hunk at all falls back to a flat, numberless raw-line block behind a visible 'doesn't look like a unified diff' notice, never a crash or a silently wrong parse. COLOR: every added/removed line gets a 3px left-rail marker plus a muted +/− gutter glyph instead of red/green fill — additions get a solid bar (bg-foreground/60), deletions get a 135° diagonal hairline hatch (repeating-linear-gradient read off var(--foreground)) — so the add/del distinction survives grayscale and every common colorblindness type; a very light bg-foreground/[0.025] wash marks changed rows without relying on hue at all. Every diff line (in both modes; in split mode the whole two-pane row via a group-hover) lifts to a slightly stronger bg-foreground/[0.05] on hover so the line the pointer is over reads as targetable for the annotation/widget slot — a tokened wash, never a hue. Line numbers (both old and new, in unified mode; the relevant one only, in split mode) are font-mono tabular-nums. SPLIT MODE: consecutive deletions and consecutive additions within a hunk are zipped into aligned rows (shorter side padded with an empty cell), context lines mirror onto both sides — the standard side-by-side diff algorithm, not a naive two-array print. WIDGET SLOT: an optional `widgets` prop, `Record<string, ReactNode>`, keyed by a stable per-line address — `n<newLineNo>` for any line that exists in the new file (context or addition), `o<oldLineNo>` for a line that only exists in the old file (a pure deletion). A matched widget renders as a full-width row directly beneath that line, in both view modes, so an agent's review note, a teammate's comment, or a suggested-edit card with its own controls (e.g. an 'Apply suggestion' button) sits inline at the exact line it concerns rather than in a disconnected side panel. Widget insertion points also serve as hard segment breaks for the two-pane grid in split mode, which is what keeps the left/right row counts — and therefore the visual alignment — exact even when only one side of a pair carries a widget. PERFORMANCE: parsing and row construction are pure, memoized on `diff`/mode/`widgets`, and rendering is a flat list of plain DOM rows (CSS Grid per row for column alignment) with no virtualization needed for a several-hundred-line diff. REDUCED MOTION: the only animation is the mode toggle's color transition, dropped entirely via motion-reduce:transition-none; nothing else moves, so there's nothing to lose. Every color is a token (--background/--foreground/--muted/--border/--accent, plus --surface for header/hunk/widget bands); --accent appears only on the toggle's focus ring, never as decoration. Zero dependencies, no canvas."
      }
    },
    {
      "name": "dock-cursor-magnify",
      "type": "registry:ui",
      "title": "Dock Cursor Magnify",
      "description": "Cursor-proximity magnification row with Gaussian falloff — macOS-dock physics for any children.",
      "files": [
        {
          "path": "registry/core/dock-cursor-magnify/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/dock-cursor-magnify.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "nav",
          "cursor",
          "dock",
          "micro-interaction"
        ],
        "instruction": "A horizontal dock that magnifies its children based on cursor proximity with a Gaussian distance falloff: items scale up and lift as the pointer nears, neighbors swell progressively less, everything settles back with interruptible easing on leave. Direct-DOM rAF loop with no React state on the hot path, transform-origin bottom, static under prefers-reduced-motion."
      }
    },
    {
      "name": "dock-shelf-lean",
      "type": "registry:ui",
      "title": "Dock Shelf Lean",
      "description": "A dock/toolbar where items lean like books on a shelf — hover or focus straightens one and cants its neighbors away, reorder is a pick-up/move/drop cycle, nothing ever scales.",
      "files": [
        {
          "path": "registry/core/dock-shelf-lean/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/dock-shelf-lean.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "nav",
          "dock",
          "toolbar",
          "reorder",
          "micro-interaction",
          "accessibility"
        ],
        "instruction": "Build a horizontal toolbar/dock whose items behave like books leaned on a shelf rather than icons that magnify. Each item is a flex child with transform-origin at its own bottom edge. Hovering an item, or focusing it via :focus-visible (identical trigger for both — a mouse-click-produced focus that isn't :focus-visible does not count), straightens that item to rotate(0deg) translateY(-2px) and opens a 4px total breathing gap beside it (2px added to each side via margin, transitioning together with the rotation). Every other item cants away from the active one: immediate neighbors rotate ±3.5deg (sign away from the active item — left neighbors tip left, right neighbors tip right), and the amplitude decays with distance as 3.5deg * 0.5^(distance-1), so only the closest one or two neighbors visibly move. Nothing ever changes scale — emphasis is entirely rotation and spacing, so every hit target stays exactly where it was except for that 2px lift, which is what makes this the quieter, denser-row-friendly alternative to a magnifying dock. Reorder is a grab/move/release cycle: clicking an item (or pressing Space/Enter on the focused item — a native <button> turns both into the same click event) toggles it 'picked': aria-pressed and a data-picked attribute go true, its lift grows to 6px, its border picks up --accent (the only place accent color appears, since this is a genuine armed interaction state), and its neighbors cant a little further out as if the book were pulled proud of the shelf. While an item is picked, ArrowLeft/ArrowRight move it one slot at a time instead of moving focus; each move commits the array swap and plays a FLIP animation — the moved item and the sibling it swapped past both invert to their pre-swap screen position on an untransformed wrapper element (kept separate from the lean transform, which lives on the button itself) and spring to their new rest position over 420ms on a cubic-bezier(0.34,1.56,0.64,1) curve, which is what reads as the remaining books slumping into the freed gap and then pushing apart again to receive the picked one back. Escape while picked drops it in place. Native HTML5 drag-and-drop (draggable buttons, dragstart/dragover/dragend) drives the identical move() for pointer users, dragging one item across its neighbors to reorder live. The row is a single roving tabindex stop (role=toolbar, aria-orientation=horizontal): Tab reaches whichever item last had focus (or the first, initially); ArrowLeft/ArrowRight move focus among items when nothing is picked. A visually hidden aria-live=polite region announces every pick, drop and move by name and new 1-based position, e.g. 'Moved Terminal to position 3'. prefers-reduced-motion removes the rotation, lift, gap-margin and FLIP animation entirely — the focused/hovered item instead gets a flat --border background highlight, with the picked accent-bordered state kept (an armed state should still be visible, just without the motion that produces it). Colors only from --background, --foreground, --muted, --border, --surface and --accent (accent for the picked/armed state only, never decorative); DOM and CSS only, no canvas, zero dependencies."
      }
    },
    {
      "name": "drawer-counterweight",
      "type": "registry:ui",
      "title": "Drawer Counterweight",
      "description": "Side drawer hung on a counterweight like a sash window: a thin track on the drawer's leading edge carries a weight pill that travels opposite the drag at -0.6x, and wherever it sits at release — before or past the tick — decides whether a spring pulls the drawer shut or bottoms the weight out and holds it open.",
      "files": [
        {
          "path": "registry/core/drawer-counterweight/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/drawer-counterweight.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "drawer",
          "sheet",
          "dialog",
          "physics",
          "spring",
          "drag",
          "panel",
          "nav"
        ],
        "instruction": "Side drawer built on the native <dialog> element (showModal()/close()) for a free focus trap, Escape-as-cancel, top-layer stacking, and background inertness — only the entrance/exit motion and the counterweight are hand-rolled. The drawer is a right-anchored panel (position:fixed, full height, width min(24rem,88vw)) translated via a CSS custom property (--sash-tx, written every frame by a direct-DOM rAF loop, zero React state on the hot path) from 0 (open) to its own measured width (closed, off-screen). Its leading (left) edge carries an aria-hidden vertical rail: a 200px 1px --border track, a fixed tick mark at the track's midpoint (the balance point), and a 24px --foreground pill (--sash-wy custom property) that during a drag moves at -0.6x the drawer's pixel delta and in the opposite sense — drag the drawer toward closed and the weight rises toward the tick; drag it open and the weight falls past it. On pointerup, release velocity is computed from the mean pointer delta over the last 80ms of samples: above 500px/s the fling direction decides outright, otherwise whichever side of the tick the weight is currently sitting on decides — above the tick (hasn't reached it) springs to fully closed and calls dialog.close(), at or past the tick (bottomed toward open) springs to fully open, both via a semi-implicit-Euler spring (stiffness 260, damping 24 opening / 30 closing, mass 1) seeded with the actual release velocity so the settle reads as one continuous motion rather than a snap then a separate glide. The trigger button and Escape/backdrop/close-button paths run the identical spring (seeded at zero velocity) — dragging is an optional, purely pointer-driven alternative to the same open/close contract, not a separate code path. Scrim opacity (::backdrop, which inherits custom properties from its originating dialog element) is slaved every frame to (1 - tx/width), clamped to a max 0.55 alpha, token-derived per theme via color-mix. A single engine.request(target, velocity) function is the sole entry point for every open/close intent; two calls already resolving toward the same boolean collapse into one (only the first supplies a velocity, a later echo just refreshes the forced-settle deadline), so a React effect that reflects a controlled `open` prop can never stomp a drag-release's velocity back to zero. role=dialog (native, aria-modal, aria-labelledby the visible heading) with a real close button, Escape closes and returns focus to the trigger via native dialog behavior, and the trigger fully opens/closes via keyboard (aria-expanded, aria-haspopup=dialog) with no drag required. A visually-hidden aria-live=polite status region announces 'opened'/'closed' since the weight's position carries no non-visual signal. Reduced motion drops both springs for a 150ms CSS opacity fade on the panel itself and disables weight travel entirely — drag is ignored and the weight appears already parked at whichever end matches the resting state. Unlike pricing-scale (which balances two DOM subjects against each other on a single shared beam) or Vaul/shadcn Sheet (an invisible velocity threshold resolved internally), this externalizes the drawer's own snap decision as a second visible moving part whose rest position is the state — weight down always means held open, readable before anyone touches it. Demo is a catalog page with a search toolbar, a Filters trigger showing an active-count badge, and the drawer holding category checkboxes, a sort select, and Apply/Reset actions over a product grid."
      }
    },
    {
      "name": "drill-down-spines",
      "type": "registry:ui",
      "title": "Drill Down Spines",
      "description": "Drill-down navigation where every pushed level compresses into a slim clickable book spine instead of vanishing behind a back button, so the whole navigation history stands on a shelf beside you.",
      "files": [
        {
          "path": "registry/core/drill-down-spines/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/drill-down-spines.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "navigation",
          "drill-down",
          "master-detail",
          "breadcrumb",
          "nav",
          "stack",
          "history"
        ],
        "instruction": "Build a drill-down navigation primitive for master-detail and multi-level record hierarchies (folders, nested tickets, org charts) where pushing a new level does not hide the previous one behind a back button: the outgoing page compresses in place into a slim clickable spine that stays permanently visible on a shelf to the left of whatever is currently active, at any depth. Manage the stack uncontrolled, driven imperatively via a ref handle: `push({ id, title, content })` appends a new active level (no-op if the id already exists in the stack, or if a pop is mid-animation), `popTo(id)` collapses every level above `id` back to it, `pop()` is a one-level-back convenience, `reset(level)` replaces the whole stack instantly with no animation. Critically, every level in the stack is ONE persistent wrapper element for its entire lifetime — mounted once when pushed, unmounted only when actually popped past — never two different elements swapped between an 'active' tree position and a 'spine' tree position. Inside that wrapper are two always-mounted, absolutely-positioned faces: a spine-face `<button>` (vertical/horizontal title, click-to-popTo) and an active-face `<div>` (heading + content), cross-fading via opacity, with `inert` applied to whichever face is currently not the front one so its buttons/inputs are unreachable and hidden from the accessibility tree without unmounting it. The active-face additionally gets `pointer-events: none` while inert (not just `inert` alone) — `inert` removes an element from the accessibility tree but `document.elementFromPoint`/real hit-testing still resolves to whatever is topmost in paint order, so without an explicit `pointer-events: none` the invisible (opacity 0) active-face sitting on top would swallow clicks meant for the spine-face button beneath it. Because the wrapper never remounts, the wrapper's own width — an explicit pixel value every render, computed from a ResizeObserver-measured container width minus the sum of every other level's current spine width (14px resting, 22px widened) and inter-item gaps, never `flex:auto` — transitions cleanly on a single `transition: width` in EITHER direction on that same node: springing down from full to 14px as a level is pushed past, or re-inflating from 14/22px back to full as a popTo target becomes active again. The timing function is a spring-approximating overshoot cubic-bezier (`cubic-bezier(0.34, 1.56, 0.64, 1)`), not a hand-rolled physics integrator — an honest choice for a DOM/CSS-only nav primitive, and it still reads as a small bounce settling into place. Pushing a level: the previously-active wrapper's width springs down to spine width while its active-face fades out and its spine-face fades in — a horizontal heading crossfading to a `writing-mode: vertical-rl` Geist Mono title in `--muted` (two absolutely-positioned spans within the spine-face, both `aria-hidden` since the button's real accessible name is a plain aria-label carrying the full title); the freshly pushed level is a brand-new wrapper, mounted already at its resolved active width, playing a short slide-and-fade-in keyframe from the right (`translateX(18px)` -> `translateX(0)`) since there is no prior state for a new element to interpolate from. Each spine-face carries a 1px inset right edge via `box-shadow: inset -1px 0 0 0 color-mix(in srgb, var(--foreground) 22%, transparent)` — a decorative lit edge (never `--accent`, which appears only on focus rings) so the row reads as physical stacked thickness even at rest. Clicking any spine calls `popTo` on it: every level above cascades off in reverse-push order on its own still-mounted wrapper (the most recently pushed level's width and opacity go to 0 first, each earlier one staggered 90ms further behind via `transition-delay`) while the clicked level's own wrapper simultaneously transitions its width back up to full and crossfades its active-face back in — the re-inflation runs in parallel with the cascade above it, not after, because it is the same element animating, not a fresh mount. A11y: spine-faces are real `<button>`s, all levels sit inside one `<nav aria-label=\"Navigation history, N levels\">` landmark (N = total stack depth including the active page — the active page's content living inside the same landmark as the history is an accepted minor semantic tradeoff for keeping every level a single animatable element); the rotated/truncated visual label is decorative only, the accessible name is always the full title via `aria-label` plus a native `title` tooltip. Above a ~640px container-width breakpoint every spine permanently widens to 22px with a horizontal truncated label instead of the rotated 14px one (rotated text is genuinely harder to read); below that breakpoint, hovering or focusing one individual spine widens just that spine to 22px with the same horizontal label, reverting once hover/focus leaves. Focus moves to the newly active page's heading (a `tabIndex={-1}` `<h2>`, one per level, each inert while its level isn't active) on every push and once every pop's cascade actually commits (never mid-cascade) — the same SPA-route-change focus pattern a full page navigation would use, and `onNavigate(id)` fires at that same moment. `prefers-reduced-motion` drops every transition, the stagger, and the entrance keyframe: pops commit their new stack synchronously and every level renders at its resolved final width immediately, fully usable and legible, just not eased into. Zero dependencies, DOM+CSS only — no canvas. Differs from toast-gravity-stack, which accumulates dropped toast data as horizontal strata piling under gravity (a core sample of events that arrived, read once and dismissed): drill-down-spines accumulates navigation depth as vertical spines that are each a live, permanently clickable route target back into the hierarchy — a bookshelf you can reach into at any depth, not a core sample you only ever read from the top."
      }
    },
    {
      "name": "dropdown-drape",
      "type": "registry:ui",
      "title": "Dropdown Drape",
      "description": "Dropdown whose panel is a live verlet cloth pinned to the trigger — it falls and drapes like an awning, labels ride the weave, the cursor billows the fabric, and close yanks it back up.",
      "files": [
        {
          "path": "registry/core/dropdown-drape/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/dropdown-drape.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "menu",
          "dropdown",
          "cloth",
          "physics",
          "verlet",
          "cursor",
          "nav"
        ],
        "instruction": "Build a dropdown menu whose panel is a live 2D verlet cloth pinned along its top edge to the trigger's bottom edge. Simulate a 10x14 point grid with structural constraints only and 3 relaxation iterations per frame; gravity 2400 px/s^2, velocity damping 0.99 per step. On open the vertices start compressed at the trigger's bottom edge and fall freely, draping taut in roughly 380-500 ms — emergent from the physics, not tweened. A Canvas 2D layer underneath draws per-quad fold shading from a vertex-normal approximation (horizontal stretch = sheen toward the lighter ink, lateral lean = shadow toward the darker ink), with every drawn color derived from getComputedStyle CSS tokens (--surface/--border/--foreground/--background) at mount and re-derived live via a MutationObserver on the documentElement class so both themes shade correctly. Menu items are real DOM nodes (role=menu/menuitem) anchored to designated vertex rows: each item's transform is its anchor vertex translate plus a rotation from the local row slope clamped to +/-6 degrees, and each label fades in over 120 ms once its anchor row's vertical velocity drops below 8 px/s. Cursor proximity billows the weave: an upward force F = 900 * exp(-d^2 / (2*60^2)) px/s^2 on vertices within ~150 px of the pointer. Close yanks every vertex back to the trigger point on a per-vertex critically damped spring (k = 120 s^-2, zeta = 1.0) and unmounts once max displacement is under 0.5 px. Hot path is refs-only direct-DOM in a single rAF loop that sleeps when max per-frame displacement is under 0.02 px, or unconditionally 1200 ms after open (a backstop for imperceptible verlet jitter that can otherwise keep the epsilon from tripping), and wakes on pointermove/open/close; ResizeObserver re-derives cloth width from the trigger rect with a zero-size guard, and all listeners, observers, and the canvas are torn down on close/unmount. Full menu semantics: aria-expanded trigger, arrow-key navigation, Home/End, Enter selects, Esc closes and returns focus to the trigger, outside click closes; hovered/focused items get a token-relative bg-surface highlight plus an accent left rail and accent focus-visible ring. Under prefers-reduced-motion render a plain dropdown with a 150 ms opacity/scale-98% fade and no cloth or canvas."
      }
    },
    {
      "name": "empty-state-dashed",
      "type": "registry:ui",
      "title": "Empty State Dashed",
      "description": "Empty state whose dashed SVG boundary drifts like a plot marked out and not yet planted, and contracts toward the CTA on hover.",
      "files": [
        {
          "path": "registry/core/empty-state-dashed/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/empty-state-dashed.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "empty-state",
          "panel",
          "svg",
          "cta",
          "micro-interaction"
        ],
        "instruction": "An empty state for a list that has nothing in it yet: a headline, one line of muted supporting copy, one accent CTA, and an optional icon slot above the headline. The boundary is the interaction — a single SVG rect stroked with a dashed line in the muted token at low opacity, its stroke-dashoffset drifting on a seamless nine-second CSS loop so the panel reads as a plot marked out and waiting rather than a dead box. Pointing at (or keyboard-focusing) the CTA tints the outline to the accent token and contracts it toward the button on a spring-like ease, with the transform-origin measured from the button's real centre via a ResizeObserver so it stays true at any panel size or copy length. Deliberately renders no ghost or placeholder content rows — the emptiness is the message, and that silhouette belongs to a skeleton loader. CSS and SVG only: no canvas, no requestAnimationFrame, no timers, and the observer is disconnected on unmount. Colors come from --muted, --accent, --border and --foreground, so it reads correctly in both themes. prefers-reduced-motion leaves the outline static and un-contracting, still fully readable and usable."
      }
    },
    {
      "name": "empty-state-pegboard",
      "type": "registry:ui",
      "title": "Empty State Pegboard",
      "description": "Empty state modeled on a workshop pegboard — dashed silhouettes preview the exact card/avatar/title geometry of future items, and creating one hangs it onto its shadow with a spring settle.",
      "files": [
        {
          "path": "registry/core/empty-state-pegboard/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/empty-state-pegboard.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "empty-state",
          "list",
          "onboarding",
          "svg",
          "cta",
          "aria-live",
          "micro-interaction"
        ],
        "instruction": "An empty state for a list, board, or dashboard whose future content has a known, repeatable per-item shape (a card: avatar circle, title bar, subtitle bar). Instead of an illustration, ShadowBoard renders that exact card markup as a 'shadow' for every slot with nothing in it yet — a 1.5px dashed --border outline, --muted fill at 40% opacity, and a tiny inline-SVG hook notch overlapping the card's top edge, evoking a pegboard silhouette painted where a tool hangs. `items` (controlled, required) is the list of real items already created; `slots` (default 4) sets how many silhouette positions the board previews at once — slots beyond `items.length` stay dashed, and an item beyond `slots` simply renders without a paired shadow. This component never invents an item: the create button calls `onCreate`, and the parent is responsible for actually appending to `items` (e.g. after a form submit or an API call resolves) — ShadowBoard only reacts once that id shows up. When an id appears that wasn't present on a prior render, its slot mounts the real card at scale(0.96) translateY(-8px) and spring-settles it onto the silhouette's position (cubic-bezier(0.34, 1.56, 0.64, 1), an overshoot-then-settle 'click into place'), while that slot's dashed shadow fades to transparent over the same ~420ms — the shadow and the real card occupy the same grid cell throughout, so the settle reads as the item literally landing on its own outline. Items already present when the board first mounts render at rest with no animation; only ids that appear on a later render get the entrance, so re-sorting or editing an existing item's title never replays it. Once some but not all slots are filled, a plain text button, 'Dismiss remaining', removes the leftover silhouettes as a group without fabricating items for them — for lists that won't reliably fill every previewed slot. Distinct from empty-state-dashed, which is a single dashed boundary around nothing with no per-item shape to teach, and from skeleton-develop, which shimmers over content that already exists and is mid-fetch — ShadowBoard's silhouettes are prescriptive scaffolding for content that doesn't exist yet, drawn from the same markup the real item will use. A11y: every silhouette (including its hook notch) is aria-hidden — decorative preview, not information; the region carries its plain-text empty-state copy ('No projects yet. Created projects will appear here.') via aria-describedby; the create button is a normal, always-first-in-tab-order control, never buried behind the silhouette grid; the item list carries aria-live=\"polite\" so a newly-settled item's visible title is announced without a separate status region. prefers-reduced-motion mounts new items in place at rest immediately — no translateY/scale offset, no transition — and the silhouette disappears instantly rather than fading, while every state change and the live-region announcement stay identical. DOM + inline SVG + CSS only: no canvas, no requestAnimationFrame loop (two rAFs only to trigger each new item's own CSS transition), and every color is --background/--foreground/--muted/--border/--accent so both themes render correctly."
      }
    },
    {
      "name": "empty-state-sonar",
      "type": "registry:ui",
      "title": "Empty State Sonar",
      "description": "An empty state that pings like sonar every 6-8s to actively demonstrate emptiness, then freezes and grows skeleton rows outward the instant real results interrupt it.",
      "files": [
        {
          "path": "registry/core/empty-state-sonar/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/empty-state-sonar.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "empty-state",
          "skeleton",
          "loading",
          "list",
          "search",
          "svg",
          "aria-live",
          "sonar"
        ],
        "instruction": "A list/search result region, `<EchoSound items={items} query={query} .../>`, that unifies empty, loading and loaded into one continuous vocabulary instead of an illustration empty state handing off jarringly to an unrelated skeleton loader. `items` is the whole state machine: `null` means nothing has resolved yet and the region probes; a non-empty array means results landed. While probing, a single SVG circle (stroked in --border, fill none, vector-effect non-scaling-stroke) sits centered in a stage sized to the expected row count, and CSS keyframes alone (no rAF) grow its `r` from 3 to 47 and fade its stroke-opacity from .85 to 0 over a per-mount randomized 6-8s cycle, restarting forever — calm and continuous, never a quick blip. The stage's SVG uses preserveAspectRatio=none against a 0-100 viewBox, so the ring reads as a probe reaching toward the stage's real proportions rather than a decorative fixed icon. The instant `items` flips from empty to populated, the SAME ring is interrupted: its current mid-flight radius and stroke-opacity are read once via getComputedStyle and re-applied inline (so nothing jumps), the keyframe class is dropped, and a single CSS transition carries it to the exact radius that reaches row one's vertical center — a radius computed analytically from rowHeight/rowGap/stageRows, no DOM measurement needed. The ring holds there while skeleton rows (flat --border bars) scale in from their own center point one at a time at a 60ms stagger with ease-out-expo, each row then crossfading its skeleton bar to the real rendered item over 240ms once every row has appeared and settled briefly. That is the entire mechanism: two timeline handoffs (ping-to-contact, contact-to-resolved) driven by a handful of setTimeouts, zero per-frame JS, zero canvas. If items empties again (a new, zero-result search) the region resets straight back to probing with no reverse animation; if items changes while already loaded, the new batch swaps in place without replaying the reveal. Accessibility: the region carries aria-labelledby pointing at a single persistent paragraph that visibly reads 'No results for \"{query}\"' while probing and becomes the sr-only accessible name once loaded (same node throughout, so the region's name is always literally the empty-state text or its resolved successor); the ring is aria-hidden; skeleton bars are permanently aria-hidden and the row's real content is aria-hidden until the crossfade completes so nothing is announced early; arrival fires exactly one polite aria-live announcement, 'N results loaded', at the moment data lands, not per row; focus is never moved automatically. Under prefers-reduced-motion the ring never renders and the reveal never plays — the empty paragraph is simply replaced by the resolved list the instant items arrive, no ping, no stagger. Differs from status-glyph-cadence: that is a 20-64px inline status lamp whose cadence blinks forever to encode a state; this is a full content region where the ring is a spatial probe that the arriving data itself interrupts and answers, and the reveal choreography is the same SVG object as the empty state, not a swapped-in skeleton loader."
      }
    },
    {
      "name": "empty-state-survey",
      "type": "registry:ui",
      "title": "Empty State Survey",
      "description": "First-run empty state as a surveyor's staked plot: corner stakes drop in, then dashed strings tauten around the future layout and tie off at the CTA.",
      "files": [
        {
          "path": "registry/core/empty-state-survey/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/empty-state-survey.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "empty-state",
          "onboarding",
          "first-run",
          "svg",
          "cta",
          "dashboard"
        ],
        "instruction": "An empty state for a list, board, or dashboard that has nothing in it yet, staged as a plot of land marked out and not yet built on: <StakeLine title description actionLabel onAction shape />. `shape` is one of `{ kind: 'table', rows? }`, `{ kind: 'cards', count?, columns? }` or `{ kind: 'chart' }` and describes the real silhouette of the content that will eventually live here. That silhouette is never guessed at — a template of the real layout (rows stacked full-width, or a CSS grid of card cells, or a plain plot box) is rendered with `visibility:hidden` so it occupies genuine layout space without painting anything, then measured with getBoundingClientRect (and re-measured on resize via a ResizeObserver) so the SVG overlay drawn on top always matches the actual slots, not a fixed illustration. On mount, four short SVG tick marks land at the plot's outer corners one by one (40ms stagger, translateY(-6px) into a back-eased spring settle, stroke in --muted) — literal surveyor's stakes. Once they've settled, dashed string paths (a static 4-3 dash pattern in --border) draw themselves in via stroke-dashoffset on an ease-out-expo curve, outlining the plot's outer boundary plus every future row or card individually, so the emptiness reads as 'planned', not 'loading'. The last string doesn't stop at the plot: it continues down and terminates at the CTA's top border, closing in a small tied-off loop, so the one thing to do is physically wired to the thing that's missing rather than just sitting nearby. Hovering or keyboard-focusing the CTA nudges that connector's control point ~3px sideways — a gentle tug on the string, not a state change — via a CSS `d` transition, and releases back on blur/leave. The whole intro runs about 1.5 seconds; after that every stake and string is inert and the component does nothing until the CTA is used. Unlike skeleton-develop (and every other skeleton loader), which imitates content that is imminently arriving with grey placeholder blocks and offers no action, empty-state-survey is explicitly provisional — surveyor's marks mean 'planned', not 'loading' — and it always ships exactly one CTA that the layout points to, never a silent promise of data. The survey graphic (template + SVG overlay) is entirely `aria-hidden`; the accessible content is an ordinary heading, one sentence of body copy, and a real `<button>`, the only focusable element in the region. Colors are strictly --background, --foreground, --muted, --border and --accent (via the button's existing token classes) — no hex, no canvas, DOM+SVG+CSS only. `prefers-reduced-motion` renders every stake and string already in its settled, fully-drawn state with no draw-in, and the hover tug snaps instead of easing."
      }
    },
    {
      "name": "feature-grid-ascii-rule",
      "type": "registry:ui",
      "title": "Feature Grid ASCII Rule",
      "description": "A feature grid where hovering or focusing a cell draws real box-drawing connectors to its related features, routed orthogonally through the grid's gutters and retracted glyph by glyph on leave.",
      "files": [
        {
          "path": "registry/core/feature-grid-ascii-rule/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/feature-grid-ascii-rule.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "grid",
          "feature",
          "ascii",
          "mono",
          "box-drawing",
          "canvas"
        ],
        "instruction": "Build <FeatureGridAsciiRule items cols? className?> where items is FeatureGridAsciiRuleItem[] ({id, title, description, relatedIds: string[]}) laid out in a CSS grid (default 3 columns, 2 rows for 6 items). Each cell is a real <button data-feature-cell={id} aria-label=\"{title}: {description}\"> so hover, focus and click all reach it natively. A single <canvas aria-hidden> absolutely covers the grid. THE MECHANIC: on pointer-enter or focus of a cell, for every id in that cell's relatedIds, a connector is routed from the source cell to the target cell through the ONE shared horizontal gutter between the grid's two rows — never straight through an intervening cell. The route is always 3 orthogonal segments measured live via getBoundingClientRect: a vertical run from the source cell's gutter-facing edge (bottom edge if it's in the top row, top edge if bottom row) down/up to the gutter's mid-line, a horizontal run across the gutter to the target's x, and a vertical run into the target's gutter-facing edge — collapsing to a single straight vertical run when both cells share the same column. This whole path is rasterized into individual box-drawing glyphs (─ for horizontal steps, │ for vertical steps, one of ┌ ┐ └ ┘ at each bend, chosen by which two of up/down/left/right that bend actually connects) at a fixed ~11px pitch, each drawn via ctx.fillText in a monospace face. Glyphs reveal one at a time, source to target, on a per-glyph stagger (~26ms step, ~140ms own fade, eased) while the pointer/focus is on the source cell; on pointer-leave or blur they retract in the OPPOSITE order — the glyphs nearest the target fade first, working back toward the source — driven by one rAF loop keyed off an activation timestamp, never a CSS transition per glyph (there is no DOM node per glyph). Only one cell's connectors are visible at a time. Colors are token-only: canvas ink is var(--foreground) read via getComputedStyle at mount and re-read through a MutationObserver on the root's class/style attributes, so both themes stay correct with no remount; cell borders and backgrounds are Tailwind token utilities. Reduced motion skips the stagger/fade entirely: connectors snap fully in on activation and fully out on deactivation in one paint. Zero dependencies."
      }
    },
    {
      "name": "feed-escapement",
      "type": "registry:ui",
      "title": "Feed Escapement",
      "description": "Live feed where a small SVG anchor fork gates arrivals one at a time — a burst becomes a metered tick-tick-tick instead of a dogpile, held open only by each item's own spring-settle, not a schedule.",
      "files": [
        {
          "path": "registry/core/feed-escapement/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/feed-escapement.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "feed",
          "notification",
          "queue",
          "physics",
          "aria-live",
          "log",
          "activity"
        ],
        "instruction": "A live feed (notification center, activity stream, log tail, chat pane) that admits queued arrivals one at a time through a physical gate rather than a scheduler. Arrivals mount immediately as real, hidden DOM rows (height 0, translateY -8px, aria-hidden) — the queue is genuine list items, not a data array waiting off-DOM. A small SVG anchor fork (aria-hidden) sits in the header gutter; each time it releases exactly one row it rocks +-12deg on a spring-eased CSS transition, and it will not rock again until that row's own settle spring — height and translateY driven by a single refs-only rAF spring (k 210, c 25, epsilon 0.003 displacement / 0.02 velocity, underdamped just enough for one small wobble) — has displacement AND velocity both under epsilon. That rest event is what pulls the next queued id off a FIFO, so cadence follows real content height and settle time rather than a fixed stagger offset: a tall row metered the same as a short one takes visibly longer to clear the gate. Because the released row is growing in normal block flow, every row already on screen shifts down together as one connected chain — there is no per-row animation to independently schedule, which is the load-bearing difference from avatar-stack-flock (a cohort of avatars animated together on one scheduled hover-driven formation change): here release N+1 is triggered by the physical rest of item N, one gate, one row at a time, and nothing else in the registry meters feed ingestion this way. A hairline font-mono --muted counter beside the anchor tracks queue depth, decremented only when a row's spring actually settles. The list itself is role=log aria-live=polite with aria-atomic per row, so the serialization screen readers already require lines up naturally with the escapement's own cadence — sighted and screen-reader users get the same metered order for free. A visible RELEASE ALL button, and Escape from anywhere in the component, flushes every pending row to its settled state instantly, for when metered admission isn't what's wanted. prefers-reduced-motion disables the escapement entirely: rows append in their settled state immediately on arrival (no rocking, no spring, no queue), with the depth counter still present and correctly reading zero since nothing is actually held back. No canvas — pure DOM, SVG and CSS."
      }
    },
    {
      "name": "file-upload-seal",
      "type": "registry:ui",
      "title": "File Upload Seal",
      "description": "File upload where progress is the container: a loose dashed outline 24px outside the card pulls taut as the file uploads, clicks shut with a pucker at 100%, and relaxes back out with a wobble on failure.",
      "files": [
        {
          "path": "registry/core/file-upload-seal/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/file-upload-seal.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "dropzone",
          "file-upload",
          "progress",
          "form",
          "svg",
          "physics",
          "accessibility"
        ],
        "instruction": "Build a file upload zone (focusable role=button zone plus hidden <input type=file multiple>, real Enter/Space/click parity, HTML5 drag-and-drop as a bonus path) where each accepted file renders as a card with its own SVG rounded-rect outline drawn 24px outside the card's silhouette. One per-file critically-damped spring (k≈90, zeta=1) drives a single generalized 'seal progress' value dp (0 = loose outline 24px out, 1 = flush/taut) toward whatever the file's real upload progress currently is; every frame, direct SVG attribute writes (never React state) recompute the rect's x/y/width/height from that offset, its corner radius (grows slightly with offset so it always reads concentric with the card), and its stroke-dasharray, interpolating from loose stitches (8 4) at dp=0 to a taut near-solid line (1 0) at dp=1. Stroke color is a literal crossfade: two overlapping rects, one --muted one --foreground, whose opacities are 1-dp and dp respectively — never a JS color-lerp. Because the upload driver reports progress in irregular chunks (not a smooth ramp) every ~170-280ms, and the spring re-targets on every chunk without ever fully settling between them, the outline visibly breathes toward the card rather than snapping in discrete steps. On arrival at progress 1 the file's status flips to 'sealed' and the card plays one one-shot CSS keyframe — scale(1) to scale(0.99) to scale(1) over 260ms — a physical click, not a fade. A failed upload's spring instead retargets to dp=0 (loose/open) with light underdamping (zeta≈0.4, lower k) so it visibly overshoots past the 24px rest point and wobbles before settling — the exact same spring engine, just less damped, its rects redrawn in a single dashed var(--error, #ea001d) stroke rather than the muted/foreground pair. Multiple files each get their own card, own spring, own outline; nothing is shared or queued. Files pre-supplied via defaultFiles (with an explicit status and, for 'uploading', a progress) render in that exact end state at mount with no entrance spring at all — their geometry is written once, synchronously, in a layout effect before first paint, so the idle screenshot is a deterministic gallery of a sealed file, a mid-upload file, and a failed file, not a random mid-animation frame. A consumer supplies an uploadFile(file, onProgress) => Promise function for real uploads (reject to fail the seal); omitting it runs a built-in simulated upload for prototyping. Accessibility: the seal SVG is aria-hidden and purely decorative; each card carries a real role=progressbar with continuously-updating aria-valuenow/aria-valuemax and an aria-valuetext that reads 'upload failed' or '58%' as appropriate (pull-model, not itself aria-live, so it never spams); a single shared role=status aria-live=polite line separately announces only at 25/50/75% and on the terminal sealed/failed outcome, never per-percent; and every card also carries a plain, non-live Geist Mono status line ('uploading 41.2 MB… 58%', '2.6 MB · sealed', 'upload failed · 184 KB') for sighted users, updating freely without triggering announcements. Under prefers-reduced-motion the spring is skipped entirely: dp snaps directly to the nearest quarter (0/0.25/0.5/0.75/1) on every progress update with no interpolation and no overshoot, the pucker keyframe is suppressed, and a failed upload snaps straight back to the loose 24px outline with no wobble — fully legible, just discrete. No canvas anywhere; the outline is real SVG measured against the card via ResizeObserver, matching the light and dark theme through --muted/--foreground/--border tokens and the --error status token with its established repo-wide #ea001d fallback."
      }
    },
    {
      "name": "file-upload-thermal",
      "type": "registry:ui",
      "title": "File Upload Thermal",
      "description": "File dropzone as a thermal field: dragging spawns rising accent convection wisps, accepted drops buoy off the drop point and squash-land into a docked queue rack, rejected files sink and fade.",
      "files": [
        {
          "path": "registry/core/file-upload-thermal/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/file-upload-thermal.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "dropzone",
          "file-upload",
          "form",
          "canvas",
          "particles",
          "physics",
          "input"
        ],
        "instruction": "Build a file dropzone as a thermal field: a canvas 2D wisp layer over the zone (canvas-over-control pattern, explicit style.width/height because a canvas is a replaced element) plus DOM file chips animated with direct-DOM transforms, and a hidden <input type=file multiple> behind a real browse button. DRAGOVER: spawn convection wisps at 18/s (cap 30 live) from the zone floor biased toward the dragged pointer's column, rising 30-60 px/s with sinusoidal x-wobble amplitude 6px period 1.2s, life 1.5-2.5s, drawn as short quadratic strokes at peak alpha 0.12 from --accent with a sin-envelope fade; pruned particle array with a full clear + redraw per frame, never destination-in fades. ACCEPTED CHIP: chips are DOM list items anchored to their dock slots and flown with OFFSET transforms measured from drop point to slot center: a 900 px/s^2 buoyancy boost for the first 180ms feeding a near-critically damped spring pull to the slot, horizontal sway +-10px at 2 Hz with a zeta~0.5 decay envelope, landing squash scaleY 0.88 held 120ms then sprung back with k=300 s^-2 zeta=0.6; multi-drop flights stagger 90ms (chips hidden at the drop offset until their turn) so flights never overlap, and every chip carries a forced-settle deadline of 1.4s. REJECTED CHIP: spawns at the drop point inside the zone, sinks 40px max under 600 px/s^2 gravity while fading over 500ms, then is removed; the rejection reason (type not accepted / exceeds size limit) and each accepted file's name are announced via a polite aria-live status line. INTERACTION: zone is focusable role=button with Enter/Space opening the picker, aria-describedby points at the accepted-types + max-size rules line, docked chips are focusable list items removable via Delete/Backspace or an explicit remove button with focus handed to a neighbor. Validation supports extensions (.png), exact mime, and mime wildcards (image/*) plus a per-file byte limit. REDUCED MOTION: no wisps, chips appear docked instantly, dragover falls back to the static token-border accent highlight. ENGINE: all canvas ink parsed from getComputedStyle CSS tokens at mount and re-derived live via a MutationObserver on documentElement class so both themes render correctly; single direct-DOM rAF loop that sleeps when all chips are docked and all wisps dead, pauses offscreen via IntersectionObserver and on document hide, guards zero-size zones and zero-size chips before animating, and tears down every listener, observer, and frame on unmount. Hover/focus/dragover affordances are token-relative (border-foreground/25, ring-accent, border-accent), never hardcoded white."
      }
    },
    {
      "name": "filter-facet-mesh",
      "type": "registry:ui",
      "title": "Filter Facet Mesh",
      "description": "Faceted filter chips as a sieve — active facets read as taut, bright threads in a hairline mesh fanned from a hub above the chip row, hovering a chip visibly tightens its own thread, and deselecting one opens a gap that shakes a few result particles through as the count sifts to its new value.",
      "files": [
        {
          "path": "registry/core/filter-facet-mesh/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/filter-facet-mesh.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "filter",
          "facets",
          "chips",
          "toggle",
          "mesh",
          "set-membership",
          "svg"
        ],
        "instruction": "Build a faceted filter as a row of real toggle buttons (plain <button aria-pressed>, no role override needed — native button + aria-pressed is the correct toggle-button pattern) with an SVG hairline mesh above the row: a small hub dot centered above the chips, with one quadratic-bezier path per chip running from the hub down to that chip's top-center anchor (measured via getBoundingClientRect against the row's own rect in a useLayoutEffect + ResizeObserver, stored as plain x-offsets in state — this is a layout measurement done occasionally, not a per-frame simulation, so ordinary React state is correct here, not a rAF loop). Active facets render their thread bright (stroke var(--foreground), opacity ~0.55) and thicker; inactive facets render the same thread faint (stroke var(--border), opacity ~0.35) — the mesh is always structurally present, but only active facets read as the prominent net. Each path's control point sits below the midpoint of hub-and-chip by a 'sag' amount (~13px at rest); hovering (or focusing) a chip drops that specific path's sag to ~3px, and because the path's command structure (M...Q...) stays identical between states, transitioning the `d` attribute directly via CSS (`transition: d 220ms ease-out`) animates it as a native browser tween with zero JS on the hot path — this is what makes hovering visibly tighten the mesh line nearest the hovered chip. Clicking a chip toggles aria-pressed and recomputes a mocked result count (a fixed pool of 240 multiplied by each active facet's narrowing factor, rounded, minimum 1) rendered as a horizontal bar of up to 16 small dots — filled-vs-unfilled dot count reflecting the new total — where each dot's opacity/scale transition carries a staggered transition-delay keyed to its index (~22ms apart), so the count visibly sifts into its new shape dot-by-dot rather than a tweened number ever appearing anywhere. Deselecting a previously-active facet (widening the result set) additionally spawns 4 small aria-hidden particle dots at that chip's mesh-anchor x-position, each playing a one-shot CSS keyframe (translateY down ~56px, scale down, fade out over ~520ms) representing the newly-included results shaking through the gap the facet left in the sieve; removed from the DOM via onAnimationEnd, never a rAF loop. When zero facets are active, the whole mesh group fades and scales down slightly (an 'all-clear' dissolve) via a CSS transition on the SVG's own opacity/transform. An sr-only aria-live=\"polite\" aria-atomic region announces \"<count> results\" on every toggle. Core restraint: hairline mesh only (var(--border)/var(--foreground)), monochrome particles and dots, zero color beyond the standard focus-visible ring (ring utility, not outline, paired with plain outline-none — never combine a base outline-none with a focus-visible:outline utility on the same element). Reduced motion: skip particle spawning entirely (deactivating a facet updates the count with no shake) and drop the dot bar's stagger (apply all dot opacity/scale transitions with zero transition-delay, a simultaneous crossfade instead of a sifting sequence) — the mesh hover-tighten and active/inactive thread brightness stay, since those are simple discrete state transitions, not continuous motion."
      }
    },
    {
      "name": "footer-ascii-rule",
      "type": "registry:ui",
      "title": "Footer ASCII Rule",
      "description": "A sitemap footer whose back-to-top control is a real instrument: an aria-hidden vertical rail beside it continuously reads actual scroll position, and the button drives a real spring back to the top rather than a jump — grabbing the wheel mid-flight yields it immediately.",
      "files": [
        {
          "path": "registry/core/footer-ascii-rule/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/footer-ascii-rule.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "footer",
          "sitemap",
          "back-to-top",
          "scroll",
          "spring",
          "ascii",
          "mono"
        ],
        "instruction": "Build <FooterAsciiRule brand? columns className?> where columns is FooterColumn[] ({heading, links: {label, href}[]}). STRUCTURE: a <footer data-footer-ascii-rule> with a static, decorative top rule (a repeated ─ character, aria-hidden — this is house-style dressing, not the mechanic), a responsive grid of sitemap columns (real <a> links, hover text-muted to text-foreground, focus-visible:outline-2 outline-offset-2 outline-accent), and a bottom row holding a copyright line (auto-computed year) and a 'back to top' button. THE MECHANIC — the one only a footer's job (closing the page and offering a way back) makes sense of: a 6-row aria-hidden <pre> rail sits beside the button and renders │ characters with one ● marking the current scroll position, mapped from window.scrollY / (scrollHeight − innerHeight) to a row index. This rail is driven by a passive, rAF-throttled scroll listener that runs continuously and unconditionally — not only while the button is mid-flight — so it is an honest, always-live readout of real scroll position, never a decoration animating on its own clock. Clicking the button does not call scrollIntoView or a native smooth scroll: it starts a semi-implicit-Euler spring (stiffness 120, damping 22, mass 1) seeded at the current scrollY with zero velocity (shortcut taken: release velocity isn't measured here, unlike drawer-counterweight's drag, since there is no drag gesture to sample it from) and integrates it in a requestAnimationFrame loop that calls window.scrollTo(0, y) every frame — which is itself what feeds the rail's existing scroll listener, so the car's motion during the flight is the same code path as at rest, not a duplicate animation. If the user grabs the wheel, touches the screen, or presses an arrow/Home/End/PageUp/PageDown/Space key while the spring is in flight, temporary listeners cancel the animation immediately and hand control back — the page never fights the user for who is scrolling it. The flight force-settles at 2.5s if the spring hasn't converged (a forced deadline, matching the pattern used elsewhere in this registry for spring-driven UI) and snaps to exactly 0 either way. prefers-reduced-motion skips the spring entirely and jumps straight to window.scrollTo(0, 0). Colors are token-only (--border, --foreground, --muted, --accent, --surface) with no hex, including the rail's plain-text glyphs which inherit currentColor from Tailwind text-* classes. Demo page is a real, moderately tall scroll ahead of the footer (not a scripted auto-scroll) so /preview stays the honest interactive reference."
      }
    },
    {
      "name": "gallery-coverflow-caustic",
      "type": "registry:ui",
      "title": "Gallery Coverflow Caustic",
      "description": "3D coverflow gallery whose frosted-glass cards run a live caustic-light simulation — drifting light pools refract across each pane, and drag velocity fringes the focused card's edges with chromatic aberration that settles to zero at rest.",
      "files": [
        {
          "path": "registry/core/gallery-coverflow-caustic/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/gallery-coverflow-caustic.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "gallery",
          "coverflow",
          "3d",
          "canvas",
          "caustics",
          "glass",
          "chromatic-aberration",
          "drag",
          "momentum",
          "ambient",
          "showpiece"
        ],
        "instruction": "A 3D coverflow gallery where every card sits behind frosted glass running a lightweight caustic-light simulation. RENDERING: DOM cards under a perspective-1200px stage, anchored at the stage center with translate(-50%,-50%) plus offset transforms (never absolute coords) — center card scale 1.0, side cards rotateY ±35° and scale 0.82, x-spacing 46% of card width, translateZ −60px per index of distance with explicit z-index depth sorting. Each card is a frosted pane: backdrop-filter blur(14px) saturate(1.1) over color-mix(--surface 60%, transparent), 1px --border, rounded-md, over a token-derived generated abstract thumb (grayscale value-noise field mixed from --surface/--border/--muted/--foreground, no external images). A per-card pointer-events-none Canvas 2D overlay (explicit style.width/height + dpr-capped-2 backing store — CSS inset never sizes a canvas) renders caustics: three radial gradients (radius 40% of card width, --foreground ink at alphas 0.10/0.07/0.05) whose centers travel incommensurate Lissajous paths with periods 7s/11s/13s and amplitude 30% of card size, drawn onto a fully cleared quarter-resolution offscreen buffer each tick (no alpha accumulation, ever) and upscaled with imageSmoothing for soft pool edges. Only the focused card and its two neighbors run live caustics; outer cards keep a fixed-phase static frame. CHROMATIC ABERRATION: focused card only — its rounded border restroked twice, offset ±k px horizontally, in red/blue channel inks split from the foreground token with a fixed minimum-saturation floor so the fringe stays colored in light theme too (rgba(max(fg.r,190),0,0) / rgba(0,0,max(fg.b,190))), composited 'lighter' on dark themes and source-over on light; k = min(6, |angular velocity in deg/s| × 0.04) where the focused card turns 35°/index, exponentially damped (τ≈100ms, settles < 500ms) with a 900ms forced-settle deadline. INTERACTION: pointer drag scrubs the continuous index (rubber-band past the ends); release applies momentum with friction 0.92/frame (fps-normalized) then hands off to a snap spring (k=170, ζ≈0.95) once |v| < 0.05 idx/s, itself under a 900ms forced-settle deadline so a flick can never oscillate forever; wheel steps one card per 260ms with a 250ms ease-out-expo tween; clicking a side card centers it (taps under 5px of travel); ArrowLeft/Right step with a visible token-relative focus ring on the stage; index dots below the stage navigate and mirror the active card via data-active. IDLE: pools keep drifting at half amplitude (ambient default look) capped at 30fps, blending back to full amplitude while interacting. REDUCED MOTION: no rAF loop, one static caustic frame per card, no aberration, navigation via instant 200ms CSS ease with no momentum. LIFECYCLE: all canvas inks derived from getComputedStyle tokens at mount and re-derived by a MutationObserver on documentElement class (thumbs and static frames regenerate per theme); IntersectionObserver pauses the loop offscreen and the loop fully sleeps when settled AND the tab is hidden; zero-size containers guard all drawing; hover is a 2px inner-wrapper lift plus border-foreground/25 so the rAF transform is never fought; every listener, observer, and rAF torn down on unmount. Demo: padded 'Field notes' gallery section on a surface card with mono eyebrow, muted subline, seven believable archive entries (generated thumbs, Geist Sans titles, mono date + index meta) and index dots below the stage."
      }
    },
    {
      "name": "gauge-capacity-waterline",
      "type": "registry:ui",
      "title": "Gauge Capacity Waterline",
      "description": "Capacity-vs-legal-limit meter drawn as a ship's side-profile hull with Plimsoll load-line marks: the waterline rises with load, the hull itself settles a few px deeper on a separate spring, and the S mark flips to amber the moment load passes the legal limit.",
      "files": [
        {
          "path": "registry/core/gauge-capacity-waterline/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/gauge-capacity-waterline.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "gauge",
          "meter",
          "capacity",
          "maritime",
          "svg",
          "spring",
          "status",
          "hover"
        ],
        "instruction": "Build a capacity/usage gauge shaped as a ship's side-profile hull cross-section with Plimsoll load-line marks (TF/F/S/W) — deliberately NOT a liquid-in-a-glass-vessel meter (surface tension is a different component's job) and NOT an input-strength meter; this one is legal-limit-vs-current-load semantics where the hull itself displaces. Render a single hairline hull silhouette (SVG path, ~1.25px stroke, ~5% foreground fill so it reads as a solid object) inside a fixed-size stage (280x170 works well): flat deck top, sides curving out then in to a rounded keel bottom. Two props drive everything: `value` (0-150+, percent load) and `limit` (percent, default 100, where the legal 'S' load line sits). Two independent, small effects compose into one physical read: (1) the waterline — a rect+line pinned to the stage's fixed frame, NOT tied to the hull — rises directly as a function of `value` on a critically-damped spring (stiffness ~150, zeta ~0.85); (2) the hull silhouette's own group gets a small additional translateY sink (spring, stiffness ~90, capped around 8-10px) as load rises, layered on top of the waterline so the ship visibly settles under its own cargo rather than just watching a tide come in. On top of the settled waterline position, when motion is allowed, add a continuous sine 'lap' (roughly 0.5Hz, amplitude ~1.6px) so idle default state never looks frozen; the lap's amplitude roughly doubles while overloaded. Four load-line marks are laid out relative to `limit`: TF at limit+10, F at limit+5, S at exactly `limit` (the only one that matters functionally), W at limit-6 (all clamped to a sane visible range) — each a short hairline tick plus a small mono label (TF/F/S/W) drawn INSIDE the same group as the hull (so they move with its sink offset, staying visually attached to the hull). All four render in --muted ink normally; only the S mark, and only while `value > limit`, flips its tick+label to --warning amber — the other three never change color, they're fixed decorative reference lines. The whole gauge is wrapped in exactly one `<button>` (accessible name summarizing the state, e.g. 'Capacity gauge: 96% of 84% legal limit, over limit') — there is no second interactive control anywhere in the component; load changes and overload are driven purely by the `value`/`limit` props from outside (a script in the demo, or real app state), never by a click. Hovering or focusing that button reveals a depth-sounding line: a vertical hairline div (never SVG dasharray — this project has a known Chromium bug combining pathLength with vectorEffect=non-scaling-stroke for dash-based indicators, so straight indicator lines are always two plain divs) dropping from near the top of the stage down to the CURRENT waterline y-position, positioned at the pointer's x (clamped inside the stage) or, on keyboard focus, centered — plus a small mono readout chip near the bottom of that line showing the exact rounded value (e.g. '96%'). The line/chip are positioned by a direct ref write on pointermove/enter (not per-frame rAF; they only need to move when the pointer or focus target actually moves) and hidden entirely in the default state, so hover is trivially, unmistakably different from default regardless of the ambient lap. Reduced motion drops the spring and the lap outright: value/limit changes land as an instant discrete step (no interpolation), the hull/waterline just jump to their new position, fully legible with zero animation. An sr-only `role=status aria-live=polite` span announces only on the overload transition ('Over capacity limit' / 'Within capacity limit'), not on every render. The button needs a visible `:focus-visible` ring (outline utilities alone, never paired with a bare `outline-none`, or the ring silently disappears in Tailwind v4). Zero dependencies, no canvas — plain SVG + two overlay divs."
      }
    },
    {
      "name": "grid-bento-ascii",
      "type": "registry:ui",
      "title": "Grid Bento ASCII",
      "description": "A 2x2 bento layout primitive built on its own content/seam track grid — a real vertical rule, horizontal rule and ┼ junction between the four tiles — where activating a tile re-spans it across every track, seam included, so the junction has nowhere left to be drawn.",
      "files": [
        {
          "path": "registry/core/grid-bento-ascii/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/grid-bento-ascii.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "grid",
          "bento",
          "layout",
          "ascii",
          "mono",
          "box-drawing"
        ],
        "instruction": "Build <GridBentoAscii cells className?> where cells is exactly a 4-tuple of BentoCell ({id, title, description?, content?}). STRUCTURE: a CSS grid with an explicit 3x3 track template — gridTemplateColumns/Rows of `1fr 1.4em 1fr` — so tracks 1 and 3 hold content and track 2 on each axis is a dedicated seam track, not a gap. At rest (no tile activated) the four tiles occupy the four content x content corners (col/row `1/2` and `3/4` in every combination) and three real seam elements are rendered at the seam tracks: a 1px-wide vertical bar (bg-border) spanning the full grid height at column `2/3`, a 1px-tall horizontal bar spanning the full grid width at row `2/3`, and a `┼` box-drawing glyph (text-border) at their crossing, column `2/3` row `2/3` — three genuine elements at real grid positions, not a painted picture of a cross. THE MECHANIC — the one that needs more than one cell and a real track topology to exist: each tile is a real <button data-cell={id} aria-pressed aria-label> covering its own content; clicking a tile that isn't the current hero sets it as hero, re-spanning ONLY that button's own gridColumn/gridRow to `1/4` on both axes — across every track, seam tracks included — while the seam elements and the other three tiles unmount their visible presence (opacity-0, pointer-events-none, aria-hidden, tabIndex=-1, kept mounted rather than removed so focus/state isn't lost). Because the hero's own grid area now covers the seam tracks, the seam elements are conditionally not rendered at all while any tile is expanded — the junction doesn't fade out, it structurally has nowhere left to be drawn, which is the whole point: this could not exist on a single box, since there would be no second cell's boundary to lose. Clicking the hero tile again (aria-label flips to 'Collapse {title}', a small aria-hidden 'click to collapse' badge appears in its corner) sets hero back to null, restoring all four positions and all three seam elements in the same render. ACCESSIBILITY: every tile is a real <button> (not a div with an onClick) so Tab/Enter/Space work natively with no keydown handler required; aria-pressed tracks whether that specific tile is the current hero; aria-label always names the action ('Expand {title}' / 'Collapse {title}'), never just the state; hover (border-foreground/25) and focus-visible (outline-2 outline-offset-2 outline-accent, no outline-none on the same element) are visually distinct from rest; the three hidden-while-expanded tiles are excluded from the tab order via tabIndex=-1 so Tab doesn't land on invisible controls, and aria-hidden keeps them out of the accessibility tree while a tile is expanded. Reduced motion drops the small opacity transition on tile visibility to an instant change. Colors are token-only (--border, --foreground, --muted, --accent, --surface), no hex."
      }
    },
    {
      "name": "grid-bento-dense",
      "type": "registry:ui",
      "title": "Grid Bento Dense",
      "description": "A composable bento grid where activating a tile genuinely promotes it: the tile takes a 2x2 slot and every other tile re-packs around it via CSS grid-auto-flow: dense, FLIP-animated so the reflow reads as tiles sliding into new slots rather than jumping.",
      "files": [
        {
          "path": "registry/core/grid-bento-dense/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/grid-bento-dense.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "grid",
          "layout",
          "bento",
          "dashboard",
          "keyboard-navigation",
          "reflow",
          "accessibility"
        ],
        "instruction": "A bento grid built as a real layout primitive rather than a fixed grid-template-areas arrangement: it takes a `cells` array of arbitrary length and content (`{ id, title, meta?, body?, size? }`, size one of 1x1/2x1/1x2 as its resting footprint) and a `cols` count, and lays every cell out with native CSS `grid-auto-flow: dense` — the browser's own packing algorithm, not JS-computed positions. Clicking, or Enter/Space on, any tile ACTIVATES it: that tile's span becomes 2x2 regardless of its resting size, the previously-featured tile drops back to its own resting size, and dense re-runs the pack — every other tile can shift to fill the gap, which is what makes this a primitive the rest of a dashboard composes into rather than a decoration. The reflow is FLIP-animated: before the state write, every tile's current getBoundingClientRect is captured; after the new layout has painted, each tile that moved is inverted back to its old screen position with transitions off, forced to reflow, then released into a 380ms transform transition back to zero — so the motion is a real position change riding a transform, not a fake. Size changes happen INSTANTLY, never scaled: animating a bordered, rounded tile's width/height via scale is what smears its border and distorts its text, so only translation is animated and the grid-column/row spans swap in the same frame as the state update. The container's own height never changes across activations — `computeRows` evaluates every cell as the hypothetical featured one, takes the worst-case total area, and fixes `grid-template-rows` to that count once, so a dashboard embedding this grid never has its neighbors jump when someone taps a different tile. ARROW KEYS move focus by actual on-screen geometry, not DOM order: from the focused tile's centre, candidates in the pressed half-plane are scored by distance along that axis plus double the perpendicular drift, and the closest wins — a tile below and slightly right of the current one is `ArrowDown`, not `ArrowRight`, which flat DOM-order roving tabindex gets wrong the moment tiles vary in size. Every tile is `role=button` with roving tabindex (exactly one at 0), `aria-pressed` reflecting featured state, and an `aria-label` from its title; a visually-hidden `role=status` region announces which tile just got featured. Hover and keyboard focus both lift the border from --border to --muted and reveal a small 'feature this' hint (opacity 0 to 1), and the featured tile alone carries a small --accent dot — the only place accent appears. `prefers-reduced-motion` skips the FLIP capture entirely: the new layout still applies, cells still move, there's just no transform animating the transition. All color from --background/--foreground/--muted/--border/--accent tokens via Tailwind utility classes, zero canvas, zero dependencies."
      }
    },
    {
      "name": "grid-magnetic-lattice",
      "type": "registry:ui",
      "title": "Grid Magnetic Lattice",
      "description": "Card grid on a magnetized hairline lattice — the cursor bends the grid lines and the DOM cards sample the same displacement field, riding the bent sheet.",
      "files": [
        {
          "path": "registry/core/grid-magnetic-lattice/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/grid-magnetic-lattice.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "grid",
          "cursor",
          "cards",
          "field",
          "micro-interaction"
        ],
        "instruction": "A functional 3x2 DOM card grid drawn over its own full-bleed canvas lattice, where the lattice is the magnetized medium. Lattice: 32px cells, every line sampled every 8px, stroked with the theme's --border token (read via getComputedStyle at mount, re-read on a MutationObserver watching documentElement's class list so dark/light toggles stay correctly weighted) with alpha rising 0.25 to 0.6 inside the field. One pure displacement function shared by canvas and cards: pull = (cursor - p) * A * exp(-r^2 / (2 * 140^2)), A tuned for a 22px peak bend, magnitude clamped to 0.35r so lines never cross the pointer. At rest (no pointer) the field target drifts a slow lissajous orbit at 0.16 of full strength so the lattice always has ambient motion instead of sitting dead flat; hovering overrides the target with the real cursor at full strength. The cursor/idle position itself is smoothed at lerp 0.14 per frame so flicks lag and the sheet visibly relaxes behind fast moves. Cards translate field(center) * 0.35; the nearest hovered card gets 0.6x field plus scale 1.02 and a #006bff border — the only accent. A single direct-DOM rAF loop writes both canvas and card transforms with no React state, sleeps once a steady hover position has fully caught up, and is paused by an IntersectionObserver while the lattice is off screen. pointerleave eases everything back down to the ambient idle state with the same interruptible lerp. prefers-reduced-motion: static flat lattice, static cards, hover reduced to a border highlight. Zero dependencies."
      }
    },
    {
      "name": "header-scroll-pill",
      "type": "registry:ui",
      "title": "Header Scroll Pill",
      "description": "A page header that morphs between a full-width quiet nav bar at the top and a floating centered pill once scrolled — spring-like width/radius/padding morph, section label roll, and a progress hairline, with hysteresis so it never flutters at the threshold and a fast upward scroll flicks it back open with overshoot.",
      "files": [
        {
          "path": "registry/core/header-scroll-pill/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/header-scroll-pill.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "header",
          "navigation",
          "scroll",
          "sticky",
          "morph",
          "pill",
          "chrome"
        ],
        "instruction": "Build a scroll-reactive site header that behaves like a 'dynamic island': at scrollY≈0 it renders as a full-width, quiet nav bar (left: font-mono wordmark; right: a row of section links; a 1px --border bottom edge) and once the page scrolls past a threshold it morphs into a smaller, centered, floating pill (fixed width around 300px, fully rounded, vertically inset from the viewport top) whose only visible content becomes the current section's label plus a thin progress hairline along its bottom inner edge. The morph is driven by a single continuous 'openness' value in [0,1] (1 = full bar, 0 = compact pill) computed every frame inside one requestAnimationFrame loop off window.scrollY, and written directly to the pill container's inline styles (width, height, borderRadius, paddingInline, marginTop/translateY) via a ref — never through React state per frame, so there is zero re-render on the scroll hot path. React state is reserved for things that change rarely: which section is active, whether reduced motion is on, whether the pill is temporarily force-expanded by focus. Use a hysteresis band around the compact/expanded threshold: define CLOSE_Y (e.g. 72px) and a lower OPEN_Y (e.g. 40px); once compact, only re-expand once scrollY drops below OPEN_Y, and once expanded, only compact once scrollY exceeds CLOSE_Y — resting exactly on one pixel value between them can never cause the header to flicker between states every frame. The openness value itself eases toward its target with an exponential approach (1 - exp(-rate*dt)) rather than snapping, giving it a soft spring feel without a physics library. Detect a 'fast upward scroll' by comparing this frame's scrollY to last frame's: if the negative delta exceeds a threshold (a big upward jump in one rAF tick, e.g. a user flinging the scrollbar or hitting Home), immediately drive the openness target to 1 and layer a brief (~250ms) overshoot pulse on top — a small extra scale (e.g. up to 1.02) that decays back to 1 via a sine ease — so the reopen visibly overshoots and settles rather than just snapping open. When the active section changes (track via IntersectionObserver over the caller's section ids, or accept a controlled `activeId` prop), the compact-state label performs a 1-line vertical roll: the outgoing label's text node is swapped and the label span is translated from -100% back to 0% over ~260ms with an ease-out-expo curve, so it reads as a roll, not a crossfade. The progress hairline is a plain absolutely-positioned div whose width tracks the active section's index/(count-1) as a percentage, sitting on top of a full-width 1px --border baseline div — deliberately NOT an SVG stroke-dasharray/pathLength trick, since combining pathLength with vectorEffect=\"non-scaling-stroke\" for a dash-based progress indicator is broken in Chromium (the dash computes in screen space while every attribute still reads correct) — two stacked divs are simpler and correct. Keyboard reachability: the pill container is a navigation landmark (role=navigation, aria-label='Primary'); when any link inside it receives focus (a wrapping onFocus/onBlur pair checking relatedTarget), force the openness target to 1 regardless of scroll position so a keyboard user always sees the full link list while tabbing through it, then release back to scroll-driven behavior on blur. Hover on the pill (only meaningfully visible while compact) lifts it 1px via a translateY and brightens its border color from --border to --foreground; this is layered on top of the scroll-driven transform, not a replacement for it. prefers-reduced-motion: skip the rAF loop and the continuous openness interpolation entirely — instead listen to scroll and set exactly two discrete states (full bar at scrollY<=72, compact pill above it), transitioning between them with a plain CSS opacity crossfade only (no width/radius animation, no overshoot, no vertical roll — the label just swaps text instantly). The demo page is tall (~3600px) with 4 real `<section id>` elements the header's own IntersectionObserver watches, and self-drives by scripting a sequence of window.scrollTo checkpoints (top, each section in turn, then a fast double-step scroll back to top to demonstrate the flick-open overshoot) on a timer, so the component demonstrates its own full behavior with no user input. Zero dependencies."
      }
    },
    {
      "name": "heatmap-year-stipple",
      "type": "registry:ui",
      "title": "Heatmap Year Stipple",
      "description": "A GitHub-style year activity calendar where intensity is stipple density, not color — every day cell holds a handful of deterministically jittered ink dots, denser for more activity, like an engraved print. Hovering or arrow-keying through days opens a zoomed loupe with the exact count, and each new day's dots spring into the loupe with a brief ink-settle scatter, like fresh ink landing on paper.",
      "files": [
        {
          "path": "registry/core/heatmap-year-stipple/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/heatmap-year-stipple.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "calendar",
          "activity",
          "heatmap",
          "svg",
          "keyboard-navigation",
          "tooltip",
          "accessibility",
          "data-visualization"
        ],
        "instruction": "Build a GitHub-style year contribution calendar where activity intensity is communicated by STIPPLE DENSITY rather than color — a monochrome ink-on-paper engraving metaphor using only var(--foreground) dots on var(--background). Layout: a week-columns x weekday-rows grid (Sunday-aligned like GitHub, 16px cells, 4px gaps), built from a `values` prop (Record<ISO date string, activity count>) and an `endDate` prop (defaults to today) covering the trailing ~365 days rounded out to full weeks (so 52-53 columns depending on alignment). Render everything in one SVG for the whole grid: month abbreviations in Geist Mono above the column where each new month's first week starts, and 'Mon'/'Wed'/'Fri' row labels to the left (GitHub's convention of skipping Sun/Tue/Thu/Sat labels). Each day's dot count is derived from its value via `count = value<=0 ? 0 : min(8, 1 + round((value/max)*7))` where max is the largest value in the visible range. Dot POSITIONS are generated in normalized [0,1] space by a small deterministic PRNG (an FNV-1a-style string hash of the ISO date feeding a mulberry32 generator) — same date always produces the same pattern — and only scaled to actual pixel coordinates at render time (position = margin + unit * (cellSize - 2*margin)). This normalized-first design is what lets the loupe be a literal zoomed copy: it re-renders the SAME normalized units at a larger cell size (68px vs 16px) rather than regenerating a new pattern. Every day cell is a focusable SVG rect (role=\"button\", roving tabindex — exactly one cell has tabIndex 0 at a time, matching whichever cell is currently 'active'; every other cell is -1) with an aria-label stating the fact directly as text: 'N contributions, Mon D' (or 'No contributions, Mon D' style pluralization for 0/1). Arrow keys move the active cell and call .focus() on the new one: Left/Right move to the same weekday in the adjacent week (column ±1), Up/Down move to the adjacent weekday within the same week (row ±1), resolved by column/row rather than flat-index arithmetic so a move at the top/bottom edge of a week column stops instead of wrapping diagonally into the neighboring week — both clamped to the valid date range and to cells that actually exist (no wrapping onto out-of-range trailing cells in the final partial week). Hovering OR focusing a cell fills a fixed-footprint loupe panel in a permanently reserved slot BESIDE the grid (never overlaid on top of it — an overlay large enough to be legible also covers the cell it's magnifying, which reads as the hover jumping elsewhere) showing that day's stipple pattern at ~4x scale plus a Geist Mono caption reading '14 contributions - Mar 4' (or the exact singular/plural + date for whatever cell is active). The panel occupies the same box whether or not anything is hovered — only its contents toggle via `visibility` — so the component's own size never changes on hover and no cell ever shifts under the cursor. The loupe's entrance is a real animation — scale 0.55->1 and opacity 0->1 over 160ms with an ease-out-expo-style curve, replayed on every new cell via a React `key` keyed to the date so the keyframe restarts each time. Hovered/focused cells also get a slightly heavier cell-border stroke than the resting hairline var(--border), and keyboard focus additionally gets a var(--accent) focus-visible outline distinct from the hover stroke. The main grid's stipple pattern itself is never animated — only the loupe moves. The loupe has its own second, distinct entrance: every time it re-magnifies a new cell, its dots don't just appear at rest — each one starts flung a short distance from its resting position (a per-dot offset drawn from its own deterministic PRNG stream, seeded off the same ISO date so it's stable and never Math.random()) and springs into place on an ease-out-expo curve, staggered a few milliseconds apart per dot, reading as ink landing and settling on paper rather than a uniform pop-in. This settle runs longer than the loupe box's own 160ms zoom-in (roughly 460ms, vs the box's scale/opacity entrance) so the two are legible as two separate events instead of one blurred bloom, and it fires on every hover AND every keyboard-focus change alike (both drive the same `hoverIndex`), so keyboard users see identical motion to pointer users. The scattered dots are clipped to the loupe box's own rounded-rect bounds so none can visually escape it even at their widest starting offset. Implemented with a CSS `@keyframes` animation driving `transform: translate()` from a per-dot `--sx`/`--sy` custom-property starting offset to `(0, 0)`, `animation-delay` staggered per dot index, and `animation-fill-mode: backwards` so a delayed dot sits at its scattered start position rather than its resting one during the delay — no React state, no per-frame JS, the browser's own compositor drives it. `prefers-reduced-motion: reduce` removes both the loupe's zoom-in and its ink-settle animation entirely (dots simply render at their resting position) while leaving the stipple pattern and every non-motion interaction untouched. No color scale anywhere, no canvas — pure SVG, zero dependencies."
      }
    },
    {
      "name": "hero-ascii-wordmark",
      "type": "registry:ui",
      "title": "Hero ASCII Wordmark",
      "description": "Hero wordmark rendered as ASCII block-letters, lit by the pointer like a torch over a density ramp.",
      "files": [
        {
          "path": "registry/core/hero-ascii-wordmark/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-ascii-wordmark.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "hero",
          "ascii",
          "type",
          "canvas",
          "cursor"
        ],
        "instruction": "A display-type hero block: the `text` prop is rasterized through a hand-authored 5x7 block font (A-Z, 0-9, space, '-', '.', '!'; unsupported characters render blank) into a monospace character grid drawn on canvas, one glyph from the density ramp ' .:-=+*#%@' per lit pixel of the font bitmap. At rest every lit cell renders the same mid-high ramp character so the wordmark is a clean, fully legible block-type lockup on its own — the ramp only starts varying once a pointer is present. The pointer is a light source: an eased cursor position (12% lerp per frame, so it trails rather than snaps) feeds a Gaussian boost that pushes nearby cells up the ramp toward denser glyphs, while cells beyond ~1.15x the radius are pushed down the ramp toward thinner glyphs by a linearly-ramped 'far' term, and both terms are scaled by an eased 0..1 'engagement' value that rises on pointer-enter and decays on pointer-leave so the whole effect fades in and back out rather than snapping to the resting state. The canvas measures its own height from the wrapping div's width via ResizeObserver (width / total-grid-columns = cell size, height = cellSize * 7), so it never needs an explicit container height and reflows on any width change. Direct-DOM rAF loop mutating refs only; glyph color read via getComputedStyle on mount and re-read through a MutationObserver on `<html>`'s class attribute (the site's theme toggle flips `.dark` live, with no remount, so a prefers-color-scheme listener alone would miss it); one static resting frame under prefers-reduced-motion. The canvas is aria-hidden with the literal text exposed via a sr-only sibling span."
      }
    },
    {
      "name": "hero-dipole-field",
      "type": "registry:ui",
      "title": "Hero Dipole Field",
      "description": "Full-bleed hero where the headline exists twice: crisp DOM type above, and beneath it a canvas field of iron-filing strokes solved from a two-pole dipole field, so the type visibly iron-files into existence as the cursor approaches and CTA hover bends the whole field toward the button.",
      "files": [
        {
          "path": "registry/core/hero-dipole-field/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-dipole-field.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "hero",
          "vector-field",
          "dipole",
          "particles",
          "cursor",
          "letter-mask",
          "text",
          "ambient"
        ],
        "instruction": "A full-bleed hero where the headline exists twice: crisp DOM type above, and beneath it a Canvas 2D field of iron-filing strokes solved from a two-pole dipole field (cursor pole + fixed anchor pole behind the primary CTA). The canvas sits absolutely under the DOM copy with pointer-events none so every CTA stays interactive. A letter-mask is built by drawing the headline text into an offscreen canvas using the font shorthand read from the live DOM headline via getComputedStyle, aligned and x-scaled to each line span's measured rect; ~1,800 filing positions are rejection-sampled inside the mask plus ~600 sparse ambient filings outside (counts halved when devicePixelRatio >= 2 and viewport < 900px), each filing a 6-10px line segment stored in structure-of-arrays Float32Arrays. Field model: cursor pole strength 1.0 (radial plus mild tangential swirl), anchor pole 0.7 (pure radial, its position computed as a getBoundingClientRect offset from the container origin, never page coords); contribution = strength / max(r, 48)^2; filing target angle = atan2 of the summed vector, chased per frame with angle += delta * (1 - exp(-dt*10)) for a critically-damped no-overshoot feel. CTA hover/focus springs the anchor pole 0.7 to 1.4 over 250ms ease-out-expo; pointerleave decays the cursor pole to 0 over 600ms; both are time-based tweens whose duration is the forced-settle deadline (under the 800ms budget), so no spring can hunt forever. After 500ms pointer idle, angles drift on 2-octave value noise at plus/minus 8 degrees and 0.1Hz inside a 12s breath envelope with a real rest phase. Ink is parsed from getComputedStyle tokens at mount and re-derived live via a MutationObserver on documentElement class: mask filings alpha 0.25 to 0.9 by field magnitude in --foreground, ambient filings 0.06 to 0.18 in a --muted/--border mix, and --accent permitted only on the ~5% of filings nearest the anchor pole while the CTA is hovered or focused; strokes are batched into 16 alpha buckets per color group so the draw loop sets style once per batch, with a full clear + redraw each frame (no destination-in accumulation). The canvas is sized as a replaced element: style.width/height set explicitly from the measured rect, backing store scaled by DPR (clamped 2). The rAF loop sleeps when the max per-filing angular delta drops below 0.001 rad, no pole tween is active, and the ambient drift is in its rest phase (a timeout wakes the next breath); IntersectionObserver pauses offscreen, zero-size containers bail, ResizeObserver resamples the letter-mask, document.fonts.ready rebuilds it after webfont swap, and every listener, observer, timer, and rAF is torn down on unmount. Under prefers-reduced-motion one static solved frame renders (anchor pole only, cursor pole off) and the loop never starts. Demo is the full weighted composition: full-bleed hero on bg-background with mono eyebrow, 2-line Geist Sans 600 display headline (the mask source), muted sub-copy, accent primary CTA + ghost secondary (rounded-sm, token-relative hover/focus), and a thin bordered strip of three mono stats."
      }
    },
    {
      "name": "hero-long-exposure",
      "type": "registry:ui",
      "title": "Hero Long Exposure",
      "description": "Hero card whose canvas behaves like a long-exposure photograph: cursor movement burns light streaks that never clear per frame, only decay on a 4s half-life, accumulating a light painting of the session.",
      "files": [
        {
          "path": "registry/core/hero-long-exposure/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-long-exposure.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "hero",
          "cursor",
          "trail",
          "long-exposure",
          "accumulation",
          "ambient",
          "blend-mode"
        ],
        "instruction": "A hero card whose background canvas behaves like a long-exposure photograph — persistence, not live emission. Two offscreen accumulation buffers (never cleared per frame) hold pure white intensity strokes and are composited onto a visible DPR-clamp-2 canvas sitting behind fully clickable DOM hero copy (canvas pointer-events none, pointer tracked on the hero root). Pointer samples are exponentially smoothed and joined as quadratic curves through midpoints, line width 1.5 + clamp(speed/1200, 0, 1.5) px with shadowBlur 12 for glow; each segment lands in buffer A with 60% probability (primary tint) or buffer B (secondary). Decay is framerate-independent: elapsed real time is accumulated and, every ~150ms of it, destination-out black is painted at alpha = 1 - exp(-dt/tau) for that accumulated dt (batched rather than per-frame, since canvas alpha is 8-bit and a single frame's erosion rounds away to nothing), tau = halfLife/ln2 (~4s half-life), and strokes draw at globalAlpha 0.35 so repeated passes asymptote instead of whiting out. Tint is applied at composite time via a source-in fill of each intensity buffer, stacked source-over then lighter — so the recolor pass is inherent and a theme flip re-tints the whole accumulated exposure on the next composite. Theme mode is chosen from the derived --background luminance via getComputedStyle at mount and re-derived live through a MutationObserver on documentElement class: dark composites with CSS mix-blend-mode screen using --accent (60%) and --foreground (40%) tints; light flips to multiply with tints from --muted and a --border/--muted mix so trails darken instead of vanishing on white. The rAF loop tracks an energy scalar (sum of recent stroke alpha, decaying with the same tau) and sleeps when no pointermove for 500ms AND energy < 0.02 (wiping the invisible residue), wakes on pointermove, pauses offscreen via IntersectionObserver; ResizeObserver resizes buffers preserving the exposure via a scaled copy and guards zero-size containers; all observers and listeners torn down on unmount. On load a scripted 2s figure-eight (Lissajous) drives the pointer so the exposure photographs before any real input; a real pointermove cancels it. Under prefers-reduced-motion: one seeded synthetic static exposure along the figure-eight at low alpha, no accumulation, no loop, re-rendered on resize and theme change. Demo is a full hero composition: padded surface card, mono eyebrow, Geist Sans 600 tight-tracked H1, muted subhead, dual CTA row (accent primary with accent-hover, ghost secondary with border-foreground/20 hover and accent focus ring), and a 4-mark grayscale trust-logo row."
      }
    },
    {
      "name": "hero-particles-webgl",
      "type": "registry:ui",
      "title": "Hero Particles WEBGL",
      "description": "Full-viewport hero with a cursor-reactive WebGL particle field and staggered text reveal.",
      "files": [
        {
          "path": "registry/core/hero-particles-webgl/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-particles-webgl.tsx"
        }
      ],
      "dependencies": [
        "three",
        "@react-three/fiber",
        "motion"
      ],
      "meta": {
        "collection": "core",
        "tags": [
          "hero",
          "webgl",
          "particles",
          "text-reveal"
        ],
        "instruction": "A dark full-viewport hero section: a WebGL particle field of thousands of small gray dots that drift gently, and the cursor acts as a reversed-polarity magnet — dots within roughly 170px are displaced outward along the (dot - cursor) direction with a smooth falloff, opening an elastic void that trails the pointer and springs back to rest when it leaves. No glow, no halo, no linking lines; brightness and dot size never change. Behind it sits a staggered spring-physics text reveal (mono eyebrow, large tight headline, muted subline) and a single accent CTA, with a radial vignette keeping edges quiet. Strictly monochrome dots from the muted token, no accent. Static dot-grid fallback without WebGL, reduced-motion respected (drift and repulsion frozen)."
      }
    },
    {
      "name": "histogram-live-grain",
      "type": "registry:ui",
      "title": "Histogram Live Grain",
      "description": "Live distribution instrument where the histogram IS the samples: each arrival falls as a grain into its bin and stacks, the heap visibly re-settles as the rolling window slides, and P50/P90 fences ride the same scale.",
      "files": [
        {
          "path": "registry/core/histogram-live-grain/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/histogram-live-grain.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "data-viz",
          "histogram",
          "distribution",
          "live",
          "latency",
          "percentile",
          "monitoring",
          "accessibility"
        ],
        "instruction": "A live histogram built grain-by-grain instead of bar-by-bar: the `samples` prop is a rolling window of `{ id, value }` objects, and every sample renders as one small rounded rect keyed by its stable id. A new arrival mounts 14px above the frame and falls to the top of its bin's stack on a 500ms transform transition with a slightly-overshooting cubic-bezier(0.3,1.25,0.5,1), so it lands with a settle rather than teleporting; when the window slides and old samples leave, every grain above them re-computes its stack position and rides the SAME transition downward — the heap visibly re-settles, which is the honest visual for 'old data aged out'. The DOMAIN IS FIXED by `min`/`max` props (never auto-ranged: a live instrument whose scale moves on every arrival makes everything else move too), values are clamped into it, and grain height adapts (5px down to 1.5px) as the tallest bin grows so the heap compresses before it ever clips. Each grain carries a deterministic per-id alpha (FNV-1a hash → 0.5–0.9 of --foreground) so the heap has granular texture rather than flat fill; only the newest grain renders at full opacity. PERCENTILE FENCES: P50 and P90 are computed from the current window (linear-interpolated quantiles) and drawn as 1px vertical hairlines with mono labels that translate along the scale on a 500ms ease-out — a latency burst visibly drags the P90 fence right, then it creeps back as the burst ages out of the window. INSPECTION: pointer movement over the frame highlights the bin under the cursor with a tokened wash and swaps the footer readout to that bin's range and count ('340–360ms · 6 samples'); the frame is a focusable role=group with an accessible name, ArrowLeft/ArrowRight step the inspected bin from the keyboard, Escape clears it, and focus shows a visible accent outline (the only place --accent appears). Header shows the label and the latest value in tabular-nums mono; footer shows min/max domain labels and a p50/p90/n summary when nothing is inspected. A visually-hidden role=status region announces median/P90/count every 4 seconds — throttled deliberately, because announcing every arrival is noise, not signal. REDUCED MOTION: fall, re-settle and fence transitions all drop via motion-reduce so grains and fences place instantly; the instrument stays fully readable and inspectable. All color is tokens (--foreground grains and fences, --border frame and baseline, --muted labels, --background frame fill); no canvas, pure DOM/CSS with one ResizeObserver for the frame width, zero dependencies."
      }
    },
    {
      "name": "hover-card-dwell",
      "type": "registry:ui",
      "title": "Hover Card Dwell",
      "description": "A hover-card trigger that has to be wound before it opens: a hairline arc coils clockwise around a 5px dot while the pointer dwells, and drifting away mid-wind visibly unwinds it back instead of firing.",
      "files": [
        {
          "path": "registry/core/hover-card-dwell/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hover-card-dwell.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "hover-card",
          "popover",
          "link-preview",
          "dwell",
          "svg",
          "gesture",
          "accessibility"
        ],
        "instruction": "Build a hover-card trigger whose gate you can see charging: a hairline SVG arc (circle, r=5, 1px stroke, --muted, pathLength=1 so stroke-dashoffset is normalized 0..1 with no circumference math) coils clockwise around a small 5px filled dot appended right after the trigger link's label. Drive the arc's progress with dwell time on a single rAF loop, not a CSS transition — the wind has to be interruptible mid-frame. On pointerenter of the trigger, progress ramps 0 to 1 over 350ms (dashoffset = 1 - progress, so 1 = empty/hidden and 0 = the full ring drawn; rotate the circle -90deg so the sweep starts at 12 o'clock and reads clockwise). Reaching progress 1 opens the card — a plain --background surface, 12px radius, 1px --border, positioned directly below the trigger in normal DOM flow (no portal) so its content simply falls into the tab order in document position rather than needing to be spliced into it — with a spring-style scale-in from 0.95 (a bouncy cubic-bezier(0.34, 1.56, 0.64, 1) over ~220ms reads as 'springs open' without needing real spring physics), while the now-fully-wound arc plays a release: a fast ~117ms (350/3) ease-out-expo transition back to dashoffset 1, the visual discharge of the intent that just fired. If the pointer leaves the trigger before progress reaches 1, the same rAF loop reverses direction and unwinds progress back to 0 at 2x the winding rate — the arc visibly retreats — and the card never mounts at all: no flicker, no half-open state. Hovering the open card itself (not just the trigger) keeps it open, with a short ~160ms grace after leaving either one before it actually closes, so crossing the small gap between trigger and card doesn't flicker-close. Keyboard gets no coil at all: focusing the trigger arms a plain 300ms dwell timer that opens the card with zero arc animation (a keyboard user already stated intent by tabbing here — gating them behind the same visual charge a mouse gets would be a real accessibility bug), and pressing Enter — or a decisive click, since aria-haspopup=\"dialog\" means this link's primary action is to invoke the popover rather than navigate — skips even that timer and opens immediately; both are idempotent against an already-open card rather than toggling it shut. Escape closes the card from anywhere (focus may already be inside it) and returns focus to the trigger. Every color is a token (--background --foreground --muted --border --accent) with zero dependencies and no canvas — DOM and SVG only. Under prefers-reduced-motion the coil is hidden entirely and the release skipped; hovering opens the card after the same 350ms dwell window via a plain opacity fade instead of the scale-spring, so the timing stays consistent but nothing is rendered mid-motion."
      }
    },
    {
      "name": "image-crop-mat",
      "type": "registry:ui",
      "title": "Image Crop Mat",
      "description": "Image cropping as a passe-partout — four mat boards slide over a photo to define the crop window, snap to ratio presets with a settle, and show a rule-of-thirds grid only while dragging.",
      "files": [
        {
          "path": "registry/core/image-crop-mat/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/image-crop-mat.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "image",
          "crop",
          "photo",
          "editor",
          "gesture",
          "accessibility"
        ],
        "instruction": "Build an image-crop control styled as a physical passe-partout (picture-frame mat), not a dashed-rectangle overlay with corner squares. A fixed-size stage (e.g. 480x300 display px, representing a photo scaled down from a larger 'real' resolution — pick a NATURAL_SCALE constant, e.g. 4x, and multiply every display-px measurement by it for the readout and the `onChange` payload, so the numbers look like a real photo's pixel dimensions rather than tiny CSS px values) holds a placeholder photo (a small inline monochrome SVG landscape rendered via a real `<img src=\"data:image/svg+xml,...\">`, alt=\"\", aria-hidden — never `dangerouslySetInnerHTML` for static markup, and never a literal photo asset dependency) and four mat boards (`bg-background` panels — real theme tokens, since the mats are UI chrome even though the photo's own pixels are exempt content) positioned top/right/bottom/left to cover everything outside the current crop rect, each with a hairline border on its inner edge and a soft inset box-shadow reads as pressing gently onto the photo.\n\nCrop state is one rect `{top, left, width, height}` in display px. Each mat's size is derived from it (top mat height = rect.top, bottom mat spans from rect.top+rect.height to the stage bottom, etc.) and painted via direct ref `style` writes, not by re-rendering four positioned divs from React on every pixel of drag — React state updates on drag are fine for cheap re-renders elsewhere (the readout text), but the four mat elements' actual position must be imperative writes so dragging never taxes React's reconciler per pointermove.\n\nDragging: a thin invisible 'grip' strip sits along each edge (and a small square at each corner, combining two edges) as the actual pointer-drag target — not the mat itself, so the hit area stays a predictable few px regardless of mat size. During an active drag, tracking is 1:1 with the pointer (a deliberate simplification — true continuous 'paper friction' physics on every pointermove was cut for scope; if revisited, exponential-smoothing the painted rect toward the raw pointer target each rAF tick would add it). Ratio presets (1:1, 4:5, 16:9, Free — real buttons, `aria-pressed` reflecting the currently-matching ratio) DO get the explicit 'settle' the brief calls for: fit the largest centered rect at that ratio within the stage, then paint it with a short (~180ms) no-overshoot ease-out transition (a stand-in for 'critically damped, no bounce' — an actual spring simulation is unnecessary to read as non-bouncy). Free does not force a resize, it just relabels. Reduced motion drops that transition — every preset and drag-release lands instantly.\n\nThe crop window shows a rule-of-thirds grid (two vertical hairlines via CSS pseudo-elements, never SVG dashes) ONLY while a drag is in progress (a `dragging` boolean toggling a class) — it must not be visible at rest.\n\nReadout: a Geist Mono line below the stage reads like `1240 x 775 — 16:9` (or 'Free' when the current rect doesn't match a preset within a small tolerance), computed from the NATURAL-scaled width/height.\n\nKeyboard: each edge doubles as a labeled `role=\"slider\"` (`aria-label` 'Top edge'/'Right edge'/etc., `aria-valuemin/max/now` in natural-scale units, `tabIndex=0`) layered on the same grip strip element used for pointer dragging. Arrow keys nudge that edge by 1 display px, Shift+Arrow by 10 — for simplicity every edge responds to both axis pairs (ArrowUp/Left decrease, ArrowDown/Right increase) rather than requiring the 'correct' physical axis per edge, since the meaningful accessibility contract is 'reliably nudge each edge independently by a known amount,' not which specific key increases vs decreases.\n\nHover on a grip strip reveals a thin (2px) `var(--accent)` line along that edge via a CSS `:hover`/`:focus-visible` rule (no JS), plus the matching resize cursor (`ns-resize` for top/bottom, `ew-resize` for left/right, corner cursors for corners). `onChange` fires with `{x, y, width, height, ratio}` in natural-scale units on every committed change — preset click, drag release, and keyboard nudge — not continuously mid-drag. No dependencies."
      }
    },
    {
      "name": "input-focus-membrane",
      "type": "registry:ui",
      "title": "Input Focus Membrane",
      "description": "Text input whose focus ring is a living membrane: a noise-displaced canvas loop that breathes on idle, dilates on focus, sends a peristaltic pulse from the caret on each keystroke, constricts and quivers on error, and exhales once on valid submit.",
      "files": [
        {
          "path": "registry/core/input-focus-membrane/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/input-focus-membrane.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "input",
          "form",
          "canvas",
          "noise",
          "membrane",
          "micro-interaction",
          "focus-ring"
        ],
        "instruction": "Build a text input whose focus ring is a living membrane. Keep a real, fully native <input> (focus, selection, autofill intact, aria-invalid mirroring the error prop) and overlay a decoration-only aria-hidden <canvas> (position absolute, inset -24px, pointer-events none). Critical sizing: a canvas is a replaced element, so absolute + inset does NOT stretch it — on every resize set both the backing store (css size * devicePixelRatio, dpr capped at 2) AND style.width/height explicitly, or the membrane renders dpr-times too large as a giant polygon. Compute the membrane as a closed loop of 96 points parameterized around the input's rounded-rect perimeter (6px corner radius, positions plus outward normals precomputed on resize), each point displaced along its outward normal by 2-octave value noise sampled on a closed ring (the noise2(s*3, t*0.15) frequency mapped onto a circle of radius 3/2pi so the loop has no seam), displacement clamped to the 24px overhang so the ring always hugs the field. Draw the displaced ring as a closed Catmull-Rom spline (bezier segments, tangents (p2-p0)/6) — 96 linear segments leave each corner arc with ~1 sample and read as sharp angles. Stroke colors come from CSS tokens resolved via getComputedStyle on documentElement at mount (--muted, --foreground, --error with #ea001d fallback, normalized through a scratch canvas fillStyle) and re-resolved by a MutationObserver watching the documentElement class, so theme flips restyle the membrane live. Idle breath: 1.5px amplitude on a 4.5s sine period. Focus: base offset glides 0 to +6px over 350ms on cubic-bezier(0.22, 1, 0.36, 1) via a Newton-Raphson bezier solver, breath amplitude rises to 2.5px, stroke lerps the muted token toward the foreground token. Keystroke: on each input event, map the caret x (measureText with the input's computed font, selectionStart in try/catch with end-of-value fallback for email inputs) to the top-edge perimeter position and launch a Gaussian pulse (sigma 0.06 normalized perimeter, 5px amplitude) as two wavefronts traveling opposite directions at 1.2 perimeters/s, decaying with tau 0.6s. Error prop: constrict -3px, quiver at 14Hz/1.2px for 500ms on the rising edge, stroke blends toward the error token at 0.6 alpha (status use only). Valid submit (bumped exhaleKey prop): breath amplitude spikes +8px and decays over 700ms cubic ease-out. Stroke stays 1px. All animation runs on a direct-DOM rAF loop with locals/refs only, no React state on the hot path; the loop sleeps when the input is blurred and every transient has settled below 0.05px, waking on focus, input, error, exhale, or theme changes, and an IntersectionObserver parks it entirely while the field is offscreen. All listeners and the Resize/Mutation/Intersection observers are torn down on unmount. prefers-reduced-motion: no canvas, standard 1px border-border (error state switches the border to the error red) with the default focus ring. Zero dependencies; Geist dark tokens (bg-surface field, text-foreground, rounded-sm)."
      }
    },
    {
      "name": "keymap-ascii-heat",
      "type": "registry:ui",
      "title": "Keymap ASCII Heat",
      "description": "An ASCII keyboard layout that accumulates real ink density per key as you type into it — each keystroke inks the key, heat decays on an exponential half-life, and the legend rescales to whichever key is currently hottest.",
      "files": [
        {
          "path": "registry/core/keymap-ascii-heat/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/keymap-ascii-heat.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "keyboard",
          "heatmap",
          "ascii",
          "mono",
          "typing",
          "data-visualization",
          "live"
        ],
        "instruction": "Build <KeymapAsciiHeat placeholder label className> around a real, visible <input type=text> that the user actually types into (aria-label from the `label` prop). MECHANISM: the keydown handler lives directly on the input's own onKeyDown (never a document-level listener gated on document.activeElement — an autoplay-driven demo runs inside an inert subtree where focus never truly lands, so a focus-gated listener would leave the card dead; binding straight to the input's React event fires regardless). Every keydown whose key resolves to a letter or Space maps to a key id (A-Z, or 'SPACE') and calls pulse(key): a Map<string,{ink,lastAt}> in a ref holds each key's raw ink and the timestamp it was last touched; pulse reads the PREVIOUS entry, decays it forward to now via ink * 0.5^(dt/7000) (7s half-life), adds 1, and stores the new {ink,lastAt} — so heat is a pure function of elapsed time, never a per-frame accumulator, and 'what is this key's ink right now' can be asked at any instant without having run every frame in between. A throttled rAF loop (repaints at most every ~90ms) recomputes every key's CURRENT decayed value from its stored {ink,lastAt} and the loop's own `now`, finds the live max across all keys, and stores both in React state; the loop sleeps (cancels itself) once every key's decayed value has fallen under a small epsilon, and pulse() wakes it again on the next keystroke — so there is no animation running while nobody is typing. RENDERING: each key is a fixed-size cell showing its letter in the foreground, always as the ONLY glyph in the cell — heat never draws a second character on top of the letter (that overlay was a real bug: hammering A/S/D/F used to stack a heavy ramp glyph directly over the letterform and render it illegible). Instead each cell has an absolutely-positioned fill behind the letter, a plain --foreground rect whose opacity is that key's current decayed ink divided by the LIVE max across all keys (0 at rest, up to ~0.55 at the hottest key), so heat reads as density/opacity, not as stacked ink. A legend line beneath the keyboard still prints the 10-step ASCII ramp (' .:-=+*#%@') alongside 'max=<current max ink, one decimal>' as a scale reference, and a second readout beside it names whichever key is currently hovered along with its live decayed ink value, updating on pointerenter/leave — since the keyboard is otherwise a static picture at rest, this hover readout is what makes hover state visibly differ from resting. REDUCED MOTION: skip the rAF loop outright — pulse() still runs the exact same decay math and still updates state, it just does so synchronously inside the keydown handler instead of on a subsequent repaint tick, so a key's ink is still correct at every keystroke, there is simply no continuously-fading glyph between keystrokes. A11Y: the input is the only real control and carries the accessible name; every key cell is a plain aria-hidden decorative div (not a button, not tabbable) since it represents live derived state rather than something to activate — Tab reaches the input, which is the control the 'Tab must reach something' rule cares about. No gate: heat can only be produced by real keystrokes, which the verify gate's single-click model cannot simulate — the `autoplay: type` descriptor is what exercises this component's characteristic non-resting state instead, and its screenshot is what an owner should look at, not a synthetic gate click. Colors are token-only: --foreground at full and reduced opacity for the glyph/letter, --border/--background/--surface for the chrome, --accent only on the input's focus ring. No canvas, zero dependencies."
      }
    },
    {
      "name": "lens-ascii-magnify",
      "type": "registry:ui",
      "title": "Lens ASCII Magnify",
      "description": "A circular lens that resolves the text under it into a denser dot-matrix of ASCII glyphs — every character it passes over is redrawn as its own 5x7 grid of ramp characters, more glyphs per glyph rather than the same glyph bigger.",
      "files": [
        {
          "path": "registry/core/lens-ascii-magnify/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/lens-ascii-magnify.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "lens",
          "magnifier",
          "ascii",
          "text",
          "mono",
          "canvas",
          "cursor"
        ],
        "instruction": "A circular lens, driven by a `text` prop rendered as real plain DOM text (one `<span>` per character, plain content, no aria-hidden anywhere on the real text — it's never hidden, so accessibility needs nothing special here). Layered on top is a purely decorative `aria-hidden` `<canvas>` plus a DOM ring div; together they are the lens. Every character in `text` has a hand-built 5x7 dot-matrix representation (a compact bitmap font covering A-Z, 0-9, space and basic punctuation; an unmapped character falls back to a light diagonal-hatch placeholder rather than rendering blank). On every frame, for each character whose measured `getBoundingClientRect()` falls within the lens radius of its current center, the canvas fills the lens circle's background with a `--background`-token flat fill (occluding the flat glyph underneath) and then redraws that character as its OWN 5x7 grid of cells, each cell either a solid ramp glyph (`--foreground` at full alpha, for an 'on' bit) or a faint one (28% alpha, for an 'off' bit) — that grid is strictly finer than the single flat character it replaces, which is the entire mechanic: MORE glyphs resolving one glyph, not the same glyph scaled up (that's slider-loupe's job) and not a channel-split refraction (text-prism-split's job). The lens position chases the pointer with an exponential lag (10/s time-constant) via one direct-DOM `requestAnimationFrame` loop that sleeps once settled, wakes on `pointermove`, and returns to a PARKED position at the text block's own center on `pointerleave` — so the resting/default screenshot already shows the lens sitting mid-block resolving whatever text is there, not a plain, unmagnified paragraph waiting for a cursor. All canvas ink (`--foreground` fill/background fill) and the mono font family are read via `getComputedStyle` at mount and re-derived on a `MutationObserver` watching `documentElement` class changes, so both themes render correctly with no baked-in hex. `prefers-reduced-motion: reduce` disables the chase loop and the pointer-follow entirely: the lens renders once, parked at center, with no rAF running — still legible as the concept, just not animated. ResizeObserver and IntersectionObserver keep it correctly sized and paused off-screen. Zero dependencies."
      }
    },
    {
      "name": "listbox-sticky-groups",
      "type": "registry:ui",
      "title": "Listbox Sticky Groups",
      "description": "A grouped listbox for long lists (timezones, countries, currencies) where every passed group header sticks in place, stacking into overlapping 20px slivers you can still read and click — a live table of contents made from the headers themselves.",
      "files": [
        {
          "path": "registry/core/listbox-sticky-groups/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/listbox-sticky-groups.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "select",
          "listbox",
          "form",
          "sticky",
          "scroll",
          "navigation",
          "keyboard",
          "grouped-list"
        ],
        "instruction": "A grouped single-select listbox built for hundreds of options. Structure: one <ul role=\"listbox\" tabIndex=0> containing one <li role=\"group\" aria-labelledby={headerId} className=\"contents\"> per group; each group holds a real <button> heading (position: sticky, inline style top: groupIndex * 20px, explicit z-index groupIndex+1 so later headers reliably paint over earlier ones) followed by a plain <ul role=\"presentation\"> of <li role=\"option\"> rows. The group <li> MUST be display:contents (Tailwind `contents`) rather than a normal box: a sticky element's containing block is its nearest box-generating ancestor, so if each header were bounded by its own group's <li>, that header would release the moment its own group's rows scrolled out and the shingle trail could never accumulate past the current group — that is the plain-sticky-headers failure mode this component exists to beat. With the group box suppressed, every header's containing block is the single shared <ul role=\"listbox\">, so a passed header stays pinned (and stacked, in DOM/paint order) for the life of the whole scroll; verified against a live accessibility-tree snapshot that role=group and its label survive box suppression in evergreen engines. The heading button's accessible name is aria-label=\"Jump to {group label}\" (it IS the jump target, real tab stop); the group's own aria-labelledby points instead to a child <span id={headerId}> holding just the plain group label, so the group's announced name stays \"Europe\", unaffected by the button's jump-to phrasing sitting on the same element. Compression detection: one 1px aria-hidden sentinel per group, placed at the very start of that group's <li> (a plain, never-sticky element, so its geometry is always trustworthy), observed with an IntersectionObserver whose root is the listbox and whose rootMargin top equals -(groupIndex*20 + 1)px; comparing entry.boundingClientRect.top against entry.rootBounds.top (not isIntersecting alone) tells 'scrolled past' apart from 'not reached yet'. A group is rendered in its compressed (shingled) style — 11px uppercase muted type, bottom hairline — once the observer reports the NEXT group has arrived at its own sticky offset, i.e. is about to start covering it; this is pure detection, never layout, so nothing reflows and the browser's native sticky implementation is untouched. Every header shares one fixed height, so once two are both pinned they overlap by (height - 20)px purely as a geometric consequence of the shared 20px step — the '20px sliver' is emergent, not hand-positioned. Clicking a shingle eases the listbox's own scrollTop (never the page) with a hand-rolled rAF loop on ease-out-expo (t >= 1 ? 1 : 1 - 2^(-10t)), ~420ms, computing the target from the group's SENTINEL's live getBoundingClientRect(), never the header's own: once a header is currently stuck, its own rect reflects the pinned screen position rather than its natural document position, which would cancel the target math to a no-op for exactly the headers a user is most likely to click; the sentinel is never sticky so it stays a reliable anchor whether that group is currently pinned, released, or never yet reached. prefers-reduced-motion skips the animation and jumps scrollTop straight to the target while leaving every sticky offset and the compression styling exactly as-is — the stacking is layout, not motion, and is never gated on a motion preference. Keyboard is one tab stop on the listbox (aria-activedescendant over a flattened group/option index): ArrowUp/Down step the active option, Home/End jump to the first/last enabled option, PageUp/PageDown jump 8 at a time, printable characters build a 500ms typeahead buffer matched against label prefixes — all exactly the shape a native <select> gives you, never overridden into something exotic. The group heading buttons are additional, separate tab stops in natural DOM order (real buttons, not part of the roving listbox selection); options themselves are not individually tabbable, matching the roving-tabindex convention used elsewhere in this registry. Selection commits on Enter, Space (with an empty typeahead buffer), or option click; there is no open/close state to manage since the listbox is always rendered expanded — this component is entirely about navigating inside a long open list, not about revealing one. Colors are --background/--foreground/--muted/--border/--accent only, no canvas, the only z-index is the header stacking itself. Demo: a deploy-window card with a 112-option, 8-region default timezone dataset (Africa/Americas/Asia/Atlantic/Australia & Pacific/Europe/Indian Ocean/Antarctica) — real projects pass their own `groups` for the full list."
      }
    },
    {
      "name": "loader-braille",
      "type": "registry:ui",
      "title": "Loader Braille",
      "description": "Determinate/indeterminate loader built from braille dot patterns, where each cell's eight dots are individually addressable for far finer granularity than a block-character bar.",
      "files": [
        {
          "path": "registry/core/loader-braille/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/loader-braille.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "ascii",
          "braille",
          "mono"
        ],
        "instruction": "A loader rendered as a row of braille cells (U+2800 block), where the eight dots of each cell are addressable bits rather than a spinner glyph pulled from a fixed set. A cell's fill count 0-8 is converted to a character via a fixed per-cell dot priority list `[7,8,3,6,2,5,1,4]` (bottom row first, so a partially-filled cell reads as a rising level meter rather than a scattered dot cluster) OR'd into the bitmask added to 0x2800. Indeterminate (`progress` undefined): each of the 14 cells' fill level is `4 + 4*sin(t*2.6 - i*0.55)`, i.e. one continuous sine sampled with a per-column phase offset, so the wave genuinely travels left to right across the row with dots rising and falling like a stadium wave, not a rotating sprite. Determinate (`progress` 0-100): the value maps onto all 14*8=112 dots in reading order — cell 0's eight dots fill before cell 1's — giving well over an order of magnitude finer resolution than a block-character bar could offer at the same character width; reaching 100 triggers a one-shot ~480ms accent-color pulse across the row (a discrete React state transition, the only state change in the whole animation) before it settles back to foreground ink. Everything else is a single direct-DOM rAF loop that builds the full row string each frame and writes it once to a ref's textContent — never per-frame React state — reading glyph color from the surrounding `text-foreground`/`--accent` tokens so it works unmodified in both themes. `role=progressbar` carries `aria-valuemin/max/now` only in the determinate case (per spec, an indeterminate progressbar omits `aria-valuenow` rather than reporting a fake value); the glyph row itself is `aria-hidden` since the numeric state is exposed through the ARIA attributes, not by parsing braille. prefers-reduced-motion renders one correct static frame — a gentle standing arc for the indeterminate case, the literal frame implied by `progress` for the determinate one — and a separate light effect keyed on `progress` keeps that static frame in sync if the value changes while reduced motion is on, since the animated loop (which would otherwise pick that up) never starts."
      }
    },
    {
      "name": "loader-die-tumble",
      "type": "registry:ui",
      "title": "Loader Die Tumble",
      "description": "An ambient loading glyph shaped as a die tumbling face over face on a fixed axle — each 90deg landing overshoots by 8deg and corrects on a spring, with a floor shadow compressing and flaring in sync, rather than rotating cleanly to a stop.",
      "files": [
        {
          "path": "registry/core/loader-die-tumble/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/loader-die-tumble.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "loading",
          "spinner",
          "indicator",
          "3d",
          "svg",
          "css-animation",
          "physics",
          "aria-live",
          "icon"
        ],
        "instruction": "A compact, always-on loading glyph (20-96px, sized via a `size` prop) built as a 3D CSS cube ring — four faces (`transform-style:preserve-3d`, each `rotateY(0/90/180/-90deg) translateZ(size/2)`) marked with die pips (1/2/3/4, a 3x3 grid per face) so every quarter-turn is legible as a distinct landing rather than an ambiguous spin. One `rotateY` keyframe drives the whole cycle in four identical beats: each beat holds still, then eases fast toward its target face on ease-out-expo, overshoots the landing angle by 8deg, and corrects back on the house spring curve (cubic-bezier(.34,1.56,.64,1), the same overshoot wizard-dovetail uses for a chip seating into its rail) — set via a distinct `animation-timing-function` on the keyframe stop straddling each leg, not a separate easing library. A soft blurred ellipse beneath the cube is a second, synced keyframe: it compresses and dims as the cube rocks up off it mid-topple, then flares wider and brighter for a beat on landing before settling — a floor absorbing an impact, not a static shadow prop. Every face background is `var(--surface)` with a `var(--border)` outline and pips in `var(--foreground)`; face-to-face shading (front lightest, opposite face darkest) is a `color-mix(in srgb, var(--foreground) N%, transparent)` overlay per face, never a hex literal, so it holds correctly in both themes. The whole glyph is a single `role=\"status\" aria-live=\"polite\"` element; the cube and floor are `aria-hidden` and a visually hidden text node (the `label` prop, default 'Loading') is the only thing announced — nothing here is interactive, so it renders zero controls and is correctly exempt from the tabbability check. `prefers-reduced-motion` removes both animations entirely, leaving the cube resting on face one and the floor at its neutral resting scale — legible as 'something is here' with zero motion. Props: size (px, default 48), periodMs (full four-face loop, default 4800), label, className. Pure DOM + CSS 3D transforms, zero dependencies, no canvas, no framer-motion despite the source this was rebuilt from declaring it as a dependency it never actually imported."
      }
    },
    {
      "name": "loader-ink-blob",
      "type": "registry:ui",
      "title": "Loader Ink Blob",
      "description": "A small canvas-2D assistant-state indicator: a soft ink blob traced through 72 points around a circle, each perturbed by one of six distinct motion signatures — idle, thinking, listening, speaking, success, error — with a checkmark or X that strokes itself in for the two settled states.",
      "files": [
        {
          "path": "registry/core/loader-ink-blob/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/loader-ink-blob.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "status",
          "canvas",
          "ai",
          "chat",
          "indicator",
          "spinner"
        ],
        "instruction": "A small fixed-size (default 48 CSS px, plain `size` prop) canvas-2D indicator for an AI assistant's current state, driven by a `state` prop: 'idle' | 'thinking' | 'listening' | 'speaking' | 'success' | 'error'. Internally it traces a smooth closed blob through 72 points sampled evenly around a circle of radius 0.32×size, each point's radius perturbed by a state-specific function of angle and time (no shared noise field across states — each is its own formula): idle is one slow sine wobble; thinking sums three sine bands at different frequencies/phases plus a tiny deterministic per-point jitter (hash-seeded, not Math.random, so it's reproducible) for a turbulent surface; listening keeps the blob almost still while a ring emits from its edge and expands/fades on a fixed 1.2s period (sonar pulse) in the --accent token; speaking rides a 6-lobe standing wave around the outline under a slow amplitude envelope; success eases the wobble amplitude to zero over 0.5s (settling into a near-circle) while a checkmark strokes itself in over 0.4s in the --success token, timed from the moment `state` became 'success' (tracked via a ref updated during render when the prop changes, not in an effect, so the timer never lags a rapid state change); error plays a short exponentially-decaying horizontal shake burst every ~950ms while an X strokes itself in over 0.35s in the --error token, timed the same way. The blob itself is filled at 14% alpha and stroked at ~92% alpha in the --foreground token (--muted while thinking, to read as 'processing' rather than 'settled'). All ink is read via getComputedStyle(document.documentElement) at mount and re-read on a MutationObserver watching documentElement's class attribute. The rAF loop pauses on document visibilitychange and under prefers-reduced-motion (draws one static settled frame instead) and can be frozen on its current frame via a `paused` prop (checked through a lightweight ref poll so toggling it doesn't tear down the canvas). `speed` scales every state's internal clock. Backing store is dpr-clamped to 2. The wrapper carries role='img' and an aria-label naming the current state ('Assistant is thinking', etc.) since this is a display-only status glyph with no interactive control — screen reader users get the state as text instead of inferring it from motion."
      }
    },
    {
      "name": "loader-iris",
      "type": "registry:ui",
      "title": "Loader Iris",
      "description": "An ambient loading glyph shaped as a six-blade camera-iris diaphragm breathing open and shut inside a fixed housing ring — one wedge shape repeated by rotation, its shared displacement keyframe overshooting each open and close before settling on a spring.",
      "files": [
        {
          "path": "registry/core/loader-iris/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/loader-iris.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "loading",
          "spinner",
          "indicator",
          "svg",
          "css-animation",
          "physics",
          "aria-live",
          "icon"
        ],
        "instruction": "A compact, always-on loading glyph (20-96px via a `size` prop) built as a six-blade iris diaphragm: one triangular wedge `<path>` authored once and repeated six times inside `<g transform=\"rotate(60*i 50 50)\">` around a 100x100 viewBox, so the hexagonal aperture in the middle is never a separately drawn shape — it's purely where the six wedge tips currently sit. Every blade runs the identical `translateY` keyframe (its own local 'up' axis, which the per-blade rotate then points radially outward), so the whole assembly opens and closes as one unit: tips retreat toward a static housing ring (`stroke:var(--border)`, drawn once, unrotated) on ease-out-expo, overshoot past the open position, correct on the house spring curve (cubic-bezier(.34,1.56,.64,1)); after a hold, the same pattern reverses to close — ease toward center, overshoot past fully-shut, spring back to rest — so both the opening and the closing read as blades under tension snapping to a stop, not a shape tweening between two states. Blade fill is `color-mix(in srgb, var(--foreground) 82%, transparent)` with a `var(--surface)` stroke so adjacent blade edges stay legible against each other in both themes; a small static center dot in `var(--foreground)` anchors the hub. The whole glyph is a single `role=\"status\" aria-live=\"polite\"` element; the SVG is `aria-hidden` and a visually hidden text node (the `label` prop, default 'Loading') is the only thing announced — nothing here is interactive, so it renders zero controls and is correctly exempt from the tabbability check. `prefers-reduced-motion` removes the animation entirely, leaving every blade retreated to a fixed mid-open position rather than either extreme, so the resting frame reads as a genuine aperture rather than a fully shut or fully open freeze-frame. Props: size (px, default 40), periodMs (full open-close breath, default 2600), label, className. Pure DOM + SVG + CSS, zero dependencies, no canvas, no framer-motion despite the source this was rebuilt from declaring it as a dependency it never actually imported."
      }
    },
    {
      "name": "loader-pendulum-sync",
      "type": "registry:ui",
      "title": "Loader Pendulum Sync",
      "description": "An indeterminate loader for long background work — a row of pendulums with slightly different periods drift from unison into apparent chaos and back into perfect sync on a knowable ~10s cycle, pure CSS with zero per-frame JS.",
      "files": [
        {
          "path": "registry/core/loader-pendulum-sync/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/loader-pendulum-sync.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "indeterminate",
          "progress",
          "status",
          "aria-live",
          "css-animation",
          "physics"
        ],
        "instruction": "An indeterminate loader for genuinely long background work (builds, indexing) shaped as a physical pendulum wave, so a long wait has a watchable arc instead of a spinner. Renders a row of 9-15 pendulums (count prop, clamped) — each a 1px hairline arm in --muted rotating about its top edge, tipped with a 4px circular bob in --foreground — via pure CSS keyframes, one rule shared by all of them: 0%/100% rotate(+amplitudeDeg), 50% rotate(-amplitudeDeg), eased with cubic-bezier(.37,0,.63,1) (a cosine-shaped ease-in-out approximating simple-harmonic velocity: fast through center, slow at the extremes). The only thing that differs pendulum to pendulum is animation-duration: pendulum i gets periodMs/(8+i), so it completes exactly 8+i whole swings in one periodMs cycle (default 10000ms) — an integer count for every pendulum, which is the entire mechanism: because they all mount and start swinging at the same instant and each returns to its own start-phase after an integer number of its own periods, the whole row is mathematically guaranteed to land back in unison, all at once, every periodMs, with zero JS driving that realignment. Between realignments the same arithmetic passes the row through a travelling-wave phase and, near the half-cycle where each pendulum's phase differs from its neighbor's by very close to half a swing, a double-helix phase — interference, not choreography. All pendulums share one identical negative animation-delay (-1ms) purely to null out any one-frame paint stagger from React mounting them in one render; being identical across every pendulum it does not disturb their relative phase. A determinate variant is available via a controlled `value` (0-100) prop: each pendulum's duration is linearly retargeted, in JS, from its natural period toward the middle pendulum's own natural period (not an arbitrary new tempo), so the whole row visibly detunes into a single shared rhythm exactly as value reaches 100 — the only place this component touches timing outside pure CSS. Because a sync moment looks identical to 'finished' by design (that ambiguity is the point — it doubles as a still-working heartbeat during the wait), completion is never inferred from rhythm: an explicit `done` boolean is required, and setting it changes the DOM structurally rather than just letting a sync land — every pendulum's animation is paused mid-swing and it translates upward out of the row with a staggered per-pendulum delay while fading to 0 opacity, a visibly different shape from any resting sync frame. The whole component sits in one role=status aria-live=polite wrapper; the pendulum row itself is aria-hidden (decorative, nothing focusable, no controls at all — a display-only status board, correctly exempt from the tabbability rule) and a single visible Geist Mono text node underneath is the sole thing announced, reading '{label}, {n} seconds elapsed' and ticking every 5 seconds (sparse — never per-frame, never per-second) until `done`, at which point the text switches immediately to `doneLabel` ('Build complete' by default) and that switch is the only place completion is ever communicated. Props: label (status prefix, default 'Building'), doneLabel (default 'Build complete'), value (0-100, optional — omit for pure ambient indeterminate mode), done (boolean, explicit completion), periodMs (full sync-chaos-sync cycle length, default 10000), count (pendulum count, clamped 9-15, default 13), amplitudeDeg (swing half-angle, default 26), className. Under prefers-reduced-motion the swing keyframes are stripped entirely (a static upright row) and the retract on done drops to a plain opacity fade with no translate — the text label carries all of the information either way. Differs from feed-escapement, which is one regulated clockwork mechanism ticking a single beat: loader-pendulum-sync is many independent free oscillators whose interference pattern is the entire display, with drift and reconvergence emerging across a family rather than one part being struck on a schedule. Also differs from status-glyph-cadence's single small state glyph — this is a wide, self-contained loader block built for a long unattended wait, not an inline icon beside other UI. Pure DOM + CSS, no canvas, no dependencies."
      }
    },
    {
      "name": "loader-spring-bars",
      "type": "registry:ui",
      "title": "Loader Spring Bars",
      "description": "An ambient loading glyph shaped as a row of leaf springs on a rail — one displacement keyframe, staggered per bar by negative animation-delay, reads as a single pulse of energy travelling down the rack rather than bars animating in isolation.",
      "files": [
        {
          "path": "registry/core/loader-spring-bars/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/loader-spring-bars.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "loading",
          "spinner",
          "indicator",
          "css-animation",
          "physics",
          "aria-live",
          "icon"
        ],
        "instruction": "A compact, always-on loading glyph (3-9 bars via a clamped `count` prop, sized via `height`) built as a row of rounded bars anchored at the bottom (`transform-origin:bottom center`) so they read as leaf springs mounted on a rail rather than a generic equalizer. Every bar runs the exact same `@keyframes` rule — rest compressed (scaleY .38) through a rise past its own resting height on ease-out-expo, a two-beat decaying overshoot on the house spring curve (cubic-bezier(.34,1.56,.64,1): stretched too far, corrects, small second bounce), back to rest — but each bar's `animation-delay` is a negative offset proportional to its index (~8.5% of `periodMs` per lath), so the identical waveform arrives at each bar slightly later than its left neighbor: the same 'one rule, phased per element' technique this registry's loader-pendulum-sync uses for its pendulum row, applied here to scaleY displacement instead of rotation. The visible result is one pulse of compression travelling left to right down the rack and looping, never N independent bars bouncing on their own clocks. Ink is `var(--foreground)` only, no gradients, no per-bar color. The whole glyph is a single `role=\"status\" aria-live=\"polite\"` element; the bar row is `aria-hidden` and a visually hidden text node (the `label` prop, default 'Loading') is the only thing announced — nothing here is interactive, so it renders zero controls and is correctly exempt from the tabbability check. `prefers-reduced-motion` removes the animation entirely, leaving every bar at a fixed mid-height with reduced opacity rather than either extreme of the pulse, so the resting frame reads as paused motion rather than an arbitrary freeze-frame. Props: count (3-9, default 5), height (px, default 40, bar width derives from it), periodMs (full pulse-sweep-and-repeat cycle, default 2200), label, className. Pure DOM + CSS, zero dependencies, no canvas, no framer-motion despite the source this was rebuilt from declaring it as a dependency it never actually imported."
      }
    },
    {
      "name": "loader-thread-spool",
      "type": "registry:ui",
      "title": "Loader Thread Spool",
      "description": "A loader that winds thread onto a spool while duration is unknown, then converts the same coil in place into a proportional gauge the instant total size arrives — the indeterminate-to-determinate handoff as one continuous object, never a spinner swapped for a bar.",
      "files": [
        {
          "path": "registry/core/loader-thread-spool/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/loader-thread-spool.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "progressbar",
          "spinner",
          "indeterminate",
          "svg",
          "spring",
          "accessibility"
        ],
        "instruction": "A progress indicator built around one idea: a fetch's wait is real elapsed time, and erasing that time every spinner revolution is a small dishonesty this component refuses. Two controlled props drive it — `total` (bytes, undefined while headers are still pending) and `loaded` (bytes so far, meaningful only once `total` is known); the component owns no other state. While `total` is undefined, an internal clock (reset whenever `total` goes back to undefined, i.e. a new job started) advances a ring counter on a steady real 700ms cadence — never a fixed-duration CSS loop — and each tick recomputes a target radius as core-radius plus a logarithmic term in the tick count, so a coil that has wound for two minutes is only modestly bigger than one that wound for twenty seconds: long waits stay visually compact instead of the radius (or a naive linear proxy) blowing past the frame. The target radius is chased by a lightly underdamped spring each frame (never a per-ring CSS keyframe), so every newly exposed thread layer settles with one small wobble rather than snapping. A taut 1px feed line runs from a fixed off-spool anchor to the coil's current edge at a fixed feed angle, its attach point oscillating a few degrees on a slow sine so the line reads as live tension rather than a static prop; a fixed pair of 1px hub circles (the spool's two flanges) anchor the whole thing at center and never move. The moment `total` becomes a positive number, nothing resets: the same spring-chased radius immediately retargets from log(ticks) to core-radius plus (max-radius − core-radius) × loaded/total, so the existing coil mass simply re-scopes to a fraction of a fixed ceiling instead of vanishing and reappearing as a bar — and a dashed 1px ghost ring pops (its own spring, radius 0 to the max-radius ceiling) marking where the coil will finish, so a viewer immediately sees both 'how far' and 'how far there is to go' as concentric facts, not a color or a second widget. From there the mechanism runs as a plain proportional gauge: the wound radius chases loaded/total on every prop update, no more clock, no more log curve, exactly a progress bar wearing a spool's geometry. Reaching loaded >= total snips the feed line — the attach point eases toward the anchor with a small spring recoil (a slight overshoot past fully retracted before it settles) rather than a hard cut, then the line and oscillation both disappear and the component goes idle (its render loop stops scheduling frames once the radius, ghost ring and snip have all actually settled, waking again only if props change). Every layer, the ghost ring and the feed line are drawn with `stroke=\"var(--foreground|--muted|--border)\"` directly in SVG presentation attributes — no canvas, no hex, no rgb()/hsl() — so both themes render correctly with zero extra logic. A visible font-mono caption under the coil mirrors the same numbers a sighted user would want ('winding — 24 seconds', '40% of 12 MB') but is `aria-hidden`, because the real accessibility contract lives on the wrapping `role=\"progressbar\"`: no `aria-valuenow`/`aria-valuemax` while indeterminate, only an `aria-valuetext` that updates roughly every 10 real seconds of elapsed wait ('Loading, 24 seconds elapsed') rather than every frame; the instant `total` is known, real `aria-valuemin=0`/`aria-valuemax=100`/`aria-valuenow` appear alongside an `aria-valuetext` like 'Loading, 40 percent of 12 MB', and a separate `aria-live=\"polite\"` region fires exactly one announcement at that transition so a screen reader hears the handoff happen without being spammed on every subsequent byte. The component renders no button, input or any focusable control — it is pure status, never focusable, with no keyboard surface — so the tab-reachability check correctly skips it as display-only; any interactive control seen in a demo belongs to the demo, not to WindSpool. `prefers-reduced-motion` removes the spring wobble (new radii, the ghost pop and the snip recoil all resolve to their target immediately, in one discrete step, rather than easing) and removes the feed-line oscillation entirely, while every state — indeterminate, determinate, complete — stays fully legible and the underlying cadence-driven radius growth is unaffected, since that growth is the actual information, not decoration. Differs from voice-recorder-meter: voice-recorder-meter visualizes a live external signal (microphone amplitude) that has no notion of accumulated duration and nothing to complete toward; loader-thread-spool has no live signal at all, only elapsed time and, once known, a byte fraction — it measures work done, never a level. Zero dependencies."
      }
    },
    {
      "name": "logo-cloud-settle",
      "type": "registry:ui",
      "title": "Logo Cloud Settle",
      "description": "A trust-wall grid of abstract, generated marks that physically settles into place: each tile drops in from lifted/shrunk/tilted with a per-item stagger and a slight spring overshoot, triggered on viewport entry and replayed if the wall leaves and re-enters view.",
      "files": [
        {
          "path": "registry/core/logo-cloud-settle/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/logo-cloud-settle.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "logo-cloud",
          "trust-wall",
          "grid",
          "entrance",
          "spring"
        ],
        "instruction": "Renders `marks` (id/name/abstract-shape triples; eight generated geometric glyphs ship by default — ring, diamond, triangle, plus, hex, venn, chevron, dot-grid, all drawn as inline SVG with stroke=currentColor, zero real company logos or wordmarks) as a responsive grid (2 columns under sm, 4 above). At rest before its first reveal, and again any time it leaves the viewport, every tile sits lifted 22px above its slot, scaled to 0.9 and rotated -4deg with opacity 0. An IntersectionObserver (threshold 0.2) on the grid flips a single `settled` boolean the moment the wall enters view; while true, every tile transitions to translateY(0)/scale(1)/rotate(0)/opacity 1 over 560ms on a spring-approximating overshoot easing (cubic-bezier(0.22,1.7,0.36,1)), staggered 42ms per tile in DOM order, so the wall reads as physically DROPPING INTO and SETTLING onto its grid rather than fading in as a block. Leaving the viewport resets every tile instantly back to the lifted/tilted starting transform (no transition on the way out), so re-entering triggers the same settle again — this is a ONE-SHOT-PER-ENTRY transition driven by a boolean, not a continuous simulation: distinct from avatar-stack-flock, which mills continuously as a boids flock at rest and only resolves into a tidy row on hover/focus. `prefers-reduced-motion` renders every tile already settled on the first frame and never touches the IntersectionObserver. The grid is `role=list` with each tile `role=listitem` and the visible mark name as its label — this is a display-only trust wall with no interactive controls (no buttons, nothing to tab to), so nothing here is clickable. `data-settled` is exposed on the grid root for anyone probing its state. Props: `marks`, `label` (the caption above the grid, also the list's aria-label), className. Zero dependencies, plain DOM + CSS transitions, no canvas."
      }
    },
    {
      "name": "map-choropleth-ascii",
      "type": "registry:ui",
      "title": "Map Choropleth ASCII",
      "description": "ASCII-density choropleth over a fully synthetic hand-seeded tessellation. Hovering or keying through a region isolates it and the legend rescales to a band around its value.",
      "files": [
        {
          "path": "registry/core/map-choropleth-ascii/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/map-choropleth-ascii.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "map",
          "choropleth",
          "data-viz",
          "ascii",
          "canvas"
        ],
        "instruction": "The registry's first choropleth. The geography is entirely invented: eight hand-placed seed points on a fixed 24x13 cell grid are tessellated by nearest-seed (a plain Voronoi partition computed in-memory, no geo dependency, no GeoJSON, no real coastline or border), each cell resolving to one of eight abstract 'sectors' with a synthetic index value. Region fill uses the family's shared ASCII ramp ' .:-=+*#%@', density tracking each sector's value against the global min/max, with a small fixed-hash per-cell jitter (deterministic, not Math.random) so a solid sector still reads as ink texture rather than a flat block; thin var(--border) lines trace every sector boundary at rest. The mechanic: pointing at a cell (exact grid lookup under the cursor) or moving keyboard focus through the sector list (Tab into the map, then ArrowLeft/Right or Up/Down cycle sectors; Escape clears) isolates that sector — every other sector's cells drop to a fixed low background density at reduced opacity, muted-ink, while the isolated sector keeps its true density and gains its own boundary re-traced in var(--accent) — and the legend gradient bar beneath the map rescales its domain from the global min/max to a tight +/-14 band around the isolated sector's own value, with an accent tick marking exactly where that value falls in the rescaled band. Losing hover or focus restores the global view and the full-domain legend. var(--accent) never appears in the data fill itself, only the isolation boundary and the legend marker, matching the family's reserved-for-interaction convention. Tokens are read via getComputedStyle at mount and re-read through a MutationObserver on the document root's class attribute, so both themes repaint correctly on toggle. On mount the whole map fades in over 340ms; prefers-reduced-motion renders at full opacity immediately and isolation still updates instantly on interaction. Zero dependencies, zero geo/mapping libraries."
      }
    },
    {
      "name": "marquee-ticker-glyph",
      "type": "registry:ui",
      "title": "Marquee Ticker Glyph",
      "description": "A grabbable ticker tape: drag to scrub through its content, release to fling it with momentum, and legibility itself is a function of speed — fast motion blurs characters into noise glyphs, slowing down resolves them back into real text.",
      "files": [
        {
          "path": "registry/core/marquee-ticker-glyph/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/marquee-ticker-glyph.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "marquee",
          "ticker",
          "scrub",
          "drag",
          "mono",
          "glyph",
          "instrument"
        ],
        "instruction": "A horizontal ticker tape built from `items: string[]` joined by an optional `separator` (default \"/\"), rendered as fixed-width monospace cells inside an overflow-hidden track. Unlike an auto-scrolling marquee, the tape is a real scrub instrument: pointerdown+drag on the track moves the tape 1:1 with the pointer (no easing while held) and computes instantaneous velocity in characters/sec from the pointer delta each move; release keeps that velocity as momentum, which decays under per-frame friction (0.94^ (dt*60)) back toward the ambient auto-scroll speed. Arrow keys nudge the offset the same way, briefly crossing the resolve threshold so keyboard users feel the same effect the pointer path gets. The mechanic distinct from any other ticker in this registry: PER-CHARACTER LEGIBILITY IS A FUNCTION OF |INSTANTANEOUS SPEED|. Every visible cell is repainted every animation frame from the current fractional tape offset; when |speed| stays under `resolveThreshold` (default 260px/s — comfortably above the ambient auto-scroll speed of 34px/s) each cell shows its real character. Above that threshold — an active fast scrub, or the fast part of a fling — every visible cell instead shows a randomly re-rolled member of a fixed noise charset (`░▒▓#%&@*+=-:.`), re-rolled every frame, so the tape visibly blurs into static and then snaps back into focus the instant it decelerates through the threshold. Cells never go blank and never change width (monospace, fixed cell size measured from a hidden probe span), so there is zero layout jitter regardless of state. One direct-DOM requestAnimationFrame loop owns position, velocity and per-cell textContent — no React state on the hot path. Hovering or focusing the track eases the ambient auto-scroll speed toward zero (same pause courtesy as any ticker) but does not touch the resolve mechanic. A leading pause/resume button (aria-pressed) offers an explicit, keyboard-reachable stop independent of hover. Accessibility: the scrub track is `role=list` with a single `aria-label` holding the full joined item text (the real content, always current, independent of what's mid-blur on screen); the per-character glyph cells inside it are `aria-hidden`. `prefers-reduced-motion: reduce` drops the animated track entirely in favor of a static flex row of real `role=listitem` elements, each with a matching `aria-label` — no scroll, no blur, no momentum, fully legible at all times. Zero dependencies, pure DOM."
      }
    },
    {
      "name": "masonry-ascii-settle",
      "type": "registry:ui",
      "title": "Masonry ASCII Settle",
      "description": "A masonry gallery whose tiles render as live ASCII halftone and resolve from a coarse print to a fine one as they drop into their packed column slot, re-packing and re-dropping every tile when a resize crosses a column-count breakpoint.",
      "files": [
        {
          "path": "registry/core/masonry-ascii-settle/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/masonry-ascii-settle.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "masonry",
          "gallery",
          "ascii",
          "canvas",
          "settle",
          "ink"
        ],
        "instruction": "Build <MasonryAsciiSettle tiles className?> where tiles is MasonryAsciiTile[] ({id, title, seed, aspect}), aspect being the tile's height as a multiple of its column width. PACKING: own shortest-column-first math (never CSS `columns`, which flows top-to-bottom per column and cannot be re-packed on demand) — columnsFor(width) breaks at 1 column below 520px, 2 below 860px, 3 above; column width is (containerWidth - gap*(cols-1))/cols and each tile's height is columnWidth*aspect, packed by always placing the next tile into the currently-shortest column. THE MECHANIC: each tile is a <canvas> rendering a deterministic 2-octave value-noise field (per-tile seed) as literal ASCII glyphs from a \" .:-=+*#%@\" luminance ramp, drawn via ctx.fillText at a glyph pitch that EASES from a coarse ~15px cell down to a fine ~6px cell over ~620ms (eased, one rAF loop redrawing every animating tile's canvas each frame) — this is a real resolution change, not a fade or blur filter over a fixed image. That same eased progress simultaneously drives the tile's drop: translateY from -26px to 0 and opacity from ~0.15 to 1, so the tile visibly resolves into focus at the exact moment it lands in its slot. Every tile's drop is staggered ~55ms apart in final top-to-bottom, left-to-right order. On mount, and again any time a ResizeObserver on the container detects the column COUNT itself has changed (not just column width), every tile's position is recomputed via the packing pass and the FULL coarse-to-fine drop replays for every tile, staggered by its NEW position — a resize that changes column count is a real re-pack with visible re-drops, not a CSS reflow that snaps. A resize that keeps the same column count only updates each tile's pixel position/size with no replay. Canvas ink is var(--foreground) on a var(--surface) tile background, both read via getComputedStyle at mount and re-read through a MutationObserver on the root's class/style attributes so both themes stay correct with no remount. This is a display-only gallery — no buttons, nothing to tab to — so an sr-only paragraph lists every tile's title as its accessible content. `prefers-reduced-motion` renders every tile already at its fine pitch and final position on the first paint, skipping the drop and the resolution ease entirely, though re-packing on a column-count-changing resize still recomputes positions instantly. Zero dependencies."
      }
    },
    {
      "name": "memory-ledger-decay",
      "type": "registry:ui",
      "title": "Memory Ledger Decay",
      "description": "Session memory that visibly patinates: each remembered fact steps its ink from --foreground toward --muted the longer it goes unused, snaps back and underlines when the agent rehearses it, and is governed by real pin/evict controls.",
      "files": [
        {
          "path": "registry/core/memory-ledger-decay/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/memory-ledger-decay.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "memory",
          "agent",
          "ledger",
          "list",
          "decay",
          "pin",
          "evict",
          "mono",
          "aria-live",
          "accessibility",
          "governance"
        ],
        "instruction": "A per-item time-decay-with-rehearsal ledger for agent session memory: one 32px row per remembered fact, 13px Geist Mono, inside a 12px-radius rail (rounded-xl border border-border bg-background). Each row's ink is `turn - lastUsedTurn` (both plain controlled props — the component derives age, holds no memory model of its own) mapped through a four-stop ramp via a `data-age` attribute (0 fresh / 1 aging / 2 fading / 3 dormant, plus a separate 'pinned' value) into a color-mix() interpolation from --foreground toward --muted (68% and 36% foreground at the middle two stops, pure --muted at dormant), transitioned over 600ms so each turn's aging step is perceptible rather than a silent jump. The moment a row is actually cited — its lastUsedTurn increases, detected by diffing the incoming memories array against the previous render's snapshot, not merely re-derived from `turn` — its color transition is suppressed for that one commit (a discontinuous snap to --foreground reads as 'revived right now', which a slow brighten wouldn't) and a 1px --foreground underline wipes left-to-right under the text over 300ms before fading, plus a single aria-live=polite announcement ('Rehearsed: <text> — turn N'). Users govern the ledger with two real per-row buttons: pin toggles aria-pressed, stamps a 4px --foreground lacquer dot, and permanently exempts the row from aging (data-age becomes 'pinned', frozen at full ink regardless of turns unused); evict is a genuine two-step confirm — the first press arms the row (both the button's accessible name and its visual state switch to 'confirm'), a second press (or a second Enter/Space on the still-focused button, since native buttons already get Enter/Space activation for free) collapses it via grid-template-rows 1fr->0fr plus an opacity drop over 250ms and only then calls onEvict so the caller removes it from the array; losing focus disarms without evicting. Rows that are both unpinned and fully dormant leave the main list and fold into an 'N dormant' disclosure row at the bottom (a real aria-expanded button with aria-controls pointing at the drawer's id) that expands, via the same grid-template-rows trick on the drawer itself, into a second bordered list holding those rows inline with fully working pin/evict — pinning a dormant row rescues it back into the main list for free on the next render, since visibility is just a derived filter over pinned/stop, never a separate 'rescued' flag to keep in sync. Because color-fade alone is invisible to screen readers and low-vision users, every row's `aria-label` spells its age out in words ('Remembers: prefers metric units — fading, 6 turns unused'; pinned rows read '— pinned'), independent of and in addition to the pin/evict buttons' own distinct accessible names. Props: `memories` (`{id, text, lastUsedTurn, pinned?}[]`), `turn` (current turn index), `ageThresholds` (`[number,number,number]`, default `[1,4,9]` turns-unused boundaries), `onPinToggle`, `onEvict`, `ariaLabel` (default 'Session memory'), `className`. prefers-reduced-motion removes every transition, the wipe animation, and the collapse/expand easing — pin, evict-confirm, rehearsal and the dormant drawer all still work, they just change state instantly instead of animating, and the evict removal timeout collapses to 0ms rather than waiting out a transition nothing will play. Pure DOM + CSS + inline SVG glyphs — no canvas. Demo: an 8-row session (one pinned, several at different ages, four already dormant so the '4 dormant' disclosure is visible at rest) with an 'Advance turn' button that ages everything a turn, and two 'Agent cites: <fact>' buttons that rehearse a specific row so the snap-and-wipe is directly reachable rather than only inferred from a live agent."
      }
    },
    {
      "name": "menu-nested-trays",
      "type": "registry:ui",
      "title": "Menu Nested Trays",
      "description": "Nested menus as telescoping card-catalog trays: opening a submenu shunts the parent back and dims it instead of spawning a flyout, leaving a 12px clickable edge per ancestor as a live breadcrumb of depth.",
      "files": [
        {
          "path": "registry/core/menu-nested-trays/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/menu-nested-trays.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "menu",
          "nested",
          "navigation",
          "breadcrumb",
          "mobile",
          "context-menu",
          "accessibility"
        ],
        "instruction": "Build a nested menu whose submenus render as absolutely positioned, full-size trays stacked in the same box rather than floating flyouts. Each level's resting transform depends only on its own index d in the open path: translateX(d * 12px) scale(1) when it is the current top tray, or scale(0.97^k) (k = levels deeper than it that are currently open) with the same translateX(d * 12px) when it becomes an ancestor — scale's transform-origin is the tray's own left edge — the exposed rail — so the sliver's screen position is scale-independent and only the tray's body recedes away behind the child, not the rail itself. Because each level's offset is fixed to its own index rather than recomputed relative to the currently open depth, and scale pivots on that same left edge instead of moving it, the stack always exposes exactly a 12px sliver of every ancestor's own left edge between its offset and the next level's, at any depth, with no extra bookkeeping. Opening a level pushes it into the path, mounts its tray at translateX(100%), and on the next two animation frames animates it to its resting translateX(d * 12px) over 280ms on an ease-out-expo curve (cubic-bezier(0.16,1,0.3,1)); the tray it covers gets a --muted-tinted overlay (bg-muted at ~20% via a decorative absolutely-positioned div, never --accent) crossfading in over 150ms and simultaneously receives aria-hidden and the inert attribute, so focus and hit-testing can never land on it while it's covered. Every ancestor's own left edge is covered by a separate, real <button> positioned outside the inert tray's subtree — at left: index*12px, width 12px, full tray height — labeled 'Back to {level name}'; clicking it shunts back to that level in one action, animating every tray deeper than the target back to translateX(100%) simultaneously, staggered 30ms apart starting with the deepest (most recently opened) tray, unmounting once the cascade settles. role=menu on each tray, role=menuitem on each row; ArrowRight or Enter on a row that owns a submenu opens it and moves focus to its first item, Enter on a leaf row fires selection instead, ArrowLeft or Escape shunts back exactly one level and restores focus to the row that had opened the level being left, and ArrowUp/ArrowDown move focus among the current tray's own rows. A visually-hidden aria-live=\"polite\" region announces the active level's name once a shunt settles. prefers-reduced-motion drops the slide/scale/stagger choreography entirely — path changes commit synchronously and only the ancestor dim film still crossfades, over 100ms — while the edge buttons keep rendering exactly as before, since depth recovery must not depend on motion. Colors only from --background, --foreground, --muted, --border and --accent (plus their standard opacity variants); zero dependencies, DOM and CSS only, no canvas."
      }
    },
    {
      "name": "meter-context-window",
      "type": "registry:ui",
      "title": "Meter Context Window",
      "description": "A context-window budget meter: one hairline stacked bar segmenting the window into system prompt / tools / history / current turn, told apart by fill pattern instead of color, that visibly resettles rather than jumps when the window compacts.",
      "files": [
        {
          "path": "registry/core/meter-context-window/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/meter-context-window.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "meter",
          "progress",
          "context-window",
          "tokens",
          "llm",
          "agent",
          "data-viz",
          "mono",
          "dashboard",
          "budget",
          "accessibility"
        ],
        "instruction": "A context-window budget meter for agent UIs: one hairline stacked bar (rounded-full, 10px tall, 1px --border ring, bg-background bed) segmenting the window into five shares — system prompt, tools, conversation history, current turn, and the unclaimed remainder — plus a font-mono tabular-nums readout and a text legend underneath. Pure DOM/CSS, no canvas, no SVG paint loop, no rAF: each segment is a flex child whose flex-basis is its percentage of max(capacity, used) so the five always sum to a full bar even when usage exceeds capacity, and a min-width floor (3px) on any segment that has real tokens keeps it from vanishing below a legible sliver — the browser's own flex-shrink gives it room by shrinking the larger segments, not a hand-rolled pixel solver, so a segment with zero tokens renders zero width honestly and one with tokens never fakes a mark it didn't earn. The palette is monochrome by house rule, so segments are told apart primarily by a stepped weight ramp (four flat, unmistakably different color-mix() tones against --foreground, never --accent — 22/30/40/52% — so even a 3px sliver reads as a distinct step, with no fill ever falling through to a bare or transparent look that would misread as free space) plus a bonus pattern layered on top of each base tone once a segment is wide enough to resolve it (system stays flat, tools gets a 45deg diagonal hatch, history a stipple, current turn a close vertical hatch that breathes gently — a 2.6s opacity pulse — while it's the live, growing share); a hairline 1px --background divider sits between every adjacent pair so boundaries stay crisp even when two neighboring tones land close together, and the free remainder is bare track. Compaction is just a prop change — react re-renders new percentages and every segment eases into them on a shared 520ms cubic-bezier(0.22,1,0.36,1) flex-basis transition, so the resettle itself is what communicates that compaction happened, never an instant jump. Approaching capacity reads as a warning through form, never a red fill: once free space drops to or below `lowFreeThreshold` (prop, fraction of capacity, default 0.08) or usage exceeds capacity outright, the track gains a slow pulsing inset ring, the readout goes bold and grows a small triangle glyph, and the free segment (if any width remains) grows a diagonal hazard hatch. Props: capacity, system, tools, history, turn (all plain numbers, tokens, fully controlled — the component holds no state of its own), lowFreeThreshold, ariaLabel, className. Accessibility: the bar itself is aria-hidden (a redundant visual against the text that follows); a role=group wraps the whole meter with an accessible name; the token readout is a real aria-live=polite paragraph so async updates get announced; the legend below the bar is plain DOM text — label, exact token count, and a percentage (values under 1% but above zero read '<1%' rather than rounding to a misleading 0%) — for every one of the five shares, so assistive tech gets the real numbers regardless of what the bar renders. The component exposes no interactive controls of its own (display-only, correctly skipped by the tab-reachability check) and renders correctly in both themes since every ink is a CSS custom property, never a literal. prefers-reduced-motion drops the flex-basis transition, the current-turn breathing, and the hazard pulse entirely — every state (empty, mid-fill, near-capacity, over-budget) stays fully legible, just static. Demo: an agent-session card (claude-4.5, 200K-token window) seeded with a realistic mid-fill baseline so the resting screenshot already shows the meter doing its job, plus a live status line, a CALL TOOL button that grows the tools share, a COMPACT CONTEXT button that shrinks history to roughly a ninth of itself and lets the resettle play, a RESET SESSION button, a FILL WINDOW button that jumps usage straight to ~96% so the near-capacity warning state (pulsing ring, bold readout + triangle glyph, hazard hatch on the free sliver) is reachable in one click rather than only after minutes of ambient growth, and a background interval that streams small turns into the current-turn share and folds each one into history after it settles — pausing near 94% usage the way a real agent would compact before running out, so the near-capacity warning state is also reachable just by leaving the demo running."
      }
    },
    {
      "name": "meter-latency-capillary",
      "type": "registry:ui",
      "title": "Meter Latency Capillary",
      "description": "Time-to-first-token rendered as calibrated capillary rise — a narrow tube climbs to a scribed p50 line, holds and trembles if it's running slow, and only surfaces retry/switch-model once it's genuinely past p95. Never fakes progress.",
      "files": [
        {
          "path": "registry/core/meter-latency-capillary/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/meter-latency-capillary.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loading",
          "status",
          "latency",
          "agent",
          "aria-live",
          "svg",
          "meter",
          "timing"
        ],
        "instruction": "Renders time-to-first-token as a calibrated capillary tube: a narrow column, exactly 6px wide and 48px tall, 1px --border walls, whose fill (--muted at 25% opacity) rises from the bottom toward a scribed p50 line the instant the request starts. The rise duration is set so the fill's top — a small concave SVG meniscus curve riding it, stroked in --foreground, animated separately from the fill itself — reaches that p50 line exactly when a response typically lands for this model/tool combo, using an eased (not linear) decelerating curve so the arrival reads as a settling motion, not a race. If nothing has arrived by the time the p50 line is reached, the component does the one honest thing a spinner never does: it stops rising. The fill HOLDS at the p50 level — it never keeps climbing toward p95, since that would misrepresent unearned progress — while the meniscus curve alone (not the fill, not the tube) gets a barely-there 0.5px vertical tremble at 2Hz, and a second scribed line for p95 fades in above it over the p50-to-p95 window. The moment elapsed time actually passes p95, the tremble stops outright (trembling forever would itself be dishonest about how much longer 'unusually slow' is expected to last) and a stall affordance — real Retry and Switch model buttons, not disabled decoys — mounts into the layout, entering tab order exactly when they appear. If the first token arrives at any point (an `arrivedAt` timestamp appears), the tube drains to empty fast, ease-out-expo, from wherever the fill currently sits, as the real streaming response takes over. The whole thing is driven by three props — `startedAt` (ms epoch, defaults to mount time), `p50Ms`, `p95Ms` (defaults are the caller's, drawn from a rolling per-model latency store) — via two scheduled setTimeouts rather than a rAF poll, and correctly handles a late mount: if the component is created e.g. 300ms into an already-running request, it computes remaining time to each threshold and eases in for only what's left, rather than restarting the rise from zero. Accessibility is phase-transition-only: a role=status aria-live=polite region announces exactly once per transition — 'Waiting, typically N seconds.' entering the wait, 'Taking longer than usual.' entering the slow hold, 'May be stalled, retry available.' entering the stalled state — and never narrates the continuously-changing fill level itself, which would be noise. Retry and Switch model are ordinary <button>s with visible text labels (real accessible names, not icon-only), rendered conditionally so they simply aren't in the DOM (and not in tab order) until the stalled phase actually begins. Under prefers-reduced-motion, the entire tube/meniscus/tick apparatus is replaced by a static three-segment strip (within-normal / slow / stalled) with the current segment lit from --muted to --foreground and the rest dim — driven by the exact same phase state machine and the exact same three announcements, so nothing about the component's honesty depends on a viewer being able to perceive motion. Distinct from status-glyph-cadence, which signals raw connection liveness with no concept of 'how long is normal' — no percentiles, no stall detection, just 'is it still alive'; and distinct from password-strength-tide, whose liquid level encodes input strength typed so far, not elapsed wall-clock time measured against a calibrated latency distribution. Props: startedAt (optional, ms epoch), p50Ms and p95Ms (required, ms), arrivedAt (optional ms epoch or null/undefined while waiting), onRetry, onSwitchModel (both optional callbacks fired by the two stall-state buttons), label (accessible name for the root group, default 'Time to first token'), className. Pure DOM + CSS + one small inline SVG path for the meniscus curve — no canvas, all ink and fill from --foreground/--muted/--border tokens."
      }
    },
    {
      "name": "meter-quota-meniscus",
      "type": "registry:ui",
      "title": "Meter Quota Meniscus",
      "description": "A quota (storage, API budget, seats) rendered as liquid in a thin vessel where surface curvature, not level, carries the reading: concave under the soft limit, flat at it, convex and overfull-but-held in the grace zone, and a bead that breaks off past the hard limit leaving a permanent stain.",
      "files": [
        {
          "path": "registry/core/meter-quota-meniscus/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/meter-quota-meniscus.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "meter",
          "quota",
          "threshold",
          "gauge",
          "svg",
          "spring",
          "status",
          "accessibility"
        ],
        "instruction": "Renders one quota reading — `value` against a `softLimit` and a `hardLimit`, all in the same `unit` (default \"%\") — as liquid in an open-top SVG vessel (two vertical wall lines plus a floor line, all `stroke-current text-border`) whose fill's top edge is a single cubic bezier: fixed endpoints at the current `levelY`, two control points sharing one `curveOffset` scalar that is the entire mechanism. Below `softLimit`, `levelY` rises linearly from the floor toward the rim as `value` goes 0 -> softLimit, and `curveOffset` is negative and shrinking toward zero — control points sit below the endpoints, pulling the middle of the curve down so the edges read higher than the center: a concave meniscus wetting the walls, deepest at value=0, flattening as `value` approaches `softLimit`. At `softLimit` the curve is exactly flat (`curveOffset` = 0) and `levelY` reaches the rim exactly. Between `softLimit` and `hardLimit`, `levelY` stops rising — pinned at the rim, because the vessel is nominally full — and `curveOffset` instead goes positive and grows toward `hardLimit`: control points rise above the endpoints, bulging the middle upward past the two wall-line tops (which stop exactly at the rim y, leaving open space above them) into a visible convex dome, unconfined by any wall above that height — over budget, physically overfull, still held by the curve, no color change required to read the zone. The instant `value` reaches or passes `hardLimit`, `curveOffset` clamps at its maximum (the dome stays pinned at its fullest rather than growing further) and, edge-triggered on that specific crossing (a ref tracks the previous zone so re-renders at the same over-hard value don't replay it), a small `text-muted` circle appears at a fixed point just outside the right wall and runs one `900ms` gravity-eased (`cubic-bezier(0.55,0,0.85,0.35)`, i.e. accelerating) keyframe animation down the outside of the glass, fading out as it lands; immediately behind it a static `1px` `text-muted` line at 60% opacity remains from the rim down a fixed drip length — the stain, a permanent record of the crossing that persists through further value changes and only clears once `value` drops back to a clear headroom read (`<= softLimit`), not merely below `hardLimit`, so a value hovering just under the hard cap after a spill still shows the mark. Both `d`-valued SVG paths (the filled liquid body and the stroked surface line) share one CSS `transition: d 700ms cubic-bezier(0.34,1.56,0.64,1)` — a back-out timing function whose control points push the eased fraction past 1 before settling back to it, which is the 'soft spring': no JS simulation loop, the curve genuinely overshoots its new target once and stills on every `value`, `softLimit`, or `hardLimit` change. A Geist Mono readout sits above the vessel: the metered label and a bold zone chip ('under soft limit' / 'over soft limit' / 'over hard limit') on one line, the live value (with a trailing '%' when `unit` is '%', a space-prefixed unit otherwise) and '/ {hardLimit} cap' on the next, and a status caption below the vessel spelling out remaining headroom, 'over the soft limit, still tolerated', or 'held — overflow marked on the glass' depending on zone — so the reading is never dependent on perceiving curvature alone. The outer element is `role=meter` with `aria-labelledby` on the visible label, `aria-valuemin=0`, `aria-valuemax=hardLimit`, `aria-valuenow=value`, and `aria-valuetext` spelling e.g. '84%, over soft limit' (value, unit suffix, zone) — the exact sentence a screen reader gets on every read, not just at a boundary crossing. A separate visually-hidden `role=status`/`aria-live=polite` span announces a fuller zone-change sentence ('over soft limit — still held', 'over hard limit — overflowing', 'within soft limit') only when the zone itself changes, not on every value tick, so assistive tech isn't spammed mid-fill. The SVG itself is `aria-hidden` — decorative once the meter node and the two text readouts carry the real semantics — and being a passive display component with no exposed control, it is correctly exempt from Tab-reachability on its own (the demo's cycle button is what Tab actually reaches). Under `prefers-reduced-motion: reduce` (checked via `matchMedia` with a live change listener) both the `d` transition and the bead's keyframe animation are stripped via a media-query CSS block: zone and curvature changes snap straight to their resting shape, the bead never renders (the stain still appears, just without the drop), and everything stays fully legible. Every stroke and fill is a `stroke-current`/`fill-current` Tailwind class tinted `text-border`, `text-muted`, or `text-foreground` at low opacity — never a hex literal or `getComputedStyle` read, since nothing here is a raster surface — so both themes restyle for free. No canvas: two SVG `<path>` elements, three `<line>` walls/floor/lip-ticks, one conditional stain `<line>`, and one conditional bead `<circle>` are the entire visual, DOM+SVG+CSS only."
      }
    },
    {
      "name": "meter-quota-rule",
      "type": "registry:ui",
      "title": "Meter Quota Rule",
      "description": "A quota meter with no bar, no fill, no pill: the printed reading ('38.2 GB of 100 GB') sits above a 4px hairline whose used portion is solid and remainder is dashed, a fixed tick marks the warning threshold, and crossing it thickens the rule and bolds the digits instead of turning anything red.",
      "files": [
        {
          "path": "registry/core/meter-quota-rule/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/meter-quota-rule.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "meter",
          "quota",
          "usage",
          "typography",
          "svg",
          "status",
          "accessibility",
          "settings"
        ],
        "instruction": "Renders one quota — a live `value` against a `max`, both in `unit` — as nothing but its own printed reading sitting above a ruled hairline, with no bar, fill, or pill anywhere. Above the rule: a small mono `label` caption (e.g. 'STORAGE') and, directly under it, the composed readout '38.2 GB of 100 GB' in tabular-nums text, the used number and unit in full ink and the 'of X unit' remainder in --muted. Below that: a single inline SVG line 4px tall (viewBox 0 0 400 6, preserveAspectRatio=none, every stroke `vector-effect=non-scaling-stroke` so widths stay literal px regardless of the container's rendered width) drawn as three overlaid `<line>` elements sharing one horizontal axis: a dashed line from the used/remaining boundary to the far end, --border ink, `stroke-dasharray=\"2 3\"`, representing the unused allowance; a solid line from 0 to that same boundary, --foreground ink, representing the used allowance, painted on top of the dashed line's origin so the seam never gapes; and a fixed vertical tick at the warning fraction (default 0.8 of max, overridable via `warning`), stroked in `var(--warning, #f5a623)` and nothing else in the whole component ever touches that token — the fill is always --foreground/--border, never colored by state, matching the rule that --warning marks a threshold, it doesn't recolor a value. On every `value`/`max` change the used/unused boundary — the shared x-coordinate the solid line ends at and the dashed line begins at — eases to its new position with a hand-rolled requestAnimationFrame tween (no dependency), ease-out-expo shaped (`1 - 2^(-10t)`), 350ms, cleanly retargetable mid-flight if a new value lands before the previous animation settles. Crossing the tick is the entire state change and it is never carried by color: as the animated boundary sweeps past the tick's x-position, the solid stroke's `stroke-width` steps 1px -> 2px (itself CSS-transitioned over 200ms) and the printed numerals' `font-weight` steps 400 -> 600 in the same beat — the rule visibly thickens and the digits visibly firm up exactly as the boundary crosses the mark, then holds that state at rest; nothing turns red, nothing recolors, the line just asserts itself harder ('clears its throat'). The rest-state crossing test (used for ARIA and for which side of the 400/600 step the component settles on) is computed from the true `value`/`max` props directly, decoupled from the animation's current frame, so assistive tech never races a mid-flight visual. Structurally the meter is `role=meter` on the wrapper around the SVG, `aria-labelledby` pointing at the visible label span, `aria-valuemin=0`, `aria-valuemax` = max, `aria-valuenow` = value, and `aria-valuetext` spelling the same reading in full words with the crossing state named explicitly — e.g. '38.2 of 100 gigabytes, below warning threshold' or '19 of 20 seats, at or above warning threshold' — built from an optional `unitLabel` prop (falls back to the short `unit` if omitted) so the short glyph used in the printed row ('GB') and the spoken word used by ARIA ('gigabytes') can differ. The SVG itself is `aria-hidden` — decorative once the meter node and the always-visible printed text carry the real reading — and a visually-hidden `role=status`/`aria-live=polite` paragraph mirrors the same `aria-valuetext` so a value change announces without needing focus, matching the printed text as the primary reading for sighted and screen-reader users alike. The component is non-interactive by design: no button, no input, no keyboard surface, nothing for Tab to reach, and it is correctly exempt from the registry's tab-reachability check as a display-only meter. Under `prefers-reduced-motion` (checked via `matchMedia` with a change listener) the boundary tween is skipped entirely — value changes apply their new resting position in a single frame — and the stroke-width/font-weight CSS transitions are stripped via a matching media-query block, so the crossing state is still fully legible, just instant rather than swept. Every ink comes from `stroke-current`/`text-*` token classes (--foreground, --border, --muted) except the tick, which is the one and only place `var(--warning, #f5a623)` appears, exactly per the semantic-color rule that warning marks a threshold marker, never a fill. Differs from sparkline-automaton, which threads a whole SERIES of points into a typographic rule as a KPI sparkline with history and a Wolfram-rule cellular-automaton texture; meter-quota-rule carries no history and no trend at all — it is a single static fraction-of-allowance, legible instantly because the numbers are always printed, meant to appear dozens of times on one settings page (storage, seats, API credits, budget) at plain text scale rather than as a hero chart."
      }
    },
    {
      "name": "meter-threshold-trip",
      "type": "registry:ui",
      "title": "Meter Threshold Trip",
      "description": "A data-driven threshold indicator built like a thermostat's bimetallic strip: it bows progressively as a metric climbs, snaps across a visible contact gap at the trip point and latches, then only re-straightens once the value falls below a lower re-arm mark — making hysteresis visible instead of implying it with a color change.",
      "files": [
        {
          "path": "registry/core/meter-threshold-trip/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/meter-threshold-trip.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "meter",
          "threshold",
          "hysteresis",
          "alert",
          "svg",
          "spring",
          "status",
          "accessibility"
        ],
        "instruction": "Renders one watched metric — `value` against an upper `tripAt` and a lower `clearAt` (with `clearAt` < `tripAt`), plus a `min`/`max` display domain (default 0/100) and `unit` (default \"%\") — as a bimetallic thermostat strip instead of a colored status badge. The strip is a single SVG quadratic bezier fixed at two mounting posts (`stroke-current text-border` rects); only its control point moves, offset upward as a `bowFrac` scalar that is the entire mechanism: while unlatched, `bowFrac` = clamp((value-min)/(tripAt-min), 0, 1) scaled to a fixed ceiling short of full bow — 0 (straight) at `min`, approaching but deliberately never reaching full bow as `value` nears `tripAt` — and while latched it is pinned at exactly 1, which is what makes the hysteresis visible (a value that dips from above `tripAt` back down into the band between `clearAt` and `tripAt` does not un-bow the strip at all) and also what guarantees the snap always has real distance to close: the unlatched ceiling and the latched pin are never numerically equal, so the moment `value` reaches `tripAt` there is always a genuine gap left for the crossing to visibly snap shut, regardless of how big a step `value` jumped by. The strip's tip closes a visible gap against a fixed contact pad mounted above it; the instant `value` reaches `tripAt` while unlatched, a `latched` boolean flips true (derived purely from data — a `value`/`tripAt`/`clearAt` effect, never user input) and, edge-triggered on that specific crossing, the path's `d` transitions on a 320ms back-out CSS curve (`cubic-bezier(0.34, 1.56, 0.64, 1)`, i.e. genuinely overshoots past full bow before settling — the ~12% spring overshoot, achieved as a CSS `d` transition rather than a JS simulation loop, matching this registry's established technique for path-level springs) while the strip's `stroke-width` jumps from 2 to 3.5 (a heavier `text-foreground` stroke — never `text-accent`, since latching is a data state, not an interaction) and the contact pad switches from an outlined `text-border` rect to a filled `text-foreground` one with an additional filled circle exactly at the touch point marking contact made. Latched, the strip stays exactly there — pinned, heavier, contact filled — no matter how `value` moves, until it falls below `clearAt`, at which point `latched` flips false and the same `d` instead relaxes on a slow 600ms ease-out-expo curve (`cubic-bezier(0.16, 1, 0.3, 1)`, no overshoot) back down to wherever the ordinary `bowFrac` formula now places it for the current (sub-`clearAt`) value — a deliberate 'un-tensing' rather than a snap. Ordinary value changes that cross neither threshold ease on a quick 260ms non-overshooting curve of the same easing family, so only the two threshold crossings get their own distinct physics. Below the strip, a muted hairline band (`fill-current text-muted opacity-40`) spans `clearAt`..`tripAt` on the exact same x-domain as the strip's own mounting posts, with a `text-border` tick at each end, a small `text-foreground` circle riding along it at the live value's position, and a Geist Mono caption row below reading 'clears {clearAt}{unit}' / 'trips {tripAt}{unit}' — the two marks are legible from a single still frame, not just inferable from strip position. A bold Geist Mono state chip ('CLEAR' / 'LATCHED') sits beside the label above the strip at all times, and a status paragraph below spells out the current state and, when latched, exactly what has to happen to clear it. The outer meter node is `role=meter`, `aria-labelledby` on the visible label, `aria-describedby` on that status paragraph (which always states the trip mark, the clear mark, and the current latch state — legible on demand, not just at the moment of a crossing), `aria-valuemin=min`, `aria-valuemax=max`, `aria-valuenow` (clamped into [min,max]), and `aria-valuetext` spelling the value, latch state, and both thresholds in one sentence; it also carries `tabIndex=0` so it is reachable and inspectable by a screen reader even though it exposes no interactive affordance of its own (not a control, correctly exempt from the registry's accessible-name-on-controls rule, though it has one anyway via `aria-labelledby`). A separate visually-hidden `role=status`/`aria-live=polite` span announces only the two edge-triggered transitions ('Latched — ... crossed trip ...' / 'Re-armed — ... fell below clear ...'), not every value tick. The SVG itself is `aria-hidden`, decorative once the meter node and the two text readouts carry the real semantics. Under `prefers-reduced-motion: reduce` (checked via `matchMedia` with a live change listener, backed by a CSS media-query override on the path's `transition` as well) every `d` transition collapses to instant (0ms) — snap, re-arm, and ordinary climbs all jump straight to their resting shape — while the same aria-live announcements still fire on the same crossings, so the state change is never silent even without motion. Every stroke and fill is a `stroke-current`/`fill-current` Tailwind class tinted `text-border`, `text-muted`, or `text-foreground` — never a hex literal, never `getComputedStyle`, since nothing here is a raster surface — so both themes restyle for free. No canvas: one strip `<path>`, two mounting-post `<rect>`s, one contact post `<line>` plus pad `<rect>` plus a conditional lit `<circle>`, one hysteresis band `<rect>` with two tick `<line>`s and a live-position `<circle>`, is the entire SVG, DOM+SVG+CSS only, zero dependencies."
      }
    },
    {
      "name": "minimap-pantograph",
      "type": "registry:ui",
      "title": "Minimap Pantograph",
      "description": "A minimap whose viewport rectangle is physically wired to the real viewport by a live SVG pantograph — two elbow arms solved by two-bar IK every frame — so dragging the small handle visibly swings and extends the linkage as the document pans, making the scale ratio legible instead of implied.",
      "files": [
        {
          "path": "registry/core/minimap-pantograph/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/minimap-pantograph.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "minimap",
          "pantograph",
          "svg",
          "drag",
          "scroll",
          "navigation",
          "linkage",
          "slider"
        ],
        "instruction": "Build a minimap + viewport pair for navigating content taller (and optionally wider) than its visible window, with a live SVG pantograph drawn between them. Layout: a small minimap track (fixed px size, independent of the content's aspect ratio, like a scrollbar rail) contains a scaled CSS-transform clone of the same children (scale(minimapWidth/contentWidth, minimapHeight/contentHeight), aria-hidden, pointer-events-none, opacity 0.7) plus a draggable rect overlay sized to the viewport's visible fraction; a real viewport pane (overflow-auto, fixed height) contains the full-size children in a contentWidth x contentHeight wrapper. Between the rect's near corner and the viewport pane's top-left corner, an aria-hidden SVG draws a two-arm scissor linkage: two mirrored 2-bar elbow arms (four 1px currentColor line segments total, text-muted, with 3px filled pivot circles at both anchors and both elbows) computed by trivial two-bar IK — given anchor distance d, each arm's fixed reach is d/2 + 26px (a constant bow past half the span), so the elbow height off the anchor line is sqrt(link^2 - (d/2)^2), always solvable, and shrinks as a fraction of d as the anchors pull apart — extending the linkage visibly flattens it, compressing it visibly opens it, which is the scissor read. The two anchor points themselves (rect corner, viewport corner) are never sprung — they track their DOM elements exactly via getBoundingClientRect every active frame, so the linkage always looks physically attached; only the two elbow joints ease toward their per-frame IK target on a k=200 s^-2, zeta=0.8 spring, so a fast drag flick shows the arms lag half a beat behind the handle before snapping into the new shape. The whole linkage group relaxes to 0.3 opacity 700ms after the last drag/scroll/key input and snaps back to full opacity on the next one (CSS opacity transition, 300ms). Dragging: pointer handlers live on the minimap track (not just the small rect) so a grab starting anywhere in the track is captured via setPointerCapture, mirroring how a real trackpad-style control is grabbed; move delta in screen px is divided by the fixed scaleX/scaleY (minimapWidth/contentWidth, minimapHeight/contentHeight) to get the content-space scroll delta, so e.g. a 140px-wide minimap over 560px content moves the document 4x whatever the handle moved, and a taller document over the same minimap height moves proportionally more — the ratio is whatever the two fixed sizes imply, not hardcoded. A11y: the rect is a focusable two-axis control, role=slider, tabIndex=0, aria-orientation=\"vertical\" (the dominant axis for the long-document case this demonstrates), aria-valuemin=1, aria-valuemax=totalRows (contentHeight/rowHeight), aria-valuenow=current start row, aria-valuetext e.g. 'viewing rows 120-160 of 900' updated directly via setAttribute every active frame (not React state, to avoid re-rendering the host tree on every scroll tick). ArrowUp/Down pan one row (rowHeight px); ArrowLeft/Right pan one 32px step (for content wider than the viewport); PageUp/PageDown pan 90% of a screenful; Home/End jump to the vertical extremes. The viewport pane's own native scroll (wheel, trackbar, touch) stays live and wakes the same render loop via a passive scroll listener, so dragging the handle and scrolling the content directly stay in sync either way; a ResizeObserver on the viewport re-measures its client box so the rect's size tracks real available width. Direct-DOM rAF: a single loop, refs only, sleeps once both elbow springs are within 0.15px / 2px-per-second of their targets and no drag is in progress, wakes on pointerdown/move, keydown, scroll, or resize. Reduced motion: the elbow springs are skipped entirely (position snaps straight to the IK target every frame) so the arms still track the handle rigidly, just without the lag-then-snap; the opacity idle-relax is unaffected (a plain fade, not translational motion). Tokens only: all linkage ink is currentColor via a text-muted class on the group, track/viewport borders are border-border, backgrounds are bg-background, and the handle uses border-foreground/60 at rest, border-accent on hover, and an accent focus-visible ring (ring-2 ring-accent, offset from bg-background) — never outline-none paired with focus-visible:outline on the same element. Cleanup: rAF, ResizeObserver, and all native listeners (attached via addEventListener in the same effect that owns the loop, not JSX props, since the geometry closures need direct refs) are torn down on unmount."
      }
    },
    {
      "name": "nav-condense-rail",
      "type": "registry:ui",
      "title": "Nav Condense Rail",
      "description": "A full-width site nav that condenses in place as the page scrolls — roomy padding, full-size wordmark and links at the top, tightening to a dense pinned rail — over a distance measured from the bar's own rendered height at each extreme, not a guessed scroll-pixel constant.",
      "files": [
        {
          "path": "registry/core/nav-condense-rail/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/nav-condense-rail.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "header",
          "navigation",
          "scroll",
          "condense",
          "density",
          "measured",
          "chrome"
        ],
        "instruction": "Build a full-width site navigation bar — fixed to the top, a wordmark on the left, a link row and an accent CTA button on the right — that condenses continuously as the page scrolls: tall vertical padding and full-size type at rest, shrinking to tight padding, a smaller wordmark (via a counter-scaled transform, not a font-size change, so it never touches layout) and smaller link/button type once the page has moved past it. The one constraint that makes this more than a CSS scroll-snap trick: the pixel distance the condense happens over must never be an arbitrary constant. Derive it instead from measurement — render the bar's real markup twice more, off-screen (position: fixed, left: -99999px, visibility: hidden, aria-hidden), once at the roomy style values and once at the dense ones, and read each one's actual height via ResizeObserver. The travel distance is roomyHeight − denseHeight: exactly as long as the bar's own vertical shrink actually is, so it stays correct across a different wordmark, a different font stack, or a narrower viewport, with no per-deployment constant to retune. A single continuous progress value in [0,1] is computed every frame inside one requestAnimationFrame loop as clamp(window.scrollY / travel, 0, 1), eased toward that target with an exponential approach (1 − exp(−rate·dt)) for a soft settle rather than a snap, and written straight to refs' inline styles — bar padding-block, wordmark transform: scale(), link font-size, link row gap, the CTA's own padding, background (color-mix'd from --background, fading in as progress leaves 0 so the roomy bar reads as transparent chrome over hero content), a 1px --border bottom hairline whose opacity fades in the same way, and a backdrop-filter blur once any condensing has begun — never through React state per frame, so there is zero re-render on the scroll hot path. A spacer div sits in normal document flow directly under the fixed bar and has its height written every frame to the same interpolated value, so the page never jumps as the bar's real height changes. prefers-reduced-motion drops the rAF loop and the continuous interpolation entirely: a plain scroll listener applies one of exactly two discrete style sets — fully roomy or fully dense — with the cut set at half the measured travel distance (still a measured boundary, never a hand-picked pixel figure), so the bar still functions, just without the tween. Links get a real focus-visible ring (outline-2, outline-offset-4, outline-accent) and a hover color change from --muted to --foreground distinct from the resting state; the CTA is a role=navigation landmark's ordinary link, not a button, since it navigates. Colors are entirely token-derived (--background, --foreground, --muted, --border, --accent, --accent-hover) including the color-mix'd background and border fades, so both themes render correctly with no per-theme branch. Demo is a real, tall page (four sections beneath the bar) with no scripted auto-scroll — /preview stays the honest, fully interactive reference — while the card's `autoplay: scroll` descriptor eases the homepage grid's iframe through the same range to demonstrate the condense without user input."
      }
    },
    {
      "name": "nav-site-condense",
      "type": "registry:ui",
      "title": "Nav Site Condense",
      "description": "A full-width site nav with the furniture a real site needs: a scroll-condensed bar, a menu trigger present at every width, and a mobile sheet built on native <dialog> for a free focus trap and Escape-to-close.",
      "files": [
        {
          "path": "registry/core/nav-site-condense/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/nav-site-condense.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "nav",
          "header",
          "navigation",
          "sheet",
          "dialog",
          "scroll",
          "mobile",
          "focus-trap"
        ],
        "instruction": "Build <NavSiteCondense brand? links condenseAt? className?> where links is NavLinkItem[] ({label, href}). STRUCTURE: a sticky (position:sticky, top:0, z-40) <header data-nav-site-condense> containing a max-width row with a wordmark <a>, a hidden-below-sm inline link row (role via a real <nav aria-label=\"Primary\">), and a menu trigger <button aria-haspopup=\"dialog\" aria-controls aria-expanded aria-label=\"Open menu\">. CONDENSE: deliberately minimal and subordinate to the sheet — a passive, rAF-throttled scroll listener compares window.scrollY against condenseAt (default 24px) and flips one boolean React state; past the threshold the header swaps to tighter vertical padding, a background-color/backdrop-blur and a border-bottom, and the wordmark's font-size drops a step, all via a plain Tailwind class swap with a CSS transition (motion-reduce:transition-none), not a continuous interpolation, a measured height, or a spring — nav-condense-rail already owns that continuous, measured density transition and header-scroll-pill already owns the silhouette-morph-into-a-pill; duplicating either here would be a restyle. The initial scroll position is read once on mount so a page that loads already scrolled starts in the correct state. TRIGGER: rendered unconditionally at every viewport width, never hidden behind a `md:hidden`/`sm:hidden` breakpoint — a trigger that only exists below some breakpoint is unreachable (and unclickable by anything driving the page, human or otherwise) at any width at or above it. It doubles as a full-sitemap entry point even next to the inline desktop links. SHEET: a right-anchored panel built on the native <dialog> element. Opening sets React state to true, an effect calls dialogRef.current.showModal() (never .show() — modal is what gives the free focus trap, Escape-as-cancel and top-layer stacking), closing calls .close(). The panel's own CSS transitions transform from translateX(100%) to translateX(0%) and opacity 0 to 1 keyed off the dialog's own `[open]` attribute, with @media (prefers-reduced-motion: reduce) dropping the transition entirely. Backdrop click (event target === the dialog element itself, since ::backdrop isn't a real element) and Escape (the dialog's native `cancel` event, preventDefault'd only to keep this component's own state in sync, not to block the close) both close it, and the `close` event is also listened to so state never drifts out of sync with reality. Every link inside the sheet closes it on click. The sheet's own <nav aria-label=\"Mobile\" data-nav-site-condense-sheet> holds the link list. ACCESSIBILITY: the header's inline links and the sheet's links both carry hover (text-muted to text-foreground) and focus-visible:outline-2 outline-offset-4 outline-accent states with no outline-none on the same element (Tailwind v4 latches --tw-outline-style to none permanently if both are present, and the ring silently never paints even though the classes look correct). The dialog carries aria-modal=\"true\" and aria-labelledby pointing at a visible heading; Escape and the native focus trap are inherited for free from showModal(). Demo is a real, tall page with no scripted auto-scroll so /preview stays the honest interactive reference, while the card's autoplay:scroll descriptor eases the iframe a short way past the condense threshold."
      }
    },
    {
      "name": "network-packet-trace",
      "type": "registry:ui",
      "title": "Network Packet Trace",
      "description": "A small hairline node/edge network where data pulses branch across random routes, easing along each edge with a fading tail — idle traffic, dense active traffic, or an error state where packets queue up at a congested hub node.",
      "files": [
        {
          "path": "registry/core/network-packet-trace/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/network-packet-trace.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "network",
          "status",
          "visualization",
          "svg",
          "animation",
          "tooltip",
          "accessibility",
          "activity"
        ],
        "instruction": "Build an activity/status visualization as a small fixed node network: 9 SVG nodes at hand-placed (not grid-aligned) coordinates inside a 320x180 viewBox, joined by 13 hairline edges (var(--border), ~1px stroke, round linecap) forming a graph with several cycles so routes actually branch rather than forming a single tree. One node (the highest-degree node in the graph, acting as a hub) is the designated congestion point. A `state` prop (idle | active | error, default idle) drives a rAF-based particle simulation: pulses are small circles (2px radius head + a 3-segment fading tail, all var(--foreground), tail opacity stepping down ~0.3 per segment) that travel along the graph via random walks — from a random start node, hop to a random neighbor (avoiding immediate backtrack unless at a dead end) for 3-6 hops, then despawn and eventually respawn elsewhere. idle keeps ~3 pulses concurrently alive with slow (900-1800ms) spawn spacing; active keeps ~10 alive with fast (150-400ms) spacing. In error, every new pulse's route is instead the shortest path (BFS over the adjacency graph) to the hub node; on arrival it does NOT despawn — it parks there, stacking visually above the node (each queued pulse offset a few px higher than the last, newest on top), capped at 6 concurrent queued pulses with the oldest evicted once the cap is exceeded so the stack keeps turning over. While `state===\"error\"` the hub node itself blinks between full opacity and 35% opacity using a CSS keyframe animation (not JS per-frame) tinted var(--error). All per-frame position writes (pulse head/tail cx/cy, opacity) are direct DOM writes via refs to pre-mounted SVG circle elements inside one requestAnimationFrame loop — never React state per frame. React state is reserved for the cheap, infrequent stuff: which node is hovered/focused (used to brighten every edge incident to that node to var(--foreground) at a heavier stroke-width and enlarge the node's radius), and the tooltip's open/closed state. Each node is a focusable SVG circle (role=\"button\", tabIndex=0, aria-label naming it e.g. \"Network node 3\", plus \", congested\" appended when it's the error-state hub) so both pointer hover and keyboard focus open the same highlight + a Geist Mono tooltip (role=\"tooltip\", aria-live=\"off\", positioned via percentage offsets derived from the node's viewBox coordinates) reading \"Node N — K routed\", where K is a running per-node visit counter incremented in the simulation loop every time a pulse's hop advances onto that node (a real live count, refreshed every 500ms while a tooltip is open via a small interval driving a re-render, not a decorative number). A dedicated sr-only span (role=\"status\", aria-live=\"polite\", aria-atomic=\"true\") announces the traffic mode in plain language on every `state` change (e.g. \"Network error — packets queuing at node 7.\"), separate from the tooltip and node elements so it can't pick up their text on an atomic re-read. prefers-reduced-motion removes the entire pulse layer: instead each edge's stroke-width is set from a fixed per-edge weight table (edges touching the hub render heavier) so the static picture still communicates which routes matter most, and the error state renders three small static stacked dots above the hub instead of an animated queue. No canvas anywhere — pure SVG + rAF. Zero dependencies."
      }
    },
    {
      "name": "notification-bell-swing",
      "type": "registry:ui",
      "title": "Notification Bell Swing",
      "description": "A notification bell whose clapper physically rings — arrivals add an impulse to a damped harmonic oscillator swinging the clapper and recoiling the bell body, bursts read as one cumulative swing rather than N separate dings, and opening the tray damps the bell to rest and drains the badge.",
      "files": [
        {
          "path": "registry/core/notification-bell-swing/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/notification-bell-swing.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "notifications",
          "bell",
          "badge",
          "spring-physics",
          "popover",
          "focus-trap",
          "aria-live",
          "svg"
        ],
        "instruction": "Build a notification bell (`items: {id, message}[]`, append-only — an id not seen on the previous render is an 'arrival') whose clapper physically swings instead of a badge just ticking up. Model the swing as a real damped harmonic oscillator on a single angle value: `accel = -K*angle - C*velocity` (K≈90 stiffness, C≈6.4 damping), integrated every frame with `velocity += accel*dt; angle += velocity*dt` and clamped to +-34deg, written straight to two SVG `<g>` refs via `style.transform = 'rotate(Ndeg)'` — the clapper (a short line + circle) gets the full angle, the bell body gets `-angle * 0.22` (a smaller, opposite recoil, since a struck bell rocks against the strike, not with it). This is a genuine rAF hot path: no React state inside the loop, and the loop only keeps running while `|angle|` or `|velocity|` stay above a small epsilon, stopping itself (canceling the frame) once the system is at rest so an idle bell costs nothing. Each new arrival does NOT start a fresh swing animation; it ADDS a fixed impulse to the existing `velocity` and ensures the loop is running — so a burst of several arrivals in the same tick reads as one increasingly wild swing that then damps out, not overlapping independent dings. The badge count increments immediately when an arrival's impulse lands (not on a literal physics zero-crossing, which isn't worth detecting for this) and gets a one-frame CSS scale-squash (`scale(1) -> scale(1.5,0.7) -> scale(0.85,1.15) -> scale(1)`, ~340ms) retriggered via a forced `offsetWidth` reflow so repeat arrivals in a burst each still visibly pulse the badge even though they share one physical swing. Badge styling inverts like this registry's other ink components: `background: var(--foreground)`, `color: var(--background)`, a small mono pill, count capped to '9+' display. Hovering the bell button tracks pointer X relative to the button's center and tilts the bell BODY (not the clapper) up to +-2deg toward the cursor via a direct ref write — skipped while the physics loop is actively running so it can't fight the swing, restored to 0deg on pointer-leave. Clicking the bell toggles a tray: a NON-modal anchored popover (`role=\"dialog\"`, positioned absolutely below-right of the trigger) with a hand-rolled focus trap — Tab/Shift+Tab cycle between the tray's first and last focusable elements, Escape closes and returns focus to the trigger button, and a `pointerdown` outside both the tray and the trigger also closes it (no native `<dialog>`, since a notification tray shouldn't dim or inert the rest of the page the way a true modal does). Opening the tray immediately (before render) cancels any in-flight rAF loop and snaps the oscillator's angle/velocity to exactly 0 (via the same `applyPose` used every frame, called once), and zeroes the badge count — the visual 'drain'. A dedicated `role=status aria-live=polite aria-atomic=true` sr-only span announces \"N new notification(s)\" for whatever arrived, throttled: repeated arrivals within a 4-second window accumulate into one pending count and reset the timer, so a burst produces exactly one announcement, not one per item. `prefers-reduced-motion: reduce` skips the physics loop entirely (arrivals still increment the badge and mark the tray, but the clapper/body never visibly move) and swaps the badge's squash keyframes for a single opacity dip-and-recover pulse instead. Zero dependencies, no canvas, no dash-based SVG tricks (the bell outline is a single static hairline path, nothing animates via stroke-dasharray)."
      }
    },
    {
      "name": "optimistic-stitch",
      "type": "registry:ui",
      "title": "Optimistic Stitch",
      "description": "Optimistic-write feedback for a single row rendered as a tailor's basting stitch along its left seam — dashed hand-stitch while pending, pulled tight into a solid hairline on ack, or unraveled to muted and dimmed on failure with an inline retry.",
      "files": [
        {
          "path": "registry/core/optimistic-stitch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/optimistic-stitch.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "optimistic-ui",
          "list",
          "form",
          "comments",
          "svg",
          "accessibility",
          "aria-live",
          "retry",
          "state"
        ],
        "instruction": "<BasteStitch status={\"pending\"|\"committed\"|\"failed\"} onRetry={() => void} itemLabel?={string} className?={string}>{children}</BasteStitch> wraps one row's real content (list text, a comment body, a form field) with a left-seam SVG stitch that encodes exactly where that row's write sits in its optimistic lifecycle: PENDING renders an uneven, hand-stitch-sized stroke-dasharray (`3 2 4 2 2 3`, 2px stroke, color var(--border)) — deliberately irregular dash/gap lengths so it never reads as a machine-perforated line — on an absolutely positioned <svg><line> running the row's left edge (no viewBox; the line's y1/y2 are 0/100% so it always spans the row's actual rendered height, however tall the caller's content makes it). COMMIT: the instant the caller flips `status` to \"committed\" (the server ack landed), the dasharray transitions — a plain CSS `transition: stroke-dasharray 300ms cubic-bezier(.16,1,.3,1)` — to `1000 0 1000 0 1000 0`: every dash value pairwise-interpolates toward a length far longer than the row (reading as one continuous line) while every gap value interpolates to zero, so the uneven stitching visibly draws itself solid rather than cross-fading or swapping images. That transition is paired with a `scaleX(0.995) -> scaleX(1)` spring contraction (`cubic-bezier(.34,1.56,.64,1)`, 360ms, slight overshoot) on the whole row, not just the seam — the fabric itself pulls taut. FAIL: flipping `status` to \"failed\" instead snaps the dash offset outward fast (`stroke-dashoffset` 0 -> 18 over a sharp 160ms ease-in `cubic-bezier(.55,0,1,.45)`, reading as the thread yanked loose), loosens the dash pattern into wider frayed gaps (`2 7 1 9 2 6`), and settles the stroke color to var(--muted) over a slower trailing 260ms — never red, never --accent, structure and value only. The row's own content (everything the caller passed as children) dims to 55% opacity in the same beat, and a real `<button>` reading \"Retry {itemLabel}\" (itemLabel defaults \"change\") mounts inline beside it and calls the caller's `onRetry` — so failure is never signaled by the border shape alone: dimmed content plus a visible, focusable, tab-reachable Retry control are the other two channels, satisfying 'never the sole failure signal' even for a colorblind or non-visual read. AT REST, ZERO MOTION: this is the component's core constraint — pending (dashed, full opacity, no button), committed (solid hairline, full opacity, no button) and failed (muted frayed dashes, dimmed content, Retry button) are three structurally distinct static frames, not three points along one continuous shimmer; a screenshot of any one of them must be legible with nothing animating, which is also what separates this from a freshness/shimmer effect — if it ever reads as \"this row is newer\" rather than \"this row's write is in state X\" it has failed at its one job. LIFECYCLE, NOT LOGIC: the component holds no fetch, no retry backoff, no timers of its own — it is a pure function of the `status` prop plus one `onRetry` callback; the caller owns issuing the actual retry request and flipping `status` again once it resolves. ACCESSIBILITY: a visually-hidden `aria-live=\"polite\" aria-atomic=\"true\"` span holds exactly \"Saving\", \"Saved\", or \"Failed to save. Retry available.\" and updates on every `status` change, so a screen-reader user gets the same three-way signal a sighted user reads from the seam; the Retry button is a real, unstyled-away `<button>` in normal tab order (never a div with a click handler) whose visible text already is its accessible name (no separate aria-label to drift out of sync). REDUCED MOTION: `prefers-reduced-motion: reduce` drops every transition on the seam, the row's scaleX and the content's opacity to `none` — the three states still swap instantly and remain exactly as legible, just without the ease-out-expo draw-solid or the spring contraction. Pure DOM + one inline SVG <line> + CSS custom properties; no canvas, no dependencies, no JS-scheduled animation — every visual change is a `data-status` attribute swap that CSS transitions pick up on their own."
      }
    },
    {
      "name": "otp-reel",
      "type": "registry:ui",
      "title": "OTP Reel",
      "description": "6-box code entry where each box is a tiny slot-machine cipher reel: keystrokes spin a canvas glyph strip into a weighted detent on the typed digit with an accent flash; backspace reverse-spins to blank and idle cells carry a faint ambient drift.",
      "files": [
        {
          "path": "registry/core/otp-reel/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/otp-reel.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "otp",
          "input",
          "form",
          "canvas",
          "slot-machine",
          "2fa",
          "micro-interaction"
        ],
        "instruction": "Build a 6-box OTP control where every box is a tiny slot-machine cipher reel. RENDER: 6 real <input inputmode=numeric pattern=[0-9]* autocomplete=one-time-code maxLength=1> cells inside a fieldset+legend, each with a per-cell canvas overlay drawing a vertical mono glyph strip (0-9 first so digit d lives on row d, plus cipher glyphs #$%&*+); the input text and caret are transparent, the canvas is the visible face, and each canvas gets EXPLICIT style.width/height (replaced element — CSS inset does not size it), backing store at devicePixelRatio clamped to 2, full clear + redraw every frame. MOTION: a keystroke spins the reel ~2 rows at 14 rows/s decelerating at 30 rows/s^2, then a per-cell strip-rotation offset lands the typed digit in a detent spring (k=380 s^-2, zeta=0.6, ~0.15-row mechanical overshoot), total ~160-220 ms, with the cell border flashing --accent at 35% alpha for 120 ms on lock and a forced-settle deadline of 600 ms per reel so nothing can spin forever. Backspace reverse-spins 8 rows/s about 1.2 rows while fading to the blank state; paste and SMS autofill distribute digits with a 70 ms left-to-right stagger of the same spin; idle empty cells drift 0.15 rows/s at 25% alpha as the default ambient look. Error prop rising edge: the row shakes +-4 px for 3 damped cycles over 260 ms while every filled reel re-scrambles out over 240 ms and the code clears — motion carries the error, the --error token appears only on the helper text. INTERACTION: auto-advance on entry, Backspace on empty moves left, ArrowLeft/Right/Home/End navigate, focus selects, paste splits, aria-live polite helper announces completion and errors. PERFORMANCE: direct-DOM rAF with no React state on the hot path; each reel sleeps the instant it rests (a locked cell costs zero redraws), the loop pauses offscreen via IntersectionObserver and on document hidden with deadlines catching up on wake, zero-size containers guarded, every observer/listener/timeout torn down. THEME: glyph and accent ink parsed from getComputedStyle(--foreground/--accent) at mount and re-derived by a MutationObserver on documentElement class changes so both themes render live. REDUCED MOTION: digits render instantly with no spin or drift and a plain focus highlight. Hover and focus affordances are token-relative (border-foreground/25, ring-accent)."
      }
    },
    {
      "name": "pagination-dog-ear",
      "type": "registry:ui",
      "title": "Pagination Dog Ear",
      "description": "A pagination control rendered as book folios — paper cards for each page number, a raised current page, and a CSS corner dog-ear that curls further as you hover prev/next to peek the destination number, leaving a small permanent crease on every page you've visited.",
      "files": [
        {
          "path": "registry/core/pagination-dog-ear/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/pagination-dog-ear.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "pagination",
          "navigation",
          "nav",
          "dog-ear",
          "aria-current",
          "roving-tabindex",
          "book"
        ],
        "instruction": "Build a pagination control (`page: number` 1-indexed, `count: number`, `onChange: (page: number) => void`) rendered as a row of book folios rather than bare numbers. Every page number is a `<button>` 'card' (fixed size, hairline `var(--border)` outline, `var(--background)` fill, Geist Mono glyph) inside a `<nav aria-label=\"Pagination\">`; the current page carries `aria-current=\"page\"`, sits `translateY(-1px)` raised with a soft `box-shadow`, and its border darkens to `var(--foreground)`. Every card has a corner dog-ear built from the classic CSS border-triangle technique — an absolutely positioned 0x0 element whose `border-width` on two adjacent sides is driven by a `--curl` custom property (0 to 1) and whose other two border sides stay `transparent`, so the shape is a right-triangle wedge in the top-right corner with zero extra DOM. `--curl` responds to three distinct situations, each with a different target value and reason: (1) hovering ANY page's own button sets that button's `--curl` to ~0.55 and darkens its ink from `var(--muted)` to `var(--foreground)` — a light 'this is interactive' tell on every number, not just the current one; (2) hovering the prev/next control (which does NOT change `--curl` on itself) instead sets the CURRENT page's `--curl` to ~0.7 and reveals a small absolutely-positioned preview span near the corner showing `page + direction` (clamped to a valid page, hidden otherwise) — this is peeking at the destination the turn would land on, so it lives on the current card, not the one under the cursor; (3) completing an actual page change (react to the `page` prop changing, not just the click that caused it, so external control changes animate identically) plays the OUTGOING page's `--curl` to a full 1 while that same card also gets a brief 280ms eased tilt (`transform: translateY(-1px) rotateZ(-3deg) translateX(-2px)`, `cubic-bezier(0.55,0,0.85,0.35)`) and opacity dip to 0.75, reverting automatically once the timer clears — this reads as the page lifting and turning away as the new one becomes current. Every page that has ever been the current page (tracked in a `Set<number>`, seeded with the initial page) keeps a SECOND, smaller, permanently static triangle in the opposite (bottom-right) corner once it's no longer current — a fixed-size crease independent of `--curl`, so page history stays visibly marked even after the interactive curl relaxes back to 0. Keyboard is a roving-tabindex toolbar spanning prev, every page button, and next as one sequence: only one control is ever `tabIndex=0` (the first non-disabled control, recalculated as availability changes at the ends), `ArrowLeft`/`ArrowRight` move focus to the previous/next ENABLED control (wrapping), `Home`/`End` jump to the first/last enabled control, and Enter/Space activate the focused button via native `<button>` behavior — no custom activation handling needed. Prev is `disabled` at page 1, next at the last page, both with a visible dimmed state and excluded from the roving sequence while disabled. `prefers-reduced-motion: reduce` removes the curl-growth, tilt, and peek-fade transitions entirely (everything still functions, `--curl` and the turn state still flip, but with `transition:none` so page changes are instant); the small permanent visited-crease is not an animation and is unaffected either way — it should still mark history even with motion off. Zero dependencies, no SVG, no canvas, no scrollHeight measurement."
      }
    },
    {
      "name": "password-strength-tide",
      "type": "registry:ui",
      "title": "Password Strength Tide",
      "description": "Password field with a canvas water tank behind the masked text: entropy raises the tide on a damped spring, keystrokes slosh a real 1D heightfield wave, deletions pull the level back down.",
      "files": [
        {
          "path": "registry/core/password-strength-tide/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/password-strength-tide.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "input",
          "password",
          "form",
          "canvas",
          "physics",
          "micro-interaction"
        ],
        "instruction": "Build a password field (real <input type=password>, visible label, standard semantics untouched) with a canvas 2D water tank behind the masked text, clipped to the field's rounded-sm 6px bounds. Size the canvas with EXPLICIT style.width/height plus dpr-scaled bitmap (replaced element — inset does not size it). Strength = length + charset-class entropy (pool 26/26/10/33, 72 bits caps the gauge) mapped 0-100 to water level percent; drive the level with a damped spring k=90 s^-2, zeta=0.55 (~8% overshoot then settle). Simulate a 1D heightfield with one column per 4 px: neighbor-coupling wave speed ~140 px/s, per-frame velocity damping 0.985; each keystroke injects a -6 px surface impulse at the caret's approximate x (charCount * measured mask-glyph width, spread over a 5-column kernel), deletions inject +4 px and lower the target, and the reveal toggle drops a 4 px ripple at the eye-icon x. Fill = mix(--muted, --accent, level fraction) at 20% alpha with a 1.5 px surface line at 60% alpha; full clear + redraw every frame — never accumulating destination-in alpha. rAF sleeps when max column |v| < 0.02 px/frame AND |level-target| < 0.3 px, with a 1.2 s forced-settle deadline (extra damping pulls flat, 2 s hard snap) and pauses offscreen via IntersectionObserver plus document visibility; guard zero-size containers before simulating. All canvas ink is read via getComputedStyle at mount and re-derived by a MutationObserver watching documentElement class changes so both themes render correctly. Reveal toggle is a real button with aria-pressed; aria-describedby points at a visually-hidden polite live region announcing Weak/Fair/Strong, mirrored by a small aria-hidden mono readout. Accent focus-within ring and token-relative hover border. At zero entropy the tank draws no fill but a faint --muted rim line at the base so the empty gauge silhouette still reads at rest. prefers-reduced-motion renders a static fill bar at target height with instant height changes and no waves, and is watched live via a matchMedia change listener (not just read once at mount) so a mid-session OS toggle snaps the tank flat immediately. Tear down every listener, observer, and rAF on unmount."
      }
    },
    {
      "name": "patchbay-ascii-cable",
      "type": "registry:ui",
      "title": "Patchbay ASCII Cable",
      "description": "A patchbay with real, persistent, user-authored topology: drag from one jack to another to create a patch, routed orthogonally onto the shared monospace glyph grid, and once connected a small pulse travels the cable on a loop. Grabbing a jack that already carries a patch unplugs it — the classic pull-the-plug gesture — leaving a loose end you can drop on a new jack or nowhere at all.",
      "files": [
        {
          "path": "registry/core/patchbay-ascii-cable/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/patchbay-ascii-cable.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "patchbay",
          "cable",
          "graph",
          "ascii",
          "box-drawing",
          "drag",
          "keyboard-navigation"
        ],
        "instruction": "Build a patchbay from a `jacks` prop (PatchbayJack[] — `{id, label, row: 'top'|'bottom', col}`), defaulting to 3 top jacks (A/B/C) and 3 bottom jacks (1/2/3) with two already-patched pairs. State is a `patches` array of `[jackId, jackId]` pairs — the real, persistent data model, not a visual-only highlight. ROUTING: each jack has a fixed `anchor` point one cell outside its box in its natural cabling direction (straight down for a top-row jack, straight up for a bottom-row one); a patch between two jacks is drawn with `orthogonalPath(anchorA, anchorB)` — a straight line if the anchors already share a row or column, otherwise a vertical-horizontal-vertical jog through the midpoint row — walked cell-by-cell into a shared direction-bit grid (`tracePolyline`, the same N/E/S/W-mask-to-box-glyph technique diagram-ascii-flow's router uses, reimplemented locally since each component folder is self-contained) so that where two patches' cables cross or run parallel, the shared cells resolve to the correct junction glyph (┼ ├ ┤ ┬ ┴) rather than one path overwriting the other. Every character is rendered as its own fixed-width span (never one flowing text string per row) so the cable grid stays pixel-aligned with the absolutely-positioned jack buttons regardless of the actual monospace glyph advance width. THE PATCH GESTURE: pointerdown on a jack starts a gesture without immediately mutating anything; only once the pointer actually MOVES does a fresh pickup (i.e. no jack was already armed) unplug that jack's existing patch, if it had one, and start drawing a live preview cable from its anchor to the current pointer cell — a plain tap that never moves must never destructively unplug, or a keyboard-equivalent click would silently break a live connection. Releasing over a different jack completes the patch (dropping any prior patch either endpoint held); releasing over empty space after a real drag cancels, leaving the plug loose; releasing back on the same jack with no movement leaves it armed, which is also exactly the state a keyboard Enter/Space produces. KEYBOARD: every jack is a real, always-focusable button with an `aria-label` stating its patched partner or 'unpatched' plus the next available action; Enter/Space on an unarmed jack arms it (a `data-patchbay-armed` readout names it beneath the canvas), Enter/Space on a second, different jack completes the patch exactly like a drag-drop would, Enter/Space on the already-armed jack or Escape cancels. PULSE: once a patch exists, a single rAF loop (skipped entirely under `prefers-reduced-motion: reduce`, whose cable still renders, just motionless) walks a small `--accent` marker span along each patch's flattened cell path on a 1.4s loop, writing its position straight to the DOM via a ref — no per-frame React state. Hover and keyboard focus both shift a jack's border toward `--accent`; an armed jack's border is a persistent `--accent`; a patched-but-idle jack's label is `--foreground` rather than `--muted`. Tokens only (`--background --foreground --muted --border --accent`, read via `getComputedStyle` on the document root, re-read on a `MutationObserver` watching its class attribute) — no hardcoded hex, correct in both themes. Zero dependencies, pure DOM + CSS."
      }
    },
    {
      "name": "picker-pareto-frontier",
      "type": "registry:ui",
      "title": "Picker Pareto Frontier",
      "description": "Model picker rendered as a legible cost/latency/quality Pareto scatter: the frontier is a rising line of solid nodes (quality can't rise without moving right into cost), dominated models are hollow nodes below it joined by a connector whose length is the quality you'd forfeit, and a readout names the selection with its three numbers plus the concrete delta once per commit.",
      "files": [
        {
          "path": "registry/core/picker-pareto-frontier/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/picker-pareto-frontier.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "picker",
          "radiogroup",
          "svg",
          "pareto",
          "comparison",
          "decision",
          "form",
          "accessibility"
        ],
        "instruction": "A model picker that replaces the dropdown-of-marketing-names with the actual decision, drawn as a small self-explaining scatter-plot instrument. Given a models[] prop of {id, name, cost, latency, score}, it computes the real Pareto frontier by pairwise dominance (a model dominates another if it is no worse on cost AND latency AND score, strictly better on at least one) independent of any display mode, then plots every model at x = a weighted blend of normalized cost and normalized latency (the blend ratio is the only thing the speed/balance/quality toggle changes) and y = normalized quality score, higher score sitting higher. The y-axis is labelled QUALITY↑ in the top-left corner — the cheap-and-excellent corner that never has a point in it — and the x-axis is captioned 'cheaper·faster' → 'costlier·slower', so at rest the form states what is being compared and which way is better. FRONTIER: the non-dominated models are connected by one rising SVG polyline and marked with solid --foreground node dots; because the line only rises left-to-right, its shape IS the tradeoff (you cannot gain quality without moving right into cost/latency). DOMINATED: every dominated model is a hollow --muted node at its own honest position below the line, joined to the ridge by a faint vertical connector whose length is literally the quality you forfeit by choosing it — never hidden or filtered out, always selectable. SELECTION: a real role=radiogroup of role=radio buttons, one per model, roving tabindex, Arrow keys move AND commit in visual x-order (Home/End jump to the ends), recomputed from the CURRENT axis mode so traversal always matches left-to-right on screen. A drag anywhere on the plot computes the pointer's x-fraction and snaps to the nearest node by 1D distance (a Voronoi partition of the x-axis) committing on every zone crossing, so dragging walks the selection node-to-node rather than echoing a free pointer. The selected node is the only place --accent appears: a filled accent dot with a soft accent halo. READOUT: beneath the plot a bordered panel names the selected model, prints its three raw numbers (cost /1k, latency s, score) in Geist Mono, tags it 'on frontier' or 'dominated', and prints — once per COMMIT, never per drag pixel — the concrete delta versus the previously selected model ('vs Atlas Mini +0.4s · +$0.35/1k · +11 MMLU'); when the selection is dominated it instead states who beats it and by how much at no higher cost or latency. A visually-hidden aria-live=polite region announces the same change in words on every focus/commit ('0.4 seconds slower, 35 cents more expensive per thousand, 11 points higher quality'). A small legend names the two node kinds. AXIS TOGGLE: a speed/balance/quality segmented control (its own role=radiogroup) re-weights the cost/latency blend that produces x; every node's position, the ridge path's `d`, and every connector transition together on an ease-out-expo curve (cubic-bezier(0.16,1,0.3,1), 450ms) — the frontier's point count never changes between modes, only x, so the path stays interpolation-safe. COLORS: --foreground for the ridge line and frontier dots, --muted for dominated dots, connectors and captions, --border for the baseline and readout panel, --accent only for the selected dot and its halo. REDUCED MOTION: every transition is skipped — positions snap to target. No canvas: one aria-hidden SVG draws only the straight lines (preserveAspectRatio=none, non-scaling-stroke), while every node dot is a real DOM element positioned by percentage so it stays a perfect circle at any aspect ratio and the hit-target buttons stay pixel-aligned with no ResizeObserver; all data lives in each radio's aria-label, never in the SVG."
      }
    },
    {
      "name": "popover-pendulum",
      "type": "registry:ui",
      "title": "Popover Pendulum",
      "description": "A popover that hangs from its trigger like a plumb bob: it drops in off-vertical, sways once or twice on a damped pendulum, and settles plumb — connected by a hairline SVG string that doubles as the anchor indicator.",
      "files": [
        {
          "path": "registry/core/popover-pendulum/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/popover-pendulum.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "popover",
          "hovercard",
          "dialog",
          "pendulum",
          "physics",
          "anchor",
          "filter"
        ],
        "instruction": "Build a popover primitive whose entrance reads as a plumb bob dropping and settling on a string, not a layer fading in from nowhere, taking `trigger` (rendered inside the trigger button), `children` (the panel body), an `interaction` mode (`\"click\"` default or `\"hover\"`), `label` (the panel's accessible name in click mode), `triggerLabel`, `placement` (`\"bottom\"` default or `\"top\"`), `panelWidth` and `className`/`panelClassName`. The panel and a 1px SVG line ('the string', stroked in `var(--border)`) live inside one wrapper whose CSS `transform-origin` sits exactly at the anchor point — the trigger's facing edge, horizontally centered — so rotating that wrapper reads as the bob swinging on a fixed line: the pivot itself never visibly moves because it IS the rotation origin. Entry is one declarative keyframe sequence, no JS physics loop: a 12px translateY drop (mirrored to +12px when the panel is flipped above the trigger) plus a damped rotation sequence of -2.5deg, 1.2deg, -0.4deg, 0deg, opacity resolving to 1 by 14% of the way through a ~480ms run. Closing is NOT the entrance reversed: the string gets its own quick opacity-only cut (90ms ease-in) while the panel, on a 70ms stagger, drops 6px and fades with ease-in over 160ms — two separate exit animations on two separate elements, which is what lets the string visibly 'let go' before the bob falls, versus one shared transform animating both at once. A one-time collision check at open (trigger rect vs viewport, measured against the rendered panel height) flips the panel to the opposite side when the preferred side doesn't fit and the other side has more room; a flipped placement mirrors the drop direction and re-orders the string to stay nearest the pivot. `interaction=\"click\"` is a non-modal popover: the trigger's click only ever opens it (never toggles closed) — Escape (from anywhere while it's mounted) and an outside pointerdown are the close paths, and both return focus to the trigger; opening moves focus into the panel (`role=\"dialog\"`, `tabIndex=-1`, labeled by `label`) exactly once per fresh open. `interaction=\"hover\"` is the profile-hovercard variant: it opens on pointer hover after a 120ms delay (debounces a fast mouse pass) or on trigger focus instantly — delaying a keyboard user's open would be a real accessibility bug — closes once both hover and focus have left the whole component (a 140ms grace absorbs the trigger-to-panel handoff), and never forces focus into the panel; the trigger carries `aria-describedby` instead of `aria-expanded`, and because the panel is a plain DOM sibling of the trigger rather than portaled, Tab from the focused trigger reaches the panel's own interactive content next in natural order — the brief's 'contents are focusable from frame one' holds because nothing about the entrance animation gates pointer-events or tabindex. No portal at all, in either mode: both trigger and panel live in one wrapper `<span>`, so outside-pointerdown containment, hover/focus-within evaluation and Tab order are all ordinary same-subtree DOM, with the standard caveat that an ancestor's `overflow:hidden` can clip it. `prefers-reduced-motion` replaces the whole sway with a flat 120ms opacity fade at the final resting position in both directions — no drop, no rotation, the string rendered already in place with no animation of its own, and no split string-cut/panel-drop staging on close. Every color is a token (`--background --foreground --muted --border --accent`); the string's `stroke` is `var(--border)` directly, not a hardcoded hex, so both themes render correctly. Zero dependencies, DOM+SVG+CSS only, no canvas."
      }
    },
    {
      "name": "post-list-ascii-index",
      "type": "registry:ui",
      "title": "Post List ASCII Index",
      "description": "A blog/post list with a live ASCII gutter: j/k and arrow-key navigation moves a roving-tabindex selection, a running index rule eases to track it, and each post's reading length redraws as an ASCII bar that recomputes on selection.",
      "files": [
        {
          "path": "registry/core/post-list-ascii-index/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/post-list-ascii-index.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "blog",
          "post-list",
          "ascii",
          "canvas",
          "keyboard",
          "index"
        ],
        "instruction": "Build <PostListAsciiIndex posts className?> where posts is PostListAsciiItem[] ({id, title, excerpt, date, minutes}). Rows are real <button data-post-row={id} tabIndex={isActive?0:-1} aria-current aria-label=\"{title}, {date}, {minutes} minute read\"> in a roving-tabindex list (only the active row is a real Tab stop, matching this registry's established roving pattern) to the right of a narrow (56px) gutter column holding one <canvas aria-hidden>. THE MECHANIC, all driven by real getBoundingClientRect measurements of the row buttons (re-measured on a ResizeObserver over the container): (1) a vertical hairline rule runs the gutter's full height; (2) every row gets a tick into the rule, its 1-based index number above the tick, and its reading time rendered as a literal ASCII bar (repeated █ glyphs, length proportional to `minutes` capped at a 10-minute full bar) plus the raw \"Nm\" label; (3) a ▸ marker EASES (lerp 0.22/frame, one rAF loop) to the vertical centre of whichever row is currently active — this is the 'running index rule': it genuinely tracks the live cursor position, redrawn every frame while catching up, then the loop stops (wakes again next selection change); (4) the ACTIVE row's bar does not just recolor — it redraws with its own left-to-right reveal sweep (a fresh eased fill from empty to its full length over ~260ms) every time selection changes, so the metric visibly recomputes on selection rather than being a static picture with a highlight layered over it. Selection moves via ArrowDown/j and ArrowUp/k (Home/End jump to the ends), each press calling focus() on the newly active row so keyboard, click and programmatic selection all funnel through one path; an aria-live=polite sr-only span announces the newly selected post's title and reading time. Canvas ink is var(--foreground)/var(--muted)/var(--border) for rest state and var(--accent) for the active row's bar and the running marker, read via getComputedStyle at mount and re-read through a MutationObserver on the root's class/style attributes so both themes stay correct with no remount. `prefers-reduced-motion` renders the marker at its final position and every bar at its final length on every paint, skipping the lerp and the reveal sweep entirely. Rows also have real hover and focus-visible states (background tint on hover, an inset accent outline on focus) distinct from resting. Zero dependencies."
      }
    },
    {
      "name": "pricing-scale",
      "type": "registry:ui",
      "title": "Pricing Scale",
      "description": "Pricing section rendered as a literal balance scale: two tier cards hang as pans from a canvas beam, billing toggle and feature checkmarks add weight, and beam-torque spring physics settles with one inertial overshoot to show which tier objectively outweighs the other.",
      "files": [
        {
          "path": "registry/core/pricing-scale/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/pricing-scale.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "pricing",
          "canvas",
          "physics",
          "spring",
          "balance-scale",
          "section",
          "interactive",
          "ambient"
        ],
        "instruction": "Two pricing tiers rendered as literal balance-scale pans hanging from a canvas beam; billing toggle and feature checkmarks add weight and the beam settles with inertial overshoot to show which tier objectively outweighs the other, with idle ambient sway. RENDERING: a Canvas 2D layer draws fulcrum, beam, and chains as 2px strokes (--foreground beam/pivot/hooks, --border chains and fulcrum hatch), fully cleared and restroked every tick — no accumulation compositing; the two pans are real DOM pricing cards (bg-surface, rounded-md, 1px border) hung from the beam ends and positioned every frame via OFFSET transforms from the container center (translate from beam-end position + chain length, plus a small angular-velocity-based lag rotation (-clamp(ω·0.3, ±0.09 rad) about the chain attachment, zero at rest) so pans read as swinging with inertia rather than rigidly tracking θ), never absolute canvas coordinates. Canvas sized with explicit style.width/height plus a devicePixelRatio-clamped backing store. PHYSICS: beam angle integrated per frame with semi-implicit Euler: th'' = -42(th - target) - 5.5 th' (stiffness 42 s^-2, damping 5.5 s^-1 → exactly one visible overshoot, settle < 1.4s), hard forced-settle deadline snap at 2.0s so stacked rapid toggles always resolve, tilt clamped ±9°. target = clamp(0.028·(weightR - weightL)); weight = monthlyPrice/10 + enabledFeatureCount. IDLE AMBIENT (default look): ±0.4° sway, ~6s period jittered ±10% per mount plus a slow low-amplitude secondary harmonic (so it never repeats identically), ramped in and suppressed for 4s after any interaction. INTERACTION: monthly/annual segmented toggle (rounded-sm) rescales both prices and re-targets the beam; six real feature checkboxes per tier add/remove weight through the same spring; hovering a tier card lifts it 2px (token shadow, border step) and previews a +0.3° tilt bias toward it; primary CTA in --accent, secondary ghost — accent appears nowhere else except focus rings. A font-mono caption under the fulcrum states the verdict ('Pro outweighs Starter by 2 features') and updates on settle, never mid-swing. REDUCED MOTION: beam and cards rendered instantly at the settled angle, no sway, discrete updates. PERF: direct-DOM rAF hot path (refs only, no React state per frame); loop sleeps when |omega| < 0.001 rad/s, no spring target pending, and sway is suppressed, with a timed wake when suppression ends; IntersectionObserver pauses offscreen and document.hidden pauses; zero-size container guard; canvas inks parsed from getComputedStyle tokens at mount with a MutationObserver on documentElement class re-deriving live; all listeners/observers/rAF/timers torn down on unmount. Demo is a full two-column pricing section: headline, muted subcopy, the scale as visual spine, Starter $19/mo vs Pro $49/mo with believable feature lists and CTAs, muted mono footnote."
      }
    },
    {
      "name": "progress-hatch",
      "type": "registry:ui",
      "title": "Progress Hatch",
      "description": "Engineering-drawing progress meter: a light hatch track and a dense shade-ramp fill with a dithered leading edge, an inline right-aligned numeric readout, and a box-drawing ruler of ticks below.",
      "files": [
        {
          "path": "registry/core/progress-hatch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/progress-hatch.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "meter",
          "ascii",
          "hatch",
          "mono",
          "engineering"
        ],
        "instruction": "A determinate progress meter in the engineering-drawing register, three stacked monospace lines: the bar itself, a box-drawing tick ruler, and a label row. The bar's track is a run of the single light hatch glyph ░; the filled run is solid █; between them is a fixed-width (4 column) dithered edge where each column's fill level is decided not by a smooth density ramp but by comparing that column's local position through the edge against its own entry in an 8-value ordered-dither sequence — a 1D analogue of the Bayer matrix used in background-ascii-dither elsewhere in this suite — so the boundary reads as grain/texture rather than a flat cut or a clean gradient. The numeric readout is not a floating overlay: it is printed directly into the same character array, right-aligned, overwriting whatever hatch or fill glyphs would otherwise occupy those trailing cells, at a fixed 4-column width (' 37%' / '100%') so the grid never jitters as the digit count changes. Below the bar, a second line draws a box-drawing ruler (├──┬──┬──┬──┤) with a ┬ at each `marks` percentage (default 0/25/50/75/100) and a third line centers that mark's number underneath, clipped to the grid bounds at the ends. `value` (0-100, controlled) glides toward its target over a fixed 420ms ease-out-cubic via a single direct-DOM rAF loop that rebuilds the bar row string each frame and writes it straight to a ref's textContent — never per-frame React state — and sleeps once the ease settles; the tick/label lines are static per render and need no loop. `role=progressbar` with aria-valuemin/max/now sits on the bar line itself, aria-label supplied by the caller. Colors are `text-foreground` for the bar, `text-border` for the tick ruler and `text-muted` for labels — no hardcoded hex, so both themes render correctly. prefers-reduced-motion (read live via matchMedia on mount and on every value change) skips the glide and paints the exact frame implied by `value` immediately. The container is sized to `${totalChars}ch` with an explicit line-height so the character grid holds its width regardless of font metrics, and stays legible at small sizes because the hatch ramp only has four density levels rather than a continuous gradient."
      }
    },
    {
      "name": "progress-narrated",
      "type": "registry:ui",
      "title": "Progress Narrated",
      "description": "Determinate progress bar whose leading edge narrates each phase in typed mono, then docks it below the track as a timestamped milestone ledger.",
      "files": [
        {
          "path": "registry/core/progress-narrated/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/progress-narrated.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "loader",
          "typography",
          "mono",
          "ledger",
          "micro-interaction"
        ],
        "instruction": "A determinate progress bar whose fill front is a live narrator, pure DOM and zero deps. Track: 4px tall, transparent bed with a hairline border; fill #ededed; the leading edge is a 2px #006bff cursor block (the only accent), which fades out once the run completes. Fill width eases toward the controlled value (0-100) with cubic-bezier(0.22,1,0.36,1) over 450ms per value change, written direct-DOM (style.width/transform) from a single rAF loop that runs only while easing, typing, or docking, then sleeps. A font-mono text-sm caption is anchored to the leading edge (clamped inside track bounds, with a small gap below its baseline before the track) and types the current phase label at 24 chars/s; the first 3 glyphs scramble-decode from an A-Z/0-9/symbol charset for ~2 frames each before locking (a small text-decrypt dose). When the fill crosses a phase's `at` percent, the caption scales to 0.92 over 220ms ease-out (origin bottom-left) and fades, and a milestone docks below the track: a 2px x 8px vertical tick at the phase's percent position plus label and elapsed mm:ss.s in font-mono text-[11px] text-muted, edge-clamped so labels never overflow; milestones fade in over 260ms. A finished bar reads as a ledger of everything the loader did. Props: value 0-100 (controlled, a decrease resets the ledger and elapsed clock), phases {at, label}[]. Accessibility: progressbar role with live aria-valuenow, milestones announced through a visually-hidden polite live region. Under prefers-reduced-motion the fill jumps instantly, captions render fully typed, and ticks appear immediately."
      }
    },
    {
      "name": "progress-telegraph-log",
      "type": "registry:ui",
      "title": "Progress Telegraph Log",
      "description": "Live telegraph feed for multi-stage operations of unknowable total duration: each real sub-step arrives as its own line with a ticking elapsed timer, then collapses into a dense done-ledger with its true cost frozen — stalls are self-evident because the active timer keeps counting while nothing new arrives.",
      "files": [
        {
          "path": "registry/core/progress-telegraph-log/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/progress-telegraph-log.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "log",
          "mono",
          "ledger",
          "deploy",
          "async",
          "accessibility",
          "timer"
        ],
        "instruction": "<WireFeed steps={WireFeedStep[]}> renders a Geist Mono telegraph log for long-running, multi-stage operations with no knowable total duration — it never shows a percentage or claims a completion it can't back up. `steps` is an append-only run log the caller drives directly off real events: push a step with status 'active' and startedAt=Date.now() the instant that sub-step actually begins (e.g. 'resolving dependencies', 'building 3/12' — the label can be mutated in place on the same id as a counter advances), then flip that same id to 'done' or 'error' with an endedAt timestamp the instant it actually finishes; the component only ever renders steps that have actually started, never a queued placeholder for what hasn't happened yet. RENDERING: a role=log, aria-live=polite container capped at max-height 280px (~12 dense rows) with overflow-y auto and a linear-gradient mask fading the top ~20px, so the view reads as a scrollback with more history above. Each active row is full height (foreground Geist Mono label left, a right-aligned tabular-nums elapsed counter that repaints once per real elapsed second — the 1Hz cadence is driven by a single rAF loop, not a timer per row) plus a 3px accent underline beneath it that grows toward, but asymptotically never reaches, full width via pct = elapsed/(elapsed+9000ms): the growth rate visibly decays, so a step that has been running for a while shows a nearly-still bar rather than a fake sprint to 100 — combined with the timer, this is what makes a stall self-evident, not a spinner. New lines mount with an 8px slide-up + fade over 250ms cubic-bezier(0.19,1,0.22,1). On completion a row's own padding eases from 7px to 2px over 300ms (compressing it into a dense ledger line), its counter freezes as static muted text showing the true elapsed seconds, and a 1px stroke SVG check draws itself in left-to-right over 220ms via getTotalLength()-driven stroke-dashoffset. A failed step never gets a color signal (this component's palette has no error hue) — it pins at font-weight 600 with its frozen duration, and an indented, bordered stderr excerpt (the `detail` field) sits under it permanently; the run simply stops narrating past a failure rather than guessing at recovery. ACCESSIBILITY: the container carries the accessible name via `aria-label` (default 'Task progress') and is the live region itself. A step still in progress is aria-hidden in full — its label can be mutating several times a second (a 'building N/12' counter) and none of that is conclusive yet, so it stays silent rather than chattering. The instant a step settles it drops out of aria-hidden and lands in the accessible tree as ordinary visible text (label plus its frozen elapsed duration), which the polite live region announces exactly once, as one clause, per finished or failed step — never per tick. A failed row additionally carries aria-live=assertive on its own wrapper, overriding the ambient polite region for that one announcement, and its indented stderr excerpt is announced as part of the same clause. The container is tabIndex=0 and keyboard-focusable independent of any child control, with ArrowUp/ArrowDown scrolling the log 28px per press so the collapsed ledger can be reviewed without a mouse; it auto-scrolls to the freshest line only while the caller hasn't scrolled up to look at history (a 32px near-bottom heuristic), so reviewing older steps isn't fought by the feed jumping back down under you. REDUCED MOTION: the entrance slide is dropped (rows simply appear), the padding-compression and check-tick draw both snap instantly instead of easing, and the underline still repaints every frame (it's live data, not decoration) — timers keep updating as plain text throughout. Pure DOM + CSS + one inline SVG check glyph, no canvas, one rAF loop that sleeps completely once no step is active, zero dependencies."
      }
    },
    {
      "name": "progress-wick",
      "type": "registry:ui",
      "title": "Progress Wick",
      "description": "A determinate progress bar that advances by capillary action — quick pull, slowing soak, brief dwell, next pull — with a faint wet-front runner previewing the track a few pixels ahead of the true fill, so bursty real-world progress (chunked uploads) reads as natural rather than janky.",
      "files": [
        {
          "path": "registry/core/progress-wick/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/progress-wick.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "loader",
          "upload",
          "physics",
          "micro-interaction",
          "accessibility"
        ],
        "instruction": "A determinate `role=progressbar` (0-100 `value`, controlled) whose visible fill does not glide to its target but chases it in discrete capillary draws. Internally a single rAF loop tracks a `target` (the true `value`, clamped) and a `display` (the eased, shown percent); whenever they differ by more than a small epsilon it starts a 'draw': `display` eases from its current position toward `display + (target - display) * 0.6` — 60% of the remaining gap — over 300ms with ease-out-expo, then holds ('dwells') for 150ms before re-checking the gap against the (possibly since-moved) `target` and either settling or starting the next draw. Because each draw only closes 60% of what's left, a fixed `target` produces a naturally decaying sequence of draws that converges asymptotically rather than one animation; a `target` that moves mid-draw or mid-dwell is simply picked up by the next draw's gap calculation, so bursty real progress (a chunked upload landing irregular chunks) drives a rhythm that already looks native rather than stuttering against a glide. A second element at `bg-foreground opacity-25`, a 14px pill centered on a CSS `left` position, rides ahead of the true fill edge during the draw phase — its lead in pixels running `10 -> 4` across the draw (quick pull, slowing soak) then continuing down to 0 across the dwell (the front settles back onto the fill) — a faint preview of where the bar is about to reach, never touching a raw hex value since both elements are `bg-foreground`. Fill width and front position are written every frame as CSS custom properties (`--wick-fill`, `--wick-front`) on the track element, read by two static one-line style rules (`width:var(--wick-fill,0%)`, `left:var(--wick-front,0%)`); the rAF loop stops entirely once the gap closes and only wakes again when `value` changes, via a ref-held retarget function set up once per mount so the value-watching effect never re-runs the engine setup. `indeterminate` (default false) drops the fill to zero width and instead sends the front pill alone traveling the same draw-dwell cadence, slowed (480ms draws, 260ms dwells) and continuous: it resets to just off the left edge and re-travels toward just off the right edge every time it arrives, looping for as long as the component is mounted, ignoring `value` entirely while active. Accessibility: `aria-valuenow` is set from the true `value` (rounded) on every render, completely decoupled from the animation loop, so a screen reader is never a beat behind what's on screen (indeterminate correctly omits `aria-valuenow` and instead sets `aria-valuetext=\"In progress\"`); a visually-hidden `role=status`/`aria-live=polite` span separately announces 25/50/75/100% milestone crossings ('Complete' at 100), computed straight off `value` with a ref tracking the last-announced threshold (reset if `value` drops back below it) so nothing double-fires; a Geist Mono percentage label sits beside the visible text label so the reading is never conveyed by bar length alone. `prefers-reduced-motion: reduce` (checked via `matchMedia` with a live change listener) removes the wet-front element from the DOM outright and switches the fill to a plain CSS `transition: width 150ms linear` driven directly by `value` with no rAF loop at all; a reduced-motion indeterminate render has nothing to animate, so it paints one static partial-width bar instead of an uninformative empty track, with the real 'in progress' state still carried by `aria-valuetext`. Props: `value` (0-100, default 0), `indeterminate` (default false), `label` (visible + accessible name, default \"Progress\"), `announceMilestones` (default true), `className`. DOM+CSS only, no canvas, no SVG, no dependencies."
      }
    },
    {
      "name": "queue-triage-ratchet",
      "type": "registry:ui",
      "title": "Queue Triage Ratchet",
      "description": "Card-triage queue driven by a literal one-way ratchet — deciding a card clicks a toothed rail forward one notch; undo means visibly lifting the pawl before the rail eases back.",
      "files": [
        {
          "path": "registry/core/queue-triage-ratchet/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/queue-triage-ratchet.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "queue",
          "triage",
          "ratchet",
          "undo",
          "listbox",
          "keyboard",
          "card-stack",
          "irreversibility",
          "aria-live"
        ],
        "instruction": "A card-triage queue for decision flows (inbox triage, review/approve, flashcards) where progress should feel earned rather than ambient. Cards sit in a flat, stacked layout — no 3D tilt, just a shallow translateY/scale/opacity falloff for the two cards behind the top one — inside a role=listbox with roving aria-activedescendant onto the top card (role=option, aria-selected on the top). Beneath the stack sits a horizontal SVG rack: an asymmetric sawtooth strip (shallow climbing ramp, steep blocking face — the geometry alone reads 'this only turns one way'), teeth stroked in --border, with a fixed pawl (a small hooked SVG path) engaging the current tooth from above; the engaged tooth's stroke switches to --foreground with a faint --foreground fill so progress is legible even as a static screenshot. Deciding a card — Archive or Keep, via on-screen buttons, Left/Right arrows, or J/K — fires the card off-stage with a spring-ish snap (cubic-bezier(0.34,1.56,0.64,1), translateX + slight rotate + fade) while the rack itself translates exactly one tooth-width to bring the next tooth under the fixed pawl, on a hard-stop ease (cubic-bezier(0.16,1,0.3,1)) with zero overshoot and a sharp settle — a firm click-snap, not a glide. Undo (button, U, or Cmd/Ctrl+Z) is a deliberately different gesture: the pawl first rotates up about 20 degrees and holds — visibly disengaging from the tooth — and only once it's lifted does the rack ease backward one tooth, slowly and on a labored curve (cubic-bezier(0.65,0,0.35,1), ~2.5x the forward duration) before the pawl drops again; undo always plays this lift/hold/drop gesture when pressed (it is never a dead, disabled control) and additionally restores the archived/kept card to the top of the queue and eases the rack only when there is actually something in history — pressing it with nothing to undo still lifts and drops the pawl, honestly showing the mechanism finding nothing to release. Every outcome is spoken through a single polite aria-live status region ('Archived. 6 remaining. Press U to undo.' / 'Kept. 5 remaining. Press U to undo.' / 'Undone. <title> restored. 6 remaining.' / 'Nothing to undo.'), so the queue is fully legible without ever looking at the rack — the rack (including the pawl) is aria-hidden, a redundant reinforcing visual, not a source of information. Every gesture has a single-keystroke and a visible-button equivalent; nothing requires drag. Reduced motion: the card exit collapses to a fast plain opacity fade (no translate/rotate/spring), the rack's transform transition duration drops to 0 (steps instantly to its new tooth), and the pawl's rotation is an instant attribute flip with no tween — all the same state changes and the same aria-live announcements, just without the motion. Pure DOM/SVG/CSS, no canvas; every color is a CSS custom property (--foreground for ink and the engaged tooth, --border for resting teeth and hairlines, --muted for secondary text, --accent reserved for the keyboard focus ring only). Distinct from avatar-stack-flock (a milling boids avatar formation with no commit/undo semantics at all) and segmented-control-fling (an elastic drag-to-fling segmented control with no notion of irreversibility) — queue-triage-ratchet's whole point is the one-way mechanical grammar of teeth, pawl, and notch-advance; the card stack is just the cargo riding on top of it."
      }
    },
    {
      "name": "radio-ballot-drop",
      "type": "registry:ui",
      "title": "Radio Ballot Drop",
      "description": "A single-choice input as a paper ballot — the chosen option folds into a slip and drops through the ballot box's slot with two-phase paper physics (flutter, then settle), and switching your vote pulls the old slip back out first.",
      "files": [
        {
          "path": "registry/core/radio-ballot-drop/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/radio-ballot-drop.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "radio",
          "form",
          "input",
          "single-choice",
          "paper",
          "physics",
          "vote"
        ],
        "instruction": "Build a functioning single-choice input (a real WAI-ARIA radiogroup: an outer role=\"radiogroup\" wrapping one role=\"radio\" button per option, aria-checked reflecting selection, roving tabindex — only the checked option, or the first when none is checked, has tabIndex 0, the rest -1 — and ArrowLeft/Up, ArrowRight/Down, Home, End all move both focus and selection, exactly the standard radiogroup keyboard pattern) styled as a paper ballot. Render a small ballot-box glyph above the option list: a hairline-bordered rounded-top rectangle (border-border, bg-surface) with a short pill-shaped \"slot\" notch bridging its bottom edge, facing the options below it. Each option is a paper-slip button: a fixed off-white paper color (not a theme token — real paper reads the same in both themes, matching the registry's precedent for deliberately fixed non-token colors) with dark ink text, a small hairline-bordered corner square clipped to a triangle in the top-right that lifts (translate + slight rotate, 200ms ease-out) on :hover via a group-hover transform — this is what makes the hover state differ from default. A small filled/hollow dot at the option's trailing edge shows checked state. Selecting an option does NOT remove it from the DOM — the real button stays put and just switches its checked dot — instead it spawns a decorative aria-hidden \"ghost\" clone (absolutely positioned over the whole component, measured via getBoundingClientRect from the clicked option to the ballot box) that plays a one-shot CSS keyframe: fold + flutter (alternating small rotate wobbles while scaleY compresses toward ~0.72-0.9, translateY riding a CSS custom property --nsui-travel computed as the pixel distance from the option to the box's slot) then settle as it fades to opacity 0 partway through the rise — a discrete keyframe animation, not a per-frame physics loop, since this is a bounded one-shot flourish. Changing an existing vote spawns a SECOND ghost for the previously-selected option playing the retract keyframe (the same shape in reverse-ish motion, ending at opacity 0 by the time it reaches the option's base position so it never visibly doubles the real, already-updated unselected slip) at the same time the new choice's drop ghost plays. An aria-live=\"polite\" aria-atomic sr-only region announces \"Voted: <label>\" on first selection and \"Vote changed to <label>\" thereafter. Core restraint: zero color flourish beyond the standard focus-visible ring (focus-visible:ring-2 focus-visible:ring-accent, using a ring not an outline utility, paired with a plain outline-none base — never pair a base outline-none with a focus-visible:outline utility on the same element, since Tailwind resolves that combination to an invisible ring) — no accent anywhere else, hairline borders only. Reduced motion: skip the flutter keyframes entirely and use a short linear fade (opacity 1 to 0 over ~260ms, translateY moving only a fraction of the travel distance) for both drop and retract, still legible as \"the slip left\" without any rotation or scale wobble."
      }
    },
    {
      "name": "radio-group-pin",
      "type": "registry:ui",
      "title": "Radio Group Pin",
      "description": "Vertical radio group whose single choice is embodied by exactly one indicator: a dot resting on the checked option's notch that, on re-selection, elongates into a thin traveling line along a hairline rail (ticking each notch it passes) and contracts back down into a dot once it arrives — no spring, no overshoot, and never a line at rest.",
      "files": [
        {
          "path": "registry/core/radio-group-pin/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/radio-group-pin.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "radio-group",
          "form",
          "list",
          "picker",
          "physics",
          "micro-interaction"
        ],
        "instruction": "A stacked vertical radio list for plans, shipping speeds, environments, or any exclusive choice among ordered options — anywhere a dot swap next to the label would feel weightless and the distance between options is itself worth reading. STRUCTURE: real native input type=radio per row, visually hidden (clipped to 1x1px, not display:none, so it stays in the tab order and keeps native focus/keyboard behavior) but genuinely present — roving arrow keys between rows, form participation, and the browser's own checked-state handling all come free, none of it is reimplemented. Each row is a full-width label (min-height 44px) so the hit area covers the whole row, not just the hidden input; aria-checked is inherent to the native radio and flips the instant the browser commits a change, never waiting on the visual layer. MECHANISM: the indicator is a single absolutely-positioned --foreground dot, animated via the Web Animations API by tweening its own top/height/width against row centers measured with getBoundingClientRect (kept correct across re-layout by a ResizeObserver watching the group) in two phases: stretch (its leading edge eases straight to the new row's center while its trailing edge holds at the old row's center and its width narrows from the dot's diameter down to a thin traveling thickness, so it visibly elongates into a line across whatever sits between old and new — duration scales with how many rows are crossed, ~65ms per row crossed, floor 90ms), settle (the trailing edge eases up to meet the leading edge while the width widens back out, contracting the line back into a dot exactly at the new row, ~160ms). At rest — before the first stretch and after every settle — it is always a dot, never a line; the line only ever exists mid-travel. Both phases use one no-overshoot ease-out curve — the indicator's edges and width move straight to their targets and stop; nothing here overshoots the destination or springs back. Every intermediate row the line passes during the stretch phase gets a brief notch tick — border color and scale pulse, timed proportionally to where the line's leading edge actually is via scheduled timeouts, not a fixed per-row delay — so a five-row trip visibly ticks through rows two, three and four on its way. Notches are static 10x10 --border-outlined marks per row, sized to match the resting dot so the checked row reads as its own notch filled in; the rail connecting them is a 1px --border vertical rule spanning from the first row's notch to the last. Re-selecting the CURRENTLY checked row, or a value that resolves to the same option, is a no-op: no stretch, no settle. REDUCED MOTION: the dot's position updates with no animation at all, an instant teleport to the new row's coordinates as a dot, remaining fully legible. STYLING: unchecked row labels sit in --muted, the checked row's label text steps up to --foreground at medium weight — a typographic cue, not a second indicator glyph; the dot alone carries the selection state visually. Hovering a row tints its background with a faint --foreground wash; the keyboard focus ring (--accent, focus-visible only, never paired with an unconditional outline-none on the same element) renders on the row's visible wrapper, not the clipped input itself. Pure DOM/CSS, no canvas — every color is one of --background --foreground --muted --border --accent, read as CSS custom properties already in scope so both themes restyle for free. Distinct from segmented-control-fling: segmented-control-fling is a horizontal segmented control whose pill is directly grabbable and flingable with release-velocity physics and rubber-banding; PinTumbler never accepts a drag at all, selection changes only via click or native radio keyboard roving, and its indicator is a vertical-list dot whose in-transit trip length communicates how far apart two options sit in the ordering, not a physically-thrown object."
      }
    },
    {
      "name": "rating-stamp",
      "type": "registry:ui",
      "title": "Rating Stamp",
      "description": "Rating / level input rendered as a row of seal impressions: chosen levels stamp solid with a physical thud and an expanding impression ring, the rest wait as faint unfilled outlines, so the value reads unambiguously even in a still frame. A radio-group underneath, not a decoration.",
      "files": [
        {
          "path": "registry/core/rating-stamp/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/rating-stamp.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "rating",
          "radio-group",
          "form",
          "level-picker",
          "stamp",
          "micro-interaction"
        ],
        "instruction": "A rating / level picker (priority, difficulty, satisfaction) rendered as a row of physical seal impressions instead of stars. Each level is one SVG square seal glyph, subtly varied per index by a deterministic seed (small +/-2.5deg rotation, 3.2-5 corner radius) so the row reads as individually struck rather than one glyph rubber-stamped M times. STRUCTURE: real ARIA radiogroup — one role=radio button per level, only the committed level's radio carries aria-checked=true (the rest of the visually-filled marks below it are aria-checked=false, mirroring how sighted-only 'fill up to N' works in a star rating: screen readers hear a single-select list, not a range), roving tabindex so Tab reaches exactly one stop, and each radio's accessible name is 'N of M' plus an optional word from levelNames (e.g. '3 of 5 - High'). MECHANISM: committing upward — click any level, or ArrowRight/Up/Home/End on the group (arrow keys move AND commit, matching native radiogroup behavior; DOM focus is moved imperatively to the newly-committed radio so the visual focus ring always tracks the ARIA-checked one) — scales each newly-filled mark 1.15 -> 1.0 over 140ms ease-out-expo (cubic-bezier(0.16,1,0.3,1)), the press landing, while a single concentric ring in --border expands from scale(0.9) to scale(1.6) and fades opacity 0.9 -> 0 over 380ms, reading as paper taking the impression; ink itself (fill: none -> var(--foreground)) appears fast, over 90ms, a thud rather than a fade-in. Lowering the value drains the marks that fall out of range right-to-left (highest index first) with a 30ms stagger per mark, fill fading out over 170ms — ink lifting off in sequence, the mirror image of stamping down. Both directions are driven by one CSS custom-property-timed transition/animation pair (--cp-delay, --cp-fill-ms) computed in a layout effect keyed off the committed value, so a controlled value change from outside animates identically to a click. Unfilled marks are a 1px --muted outline square; filled marks add two small L-shaped corner strokes (top-left in a --background-mixed highlight, bottom-right in --border) suggesting an embossed bevel. HOVER / FOCUS PREVIEW: hovering (or keyboard-focusing) a level dashes the outline (stroke-dasharray, stroke shifted toward --foreground) of every mark whose filled state would change if you committed there — whether that means marks about to gain ink or marks about to lose it — without touching the committed value; the same state variable drives both pointer and focus so the preview genuinely mirrors between mouse and keyboard. REDUCED MOTION: the press/ring/drain animations and their transition-delays are stripped entirely (both via a JS matchMedia check that skips building the transition map, and a belt-and-suspenders prefers-reduced-motion CSS block) — marks snap straight to their filled/unfilled state, fully legible and functional, just not animated. Pure DOM+SVG+CSS, no canvas; all ink is token-relative (--foreground, --muted, --border, --background, --accent only for the keyboard focus ring) so both themes restyle for free. Distinct from pricing-scale (a two-pan pricing/plan balance-scale with canvas beam physics) by being a compact inline rating field for ordinary forms whose entire mechanism is the stamp impression, not a weighing metaphor — the registry otherwise has no rating input at all."
      }
    },
    {
      "name": "redaction-hold-reveal",
      "type": "registry:ui",
      "title": "Redaction Hold Reveal",
      "description": "Inline redaction bar sized by the real text beneath it — hold to peek and the ink flows back after release, click to latch it open; the lifted bar stays hovering as a thin overline so the redaction never disappears.",
      "files": [
        {
          "path": "registry/core/redaction-hold-reveal/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/redaction-hold-reveal.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "redaction",
          "typography",
          "privacy",
          "reveal",
          "hold",
          "inline",
          "accessibility"
        ],
        "instruction": "An inline redaction primitive: `<UnderInk label=\"email\">p.raghavan@…</UnderInk>` drops into running prose. The bar is drawn OVER the real text, which stays in flow and sets the width — a fixed-width bar lies about the hidden content's length and reads as fake; this one cannot. An optional `label` renders as a tiny uppercase mono tag centered on the ink ('NAME', 'AMOUNT'), naming what is hidden without revealing it, so a redacted document self-explains at rest. TWO WAYS IN, one control: press-and-hold (≥280ms) lifts the ink for a peek — on release the text stays exposed for `resealMs` (default 900) and then the ink flows back, because real ink takes a moment to pool; a short press, plain click, or keyboard Enter/Space latches the reveal open until activated again (e.detail===0 keyboard clicks always toggle). The pointer paths are disambiguated by press duration with a suppressed synthetic click after a genuine hold, and pointercancel/pointerleave end a peek safely, so a drag off the bar never wedges it open. THE LIFT: revealing doesn't delete the bar — it scales to a 14%-height overline hovering above the text (scaleY transform, transform-origin top, 240ms cubic-bezier(0.22,1,0.36,1)), so what was redacted remains visibly marked as redacted even while exposed, and re-sealing is the same motion reversed. The text beneath fades in 60ms behind the lift so ink and ink-shadow never show doubled. ACCESSIBILITY: the whole thing is one real <button> with aria-pressed tracking exposure and an accessible name that states both the label and the interaction contract ('Reveal redacted email — hold to peek, click to keep open'); the hidden text is aria-hidden while sealed so assistive tech cannot read through the ink, and un-hidden the moment it is exposed. Focus shows a visible accent outline ring; --accent appears nowhere else. Escape is the panic key: while anything is exposed a document-level listener re-seals it instantly, latched or mid-peek. REDUCED MOTION: lift and fade transitions drop to instant toggles — every state still reachable, nothing hidden. Colors are tokens only: the ink is --foreground, the tag text is --background over it, so the bar is true black-on-white and white-on-black in the two themes with zero hue. Pure DOM/CSS, zero dependencies, no canvas."
      }
    },
    {
      "name": "refresh-pull-flywheel",
      "type": "registry:ui",
      "title": "Refresh Pull Flywheel",
      "description": "Pull-to-refresh as a crank and flywheel: pulling down winds a spoked SVG wheel through a rack-and-pinion drivetrain, release hands it angular momentum, and the wheel freewheels through the request in flight before the response brakes it to a stop as new rows settle in.",
      "files": [
        {
          "path": "registry/core/refresh-pull-flywheel/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/refresh-pull-flywheel.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "refresh",
          "pull-to-refresh",
          "feed",
          "physics",
          "gesture",
          "svg",
          "loading",
          "aria-live"
        ],
        "instruction": "A pull-to-refresh mechanism built as a real crank and flywheel instead of a threshold-triggered spinner swap. A decorative (aria-hidden) SVG assembly sits above the feed: a vertical rack (a clipped window of teeth), a small pinion gear meshed against it, a drive shaft, and a large spoked flywheel — eight spokes in var(--border), one index spoke in var(--foreground) so a single rotation is countable frame to frame. Dragging down on the wheel zone translates the rack's teeth 1:1 with pull distance and winds the wheel and pinion by pullPx * 1.05deg, clamped to a 130px pull with rubber-band resistance (0.32x) beyond that. Releasing hands the wheel an angular velocity of pullDistance * 1.65 deg/s, integrated per requestAnimationFrame with low friction (exp(-0.55/s) decay) for as long as the refresh request is in flight — this is the loading state: on a slow connection the wheel visibly bleeds off an overcommitted yank's energy before the response ever lands, an outcome that is inspectable rather than hidden behind a canned spinner. The moment the request resolves, friction jumps to 10/s so the wheel decays to rest in roughly 400ms; committing the new rows is deferred until the wheel actually stops (or a 6s forced-settle deadline, so physics can never spin forever), so the settle and the content arriving read as one event. New rows are prepended with a lightweight FLIP: existing rows' pre-update positions are captured via getBoundingClientRect, then any that shifted animate from their old offset back to rest (420ms cubic-bezier), while the freshly prepended rows fade/slide in individually staggered 70ms apart, finishing around the same moment the flywheel's index spoke stops ticking. Grabbing the wheel mid-freewheel or mid-brake is legal — the crank always yields to a fresh yank, cancelling the coast and re-winding from wherever it was, and a stale in-flight response that resolves after being superseded is silently dropped rather than double-committing rows. A visible Refresh button is always present as the primary path (the drag is enhancement, never a requirement): clicking it dispatches a synthetic 96px pull onto the same drag-zone listeners, so the button and the gesture share one code path and one physics, not a separate instant fetch. onRefresh is an optional prop returning a Promise of new rows for a real feed to supply; omitted, a built-in 900-1500ms simulated request manufactures a handful of sample rows so the mechanism is demonstrable standalone. Accessibility: the wheel assembly is aria-hidden (decorative only); the list region is aria-busy while a request is in flight; a role=status aria-live=polite region announces 'Refreshing…' then 'Updated, N new items' (or a failure message, which still brakes the wheel to rest rather than leaving it spinning); a small visible status line beside the button carries the same text for sighted users, not just screen readers. prefers-reduced-motion drops the wind/freewheel/brake choreography and the row FLIP entirely: the wheel stays at a static, always-legible index-spoke position, dragging past a 4px threshold or clicking Refresh goes straight from idle to aria-busy to the resolved list with no rotation, and rows appear without motion. Zero dependencies, DOM+SVG+CSS only (no canvas), every color a CSS custom property (--background --foreground --muted --border), --accent reserved for the focus ring. Distinct from status-glyph-cadence: status-glyph-cadence is an inline status glyph encoding five agent states in idle cadence; refresh-pull-flywheel is a feed's primary refresh control where the loading indicator IS conserved gesture energy, not a state-cadence signal — reach for refresh-pull-flywheel specifically when the loading phase should feel mechanical and its duration should read as a physical consequence of how hard the user pulled."
      }
    },
    {
      "name": "refusal-negotiation",
      "type": "registry:ui",
      "title": "Refusal Negotiation",
      "description": "A refusal that negotiates instead of stonewalling: the offending span of a blocked request is echoed back struck through, with narrowing-lever toggle chips that rewrite it and unlock resend the instant a recheck passes.",
      "files": [
        {
          "path": "registry/core/refusal-negotiation/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/refusal-negotiation.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "guardrail",
          "refusal",
          "negotiation",
          "toggle",
          "diff",
          "accessibility"
        ],
        "instruction": "Renders one guardrail refusal as an editable negotiation, not a dead end. Props are `span` ({before, flagged, after} — the user's original request pre-split around the offending substring), `reason` (one sentence, why it tripped), and `remedies` (2-3 `{id, label, rewrite}` narrowing levers, each `rewrite` a pure function from the original span to a candidate span). The component echoes `before` + the flagged span + `after` as one line of body text; the flagged span alone renders inside a `role=mark` element with an accessible name of `flagged: {reason}` — so a screen reader gets the 'what and why' as a single unit without depending on visually finding the struck text. A muted 1.5px bar underlines exactly the flagged span's width and draws left-to-right once over 200ms on mount (never a red flash, never the whole line changing color) — this is the strikethrough, drawn as an animatable bar rather than text-decoration so its direction is controllable both ways. Beneath the echoed line, `reason` renders in --muted, then a always-visible status line reading literally 'Blocked' or 'Allowed — ready to resend' in Geist Mono uppercase — this exists specifically so the outcome never rides on the strikethrough alone; a user who can't see the strike animate still reads the word. Below that, the remedies render as a labeled switch group (`role=group aria-label=\"Narrowing levers\"`, each lever a `role=switch aria-checked` chip told apart from its OFF state by fill and weight, never colour — filled foreground-on-background when ON, outlined muted when OFF). Flipping a lever folds every currently-active remedy's `rewrite` over the *original* span in remedies-array order (never chaining off another lever's already-rewritten output, so toggling one off cleanly restores exactly what the others produced) and the flagged span's old text cross-fades into the new one in the same CSS grid cell — both copies stacked in one grid area so the cell sizes to whichever is wider/taller and nothing else on the line reflows mid-fade. An optional `recheck(span, activeRemedyIds)` prop decides whether the rewritten span now clears the guardrail; the default recheck passes once at least one remedy is active (a real integration should pass its own check against the actual guardrail instead — the default only proves the wiring). The instant `recheck` passes, the status line flips to 'Allowed', the strike bar retracts the opposite direction (same 200ms bar, animating scale back toward zero, transform-origin held at the left edge throughout so 'draw' and 'retract' are the same transition read in reverse), and the Resend button's border transitions --border to --accent — the single, sole use of --accent anywhere in this component, reserved for exactly this one interaction cue and nothing decorative. The Resend button stays disabled (native `disabled`, so it's correctly out of tab order and exempt from the accessible-name-on-enabled-controls audit) until `recheck` passes; clicking it while enabled calls `onResend({request, activeRemedies})` with the fields reassembled from the current span plus which remedy ids were active, and briefly swaps its own label to 'Sent' for 1.4s as a lightweight, non-terminal acknowledgement — unlike approval-inline-diff's one-shot collapse, a lever can still be flipped back afterward; this is a negotiation surface, not an audit receipt. Every lever flip also pushes one sr-only `aria-live=polite` announcement reading 'rewritten: {new flagged text}, request now allowed/blocked', so the outcome of a flip is heard immediately even before Tab reaches the visible status line. Full keyboard path: Tab reaches the flagged mark first (focusable, `tabIndex=0`, its own visible focus ring), then each lever switch in order (Space flips, native button semantics), then Resend once it's enabled (Enter/Space sends, again native). Under `prefers-reduced-motion: reduce` every transition (strike draw/retract, cross-fade, button border) is removed via a scoped media query while the underlying state — which text renders, which levers are on, whether Blocked or Allowed shows — is unaffected, so the component is fully legible and operable with zero motion. Zero dependencies, no canvas — DOM, SVG-free markup and CSS transitions only."
      }
    },
    {
      "name": "reorder-drag-wake",
      "type": "registry:ui",
      "title": "Reorder Drag Wake",
      "description": "Drag-to-reorder where the dragged row pushes a continuous falloff field through its neighbors — they shoulder sideways and the gap ahead widens before the card ever arrives, instead of a hard placeholder line.",
      "files": [
        {
          "path": "registry/core/reorder-drag-wake/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/reorder-drag-wake.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "drag-reorder",
          "list",
          "kanban",
          "spring",
          "physics",
          "keyboard",
          "reorder",
          "micro-interaction"
        ],
        "instruction": "A vertical drag-to-reorder list for task lists or a kanban lane's cards. RENDERING: absolutely-positioned DOM rows inside one relatively-positioned container (no canvas); every row's transform is written per-frame on a refs-only rAF loop, never React state on the hot path. MECHANISM: picking up a row's grip button and moving the pointer past a 4px threshold starts a live drag — the dragged row tracks the pointer 1:1 on Y (pinned to X=0, scale ramping to 1.01). Every OTHER row is pushed by a continuous field centered on the dragged row's live position: translateY reflows it into the slot the card would occupy if dropped right now (computed by splicing the dragged id into its base order at the pointer-derived insertion index, so the gap between two rows is always exactly the width of one settled row, reading as the drop target before the card arrives — no separate placeholder line); translateX shoulders it up to 10px sideways, sign set by which side of the drag point it sits on, magnitude a smoothstep falloff over 1.5 row-heights (~96px) of distance to the drag point, so a neighbor's push grows and fades continuously as the field sweeps past rather than snapping on or off. Both axes are damped springs (k=220, zeta=0.92) so entering and leaving the field is always continuous, even under fast pointer motion. The dragged card's own shadow deepens with instantaneous drag velocity (a fixed dark rgba ink, not a theme token, so it never inverts to a light halo in dark mode — the same reasoning toast-gravity-stack's shadow uses). DROP: the underlying order commits immediately; neighbors are already converging on their final slots so the wake collapses inward first via the same near-critical spring, while the dragged card itself holds for 90ms and then springs the rest of the way (k=170, zeta=0.58 — one clear overshoot) so it visibly settles last, after the water has closed behind it. Escape while dragging cancels: the card springs back to its pre-pickup slot and the order never commits. A11Y: every row exposes a grip button (`aria-label` names the item and its live position); a click or Space/Enter arms discrete keyboard-reorder mode instead of a live drag (`aria-pressed` on the handle, an `aria-live=\"polite\"` region announces 'Grabbed <item>. Now at position N of M.'); Arrow Up/Down step the armed row through the same slots with a 'now at position N of M' announcement; Space or clicking the handle again drops it and announces the final position; Escape restores the pre-pickup order and announces the restore. Escape is a global document listener (not scoped to focus) since the operation is 'in flight' regardless of where focus sits. REDUCED MOTION: the field is off entirely — rows reflow with an instant snap (no spring, no lateral shoulder) and a plain dashed placeholder box marks the slot the card would drop into while dragging, replacing the wake as the anticipatory cue. Distinct from avatar-stack-flock: nothing here trails a leader on a shared boids sim — every neighbor reacts independently and continuously to a falloff field around one dragged card, and the insertion point is legible before the card lands, not after a leader settles."
      }
    },
    {
      "name": "reveal-ripple-tiles",
      "type": "registry:ui",
      "title": "Reveal Ripple Tiles",
      "description": "Media reveal driven by a height-field water sim: a ripple front sweeps from the trigger point, popping grid tiles open as the wave crosses them and refracting revealed tiles like wet glass; the cursor keeps stirring the water after full reveal.",
      "files": [
        {
          "path": "registry/core/reveal-ripple-tiles/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/reveal-ripple-tiles.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "media",
          "reveal",
          "wave-sim",
          "tiles",
          "refraction",
          "cursor",
          "scroll-trigger"
        ],
        "instruction": "A media surface behind a coarse tile grid on a single DPR-aware Canvas 2D (DPR clamp 2), image pre-drawn to an offscreen canvas (or a token-derived generative artwork when no src is given). A height-field wave sim runs on a grid matched to tile resolution (~28x18 cells, tile ≈ 32px at 900px width; cols derived from width/32, zero-size guarded): classic discrete wave equation — v[c] += (4-neighbor avg − h[c]) * 0.5 per step, h += v, damping ×0.985/step (×0.95/step once every tile has opened, to shorten the inaudible idle tail), 2 sim steps per frame, Neumann edges. Trigger (IntersectionObserver at 0.35 visibility, or first pointer entry) injects impulse h=1.0 at the trigger cell; a tile pops open when local |h| > 0.12 — scale 0.6→1 on a spring (k=90 s⁻², ζ=0.7) plus a rotateX-style vertical squash 35%→0 over 320ms with cubic-bezier(0.22,1,0.36,1); reveal = drawImage of that tile's source rect. Revealed tiles refract: source rect offset by (gradient of h) × 6px for a wet-glass shimmer. If the coarse wave dies below threshold before reaching the corners, stragglers are flush-scheduled outward from the trigger cell so the reveal always completes. After full reveal, pointermove injects impulse 0.35 at the cursor cell, throttled to one per frame. Pre-reveal tiles draw a border-token hairline, a faint foreground lift, and an accent-alpha shimmer proportional to |h| — every drawn color derived from --border/--accent/--foreground/--surface/--muted via a getComputedStyle probe at mount and re-derived live via MutationObserver on documentElement class changes, so both themes render correctly. The rAF loop is the sole writer (no React state on hot paths) and sleeps when max|h| < 0.004 AND max|v| < 0.004 and all tile springs are settled; it wakes on pointer or trigger and pauses offscreen via IntersectionObserver. IntersectionObserver, ResizeObserver (recompute grid, guard 0x0), MutationObserver, and all pointer listeners are torn down on unmount. Pure media surface: role=img with aria-label, no click behavior. Under prefers-reduced-motion tiles reveal instantly in a single paint with zero wave distortion, static image thereafter."
      }
    },
    {
      "name": "sankey-ascii-flow",
      "type": "registry:ui",
      "title": "Sankey ASCII Flow",
      "description": "A weighted, branching multi-stage flow diagram rendered as ASCII density bands instead of colour ribbons. Clicking a node isolates every ancestor and descendant on its path — the surviving nodes and bands re-stack from scratch using only the isolated subset's values, so they genuinely grow to fill the space rather than the rest just dimming.",
      "files": [
        {
          "path": "registry/core/sankey-ascii-flow/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/sankey-ascii-flow.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "sankey",
          "flow",
          "chart",
          "data-viz",
          "ascii",
          "graph",
          "isolate"
        ],
        "instruction": "Build a 3-stage weighted flow from `nodes` (SankeyNode[] — `{id, label, stage: 0|1|2, value}`) and `links` (SankeyLink[] — `{from, to, value}`, only ever connecting adjacent stages). Node vertical position and height are computed by `stackSpans`, a 1D cumulative-rounded stacking (the same technique as treemap-ascii-partition's layoutSlice, one axis): every node's span boundary derives from the running total of values-so-far in its stage, never from independently rounding its own share, so stacked nodes always share an exact edge. Each node is a real `<button data-sankey-node={id}>` (never a div), a bordered box sized from its span, with its label and value inside. CONNECTING BANDS are the family's shared 'draw density instead of colour' rule applied to a flow rather than a bar or rectangle: for every link, `stackSpans` partitions the SOURCE node's own span among its outgoing links (in declared order) and, separately, partitions the DESTINATION node's span among its incoming links — giving each link a sub-span on both ends. The band between those two sub-spans is rendered on the shared monospace grid by walking every column between the two stages and, at each column, linearly interpolating the top and bottom row bounds between the source sub-span and destination sub-span, filling every row inside that interpolated band with one ASCII_RAMP (' .:-=+*#%@') character whose level is `round((link.value / maxVisibleLinkValue) * 9)` — bigger flow reads as denser ink, never a color hue. THE ISOLATE MECHANIC (the real interaction, not the picture): clicking a node computes its full upstream (every node that can reach it by walking links backward, recursively) and downstream (every node reachable forward, recursively) and keeps exactly `{selected} union upstream union downstream`; everything else — other nodes AND any link not between two surviving nodes — is dropped from the data entirely, not just dimmed. Because `stackSpans` re-derives every stage's spans from ONLY the currently-visible nodes' values (never the original full-graph spans with some hidden), the surviving nodes and bands re-stack to claim the SAME ROWS_STAGE height among just themselves — a node that was a sliver next to five siblings can end up half the diagram's height once isolated, which is what makes this a genuine re-weight rather than an opacity trick. A `data-sankey-isolated` readout beneath the diagram states which node is isolated and how many of the original nodes survive, with a real 'Show all' button (and Escape) that clears the isolation and restores the full graph. Every node button carries an `aria-label` stating its value, connection count, and the isolate action; hover and keyboard focus both distinctly shift the border toward `--accent` and the label from `--muted` to `--foreground`, and the isolated node's border stays `--accent` persistently. Tokens only (`--background --foreground --muted --border --accent`), applied as Tailwind utility classes (`bg-background`, `border-border`/`border-accent`, `text-muted`/`text-foreground`) bound to the same CSS custom properties — no hardcoded hex, no JS token reads, correct in both themes via the cascade. No rAF loop — every recompute is a direct response to a click, so there is nothing animated to gate behind `prefers-reduced-motion`. Pure DOM text + CSS, zero dependencies."
      }
    },
    {
      "name": "scroll-caliper",
      "type": "registry:ui",
      "title": "Scroll Caliper",
      "description": "A vernier caliper pinned to a scroll container's edge whose spring-damped jaws close over the active section's extent while a mono readout ticks px and percent, tick marks streaking with velocity-scaled motion blur.",
      "files": [
        {
          "path": "registry/core/scroll-caliper/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/scroll-caliper.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "svg",
          "scroll",
          "instrument",
          "spring",
          "sections",
          "hud",
          "readout",
          "measurement"
        ],
        "instruction": "A vernier caliper instrument, ~48px wide, pinned to the right edge of a bounded overflow-y scroll container it measures. Built in SVG (tick scale, vernier subscale on the lower jaw, jaw blades, beam spine) plus a DOM font-mono readout chip; every transform is written directly in a rAF loop via refs, never React state. Sections are detected with an IntersectionObserver (root = the scroller) on [data-section] children, active = most visible pixels. The jaws map the active section's top/bottom to container-space y and chase those targets with a spring k=120 s^-2, zeta=0.9 (near-critical, a hair of lag); the readout needle (percent of the section swept past the container center) rides a softer spring k=90 s^-2, zeta=0.55 so it visibly over-settles and wobbles when scrolling stops. Tick scale: minor graduations every 2px (--border ink), majors every 8px and long index lines every 40px (--foreground ink), path d rebuilt only on resize and translated by -(scrollTop % 8) per frame so the scale scrolls with content. Motion blur: a duplicated tick layer offset along the scroll direction with opacity clamp(|v|/3000, 0, 0.6) and blur clamp(|v|/200, 0, 10)/2 px, velocity from per-frame scrollTop delta exponentially smoothed at alpha 0.2, layer removed entirely at rest for crispness. Section change flashes the readout label accent for 200ms and updates a throttled visually-hidden live region ('Section 2 of 4, 38%'); the instrument itself is aria-hidden and read-only, no fake affordances. The scroll listener is passive and only wakes the loop; the loop sleeps when 150ms have passed since the last scroll AND all three springs are inside the settle epsilon (|x-target| < 0.05, |v| < 0.05). All SVG ink is currentColor via token classes (text-border, text-foreground, text-accent only on the active-value marker) so both themes restyle live with zero numeric color reads; the readout chip is bg-surface with border-foreground/20. Guards: zero-height containers skip rebuild, zero-height sections never divide, IntersectionObserver/ResizeObserver/scroll listener/flash and announce timers all torn down on unmount. prefers-reduced-motion: jaws and needle snap instantly, no blur layer, no wobble. The scroll container itself is an explicit tabIndex=0, role=\"region\" element with an aria-label ('scrollable content, measured by caliper'), so it is keyboard-focusable and scrollable cross-browser rather than depending on Chromium's implicit scroll-container focus heuristic (which WebKit/Safari does not ship)."
      }
    },
    {
      "name": "scroll-particle-tunnel",
      "type": "registry:ui",
      "title": "Scroll Particle Tunnel",
      "description": "Scroll scrubs a camera through a monochrome point tunnel — velocity stretches dots into motion streaks, cursor drift adds parallax, and mono section labels at fixed depths snap into focus as you fly past.",
      "files": [
        {
          "path": "registry/core/scroll-particle-tunnel/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/scroll-particle-tunnel.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "scroll",
          "canvas",
          "particles",
          "3d",
          "parallax",
          "scrub",
          "typography",
          "monochrome"
        ],
        "instruction": "Build a scroll-scrubbed particle tunnel section: a 400vh wrapper with a position:sticky full-viewport Canvas 2D inside it, roughly 3000 points seeded in a cylinder shell (radius 120-900 world units, depth 0-4000) and projected manually with scale = 600/(z - camZ), culling anything behind the camera. Read scroll progress from the section's getBoundingClientRect in a passive scroll listener that writes a plain variable, map it to camZ = p*3600, and drive everything from a direct-DOM rAF loop with no React state on the hot path that sleeps once scroll, pointer parallax, and springs are all settled. Keep an EMA (alpha=0.12) of scroll velocity and stretch each point into a motion streak along its per-frame projected delta with length clamp(|v|*0.06, 0, 40)px, relaxed back to dots by a spring (k=60, zeta=0.8) when scrolling stops; add cursor parallax by lerping camera x/y at 0.08/frame toward pointer offset from viewport center times 0.04. Points are drawn from the live --foreground token (read via getComputedStyle at mount, re-derived through a MutationObserver on <html> class changes) with alpha rising 0.3 to 1.0 proportional to 1/(z - camZ), so near/far contrast stays correct in both light and dark themes instead of a fixed gray ramp tuned for one background. A theme-token radial-gradient vignette (transparent center to var(--background) at the edges) sits over the canvas to sell the tunnel depth on the static pre-scroll frame. Overlay four Geist Mono labels (text-sm tracking-widest) centered on screen at fixed world depths 700/1600/2500/3400 and write their opacity, blur, and scale directly to style each frame: fully focused within +-300 units of camZ, falling to opacity 0 and blur 6px over the next 900 units, scale running 0.92 to 1.08 through the pass so they fly by. Under prefers-reduced-motion draw one static starfield frame (also theme-aware and re-derived on theme change) and render the labels as a normal stacked list that fades in over 200ms via IntersectionObserver."
      }
    },
    {
      "name": "scrubber-film-strip",
      "type": "registry:ui",
      "title": "Scrubber Film Strip",
      "description": "Media scrubber rendered as a film strip: sprocket-holed edges, one cell per second, a gate-claw playhead that snaps hole-to-hole on a slow drag and releases into a continuous glide on a fast one, buffered range showing as faintly noise-filled exposed frames.",
      "files": [
        {
          "path": "registry/core/scrubber-film-strip/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/scrubber-film-strip.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "slider",
          "scrubber",
          "media",
          "video",
          "svg",
          "drag",
          "keyboard",
          "accessibility",
          "form"
        ],
        "instruction": "Build <SprocketScrub value? duration buffered? onValueChange? label? className?> as a controlled/uncontrolled (value falls back to an internal defaultValue-seeded state when omitted) playback-position slider styled as a film strip. STRUCTURE: an outer relatively-positioned wrapper contains a single role=\"slider\" div (tabIndex 0, aria-valuemin=0, aria-valuemax=duration, aria-valuenow=Math.round(value), aria-valuetext formatted \"M:SS\", aria-orientation=\"horizontal\", aria-label from the `label` prop default \"Scrub position\") that IS the track — no separate thumb element, the whole track is the interactive surface, matching how a physical scrubber works. Two full-width SVG strips (2px tall) sit at the very top and bottom edges, each filled by a single repeating <pattern> (12x8 userSpaceOnUse tile, one r=1.5 circle in var(--border)) referenced via <rect fill=url(#id)> — one <pattern> def is enough, the bottom strip's <rect> reuses the same id via a shared useId-derived string so multiple instances on a page never collide. Between the two hole strips sits the frame body, a relatively positioned region containing, in paint order: (1) a full-bleed div at 12% opacity var(--border) representing blank unexposed stock; (2) a buffered-range div, left:0, width buffered/duration*100%, filled with a faint repeating-linear-gradient noise texture (45deg, var(--foreground) 0-1px, transparent 1-3px) at ~5% opacity, standing in for exposed frames; (3) a frame-cell-divider layer, a div whose background-image is a 1px vertical line (linear-gradient(to right, var(--border) 1px, transparent 1px)) with background-size set to `${100/duration}% 100%` — because the size is a PERCENTAGE of the element's own box, the line repeats exactly `duration` times at any container width with zero JS measurement, one cell per second; (4) the gate-claw playhead, a div (ref-held, never React state) holding a small CSS-triangle cap (border-trick, pointing down, positioned above the strip so it overlaps the top hole row) and a 2px vertical line — both children move together under a single `transform: translateX(px)` written imperatively to the parent ref, never through React state or left/percentage (which would trigger layout). MOTION, the two regimes: on pointerdown on the track, capture the pointer and record clientX/performance.now(). On every subsequent pointermove while dragging, compute the instantaneous speed = |dx pixels| / |dt ms| since the last sample. If prefers-reduced-motion is NOT set and speed < 0.5 px/ms, this is the slow/discrete regime: round the raw dragged value to the nearest whole second: if that rounded value differs from the last committed one, animate the claw's transform to the new cell's pixel offset over 120ms with a slight-overshoot cubic-bezier(0.34, 1.56, 0.64, 1) — the \"pull-in\" — and commit the rounded integer value; if the rounded target hasn't changed since the last sample, do nothing (prevents the transition from restarting on every mousemove tick inside the same cell, which reads as jitter rather than a settle). If speed >= 0.5 px/ms (or reduced motion is on, which forces this branch unconditionally so there is never snap theater), this is the fast/glide regime: write the claw's transform to the raw continuous pixel position with transition:none (1:1 pointer tracking) and commit the raw fractional value on every sample. On pointerup, release capture and snap the final value to the nearest integer with one more 120ms pull-in transition, ending every drag on a clean frame boundary. KEYBOARD: ArrowLeft/ArrowDown step -1 (one perforation), ArrowRight/ArrowUp step +1, PageDown/PageUp step -10/+10, Home/End jump to 0/duration — every keyboard step commits an integer and is NOT run through the drag speed detector at all (it takes the same path as an external programmatic value change, described next), since a single discrete key press has no drag velocity to measure and is inherently already a one-step move. PLAYBACK / EXTERNAL VALUE CHANGES: a separate effect watches the `value` prop and, whenever the component is not mid-drag, always glides the claw smoothly to the new position over 220ms ease-out-expo regardless of how large the jump was — this is deliberately a single simple behavior distinct from the two drag regimes, because playback advancing (or a caller setting `value` directly) is not a scrub gesture and should never trigger the discrete-step animation. HOVER PREVIEW: independent of dragging, pointermove over the track (while not necessarily pressed) writes a small floating preview's transform to follow the cursor's x and updates its content — a small shaded swatch div plus a mono \"M:SS\" timestamp for the hovered position — shown only while the pointer is over the track (pointerenter/pointerleave toggle visibility, a rare boolean flip so ordinary React state is fine here, unlike the transform writes which are always ref-based) and positioned bottom:100% of the (padding-free) wrapper so it rises above the track without being clipped by the track's own overflow:hidden. TOKENS: all ink is var(--border)/var(--foreground)/var(--background), no orange, no gradients beyond the noise texture and hole pattern (both structural, not decorative washes). REDUCED MOTION: the discrete pull-in transition and its speed check are bypassed entirely — every drag sample takes the fast/glide branch, so motion stays continuous and legible with zero snap animation; the playback glide (220ms) still runs since it was never part of the snap theater. DEMO: a player-chrome card (filename, play/pause transport button, mono elapsed/total clock) driving a 90-second fake timeline. A self-driving loop plays for a stretch (ticking `value` forward on a plain interval, which rides the always-glide playback path), then pauses and dispatches two REAL PointerEvent sequences at the track's DOM node (data-sprocket-track attribute) — a multi-step slow drag (small deltas, ~70ms apart, well under the speed threshold) to show the perforation snap, then a fast single-jump drag to show the glide release — before resuming playback, so the two regimes are demonstrated through the actual interaction code path rather than faked."
      }
    },
    {
      "name": "search-winnow",
      "type": "registry:ui",
      "title": "Search Winnow",
      "description": "Search input that winnows its list like grain from chaff: non-matching rows tumble aside with a slight rotation and fade, then their slot collapses so survivors settle together. Clearing the query lets the chaff drift back in. Full combobox keyboard pattern with a live match counter.",
      "files": [
        {
          "path": "registry/core/search-winnow/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/search-winnow.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "search",
          "filter",
          "combobox",
          "input",
          "form",
          "micro-interaction"
        ],
        "instruction": "Build a filtering search list where rejection is a physical winnowing motion, in two phases driven purely by CSS. STRUCTURE: bordered bg-surface card; header row with an inline SVG magnifier (stroke=currentColor on text-muted), a borderless text input, and an aria-live 'visible/total' mono counter; below, a <ul role=listbox>. Each item renders inside a two-layer exit shell: an outer <li class=grid> whose gridTemplateRows transitions 1fr -> 0fr (280ms ease-out, DELAYED 140ms) and an inner div (min-h-0 overflow-hidden wrapper) that drifts translateX(14px) rotate(1.5deg) and fades over ~240ms with an ease-in curve. The delay ordering is the mechanism: the row visibly tumbles aside first, THEN its slot closes and the survivors settle up; re-matching reverses both with zero delay so returns feel immediate. FILTERING: case-insensitive substring over label and hint; the matched span in both is wrapped in a <mark> tinted bg-border/80 with font-medium (token-only highlight, no accent — accent is reserved for interaction states). A trailing 'nothing survives \\\"query\\\"' row collapses in via the same grid mechanism when zero match. KEYBOARD: the input is role=combobox with aria-expanded, aria-controls, aria-autocomplete=list and aria-activedescendant pointing at the active option; ArrowDown/ArrowUp rove the active row (clamped to the visible set), Enter fires onSelect with the active item, Escape clears the query; filtered-out rows drop their option role and id so the accessibility tree only contains real matches. Active row tints bg-border/60, hover bg-border/40. INK: tokens only — surface, border, muted, foreground; both themes render. Reduced motion: all transitions none, rows appear/disappear instantly. No canvas, no timers, no observers, no JS animation — state is the query string and active index, everything kinetic is CSS transitions."
      }
    },
    {
      "name": "seatmap-ascii-pick",
      "type": "registry:ui",
      "title": "Seatmap ASCII Pick",
      "description": "An ASCII venue floor plan where dragging a marquee selects a contiguous block of seats — taken seats and the centre aisle break the run, and the selection visibly snaps to the longest unbroken block inside the drag, with a live seat count.",
      "files": [
        {
          "path": "registry/core/seatmap-ascii-pick/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/seatmap-ascii-pick.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "seatmap",
          "selection",
          "grid",
          "ascii",
          "mono",
          "drag",
          "keyboard-navigation",
          "booking"
        ],
        "instruction": "Build <SeatmapAsciiPick seats rowLabels colLabels sectionLabel className> over a rows x cols SeatStatus grid (each cell 'available' | 'taken' | 'aisle'; default a 6-row x 11-col plan with a fixed aisle column down the centre and scattered taken seats). MECHANIC: pointerdown on an available seat sets a drag anchor and live focus cell to it (aisle cells cannot start a drag); while the pointer is down (no setPointerCapture, so native pointerenter fires on whatever seat or aisle cell the pointer is actually over, and a global window pointerup ends the drag regardless of release target) each hovered cell's onPointerEnter updates only the focus corner, and the raw marquee rectangle is the min/max of anchor and focus on both axes. THE SNAP: the rectangle itself is never the selection. On every anchor/focus change, scan every row inside the rectangle's row span for the longest unbroken horizontal run of 'available' seats whose columns lie fully inside the rectangle's column span — a 'taken' seat or the aisle column breaks a run at that exact point — and take the single longest run found across all scanned rows as the actual selection; ties keep the first (topmost) row found. This is what makes 'unavailable cells break the block, selection snaps to the largest valid run' a real, observable behavior rather than an assumption: dragging a rectangle that straddles a taken seat or the aisle visibly shrinks the highlighted block to whichever side has the longer clear run. A bare pointerdown+pointerup on one seat with no movement commits a 1x1 rectangle, so a single click on an available seat is itself a valid (length-1) run and the gate can reach a real non-resting state without a drag. KEYBOARD: one roving-tabindex button per seat (aisle cells are plain non-interactive spans, never focusable); plain ArrowKeys move the active seat and collapse the selection to it, skipping straight over the aisle column so Left/Right never lands on a gap; Shift+ArrowKeys extend the marquee from a fixed anchor exactly like a drag would, re-running the same snap-to-longest-run logic per keystroke. RENDERING: available seats render ○, taken seats render × (rendered disabled, not merely styled — a real disabled attribute, so it is excluded from the tab order and cannot start a drag), seats inside the current run render ● with an --accent-derived tint (bg-accent/[0.16], never a literal hex), and the aisle renders as blank whitespace with no seat glyph at all. A live readout beneath the plan (aria-live=polite, doubling as its own accessible announcement — no separate sr-only region needed) prints '<n> seats selected — <row label> <first seat>–<last seat>' or a 'no seats selected' placeholder at rest. A11Y: every seat button has an aria-label stating its row, its seat letter, and its status (taken / selected / available); disabled seats are real disabled buttons, satisfying the 'exposed, non-disabled interactive control needs an accessible name' rule by not being exposed as an actionable control at all. Colors are token-only (--foreground/--muted/--border/--background/--accent via Tailwind classes) — no canvas, no hex. No motion beyond a 100ms background-color transition on hover/selection, skipped under prefers-reduced-motion via motion-reduce:transition-none; there is no rAF loop anywhere in this component."
      }
    },
    {
      "name": "segmented-control-fling",
      "type": "registry:ui",
      "title": "Segmented Control Fling",
      "description": "Segmented control with a visibly grabbable pill (grip dots, hover lift) — fling it, it coasts on real release velocity, rubber-bands off the ends, and snaps into the nearest detent. Optional one-shot intro fling demos the mechanic on first view.",
      "files": [
        {
          "path": "registry/core/segmented-control-fling/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/segmented-control-fling.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "control",
          "form",
          "segmented",
          "physics",
          "drag"
        ],
        "instruction": "Segmented control whose selection pill is grabbable: drag and release it anywhere along the track and it coasts on real release velocity (mean of pointer samples from the last 80 ms) under exponential friction v*=exp(-4.5*dt), rubber-bands off the track edges (beyond-track displacement rendered scaled 0.35, spring back k=320 zeta=0.9), and below 300 px/s captures into the nearest segment detent via a spring k=280 zeta=0.8 with one small overshoot. Click and keyboard remain instant no-drag paths: measure the target segment's width first (segments may differ) and glide both x and width with a critically damped spring k=220 zeta=1, ~260 ms. Pure DOM — the pill is an absolutely-positioned node driven by offset transforms relative to its layout slot on a direct-DOM rAF loop, zero React state on the hot path; a 1 s forced-settle deadline guarantees physics never jitters forever; the loop sleeps at a velocity epsilon and pauses offscreen via IntersectionObserver. role=radiogroup of role=radio buttons: ArrowLeft/Right/Up/Down move selection, Home/End jump to first/last, Space/Enter commit the focused segment, focus ring uses the accent token, and onValueChange fires once on detent commit, never per-frame. Reduced motion: the pill repositions instantly and drag release selects the nearest segment with no coast. Grabbability is advertised visually: the pill carries a six-dot grip affordance at its right edge and lifts (deeper shadow, brighter dots) when the selected segment is hovered, with cursor-grab/grabbing. Optional introFling prop plays a one-shot scripted self-demo ~2.5 s after mount — the pill is flung to the far segment, coasts, nudges the end rubber band, and captures into the detent; it never loops, is skipped under reduced motion, and is permanently cancelled by any pointer or keyboard interaction. All ink is token-relative CSS — no canvas."
      }
    },
    {
      "name": "select-caustic",
      "type": "registry:ui",
      "title": "Select Caustic",
      "description": "Single-select whose frosted-glass trigger and listbox sit over drifting caustic light pools; hovering or arrow-keying an option bends the caustics toward that row so the highlight reads as pressed into the glass.",
      "files": [
        {
          "path": "registry/core/select-caustic/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/select-caustic.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "select",
          "listbox",
          "form",
          "canvas",
          "glass",
          "keyboard",
          "micro-interaction"
        ],
        "instruction": "Build a single-select (DOM trigger button + listbox panel) whose surfaces are real frosted glass: each surface stacks a canvas 2D layer, then a translucent bg-surface span with backdrop-blur + saturate that frosts the canvas beneath it, then the content. The canvases draw caustics as 6 (trigger) / 8 (panel) radial-gradient blobs summed with globalCompositeOperation 'lighter' at 0.06-0.10 alpha, colors mixed between --accent and --foreground, with EXPLICIT canvas style.width/height plus a DPR-2-clamped backing store, and a full clear + redraw every frame (no destination-in accumulation). Idle simmer: each blob drifts on independent sin/cos phase offsets at a 6-10 px/s peak (amplitude 10-16px with frequency = speed/amplitude), running only while the panel is open, onscreen (IntersectionObserver), and the tab is visible; the closed trigger shows one static frame and the rAF is fully asleep. Open: panel with transform-origin at the trigger top scales 0.96 to 1 over 180ms on cubic-bezier(0.16,1,0.3,1) via WAAPI while blob anchors start clustered at the origin and spring (k=60 s^-2, zeta=0.9, ~450ms settle) to positions redistributed evenly down the list height. Active-row lens: a radial displacement field of radius 56px pulls blob centers up to 6px toward the active row center plus a tight accent pool under the row; the lens position follows cursor-hover or keyboard-active targets (one unified target) on a spring k=140 s^-2, zeta=0.8, with a forced-settle deadline snapping it 1.0s after the last input; lens coordinates are panel-local offsets from getBoundingClientRect deltas, never absolute page coords. Keyboard: full listbox pattern — trigger aria-haspopup=listbox aria-expanded, Enter/Space/ArrowDown/ArrowUp open, focus moves to the listbox which tracks aria-activedescendant, Up/Down skip disabled options, Home/End, 500ms typeahead buffer with startsWith matching, Enter (or Space with an empty buffer) commits, Esc and outside pointerdown close and restore focus to the trigger, hover and keyboard drive the same lens. Ink is getComputedStyle-derived from --accent/--foreground at mount and re-derived live by a MutationObserver on documentElement class so both themes render correctly. prefers-reduced-motion: static frost texture at anchor targets, no drift or lens, instant open/close. Guard zero-size hosts before sizing canvases (the panel is display:none while closed), and tear down every rAF, WAAPI animation, observer, listener, and typeahead timer on unmount. Demo: a deployment-settings card with a labeled project input, a Region select over believable region/code option data, a Failover select, and a footer status line."
      }
    },
    {
      "name": "sheet-ascii-range",
      "type": "registry:ui",
      "title": "Sheet ASCII Range",
      "description": "A spreadsheet-style rectangular cell range select where the border draws itself in real box-drawing glyphs (┌─┐│└┘) and a live ASCII status bar prints the aggregate — count, sum, mean, min, max — as the range grows by drag or Shift+Arrow.",
      "files": [
        {
          "path": "registry/core/sheet-ascii-range/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/sheet-ascii-range.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "spreadsheet",
          "grid",
          "selection",
          "ascii",
          "mono",
          "box-drawing",
          "keyboard-navigation",
          "data-visualization"
        ],
        "instruction": "Build <SheetAsciiRange data rowLabels colLabels unit title className> over a rows x cols numeric grid (default a 6x8 synthetic shipment log). MECHANIC: pointerdown on a cell sets both the drag anchor and the live focus cell to it; while the pointer is down (no setPointerCapture — this deliberately relies on native hover/enter events firing on whichever cell the pointer is currently over, exactly like a real spreadsheet drag, and a global window 'pointerup' listener ends the drag regardless of what element the pointer released over), each subsequent cell's onPointerEnter updates only the focus corner, and the selection rectangle is the min/max of anchor and focus on both axes — recomputed on every cell gained or lost, not just on release. A bare pointerdown+pointerup on one cell with no movement commits a 1x1 rectangle immediately, so the mechanic has a resting, gate-visible non-drag path too. KEYBOARD: one roving-tabindex button per cell (tabIndex 0 only on the current focus cell); plain ArrowKeys move the active cell and collapse the selection to it (spreadsheet cursor behavior); Shift+ArrowKeys (preventDefault, so the page never scrolls under the extend) keep the anchor fixed and move only the focus corner, extending or shrinking the rectangle exactly like a mouse drag would, and the aggregate recomputes on every keystroke. BORDER: every cell on the rectangle's perimeter renders one absolutely-positioned, aria-hidden box-drawing glyph seated on the seam at its own edge/corner of the CELL (not a separate overlay grid) — ┌┐└┘ at the four corners (priority: a cell that is simultaneously top+left is the top-left corner, etc.), ─ centered on the outer edge of top/bottom perimeter cells, │ centered on the outer edge of left/right perimeter cells — so the whole rectangle's boundary reads as a real drawn box even though every glyph belongs to its own cell's DOM. Selected cells (including interior ones) get a flat --accent-derived tint (bg-accent/[0.09], via Tailwind's arbitrary-opacity utility over the token, never a literal hex) with no glyph. STATUS BAR: a visible (not sr-only — it doubles as its own accessible live region via aria-live=polite) monospace line beneath the grid prints 'n=<count> Σ=<sum> x̄=<mean> min=<min> max=<max> <unit>' whenever the rectangle is non-empty, or a plain 'n=0 — click a cell or drag a range' placeholder at rest. A11Y: role=grid on the container, role=gridcell + aria-selected on every cell, and each cell's aria-label states its row label, column label and raw value/unit so a screen reader can navigate the sheet cell-by-cell independent of the visual rectangle. Colors are token-only (--foreground/--muted/--border/--background/--accent via Tailwind utility classes) — no canvas, no hex, so there's nothing to re-derive on theme change. No motion beyond a 100ms background-color transition on hover/selection, skipped under prefers-reduced-motion via motion-reduce:transition-none; there is no rAF loop anywhere in this component."
      }
    },
    {
      "name": "shortcuts-cheat-sheet",
      "type": "registry:ui",
      "title": "Shortcuts Cheat Sheet",
      "description": "A keyboard-shortcut cheat sheet whose keycaps press themselves — matching keydowns depress the cap and are swallowed before the app behind it can react.",
      "files": [
        {
          "path": "registry/core/shortcuts-cheat-sheet/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/shortcuts-cheat-sheet.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "shortcuts",
          "overlay",
          "dialog",
          "keyboard",
          "accessibility"
        ],
        "instruction": "A grouped keyboard-shortcut cheat sheet rendered as physical CSS keycaps (bottom-heavy 0 2px 0 shadow, caps on --background so they read as separate objects against the --surface panel in both themes). Built on the native <dialog> opened with showModal(), so the focus trap, background inertness, top-layer stacking, ::backdrop and Escape-to-close come from the platform rather than a hand-rolled trap; the panel enters with a 220ms translate+scale and closes on a timer rather than transitionend so a backgrounded tab can never strand it open. While open it owns the keyboard: one capture-phase keydown listener on window, installed only while open, matches every real keypress against the listed combos, depresses that specific cap in --accent for echoDuration ms, and calls preventDefault + stopPropagation so the app's own handler behind the overlay does not also fire — pressing Mod+K over the sheet cannot reach the search box underneath. Matching is not a string compare: each chord is a set of required modifier flags plus one canonical base key, and matching runs an exact pass (every flag must agree) followed by a lenient pass that folds Shift into single printable characters, so \"?\" (which arrives as {key:\"?\", shiftKey:true} on US layouts) fires while Mod+Shift+Z stays distinct from Mod+Z. \"Mod\" folds to metaKey on Apple and ctrlKey elsewhere and never to both, resolved once after mount so SSR output stays stable, and letters/digits fall back to e.code when the chord carries a modifier, which rescues non-Latin layouts. Multi-step sequences are authored with a \"then\" token (\"G then P\") and matched with a 1.2s window. Every row is also a button: clicking it rehearses the shortcut, playing each step's caps in order. Rows carry a spoken aria-label (\"Search: Command plus K\") so the glyphs never reach a screen reader, sections are labelled regions, and the flash is decorative and announces nothing. prefers-reduced-motion drops the entrance travel and the keycap's transform, leaving a pure colour change that is still a real pixel change."
      }
    },
    {
      "name": "signature-consent",
      "type": "registry:ui",
      "title": "Signature Consent",
      "description": "Signature capture as consent — a canvas ink strip with pen-pressure feel that retraces itself in a clean witness replay before Confirm embosses it as Authorized.",
      "files": [
        {
          "path": "registry/core/signature-consent/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/signature-consent.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "signature",
          "consent",
          "canvas",
          "ink",
          "form",
          "confirmation",
          "accessibility"
        ],
        "instruction": "Build a sign-to-confirm signature strip, distinct from a checkbox-style consent tick (that's checkbox-ink-stroke) — this is a physical-feeling capture of an actual signature. Structure: a bordered rounded-[12px] card. Header row: a Geist Mono uppercase caption reading 'Sign to authorize' (or the `prompt` prop) on the left, and on the right a real <button> reading 'Type your name instead' — this button must be the FIRST focusable/clickable element in the DOM so that any generic hover/press/focus check exercises a control that is always enabled and always shows a genuine visual change (underline + color shift to --foreground on hover, focus-visible outline in --accent) — never place a possibly-disabled Confirm button first. Below the header: either the draw surface (default mode) or a type-your-name input (toggled mode), never both. Footer row: a 'Clear' text-button on the left (disabled only when there is nothing to clear) and a 'Confirm' button on the right (disabled until a signature exists), which after confirming reads 'Authorized' and disables permanently until Clear.\n\nDraw mode: a canvas (backed by a getBoundingClientRect()-relative coordinate space) sits over a baseline guide — a plain absolutely-positioned div with a 1px --muted background line near the bottom of the strip, NOT an SVG line (per the project's known trap: SVG pathLength + vectorEffect=non-scaling-stroke miscomputes dashes in screen space; a straight guide line is simplest as a div regardless). On pointerdown, capture the pointer, resolve the ink color once by reading getComputedStyle(canvas).color (set `color: var(--foreground)` in the canvas's className so this resolves correctly in both themes — canvas 2D context strokeStyle cannot parse a raw CSS variable string), and draw a filled 'ink pooling' dot at the first point (radius ~4.2px) to read as a heavier pen-down moment. On pointermove while drawing, compute velocity as distance/time between the previous and current point and map it INVERSELY to line width — slow movement produces thick ink (up to ~4.4px), fast movement thins toward ~1.1px — then stroke a round-capped/joined segment at that width from the last point to the current one; do this as direct canvas draw calls, never through React state per frame. On pointerup/leave/cancel, if the stroke has at least 2 points captured, it counts as signed.\n\nWitness replay: unless prefers-reduced-motion is active, clear the canvas and replay ALL captured points (across every stroke drawn, preserving pen-up gaps between separate strokes) at a constant pace over a fixed ~650ms using requestAnimationFrame — compute total arc length across every segment, and each frame redraw every segment whose cumulative length falls under `totalLength * (elapsed / duration)`, using a constant witness stroke width (~1.8px) rather than the original pressure-varying widths, so the retrace reads as a clean 'this is what you signed' confirmation pass rather than a pressure-sensitive scribble. Redrawing the whole path-so-far from scratch every frame is fine at signature-sized point counts (a few hundred points) — no incremental-segment bookkeeping needed. Once the replay reaches 100%, enable the Confirm button and announce via the live region. Under reduced motion, skip the replay outright: leave the original ink as drawn and enable Confirm immediately with no animation.\n\nHover affordance on the draw surface: instead of tracking the pointer with a moving DOM element, set a custom two-tone CSS cursor (a small light-ringed dark dot, or its inverse — must read on both themes since a cursor image can't use CSS custom properties) via a data-URI inline SVG on the canvas's `cursor` style, so the nib-dot hover cue costs zero JS. The baseline guide brightens (--muted -> --foreground) on hover of the draw-surface wrapper via a plain CSS `:hover` rule, no JS needed there either.\n\nConfirm: applies an embossed look — an inset box-shadow press (dark inset shadow top-ish, faint light inset at the opposite edge) on the signed surface (canvas wrapper in draw mode, or the input itself in type mode) with a short CSS transition, and swaps the header caption text to 'Authorized'. This transition is skipped (instant, no `transition`) under prefers-reduced-motion, per the brief's 'instant emboss'. Clear resets everything — captured strokes, typed name, confirmed/replay/canConfirm state — back to the initial caption and an empty, re-drawable surface, and works whether or not a confirm has already happened.\n\nType-mode fallback (the keyboard/no-pointer path — canvas drawing has no keyboard equivalent, so this IS the accessible alternative, not a nice-to-have): clicking 'Type your name instead' swaps to a labelled text `<input>` (visually-hidden `<label>`, `autoComplete=\"name\"`) styled with an italic, slightly skewed treatment (font-style italic, a small skewX transform, larger size) using Geist Sans — never a separate script-font dependency. Confirm enables as soon as the trimmed value is non-empty; there is no replay concept for typed text (nothing to smooth), so it unlocks immediately on input. The toggle button is disabled once confirmed (can't switch modes on an authorized signature) and switching modes always resets state via the same Clear logic.\n\nAccessibility: a dedicated sr-only `role=status aria-live=polite aria-atomic=true` span announces 'Signature cleared.', 'Signature captured. Ready to confirm.', and 'Authorized.' at the relevant transitions — kept separate from any button label so atomic re-reading never duplicates surrounding text. The canvas carries `role=\"img\"` with an `aria-label` describing its current state ('Signature pad — draw with your pointer' / 'Authorized signature') since it is not itself keyboard-operable — the typed-name input is the real keyboard path, not a decorative extra. Clear and Confirm are real `<button>` elements with plain text content (always named). No dependencies, no gradient backgrounds, no color outside the repo's CSS variables."
      }
    },
    {
      "name": "skeleton-develop",
      "type": "registry:ui",
      "title": "Skeleton Develop",
      "description": "Loading wrapper whose placeholder blocks develop into the real content — a masked sweep resolves the text instead of swapping it.",
      "files": [
        {
          "path": "registry/core/skeleton-develop/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/skeleton-develop.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "skeleton",
          "loading",
          "placeholder",
          "transition",
          "css-animation"
        ],
        "instruction": "A loading-state wrapper, not a grey rectangle: <DevelopSkeleton loading={isLoading} blocks={...}>{content}</DevelopSkeleton>. While loading it renders placeholder blocks described by a declarative `blocks` prop (heading / text with a line count / circle / box / row groups) — the children are never measured, because they are not mounted at all until the data arrives. Each bar sits on the --border token and carries a slow sweep built from a recessed --background trough followed by a --muted crest, staggered bar to bar by a negative animation delay so the panel reads as one wave travelling through it, rather than the white-to-grey shine every other skeleton ships. When `loading` flips false the two layers stack and the placeholder develops into the content like a print coming up in a tray: a soft-edged alpha mask sweeps downward, the ghost blocks blur out and settle upward while the children un-blur and gain contrast behind them, top first, with no swap and no flash. Everything animating is CSS, so there is no rAF loop and no canvas; React only runs a three-state phase machine (loading / developing / ready), and flipping back to loading mid-develop cancels the pending settle rather than stranding a half-developed layer. Content that mounts already-loaded skips the transition entirely. The wrapper is aria-busy while loading with an sr-only status label; prefers-reduced-motion drops to still blocks, no sweep, and an instant swap."
      }
    },
    {
      "name": "skeleton-schema",
      "type": "registry:ui",
      "title": "Skeleton Schema",
      "description": "Schema-driven skeleton for streaming structured LLM output — every field, key, and array slot renders as a dashed empty mold the instant the shape is known, then sets solid field-by-field as values pour in.",
      "files": [
        {
          "path": "registry/core/skeleton-schema/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/skeleton-schema.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "skeleton",
          "streaming",
          "json",
          "schema",
          "llm",
          "tool-calls",
          "structured-output",
          "definition-list",
          "developer-tools"
        ],
        "instruction": "Renders the full shape of a structured LLM answer — a tool call's arguments, a JSON-mode response — as a skeleton the instant its schema resolves, before a single value has arrived: `<SlipCast schema={schema} value={value} streaming={isStreaming} label=\"search_flights arguments\" />`. `schema` is an array of `SlotSchema` (`{ key, kind: \"string\"|\"number\"|\"boolean\"|\"null\"|\"object\"|\"array\", fields?, item? }`) describing every field up front; `value` is the accumulated deep-partial object so far, passed fresh on every render exactly like a streaming buffer, and re-derived (never mutated) each time a new key lands. A field absent from `value` (or explicitly `undefined`) reads as pending; any other value, including an explicit `null` on a `kind:\"null\"` field, reads as set — so a real streamed `null` is honestly rendered as arrived, not confused with 'hasn't shown up yet'. LAYOUT: real `dt`/`dd` pairs inside a real `dl` (nested objects get their own nested `dl`, array items a `dl` per row), keys typeset in Geist Mono at `--muted` and never localized or reformatted, value blanks pre-sized by type — numbers a narrow tabular-nums box, booleans a fixed five-character box (fits both 'true' and 'false' without resizing on flip), null a fixed four-character box, strings full row width so an arbitrarily long value never reflows the row around it. Nesting indents in fixed 16px steps. THE FILL: a pending slot is a 1px dashed `--border` box; the moment its value lands the box crossfades to a 1px solid `--border` box over 150ms ease-out-expo (border-style itself isn't an animatable CSS property, so this is two stacked frames trading opacity, not a literal style tween — visually indistinguishable from one), the value drops in with a 320ms spring overshoot (opacity + translateY 3px -> 0), and a 4px corner tick fades `--muted` -> `--foreground`. A nested object's own container sets the instant its key exists in `value`, independent of how many of its children have filled — a field-granular pending/set state per slot, not one loading flag for the whole payload. ARRAYS: at every moment the array renders `arrived.length + 2` rows — the two ghost rows past the stream head are already on screen, dashed, before their values exist, so the row a value is about to land in never appears out of nowhere; only the trailing ghost buffer grows as items keep arriving. A slot still dashed when `streaming` goes false stays dashed — an honest 'this field never arrived' failure state, not swept away. Content that mounts already-complete (a value with every field present on first paint) skips the fill animation entirely; transitions only arm one frame after mount, via a `live` class, so nothing plays a cascade it didn't earn. DISTINCT FROM skeleton-develop: that component is a generic content-shaped loading placeholder (bars standing in for unknown prose, one loading flag, blocks that develop into unmeasured children on a single transition) — this one is schema-driven and field-granular: the real keys are typeset before any data exists, every individual slot tracks its own pending/set state, and it is built for the exact shape of streaming JSON/tool-call payloads, not prose. It also sits one layer above streaming-markdown-caret: streaming-markdown-caret owns the character-level insertion point inside a value that is itself still arriving token-by-token (a string slot's value can be a streaming-markdown-caret span); this component owns the contract-level shape around it and has no opinion about mid-token rendering. ACCESSIBILITY: the container is `role=region` with `aria-busy` while `streaming`; a visually-hidden `aria-live=polite` node announces batched milestones ('4 of 9 fields complete'), debounced 350ms so a fast burst of arrivals collapses into one announcement rather than one per key; the only focusable control is a Copy JSON button per top-level object, which serializes the current `value` with `JSON.stringify(value, null, 2)` and confirms with a transient label swap rather than a second element. prefers-reduced-motion drops every transition — slots that set simply appear filled, solid, and ticked with no easing. Pure DOM + CSS, zero dependencies, no canvas; every color is a token (--background --foreground --muted --border --accent), with --accent appearing only on the copy button's focus ring."
      }
    },
    {
      "name": "slider-allocation-wire",
      "type": "registry:ui",
      "title": "Slider Allocation Wire",
      "description": "Two-way allocation slider built as a slack wire between two labeled anchors: a bead sliding along the wire sets the ratio while the wire's own sag encodes the unallocated remainder, so a taut straight wire means fully committed and a deep droop means budget left on the table.",
      "files": [
        {
          "path": "registry/core/slider-allocation-wire/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/slider-allocation-wire.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "slider",
          "allocation",
          "budget",
          "range",
          "svg",
          "bezier",
          "two-variable",
          "input",
          "form"
        ],
        "instruction": "Build a two-way allocation slider drawn as a slack wire strung between two labeled anchors (default 'Compute' / 'Storage', both overridable via leftLabel/rightLabel props). Two independent 0-100 values live in the geometry: `ratio` is the bead's position along the wire (share going to the left anchor) and `total` is how much of the budget is actually committed, expressed as the wire's SAG — total=100 pulls it dead straight, total=0 sags to a deep, clearly visible droop. RENDER: an SVG quadratic bezier (`M p0 Q control p2`, viewBox 400x120, preserveAspectRatio=none so it maps 1:1 to the container box) whose control point sits at the anchors' x-midpoint and a y-depth of `MAX_DEPTH * (1 - total/100)`, rebuilt every frame from a freshly-sampled 48-point arc-length lookup table (never a cached shape) so the curve, and everything derived from it, is always current. The bead is a real DOM node (not an SVG shape) layered on top, positioned every frame by walking that SAME arc-length table to the point at `ratio`% of total arc length — genuine arc-length parameterization, not a lerp on the bezier's raw parametric t, so the bead visibly slides evenly along whatever shape the wire currently has, speeding up/slowing down across the sag exactly like a bead on a real slack cord would. Stroke is `var(--muted)`; the bead is a --border-ringed dot that swaps to --accent only while focused or dragging (never at rest); two small --border anchor dots mark the wire's ends; the curve, dots, and wire are all `aria-hidden` — every bit of state lives in the two slider values, never in the drawing. INTERACTION: (1) dragging anywhere on the wire area projects the pointer's x onto the CURRENT curve's arc-length table to set the ratio directly, pinned 1:1, no lag; (2) a second, separate 'Total allocated' track below the anchors' numbers is a paired linear slider (drag it directly, same 0-100 range) that winches the total; (3) wheel or trackpad-pinch (pinch reports as a wheel event with ctrlKey set) anywhere on the wire ALSO winches the total, so the wire itself doubles as a scroll-to-commit control. Every total-changing path — the paired track's drag, wheel/pinch, and keyboard — eases the sag through the same damped spring (critically damped, ~130 s^-2) rather than teleporting, so 'winching' reads as mechanical drag, not a jump cut; when the eased sag's TARGET newly lands at zero (total reaches exactly 100, wire fully taut) the depth is handed off to a scripted decaying cosine — `depth(t) = fromDepth * e^(-3.4p) * cos(4*pi*p)` over 900ms, exactly two visible oscillations — so the wire visibly twangs taut instead of just stopping, then falls back to the normal spring. Loosening back off from 100 never wobbles, only landing on fully-committed does. A forced-settle deadline (1.2s) guarantees any pending glide or wobble always resolves. Per-anchor split numbers (Geist Mono, tabular-nums) sit directly beneath the wire and are read straight off the animated bead position every frame, so they visibly count as the bead (or the whole curve, during a total change) moves; a third mono readout shows the live total percentage above its paired track. ACCESSIBILITY: two independent role=slider controls, nothing else holds state. The bead: aria-label 'Split', aria-valuemin/max 0/100, aria-valuenow the left anchor's percentage, aria-valuetext like '60% compute, 40% storage' built from the actual anchor labels; ArrowLeft/Right (and Up/Down) step 1%, PageUp/PageDown step 10%, Home/End jump to 0/100. The paired track: aria-label 'Total allocated', same aria-valuemin/max/now/key model, aria-valuetext like '85% of budget allocated'. Both are real focusable elements (tabIndex 0) reachable by Tab in document order, both use `outline-none` paired ONLY with `focus-visible:ring-2 focus-visible:ring-accent` (never `focus-visible:outline-*` on the same element) so the focus ring is never invisible. REDUCED MOTION: both springs and the taut-snap wobble are skipped entirely — ratio and total jump straight to their target every time — while dragging still tracks the pointer 1:1 exactly as before, so the component stays fully usable and legible with zero animation. PERF: direct-DOM rAF hot path — React state holds only the two committed integers (controlled/uncontrolled, each with its own onChange), all per-frame geometry (bead transform, path `d`, number text, track fill/handle position) is written straight to refs, never through React re-render; the loop sleeps at a velocity/position epsilon, is paused by an IntersectionObserver when offscreen and on document.hidden, and a ResizeObserver keeps the SVG-to-container scale factors current. No dependencies, no canvas — pure SVG + DOM + CSS, tokens only (--background --foreground --muted --border --accent), correct in both themes."
      }
    },
    {
      "name": "slider-loupe",
      "type": "registry:ui",
      "title": "Slider Loupe",
      "description": "Slider read through a circular magnifying loupe riding the thumb: the tick beneath sits sharp at 1.8x inside the lens while the rest of the scale rests soft-focus; rubber-banding past the bounds wobbles the magnification on release.",
      "files": [
        {
          "path": "registry/core/slider-loupe/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/slider-loupe.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "slider",
          "input",
          "form",
          "canvas",
          "magnifier",
          "micro-interaction"
        ],
        "instruction": "Build a single-value slider whose readout is a 44px circular magnifying loupe riding the thumb. RENDER: canvas 2D draws the tick scale + labels twice from cached layers rebuilt only on size/theme change — a sharp layer supersampled at 2x dpr (so magnified pixels stay crisp) and a soft base derived from it once via ctx.filter blur(1.5px) into an offscreen canvas (9-tap ring-average fallback when ctx.filter is unsupported). Per frame: full clear, soft layer at 55% alpha, then a circular clip centered on the loupe (radius 22 x mag/1.8), clearRect inside the clip to knock out the soft pass, and the sharp layer drawn through translate(lens) scale(mag) translate(-lens) at 1.8x. No per-frame filter work, no alpha accumulation. Canvas gets EXPLICIT style.width/height (replaced element — inset does not size it); DPR clamp 2. Thumb (2x16px foreground hairline in a 24px focus circle) and loupe ring (44px, border-border, hover border-foreground/40) are DOM nodes on offset transforms from the container's top-left anchor, never absolute canvas coords. MOTION: loupe position springs after the thumb at k=180 s^-2, zeta=0.75 for slight optical lag; the ring also scales mag/1.8 so the wobble reads refractive. Dragging past min/max maps overshoot x0.3 capped 24px; release springs home k=260, zeta=0.7 while magnification wobbles 1.8 -> 2.0 -> 1.8 on a damped half-sine over 350ms. Forced-settle deadline 1.0s covers springs AND wobble. Direct-DOM rAF: sleeps at velocity epsilon (0.05px / 0.5px/s), pauses offscreen via IntersectionObserver and on document.hidden, zero-size containers guarded before any draw. INTERACTION: focusable thumb with role=slider, aria-valuemin/max/now/valuetext, aria-orientation; ArrowLeft/Right/Up/Down +-step, PageUp/Down +-10 steps, Home/End; keyboard and external value changes glide on the same k=260 spring with the loupe chasing; pointer drag with setPointerCapture pins the thumb while the loupe lags; accent focus-visible ring with background ring-offset. TOKENS: all canvas ink (--foreground, --border) and the mono label font are read via getComputedStyle at mount and re-derived by a MutationObserver on documentElement class changes, rebuilding the blur layer on theme flip — works in both themes. REDUCED MOTION: thumb and loupe reposition instantly, no wobble; the 1.8x magnification itself stays (informational, not decorative). Tick generation: minor interval prop or auto 1/2/5-decade nice step, every Nth tick major + labeled, count capped at 240, custom formatLabel. Controlled/uncontrolled value, typed props with defaults so the bare component mounts. Tear down rAF, ResizeObserver, IntersectionObserver, MutationObserver, and the visibilitychange listener on unmount. DEMO: audio playback card on a padded surface — loupe scrub slider over an m:ss timestamp scale with live mono readout, skip/play/pause transport, plus a second instance as a waveform zoom-level control labeled in percent."
      }
    },
    {
      "name": "slider-range-shear",
      "type": "registry:ui",
      "title": "Slider Range Shear",
      "description": "Dual-handle range slider whose selected span is a taut band of material: it visibly shears under drag velocity while the far handle stays planted, then snaps flat with a slight overshoot on release.",
      "files": [
        {
          "path": "registry/core/slider-range-shear/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/slider-range-shear.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "slider",
          "range",
          "dual-thumb",
          "input",
          "form",
          "filter",
          "micro-interaction"
        ],
        "instruction": "Build a dual-handle range slider (min + max) whose selected span is rendered as a single SVG <polygon> — the 'band' — sitting between two thin vertical grip thumbs, so the interval reads as one taut object rather than two disconnected handles. MECHANISM: while a grip is being pointer-dragged, its instantaneous pixel velocity (dx/dt between pointermove events, lightly lerped at 0.5/event to avoid jitter) maps to a clamped px 'lean' (0.09px of lean per px/s of velocity, clamped +-8px) applied ONLY to the two corners of the dragged (near) edge of the band polygon — offset in opposite directions, top one way and bottom the other — while the far edge's two corners stay pinned at its exact x. This is a deliberate departure from a literal CSS skewX(): skewX shears every row by the same amount regardless of x, so it cannot hold one vertical edge stationary while the other leans — at any magnitude large enough to actually see, a symmetric skew would drag the far edge visibly off its planted grip, reading as broken rather than as tension. Four independently-placed polygon corners pin one edge exactly while the other leans, which is what 'visibly shears, far thumb stays planted' requires; the lean is clamped to at most 90% of the current pixel span so a near-zero gap (touching thumbs) can't self-cross into a bowtie. On pointerup/cancel, the lean relaxes from its released value back to 0 on an underdamped spring (k=300 s^-2, zeta=0.22, ~1.5 visible oscillations before settling under a 0.05px/1px-per-s epsilon) rather than snapping instantly — an rAF loop that starts on release and tears itself down once settled; a fresh pointerdown cancels any in-flight spring and zeroes the lean for a clean new gesture. The lean is entirely presentation: it never touches the committed value, and is skipped outright under prefers-reduced-motion (grips and the band still reposition normally, just with no lean and no release spring). GEOMETRY: both grip and band x-positions come from a single xFor(value, trackWidth) mapping with a 14px inset so nothing clips at the ends; trackWidth is measured off the interactive root via ResizeObserver (useLayoutEffect, synchronous initial measure to avoid a mount flash) rather than assumed, so PAD-relative math stays exact across resizes. Values print in Geist Mono at each grip's x-position and slide with it on a 150ms ease-out-expo transform transition (skipped under reduced motion via motion-reduce:transition-none) — position changes from keyboard or external value updates glide, position changes from an active pointer drag are instant (no transition fighting the pointer). INTERACTION: pointer handling lives on the shared interactive root, not on the (invisible) native inputs. A pointerdown picks whichever grip's pixel position is nearer the click — proximity arbitration — with an exact-tie rule (thumbs coincident, or click exactly at the midpoint) that extends outward in whichever direction the pointer already sits past the shared span, defaulting to the lower bound otherwise; this is what makes a zero-gap (touching) span still pickable on touch, where there's no hover to disambiguate first. Picking a grip also focuses its real input, so keyboard navigation can continue from wherever the pointer left off. The dragged/focused grip thickens (2px to 3px wide, 20px to 26px tall) and lifts with a 1px box-shadow derived from color-mix(in srgb, var(--foreground) 45%, transparent) — no accent, no hue change, just weight and depth. A11Y: two real <input type=\"range\"> (min, max), visually hidden via sr-only (never display:none) so Tab reaches both and native ArrowLeft/Right/Up/Down, PageUp/Down, Home/End work with zero custom keydown code. Each input's own min/max attribute is pinned live to the OTHER thumb's current committed value (the min input's max is the current max value, the max input's min is the current min value), so keyboard adjustment can never cross the handles — no manual clamping needed on that path (pointer dragging clamps manually since it bypasses the inputs entirely). aria-valuetext names the other bound on both inputs, e.g. 'minimum, 800 of maximum 1800' and 'maximum, 1800 of minimum 800', so a screen reader hears the full interval, not a bare number; aria-label takes minLabel/maxLabel props for the accessible name. Keyboard focus shows a focus-visible-gated accent ring (checked via e.target.matches(':focus-visible') so a pointer-issued .focus() call never paints it) without touching the shear/thicken styling, which reacts to either drag or focus. TOKENS: band fill is color-mix(in srgb, var(--foreground) 10%, transparent) with a var(--border) stroke; base hairline track is var(--border); grips are var(--foreground) at reduced opacity when idle, full opacity when engaged; focus ring and nothing else uses var(--accent). Controlled ([min,max] value + onValueChange) or uncontrolled (defaultValue) API, typed props with sane defaults so the bare component mounts. Cleans up its ResizeObserver and any in-flight spring rAF on unmount. DEMO: a rental-listing filter card whose visible result count and list re-filter live against the price ShearBand, plus a second instance as a small-integer (0-6, day-of-week) stay-window picker proving the near-zero-gap case."
      }
    },
    {
      "name": "slider-vernier",
      "type": "registry:ui",
      "title": "Slider Vernier",
      "description": "Numeric input built as a true vernier caliper: a fixed coarse tick row over a sliding fine row at 0.9x pitch, where the accent-lit fine tick physically lines up with a main tick to spell the last digit — drag the body for fast absolute moves, drag the vernier scale itself for one-step-per-tick precision.",
      "files": [
        {
          "path": "registry/core/slider-vernier/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/slider-vernier.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "slider",
          "input",
          "numeric",
          "precision",
          "svg",
          "drag",
          "form",
          "micro-interaction"
        ],
        "instruction": "Build a numeric input as a working vernier caliper, not a decorated slider. GEOMETRY: a fixed coarse tick row (one tick per 10*step) sits above a sliding fine row of 11 ticks whose pitch is exactly 0.9x the coarse pitch. For quantized value v, totalSteps = round((v-min)/step), fineIdx = totalSteps mod 10, coarseIdx = (totalSteps-fineIdx)/10; the real caliper identity is that fine tick #fineIdx coincides with MAIN tick #(coarseIdx+fineIdx) — not #coarseIdx — so the coincidence point drifts down the fixed scale as the reading grows, exactly like the physical instrument. The matching fine tick and its coincidence partner both light --accent, joined by a dashed accent hairline redrawn every frame; render 9 extra padding ticks past the domain max so the coincidence always has a partner to land on. INTERACTION: one track, two drag zones split at half height — the upper half maps pointer position to value ABSOLUTELY (fast, spans the whole range), the lower half (the vernier row itself) maps pointer DELTA at one step per fine-tick-width (slow, precise); both commit through the same quantize(step) pipeline so the split changes sensitivity, never resolution. While dragging, the fine group tracks the pointer 1:1 (raw, unquantized) so the user visibly chases the coincidence into alignment; on release, keyboard nav, or external value change, the visual eases to the settled quantized position on a 260ms ease-out-expo tween. The tween is direct-DOM rAF writing transform/x attributes to refs (sleeps when settled, cancelled on unmount); tick highlighting is ordinary React state since it only changes at quantized-value granularity. prefers-reduced-motion snaps to the settled position with no tween. Pointer capture on the track; a faint full-height guide line tracks the raw pointer. ACCESSIBILITY: the track is role=slider tabIndex=0 with aria-valuemin/max/now and aria-valuetext (formatValue override supported), ArrowLeft/Right/Up/Down step by step, PageUp/Down by 10*step, Home/End to the rails; the SVG is aria-hidden and the big mono readout (last digit tinted --accent to mirror the vernier) is aria-hidden too, since the slider itself carries the value. LAYOUT: coarse pitch auto-fits the container via ResizeObserver (clamped 18-56px, labels drop below 22px, label stride widens as pitch shrinks), overflowing domains fall back to horizontal scroll. INK: everything is currentColor via token utility classes (text-foreground/70, text-border, text-accent, fill-muted) — no canvas, no hex."
      }
    },
    {
      "name": "slug-field-mirror",
      "type": "registry:ui",
      "title": "Slug Field Mirror",
      "description": "A primary field with a slugified 'carbon copy' stacked underneath it, offset like a duplicate sheet peeking out from beneath a receipt. Every keystroke stamps its transformed counterpart onto the flimsy with a tiny pressure jitter; editing the flimsy directly tears it off the coupling until a real Relink button re-stamps it.",
      "files": [
        {
          "path": "registry/core/slug-field-mirror/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/slug-field-mirror.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "field",
          "slug",
          "derived-field",
          "form",
          "carbon-copy",
          "typography",
          "micro-interaction"
        ],
        "instruction": "A primary labelled text input with a second, real, independently-labelled 'flimsy' input stacked directly under it, styled as a carbon-copy sheet: 12px border-radius, 1px --border, offset translate(4px,4px) so it visually peeks out from behind the primary's bottom-right corner, background color-mix(in oklab, var(--muted) 14%, var(--background)). RENDER: the flimsy input's own text is transparent (caret-color kept on --foreground so a real caret still shows) and an aria-hidden absolutely-positioned overlay of one <span> per character, in Geist Mono, paints the visible glyphs on top — the underlying real <input> always carries the true accessible value, the overlay never diverges from it, it is purely decorative. DERIVATION: every primary keystroke recomputes the flimsy value via a `transform` prop (default a kebab-case slugifier: lowercase, non-alphanumeric runs collapsed to single hyphens, trimmed) and diffs it against the previous derived string by common-prefix length; characters at or past that prefix are 'newly stamped' and remount (keyed on an incrementing per-batch generation id) so a CSS keyframe plays automatically on mount: translate(0,0)->translate(0.4px,0.4px)->translate(0,0) with font-weight 600->500->400 over 120ms, each newly-stamped character's animation-delay offset by its distance from the batch's start index times 15ms, so a single keystroke stamps instantly (0ms) while a paste or a full re-stamp sweeps left-to-right. DETACH: any onChange fired directly on the flimsy input (never fired by the programmatic mirror, only by real user input) immediately sets linked=false — the box's transform springs from translate(4px,4px) to translate(0,0) over 340ms cubic-bezier(.34,1.56,.64,1), its border firms from --border to color-mix(in oklab, var(--foreground) 30%, transparent), its background flattens to plain --background, the character overlay unmounts (the input's own text becomes visible and freely editable), and mirroring from the primary stops entirely. RELINK: a real <button>'Relink'</button> — rendered only while detached, an ordinary tabbable control, never the first interactive element at rest — recomputes the transform from the current primary value, sets linked=true (springing the offset/border/background back), and calls the stamp path with `full=true`, forcing every character of the fresh value to remount and sweep in together (prefixLen=0), reading as the sheet being re-inserted and stamped fresh. A11Y: both inputs have real <label for> bindings; the flimsy input carries aria-describedby pointing at a persistent helper paragraph reading 'Auto-generated from {label}. Editing detaches it.'; detach and relink each fire exactly one message into a role=status aria-live=polite region (not on every keystroke); the character jitter is purely visual and never affects the accessible value; Tab reaches the primary input, then the flimsy input, then Relink when it exists. REDUCED MOTION: prefers-reduced-motion (media query and a mirrored data-reduced attribute) drops the character keyframe entirely (characters just appear at 400 weight) and removes the box's spring transition, so detach/relink still change state instantly and legibly without any motion."
      }
    },
    {
      "name": "sparkline-ascii",
      "type": "registry:ui",
      "title": "Sparkline ASCII",
      "description": "A production-usable sparkline chart drawn entirely in characters: block-fraction bars, keyboard-navigable column by column.",
      "files": [
        {
          "path": "registry/core/sparkline-ascii/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/sparkline-ascii.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "ascii",
          "data",
          "keyboard",
          "sparkline"
        ],
        "instruction": "A numeric series renders as a monospace character grid: each column stacks the block-fraction glyphs '▁▂▃▄▅▆▇█' bottom-up to the value's normalized height across 8 rows (min/max normalized per-series; a flat series with zero range falls back to a constant mid-height rather than collapsing to empty). A dotted ground grid fills the negative space above each bar so the plot area reads as a full grid rather than bars floating on blank space. An earlier revision also overlaid a box-drawing trend line ('╱╲─') on top of the bars; it was removed because box-drawing characters and block characters have different vertical metrics in monospace fonts (the block glyphs sit flush at the cell's baseline, the box-drawing glyphs sit centered in the em box), so the line could never land flush on the bar tops it was meant to trace — it always read as a disconnected diagonal floating above them, independent of the row math driving it. A dedicated in-grid row above the bars — same monospace flow, positioned in ch units on the same pitch as the columns — prints the active column's formatted value immediately above it; nothing else on the page moves to show it. The chart is a real role=listbox/role=option widget: hovering or focusing a column selects it, arrow keys (plus Home/End) move a roving tabindex between columns and re-focus the corresponding DOM node, and the selected column's entire cell stack (including empty rows) inverts to a solid background-fill block spanning the full column height, matching how a terminal selection highlights a run of cells regardless of what character is under it. `aria-selected` and the focus-visible ring follow DOM focus alone, independent of the hover-driven inversion/readout, so hovering one column never steals the announced selection or the visible focus indicator from another. Column pitch is fixed in ch units (1ch glyph + 0.6ch gap) so both the bars and the readout row share exact character alignment without measuring anything at runtime. Every option carries its own accessible name (series label, index, formatted value); this is a real chart, not a decorative sparkline, so it renders no static fallback under prefers-reduced-motion because it never animates on its own — everything here is user-driven."
      }
    },
    {
      "name": "sparkline-automaton",
      "type": "registry:ui",
      "title": "Sparkline Automaton",
      "description": "Inline KPI sparkline grown over an elementary cellular-automaton texture — the Wolfram rule is picked by the series' volatility, one generation column per data point.",
      "files": [
        {
          "path": "registry/core/sparkline-automaton/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/sparkline-automaton.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "data-viz",
          "sparkline",
          "cellular-automaton",
          "generative",
          "scrub",
          "kpi"
        ],
        "instruction": "An inline KPI sparkline on a single DPR-aware (clamp 2) Canvas 2D where the backdrop is an elementary 1D cellular automaton that reads the data instead of decorating it. CA: cells 3px tall, state vector vertical, one generation COLUMN per data point advancing left to right — each data point extrudes the next generation. The Wolfram rule is selected by the series' coefficient of variation (stddev/mean, zero-mean and zero-size guarded): cv < 0.08 → rule 4 (sparse), 0.08–0.2 → rule 108, 0.2–0.4 → rule 110, > 0.4 → rule 30 (chaotic). The seed row derives from the first value's IEEE-754 bits through a mulberry32 walk so identical data reproduces identical texture. The CA is computed once per data update and painted to a cached offscreen canvas in --foreground ink at 8–14% per-cell hashed alpha — never per frame. Motion is entrance-only: CA and the --accent polyline reveal left→right over 900ms with cubic-bezier(0.22,1,0.36,1) — the CA via a clip-rect (source-rect blit) sweep, the line via canvas setLineDash([pathLen*frac, pathLen]) dash-offset; a data push extrudes only the new tail generations and re-sweeps just that region over 200ms. Cursor scrub: a vertical hairline eases to the snapped nearest-point x with a critically damped spring (k=250 s^-2, zeta=1.0) plus an accent dot on the line and a font-mono DOM readout positioned by direct transform writes; the wrapper is focusable, shows a visible focus ring, and left/right arrows step the readout, which is an aria-live region and also mirrors its value into the wrapper's aria-label while scrubbing. rAF exists only during entrance/tail sweeps and unsettled scrub — cancelled otherwise, the texture is the ambient interest. All drawn colors (--foreground, --accent, --muted) are read via getComputedStyle at mount and re-derived (offscreen CA repainted) by a MutationObserver on documentElement class changes so both themes survive; ResizeObserver recomputes the cell grid and geometry; every observer/listener/rAF torn down on unmount. prefers-reduced-motion: finished CA texture and full line painted instantly, scrub readout still works with the hairline snapping instead of easing. Demo: three stacked metric tiles (calm Revenue / moderate Latency / volatile Errors) on a surface card with mono labels, large tabular numbers, success/error delta pills, and per-tile RULE/CV captions so the rule-vs-volatility mapping is visible by comparison."
      }
    },
    {
      "name": "split-flap-board",
      "type": "registry:ui",
      "title": "Split Flap Board",
      "description": "A split-flap departure-board display for short status strings — each character cell hinges its top half down over the bottom on a hard-creased 3D flip, cycling through a short burst of glyphs with per-cell stagger so a text change ripples left-to-right like clattering airport signage.",
      "files": [
        {
          "path": "registry/core/split-flap-board/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/split-flap-board.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "split-flap",
          "departure-board",
          "status",
          "aria-live",
          "mechanical",
          "typography",
          "3d-transform",
          "solari"
        ],
        "instruction": "Build a Solari-style split-flap departure-board display, driven by a `value: string` prop (one character cell per string index) plus an optional `charset` prop (default `A-Z0-9 .:-`) used both for the mid-flip cycling glyphs and the hover-peek plate. Each cell is FOUR stacked absolutely-positioned layers inside a `perspective`-bearing `<button type=\"button\" tabIndex={-1} aria-hidden=\"true\">` (a real, hoverable element for interaction purposes, but excluded from the accessibility tree and the tab order because the component's real interface is a live-region announcement, not a control): (1) a static top 'under' plate, only ever exposed by the hover-peek lift, holding a plausible upcoming glyph; (2) a static bottom plate showing the settled character's bottom half, height 50%, overflow hidden, vertically anchored so only the lower half of a full-height glyph shows, with a 1px var(--background) top border plus an inset box-shadow to read as a hairline crease with real depth; (3) the flap itself — one div, height 50%, `transform-style: preserve-3d`, `transform-origin: bottom center`, containing exactly two child spans sized to the FULL cell height (so the clipping 50%-height flap only ever shows the upper half of whichever face is centered): a front face at `rotateX(0deg)` showing the outgoing glyph's top half, and a back face pre-rotated `rotateX(180deg)` in CSS showing the incoming glyph's top half, both `backface-visibility: hidden`. Rotating the flap's own transform from `rotateX(0deg)` to `rotateX(-180deg)` makes the front face visible for the first half of the sweep and the back face visible for the second half — a single element does the double-sided flip-card trick, no second layer needed. Cell plates use `background: var(--foreground)` with glyph `color: var(--background)` — an inverted ink chip that reads as a genuinely dark plate in light mode and a bright, high-contrast placard in dark mode, monochrome throughout, no gradients, hairline `var(--border)` cell outline. On every `value` change, diff old vs new per character index; for each changed cell, schedule a short chain of 3-6 quick flip steps (150ms rotation each + 45ms hold, `cubic-bezier(0.61,0,0.4,1)`), each step landing on a random `charset` glyph except the final step which lands on the target character — this reads as clattering machinery cycling through possibilities, not a lookup swap. Stagger each cell's chain start by `(20 + random*20)ms * (index+1)` so the change visibly ripples left to right. Mid-flip, update the static bottom plate's text at the halfway point of the rotation (matching the moment the flap is edge-on and invisible) rather than at the step's start, so the bottom half changes in sync with the flap passing vertical, exactly like the physical mechanism. All of this is direct-DOM `style.transform`/`style.transition` writes via refs on the flap element and text-content writes on the plate spans — nothing touches React state on the per-flip hot path; only the fully-settled character per cell lives in a plain ref object. Hover: entering the board's outer wrapper sets a paused flag that blocks any NEW step in a chain from starting (an in-flight step still finishes) until the pointer leaves, i.e. hovering the board freezes further clattering at the next natural step boundary. Entering an individual cell additionally rotates that cell's flap to `rotateX(22deg)` over 160ms (`cubic-bezier(0.16,1,0.3,1)`) — lifting its bottom-hinged edge up and back, which, given the perspective on the cell, visibly exposes the static 'under' plate sitting behind it (a plausible upcoming glyph, refreshed to a new random charset pick every time a flip lands) — and returns to flat on pointer-leave. Accessibility: the ENTIRE visual board is `aria-hidden`; the only accessible surface is one `role=status aria-live=polite aria-atomic=true` sr-only span holding the value. It updates ONLY once cycling has fully settled (a debounce timer sized to the worst-case stagger+chain-length for the current cell count, cleared and restarted on every new value change) so a screen reader hears exactly one clean announcement of the final string per change, never the intermediate cycling glyphs. `prefers-reduced-motion: reduce` (checked via `matchMedia` with a change listener) skips cycling and the flap rotation entirely: every cell jumps straight to its target glyph on both plates and the flap's front face, no transition, while the crease line and two-plate structure stay in place so it still visually reads as a (static) flap board. Zero dependencies, no SVG, no canvas — pure CSS 3D transforms on plain divs/spans/buttons."
      }
    },
    {
      "name": "split-pane-weighted",
      "type": "registry:ui",
      "title": "Split Pane Weighted",
      "description": "Split-pane divider that carries real weight: drag it and the boundary trails the pointer like a counterweighted window sash, release with speed and it coasts into the nearest detent on a critically-damped spring; released slow and far from a stop, it just stays put.",
      "files": [
        {
          "path": "registry/core/split-pane-weighted/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/split-pane-weighted.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "split-pane",
          "resize",
          "layout",
          "drag",
          "spring",
          "separator",
          "keyboard"
        ],
        "instruction": "Build a resizable split-pane divider (a real ARIA separator, not a bare draggable rule) between a start pane and an end pane, driven by one spring simulation that runs in two phases inside a single rAF loop. PHASE 1, DRAGGING: on pointerdown over an 8px hit area centered on a 1px --border divider, the live position does NOT snap to the pointer 1:1 — instead it chases a `dragTarget` (the pointer's live percentage along the container's main axis) through an overdamped spring, k=90 s^-2, zeta=1.15. That lag is the entire 'weight' effect: flick the pointer fast and the boundary visibly trails before catching up; drag slowly and it tracks almost exactly. Because the spring's own velocity state at any instant already equals the divider's real motion, no separate pointer-velocity sampling (dx/dt across pointermove events) is needed — the carried spring velocity IS the release velocity. PHASE 2, SETTLING: on pointerup, that carried velocity feeds directly into a second spring aimed at whichever user-defined detent (default [25, 50, 75]) is numerically nearest, critically damped (k=170 s^-2, zeta=1, so it closes in without ever overshooting past the stop). That settle only runs if the release was decisive: either the release point is already within 6 percentage points of a detent, or the carried speed is at least 55%/s. Released slowly and far from any stop, the divider simply stays exactly where it was let go — not every release is entitled to land on a rail, and forcing one would fight the user's evident intent to park it off-detent. Both phases share one requestAnimationFrame loop and write only two things per frame: the start pane's flex-basis percentage (style.flexBasis set directly via ref, bypassing React state on the hot path) and the divider's own aria-valuenow/aria-valuetext attributes; the end pane is never touched directly, it fills the remainder via flex:1 and resizes as a pure consequence of the start pane's basis changing. GEOMETRY: pointer position converts to a percentage via the container's own getBoundingClientRect() read fresh on every pointermove (clientX for a vertical divider, clientY for horizontal) — no ResizeObserver or cached width needed, since percentage-based flex-basis is inherently responsive to container resizes on its own. Faint tick marks (var(--muted), ~45% opacity, 1px x 8px) render at each reachable detent as small rail marks fixed near the container's leading edge, independent of the divider's current position, so the snap points read as a property of the track itself and are visible before the very first touch. A11Y: the divider is a real, tabbable role=\"separator\" with aria-orientation matching its axis and aria-valuenow/aria-valuemin/aria-valuemax expressed as a 0-100 percentage of the start pane's share, plus aria-valuetext spelling out both sides ('42% / 58%') so a screen reader hears the whole split, not a bare number. ArrowLeft/Right (vertical) or ArrowUp/Down (horizontal) move the boundary 1 percentage point instantly, no spring — a single discrete nudge doesn't need inertia. Shift+Arrow jumps directly to the next detent in that direction using the same settle spring the pointer path uses (zero initial velocity). Enter toggles between 50/50 and wherever the divider last sat off-center (tracked in a ref, not committed to state) rather than only ever going to center, so repeated Enter presses actually toggle. Double-click glides confidently to 50/50 via the identical settle spring. REDUCED MOTION: both spring phases are skipped outright — dragging tracks the pointer's exact percentage every frame with no lag, and a release only ever produces an instant jump (no coast) to the nearest detent, and only when that release already falls within the same 6-point proximity zone; otherwise it simply stays at the exact released position. VISUAL: the divider's inner bar is 1px var(--border) at rest, brightens toward a border/foreground color-mix on hover or focus-visible, and thickens to 2px at full var(--foreground) while actively dragged; a focus-visible-gated accent ring (checked via :focus-visible so a pointer-issued focus never paints it) marks keyboard focus and active drag without altering the thickness logic. No canvas — pure DOM, CSS transforms, and getComputedStyle-free token classes throughout. Controlled (value + onValueChange, percentage of the start pane) or uncontrolled (defaultValue) API; cleans up its rAF on unmount. It differs from slider-range-shear, a dual-thumb range slider whose selected SPAN visibly shears under drag velocity: split-pane-weighted owns exactly one moving boundary between two arbitrary pane contents, not a value range, and its velocity-awareness is expressed as detent-seeking coast-and-settle rather than a visible material lean — no taken component in this registry owns pane resizing via flex-basis. DEMO: a three-pane IDE-style layout (file tree, code editor, live preview) built from two nested SashWeight instances — an outer vertical divider between the file tree and the rest, and an inner horizontal divider between the editor and the preview — so both orientations and both instances' independent detent sets are exercised in one composition."
      }
    },
    {
      "name": "stat-row-baseline-spark",
      "type": "registry:ui",
      "title": "Stat Row Baseline Spark",
      "description": "A KPI row implementing the stat-tile figure contract (value, delta, sparkline) honestly: the sparkline behind each number is the same series the delta is computed from, hovering or focusing surfaces exactly which point that delta is measured against, and color only ever means 'this is good news for this metric', never 'the number went up'.",
      "files": [
        {
          "path": "registry/core/stat-row-baseline-spark/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/stat-row-baseline-spark.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "stat-tile",
          "kpi",
          "data-viz",
          "sparkline",
          "delta",
          "dashboard",
          "accessibility"
        ],
        "instruction": "A stat tile row built to the dataviz skill's figure contract — value, delta, optional sparkline — with the two parts most implementations get dishonest: what the delta is measured against, and what its color is allowed to mean. Each tile takes `value` plus `history` (chronological, oldest first, NOT including `value` — `value` is the implicit newest point) and a required `baselineLabel` string stating what the delta compares to in words ('30d ago', 'vs target'); there is no silent 'vs previous point' default with nothing said about it. `baselineIndex` (default 0, the oldest history point) selects which history entry that comparison is against, and the SAME index drives a marker drawn directly on the sparkline — a dashed guide line and a hollow ring at that exact point — so the number and the chart are never two disconnected claims about the same series. That marker, like the sparkline's own resting state, is nearly invisible until the tile is hovered or keyboard-focused, at which point it fades in over 150ms; the resting sparkline still reads as a shape (10% muted-fill area, thin muted line) but the specific comparison point is something you have to ask for, the same way a tooltip is. COLOR: every tile ships an optional `polarity` — 'higherIsBetter' | 'lowerIsBetter' | 'neutral' (default) — and it is the ONLY thing allowed to trigger the tile's one accent moment: the delta text and the sparkline's current-value dot switch from --muted to --accent when the change is favorable BY THAT POLARITY, never by raw sign. A falling error rate (lowerIsBetter, value < baseline) gets the accent precisely because a fall is what 'better' means for that metric; a falling revenue number does not, and a 'neutral' tile never gets it regardless of direction — this is deliberate, since the registry's token rule leaves no red/green to fall back on anyway, so the arrow glyph (▲/▼/–) is what carries literal direction, unconditionally and honestly, while accent-vs-muted separately carries 'is this good'. Each tile is a real `<button>` (not a div faking one) with a single computed `aria-label` stating the value, the literal direction word, the delta, the baseline label and, if polarity is set, whether the change is favorable — so a screen reader user gets the exact same honesty a sighted user gets from color, rather than the color-only version most stat tiles ship. The row itself is `role=group`. Sparkline geometry is one small pure function per tile: points are `[...history, value]` scaled into a fixed 100x34 viewBox with `preserveAspectRatio=none` so it stretches to the tile's real width, no canvas, no ResizeObserver needed. All ink is Tailwind utility classes resolving to --background/--foreground/--muted/--border/--accent (fill-muted, stroke-muted, stroke-border, fill-accent, fill-foreground) — no raw color anywhere, so both themes render from the same markup with zero JS color derivation. Reduced motion is inherent rather than stripped: the only animated properties are opacity/color transitions on hover and focus, which stay because they are what exposes the baseline marker at all — there is no motion to remove, only a state to reach non-visually via Tab, which every tile already supports."
      }
    },
    {
      "name": "stat-tile-ascii-arrive",
      "type": "registry:ui",
      "title": "Stat Tile ASCII Arrive",
      "description": "A KPI number whose arrival is ink condensing into a glyph: every dot of a 5x7 ASCII dot-matrix digit starts at a random density-ramp step and eases independently toward its true value, so the numeral visibly resolves out of noise instead of sliding or flipping into place.",
      "files": [
        {
          "path": "registry/core/stat-tile-ascii-arrive/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/stat-tile-ascii-arrive.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "stat",
          "kpi",
          "ascii",
          "canvas",
          "data-viz",
          "accessibility"
        ],
        "instruction": "Renders `value` (a number or preformatted string, optionally with a `suffix` like '%' or 'ms') as ASCII ink on a small <canvas>, one 5x7 dot-matrix bitmap per character from a fixed lookup table (digits 0-9, comma, period, minus, plus, percent, space) — the same convention as a classic LED segment display, defined once as string rows of '0'/'1'. On mount, and again whenever the formatted text changes, every dot cell across the WHOLE string is assigned an independent animation: cells that are 'ink' in the target glyph start at a random low step of a 10-step density ramp (' .:-=+*#%@') and ease up to the ramp's densest character; cells that are 'off' start near the ramp's sparse end and ease down to nothing. Each cell gets its own random start delay (0-220ms) and duration jitter (~620ms nominal, +/-17%) via a seeded PRNG, so cells don't all move in lockstep — the numeral visibly CONDENSES out of a field of noise rather than fading or sliding in as a block. Mid-transition, a cell's displayed character is the ramp glyph at its rounded current step with alpha keyed to how far into that step's range it sits, so the eye reads a continuous density gradient rather than discrete glyph pops. Once every cell has reached its target the canvas stops issuing rAF frames entirely (display-only, no reason to keep redrawing an identical frame). ACCESSIBILITY: the canvas is aria-hidden; a visually-hidden role=status aria-live=polite aria-atomic=true span holds the plain locale-formatted value (with label, if provided) as the accessible content. REDUCED MOTION: renders the fully settled glyph on the first frame, no condensation pass, no rAF loop. Ink color is read via getComputedStyle(canvas).color at mount and re-derived on a documentElement class MutationObserver so both themes render correctly with zero hardcoded hex. Zero dependencies, canvas + inline font metrics only."
      }
    },
    {
      "name": "stats-radar-sweep",
      "type": "registry:ui",
      "title": "Stats Radar Sweep",
      "description": "Dashboard KPI grid under an observatory radar: a 1px accent arm sweeps the panel once per 12s with sonar rings, and each stat card counts up and lights its border only the instant the wedge crosses its bearing, then settles back to muted until the next revolution.",
      "files": [
        {
          "path": "registry/core/stats-radar-sweep/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/stats-radar-sweep.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "radar",
          "sweep",
          "kpi",
          "dashboard",
          "stats",
          "sonar",
          "count-up",
          "data-viz",
          "ambient"
        ],
        "instruction": "Dashboard KPI grid under an observatory radar with sweep-gated activation. RENDERING: one Canvas 2D layer (dpr backing store clamp 2, explicit style.width/height because a canvas is a replaced element that ignores inset for sizing) under a DOM grid of 6 KPI cards (rounded-md, 1px --border, --surface fill) inside a padded bg-background panel. Radar pivot fixed at the panel's top-left padding corner (24,24). Arm: 1px leading line in --accent — the only accent ink in the piece — rotating at 30°/s (12s per revolution); wedge: 24° soft sector trailing the arm painted as a conic gradient, --foreground alpha 0.10 at the arm fading to 0 at the trailing edge. SONAR RINGS: one ring emitted per full revolution, stroked circle expanding at 320px/s, alpha 0.18→0 over its travel to the panel diagonal, held in a pruned list capped at 3 and re-stroked after an explicit full clearRect each frame — never destination-in fading, the accumulation trap is designed out. CARD ACTIVATION: each card's bearing from the pivot is computed from DOM rects measured relative to the panel origin (offset coords, never absolute page coords), recomputed on ResizeObserver; when the unwrapped sweep angle passes within ±2° of a card's bearing the card wakes exactly once per pass (per-card next-transit angle re-arms +360° on trigger): value counts up to target over 900ms ease-out-expo in font-mono tabular figures via direct textContent writes, border-color animates --border→--accent over 200ms then decays back over 1.6s via inline rgb mix cleared to the class token at rest, and the trend delta fades in over 300ms; the card then rests with muted text (values dimmed, never zeroed). Every wake carries a hard 1800ms end time, so a card crossed twice quickly re-targets (wakeAt reset) instead of stacking tweens. INTERACTION: hovering or focusing a card replays its count-up and raises the surface with a token-relative foreground-alpha overlay, but never grants the accent border — accent stays sweep-earned; clicking the pivot button toggles pause/resume with a visible mono PAUSED tag; a 1Hz font-mono UTC clock and a per-revolution SWEEP counter frame the panel. REDUCED MOTION: no sweep, no rings, no loop; all six cards render settled at final values with static borders; hover raise stays pure CSS. TOKENS: wedge/ring/arm/border inks parsed from getComputedStyle(--foreground/--accent/--border) at mount and re-derived live via MutationObserver on documentElement class; both themes screenshot-gated. PERFORMANCE: refs-only direct-DOM hot path (no React state per frame); rAF pauses offscreen via IntersectionObserver and on document.hidden, sleeps while paused once all card tweens hit their hard ends, guards zero-size panels; ResizeObserver, IntersectionObserver, MutationObserver, clock interval, pointerenter/focus listeners, and rAF all torn down on unmount. DEMO: full weighted dashboard panel titled 'Network observatory' with believable ops KPIs — Revenue $128.4k (+4.2%), p95 latency 182ms (-11ms), Uptime 99.98%, Churn 1.2% (-0.1), Active nodes 3,412 (+86), Error rate 0.07% — each with a small stroke icon, never a lone sweeping arm."
      }
    },
    {
      "name": "status-glyph-cadence",
      "type": "registry:ui",
      "title": "Status Glyph Cadence",
      "description": "A 20-64px inline status glyph for agent work where the motion pattern itself is the signal — five states, five distinct cadences, no color and no swapped icon.",
      "files": [
        {
          "path": "registry/core/status-glyph-cadence/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/status-glyph-cadence.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "indicator",
          "agent",
          "svg",
          "motion",
          "aria-live",
          "loading",
          "icon"
        ],
        "instruction": "An inline status glyph, 20-64px, meant to sit beside a Geist Sans text label like 'Solving…'. The whole point: the motion pattern encodes state, not color and not a swapped icon, so it stays legible to someone who cannot see color or is scanning quickly. It renders one field of six SVG dots arranged in a hexagonal ring (24x24 viewBox, clock positions starting at 12 o'clock) plus a center element used by some states, all filled from var(--foreground) with per-dot opacity as the only other channel. Five states, each a genuinely distinct cadence built from the same dot field: working spins the whole ring steadily at constant linear speed (360deg/1.2s), with the six dots pre-set to a decaying comet-trail opacity gradient so even a single frame reads as motion-in-one-direction; searching leaves the ring stationary as a base transform but oscillates it back and forth across a bounded 70deg arc (cubic-bezier ease, 1.7s) with only two adjacent dots bright and the rest dim, reading as a sweeping beam rather than a rotation; awaiting-input holds the entire ring completely still and uniformly dim and instead breathes a separate center dot (scale 0.8-1.22, opacity 0.5-1, ease-in-out, 1.9s) — the only state with motion at the center rather than the ring; blocked holds the ring at one fixed dot permanently missing (a gap the ring can't close) and snap-jitters the whole ring through a few degrees on a stepped (not eased) timing function, a juddery stall distinct from every smooth cadence; done stops moving entirely — full ring, all six dots at full opacity, plus a checkmark path that plays a one-time spring settle-in (cubic-bezier(.34,1.56,.64,1), 420ms) on mount via a React key on state, then holds static. Every state's non-animated resting opacity pattern (comet trail / bright cluster / uniform dim / broken gap / complete ring) is deliberately chosen to be legible completely on its own, because under prefers-reduced-motion every animation class is simply removed via a CSS media query and that resting pattern is exactly what's left — five distinct static glyphs, never five states collapsed into one frozen spinner. Exposes state to assistive tech itself: the root is role=status aria-live=polite wrapping an aria-hidden SVG and a visually hidden (sr-only) text node that reads a plain label ('Working' / 'Searching' / 'Awaiting input' / 'Blocked' / 'Done') by default, overridable via a label prop so it can announce the same copy the consumer displays visually (e.g. 'Solving…') — a moving glyph alone tells a screen reader nothing, this makes every transition an announced live-region update. Props: state (the five-value union, required), size (px, default 24, legible from 20 to 64 since all geometry is in viewBox units and scales proportionally with no fine detail that vanishes small), label (accessible text override), className. Pure CSS keyframes driving SVG transforms and opacity, zero dependencies, no canvas."
      }
    },
    {
      "name": "status-sphere-dots",
      "type": "registry:ui",
      "title": "Status Sphere Dots",
      "description": "A canvas-free AI thinking indicator: a rotating sphere of depth-cued SVG dots. Four states — thinking, searching, done, idle — distinct by motion, not color, legible from 24px inline to a 200px showpiece.",
      "files": [
        {
          "path": "registry/core/status-sphere-dots/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/status-sphere-dots.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "indicator",
          "agent",
          "loading",
          "spinner",
          "svg",
          "motion",
          "aria-live",
          "sphere"
        ],
        "instruction": "A self-animating AI 'thinking'/loading indicator rendered as a slowly rotating sphere of small dots — the canvas-free answer to a WebGL thinking-orb. It is pure SVG (a pool of <circle> elements) driven by a single direct-DOM requestAnimationFrame loop; there is deliberately NO <canvas> and NO WebGL, which is the whole point: because it is DOM/SVG it inherits the theme's CSS custom properties directly and renders correctly in both light and dark with no color reading. Between 80 and 140 points (scaled by size) are distributed over a unit sphere by a Fibonacci / golden-spiral placement so the field looks even rather than clustered, spun about a slightly tilted vertical axis, projected to 2D with a mild perspective bulge, then depth-sorted and repainted back-to-front every frame so nearer dots always paint over farther ones. Depth is the signature: a dot near the viewer is larger, fully opaque and inked with var(--foreground); a dot on the far hemisphere is smaller, dim, and inked with var(--muted) — that combined size + opacity + ink ramp is what makes the field read as a three-dimensional SPHERE and not a flat ring or disc. Every color comes only from CSS custom properties already in scope (--foreground, --muted, --accent), never a hex/rgb()/hsl() literal or a palette class, so both themes just work. Four states, each a genuinely DISTINCT motion cadence that a viewer can tell apart from movement alone, never from color: thinking is a steady constant-speed spin; searching keeps spinning but adds an accent-colored latitude band that sweeps smoothly from pole to pole and back (built on the spin-invariant latitude coordinate, so it reads as a horizontal scan line gliding across the globe while the dots keep turning under it); done decelerates the spin to near-stillness while the whole sphere contracts inward with a brief settle overshoot to a calmer, smaller, brighter ball; idle is a very slow drift with a gentle axis wobble layered on top. State lives in a ref so a state change never tears the loop down; entering done stamps a convergence start time for the contraction. Under prefers-reduced-motion the loop never starts and a single static, still-depth-cued sphere is painted instead — and it stays legible per state: searching keeps a static accent band, done keeps its contracted settled look, idle renders dimmer, so reduced motion never collapses the states into one frozen frame. It is a status indicator for assistive tech: the root is role=status aria-live=polite; the SVG itself is aria-hidden and the accessible name is carried by a text node that names the current state (Thinking… / Searching… / Done / Idle by default, overridable via the label prop), rendered visually beside the sphere when showLabel is set or visually hidden (sr-only) otherwise, so every state transition is announced. Performance: exactly one rAF loop that is the sole DOM writer, cancelled on unmount and paused via visibilitychange whenever the tab is hidden, so it leaks no timers and burns no frames off-screen. Props: size (px diameter, default 96, legible ~24 inline to ~200 as a showpiece; dot count and radii scale with it), state ('thinking' | 'searching' | 'done' | 'idle', default 'thinking'), label (accessible/visible text override), showLabel (render the label visibly beside the sphere), className. Zero dependencies."
      }
    },
    {
      "name": "stem-and-leaf-live",
      "type": "registry:ui",
      "title": "Stem And Leaf Live",
      "description": "A live stem-and-leaf plot: every arriving value flies in from a staging slot and drops into its tens-stem row, existing leaves shoulder-nudging aside to insert it in sorted order, so the distribution is built entirely from the digits of the data and every leaf is still an individually focusable record.",
      "files": [
        {
          "path": "registry/core/stem-and-leaf-live/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/stem-and-leaf-live.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "data-viz",
          "stem-and-leaf",
          "distribution",
          "live",
          "table",
          "latency",
          "raw-data",
          "accessibility"
        ],
        "instruction": "A live distribution instrument that resurrects the stem-and-leaf plot: the `records` prop is a rolling window of `{ id, value, meta? }` objects, and it is rendered as a real <table> whose rows are tens-stems (`<th scope=\"row\">120-129 ms</th>`) and whose cells hold a flex row of Geist Mono digit leaves, one per record, each leaf being that record's rounded ones digit. Nothing is pre-aggregated the way a histogram bucket would be — the glyphs ARE the data, and the row's own width (leaf count x leaf width) doubles as the bar, so there is no separate bar element. ARRIVAL MECHANISM: a new id is diffed against the previous render's id set; entering leaves are measured against a decorative aria-hidden staging slot rendered above the table, given an inverted transform equal to (staging rect minus final rect) with transitions off, then one requestAnimationFrame later the transform clears to 0 over a 440ms spring (cubic-bezier(0.34,1.56,0.64,1)) — a two-keyframe fly from the staging point into its sorted slot in the row. Every EXISTING sibling leaf whose position shifted because of that insertion (later stem, later digit, or a stem row appearing/disappearing at either edge as the window's min/max moves) gets the identical FLIP treatment computed from its own previous vs. current getBoundingClientRect, at a faster 320ms so it reads as a shoulder-nudge rather than a fresh arrival. The staging slot itself flashes the incoming digit for 360ms then reverts to a dashed placeholder. SORTING: leaves within a row are sorted ascending by digit; ties (repeated digits) keep arrival order because Array#sort is spec-stable, so same-digit leaves read left-to-right as chronological without extra bookkeeping. DOMAIN: stem rows span every integer stem from the current window's minimum to its maximum, including zero-leaf rows in between (rendered as a muted em-dash placeholder) so the shape reads correctly even where the distribution is sparse. ACCESSIBILITY: a <caption> states n, median and range as a sentence; each leaf is a real <button> with a roving tabindex (exactly one Tab stop for the whole table, tracked in React state and re-resolved if the active leaf ages out of the window) and an accessible name built from the RAW value (not the rounded digit) plus unit and meta, e.g. '127.4 ms, req-4821' — the digit is a compression of the data for sighted users, never what assistive tech is told the value is. Arrow keys move within a row (clamped at the ends), Up/Down step to the nearest row that still has leaves (skipping empty stems), Home/End jump to a row's first/last leaf. Hovering or focusing any leaf lifts it 2px and swaps the footer readout from the resting 'median * range' summary to that record's exact value and meta; a role=status live region debounces arrivals into one batched sentence every 900ms of quiet ('3 new values, median now 141ms') instead of announcing every tick. REDUCED MOTION: the fly-in and shoulder-nudge transforms are skipped entirely (matchMedia gate on the FLIP effect) — leaves render straight at their final table position, fully readable and keyboard-navigable either way. Distinct from histogram-live-grain (a canvas-free but still bucket histogram where grains are anonymous units stacked in a bin and the raw value is gone once counted): here every mark keeps its id and its exact value forever, addressable by hover or by keyboard, and the plot is literally typeset from the value's own digits rather than being counted into a bucket. Pure DOM + CSS, zero dependencies; all color is token-relative (--foreground digits and staging box, --border rules/dashes, --muted labels, --accent only on the keyboard focus ring)."
      }
    },
    {
      "name": "stepper-needle",
      "type": "registry:ui",
      "title": "Stepper Needle",
      "description": "Bounded numeric stepper with a large mono value readout, prominent -/+ buttons, and a labeled history strip: an inertial needle swings to each committed value while the strip advances, etching the last 20 adjustments with the newest mark highlighted.",
      "files": [
        {
          "path": "registry/core/stepper-needle/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/stepper-needle.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "stepper",
          "spinbutton",
          "input",
          "canvas",
          "physics",
          "form",
          "micro-interaction"
        ],
        "instruction": "Build a bounded numeric stepper whose VALUE is the visual hero, paired with a labeled adjustment-history strip. RENDER: real spinbutton (input type=text inputmode=numeric with role=spinbutton, aria-valuemin/max/now) rendered as a large mono readout (text-3xl, ch-sized width so an optional unit suffix hugs the digits, transparent until hover/focus), flanked by prominent 44px -/+ buttons with bold icons; beside it a ~80px-wide strip column with a tiny mono 'history' header over a canvas drum; canvas is a replaced element so set style.width/height EXPLICITLY plus a dpr-scaled (clamp 2) backing store. MOTION: needle x = value mapped across strip width with 10px travel insets; underdamped spring k=120 s^-2, zeta=0.45 (two visible oscillations), forced-settle deadline 900ms backstopping a velocity-epsilon sleep. Each committed adjustment advances the strip 26px downward over 600ms ease-out (event-driven scroll retarget, never continuous, so the rAF loop sleeps between commits). Pen trace: pruned array of the last 20 (value, strip-position) points drawn as a polyline plus dots each frame after a FULL clearRect, alpha fading 0.9 -> 0.2 by age index — no destination-in fading; the NEWEST etch mark is highlighted with a solid dot plus halo ring so the latest adjustment is unmistakable. An --error band at 16% alpha appears ONLY on the edge currently hit (value at min or max), so red always reads as 'at the limit'; clamping fires a hard-stop needle quiver, +-2px, 2 cycles over 180ms, decaying. Hold-repeat on -/+: 400ms delay then 12 steps/s via setTimeout+setInterval, needle riding continuously; timers cleared on pointerup/leave/cancel and unmount. KEYBOARD: ArrowUp/Down +-step, Shift+Arrow +-10 steps, Home/End to min/max, Escape reverts typing, typed entry clamps and commits on Enter/blur with a proportional swing; buttons aria-disabled at bounds, focus-visible rings accent, hover borders token-relative (foreground/30, never white/NN). INK: needle/trace from --foreground, drum feed lines and pen carriage from --border, bound band from --error — all read via getComputedStyle at mount and re-derived by a MutationObserver on documentElement class changes, then statically redrawn, so both themes render correctly. REDUCED MOTION: needle jumps instantly, trace renders statically, no strip animation, no quiver. PERF: direct-DOM rAF with no React state on the hot path, loop sleeps when spring/quiver/scroll all settle, IntersectionObserver pauses offscreen, zero-size drum guard before drawing, ResizeObserver re-derives px mapping (trace stores values, not px), every listener/observer/timer torn down on unmount."
      }
    },
    {
      "name": "stepper-ratchet",
      "type": "registry:ui",
      "title": "Stepper Ratchet",
      "description": "Numeric spinbutton built as a ratchet and pawl: incrementing is free and clicky and a hold repeats and accelerates, but decrementing requires holding ~250ms while the pawl visibly swings 35 degrees clear before a hold starts stepping back, also accelerating.",
      "files": [
        {
          "path": "registry/core/stepper-ratchet/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/stepper-ratchet.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "stepper",
          "spinbutton",
          "input",
          "svg",
          "micro-interaction",
          "form"
        ],
        "instruction": "Build a bounded numeric spinbutton whose mechanism is a ratchet and pawl with DIRECTION-DEPENDENT friction. RENDER: a rounded-md border-border bg-surface card with a small-caps mono label row (label left, 'min–max' right), a large font-mono text-4xl value readout that IS the spinbutton (role=spinbutton, tabIndex 0, aria-valuemin/max/now, aria-valuetext with the optional unit), then a 6px-radius (rounded-sm) border-border bg-background strip housing an SVG rack: a horizontal --border baseline, a repeating sawtooth --muted tooth path (a single continuous path built once from an array of tooth positions, not one polygon per tooth), and a small --muted (2px stroke, bolder than the 1.5px teeth so it stays legible as the actor) triangular pawl polygon whose tip rests at the tooth-peak line, positioned with transform-box:fill-box and transform-origin:50% 100% (its tip) so rotation reads as lifting clear rather than orbiting the whole shape — every mechanism stroke is --border/--muted, --foreground is reserved for the value readout above it and --accent for the focus ring, so the number stays the one high-contrast element. Below the strip, two h-11 w-11 rounded-sm bordered buttons (− left, + right), both tabIndex=-1 — the spinbutton owns the keyboard, matching the established 'buttons are pointer-only, input/display owns Arrow keys' idiom already used by this registry's other spinbutton. INCREMENT (free direction): pointerdown on the + button (and ArrowUp on the spinbutton) commits a step immediately, translates the rack's tooth <g> by +TOOTH via direct-DOM style (no React state on the hot path) with a 160ms ease-out-expo (cubic-bezier(0.16,1,0.3,1)) transform transition, and fires an 80ms pawl 'kick': rotate to 14deg over ~34ms then back to 0deg over ~46ms, both via the same imperative rotate helper used everywhere else on the pawl — reads as the pawl riding up and over the new tooth and re-seating. Holding the + button repeats that same step-and-kick for as long as the pointer (mouse or touch) stays down, starting 400ms after the first step and accelerating (×0.78 per repeat) down to a 60ms floor; it stops the instant the value reaches max. DECREMENT (resisted direction, pointer path): pointerdown on the − button starts a 250ms arm timer and immediately begins rotating the pawl to 35deg over exactly those 250ms with a LINEAR transition (no easing — the rotation's pace IS the countdown the user is watching); if released before the timer fires (pointerup/pointercancel/pointerleave/blur), nothing decremented and the pawl springs back to 0deg over 260ms on a one-shot overshoot curve (cubic-bezier(0.34,1.56,0.64,1)), the house 'settle' spring already used elsewhere in this registry; if the hold survives the full 250ms, the timer fires one decrement step (rack translates -TOOTH, same 160ms ease-out-expo, no kick — the pawl is already lifted clear, it has nothing to re-seat into) and then repeats further -1 steps on that same 400ms-accelerating-to-60ms schedule as increment for as long as the pointer stays down, stopping itself the moment the value reaches min; releasing at any point during the hold or repeat clears the schedule and springs the pawl home exactly as an early release does. Every committed step, from either direction or the held repeat, fires a short navigator.vibrate() pulse behind a typeof-navigator.vibrate-is-a-function guard — real feedback on devices with a vibration motor, a harmless no-op everywhere else (notably macOS, where no web API reaches trackpad/Force-Touch or keyboard haptics). Every step, from either direction, is a no-op (no rack animation, no value commit, no onValueChange call, no vibration) if it wouldn't actually change the clamped value — the rack must never visibly move without the number changing under it. KEYBOARD: ArrowUp and ArrowDown on the focused spinbutton both call the SAME immediate-step path as a press on + (value commit, rack nudge, 80ms kick) — the 250ms pointer arm delay never applies to the keyboard, so the first ArrowDown always steps right away and repeats only at whatever cadence the OS's native key-repeat delivers keydown events, meaning keyboard users are never slower on the resisted direction than the free one. Buttons get aria-disabled (not native disabled, so they stay hoverable/focusable-by-script) at min/max and the handlers themselves also guard the bound. INK: every SVG stroke (rail, teeth, pawl) is var(--border)/var(--muted) directly in SVG attributes, never --foreground — no getComputedStyle needed since this is DOM+SVG+CSS with no canvas; --foreground is reserved for the mono value readout and --accent appears nowhere except the spinbutton's focus-visible ring (ring utilities, never outline-none paired with focus-visible:outline — that combination silently zeroes the ring). REDUCED MOTION: every transform-setting call routes through two shared imperative helpers (rack translateX, pawl rotate) that both check prefers-reduced-motion and substitute transition:none for their normal duration — so every value still changes and the 250ms arm timing (a functional gate, not decoration) is still enforced exactly as before, but the rack jumps instead of easing, the pawl jumps between 0/35/kick angles instead of tweening, and the increment kick and release spring both collapse to instant state swaps with nothing skipped functionally. Zero dependencies, pure DOM + SVG + CSS, tokens only, no canvas."
      }
    },
    {
      "name": "streaming-ink-dry",
      "type": "registry:ui",
      "title": "Streaming Ink Dry",
      "description": "Streaming LLM text where the newest tokens arrive light and translucent, then dry to full opacity a beat behind the stream head — width-stable, so committed text never reflows.",
      "files": [
        {
          "path": "registry/core/streaming-ink-dry/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/streaming-ink-dry.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "text",
          "streaming",
          "ai",
          "chat",
          "variable-font",
          "accessibility"
        ],
        "instruction": "<WetInk tokens={string[]}> renders a live streaming-text surface: pass the cumulative, append-only array of tokens received so far (each token exactly as the model emitted it, whitespace included — the component never inserts its own spacing) and it re-renders as the array grows. Every newly appended token mounts as its own span starting at opacity .55 and a 0.4px blur, then rides a single ~600ms ease-out-expo CSS animation (`animation: ... both`, no JS scheduling) up to opacity 1, zero blur. font-weight stays locked at 400 for the token's entire life — it is never part of the animation, deliberately: interpolating a variable font's weight axis changes glyph advance widths frame by frame, and because tokens can arrive faster than one token's dry time several spans would be mid-ramp at once, so any weight animation would read as the settled text ahead of it visibly compacting while you watch. Keeping weight constant and animating only opacity/blur (neither affects layout) means committed text never shifts horizontally, and arrival time alone still provides the stagger since tokens is append-only with stable index keys (already-dried spans are never remounted when new ones land). A still frame therefore always reads as a gradient of certainty: dried body, a drying middle, and a wet tail trailing the newest token. Accessibility is a second, parallel channel: the outer element is role=log aria-live=polite holding a visually-hidden transcript, not the decorative (aria-hidden) animated spans — settled sentences are each their own static span (a plain node landing in a polite live region is itself the announcement), and unresolved text sits in one trailing span carrying aria-busy=true so a screen reader is told to hold off, not left to infer 'wet' from opacity alone; that busy span's content is released into a new settled sentence the instant sentence-ending punctuation (or a newline) closes it, or after a short configurable idle gap (default 900ms, `idleFlushMs`) if the stream stalls mid-clause, so screen readers get whole sentences, never token-by-token fragments. Shrinking the `tokens` array (a fresh message) resets both the ink and the live-region bookkeeping. prefers-reduced-motion is handled entirely in CSS: the media query drops the animation and pins every token straight to its settled opacity/blur, so reduced-motion users see plain legible text arrive with no ramp, not a stall. `dryMs` (default 600) retunes the drying duration via a CSS custom property. Pure DOM + CSS, no canvas, zero dependencies — distinct from text-decrypt (a one-shot monospace scramble-to-decode entrance on a fixed string) and text-variable-weight (a decorative, cursor-driven weight morph with no underlying state): this is a live container where opacity/blur encode a real 'not yet settled' state and keeps running for the life of the stream."
      }
    },
    {
      "name": "streaming-markdown-caret",
      "type": "registry:ui",
      "title": "Streaming Markdown Caret",
      "description": "Streaming-text renderer where already-arrived content never re-animates — only the trailing, unterminated edge carries a muted caret and settles when its markdown span closes.",
      "files": [
        {
          "path": "registry/core/streaming-markdown-caret/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/streaming-markdown-caret.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "text",
          "streaming",
          "markdown",
          "chat",
          "llm",
          "caret",
          "accessibility"
        ],
        "instruction": "A streaming-text renderer for live, unknown-length arrival (LLM/chat output), not an entrance effect on an already-known string. The only prop that matters is `text`: pass the full accumulated string so far on every render and keep appending to it — never mutate or replace earlier characters, or the offset-based keys this relies on stop holding. PARSING: a tiny hand-written scanner (deliberately zero dependencies — a real markdown parser, streamdown included, rebuilds its whole AST from the string on every token, which means fresh React elements for text that already rendered correctly last frame, i.e. the entire block remounts and shimmers; the one thing this component exists to prevent) recognizes only `**bold**` and `` `code` ``, greedily matching each opening delimiter against the next matching close. Everything before the first still-open delimiter (or the whole string, if none) is split into closed segments keyed by their fixed character offset in the source string; offsets never shift because text is append-only, so React never remounts a stable segment and it never re-animates or reflows. Whatever follows an unterminated `**`/`` ` `` — or, absent one, the growing plain run at the very end — is the live tail: rendered as literal characters, dangling delimiter included, so a stray unterminated marker never garbles the text on either side of it. STABLE VS LIVE: when a later chunk supplies the matching close delimiter, the run that used to be raw tail becomes a real `<strong>`/`<code>` element for the first time — a genuinely new key the reconciler has never seen — so it mounts fresh and plays a 120ms ease-out-expo opacity settle (0.45→1); every segment rendered before it keeps its same DOM node and key, untouched. A muted `--muted` block caret (CSS steps() blink) sits at the very end of the tail while `streaming` is true and is omitted once it flips false. SCROLL: the component never calls scrollIntoView or steals focus, and only ever appends past the end of already-rendered nodes, so it cooperates with the browser's native scroll anchoring instead of fighting it — a consumer's chat log won't get yanked as tokens land; scrolling that log to the bottom, if wanted, is the consumer's job, not this component's. ACCESSIBILITY: the visible text is real text, not decorative glyph spans standing in for a label, so a screen reader's normal virtual cursor already reads whatever has arrived at any time. Token-by-token aria-live would be unusable noise, so instead a single sr-only role=status region announces exactly two coarse state changes — 'Generating response…' on start, 'Response complete.' on end — never the content itself. Under prefers-reduced-motion the settle flash and the caret blink are both disabled (the caret renders as a steady static block) but nothing about legibility depends on either animation running. Zero runtime dependencies."
      }
    },
    {
      "name": "streaming-retraction",
      "type": "registry:ui",
      "title": "Streaming Retraction",
      "description": "Streaming-text renderer for models that take words back: a retraction strikes through mid-sentence, evaporates, and leaves a notched scar tick that reopens the revision inline.",
      "files": [
        {
          "path": "registry/core/streaming-retraction/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/streaming-retraction.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "streaming",
          "typography",
          "correction",
          "ai",
          "text",
          "revision",
          "accessibility"
        ],
        "instruction": "Renders an append-only op log — `{ type: 'append', text }` / `{ type: 'retract', chars }` — the exact shape a self-correcting stream produces (guardrail rewrites, constrained decoding, tool-output patching), instead of a final string that hides the revision or a snapshot diff that re-animates settled text. REPLAY: ops are folded into segments where consecutive appends merge into one text run keyed by its immutable character offset, so text that has already arrived keeps the same React key forever and never re-renders or re-animates; a retract pulls characters off the trailing text runs only (scars are zero-width in the live text, so a retraction can never un-retract a previous one) and deposits a scar segment at the exact inline position it happened. SCAR LIFECYCLE: a scar born after mount plays a three-beat exit on the retracted text — a 1px strike line sweeps left-to-right across it (background-size animation on a currentColor gradient, not text-decoration, which cannot sweep), the struck text holds legible for a beat, then evaporates to opacity 0 — and only then is it swapped for the settled form, so the paragraph reflow happens on invisible text and reads as evaporation rather than a jump. Scars present in the op log at mount render settled instantly: a page of historical corrections must not all strike at once on load. THE SCAR TICK: the settled form is a 7px-wide notched tick (a rounded mark with a 1px background-colored kerf cut through its middle — the mark of something excised) rendered as a real <button> with aria-expanded and an accessible name that states the word count removed ('Show retracted text (2 words removed)'); activating it reopens the retracted text inline — muted, line-through, on a surface chip — and toggles closed again. STREAMING CHROME: while `streaming` is true a muted block caret blinks at the trailing edge (steps() timing, like a terminal); a visually-hidden role=status aria-live=polite region reports generating/complete and the running self-correction count, so retractions are announced, not silent. REDUCED MOTION: the strike sweep and caret blink are dropped via media query — struck text simply dims, the timeline is unchanged, and every scar stays fully operable. Colors are tokens only (--background --foreground --muted --border --accent, surface for the ghost chip); --accent appears solely on the tick's focus ring. Pure DOM/CSS, zero dependencies, no canvas."
      }
    },
    {
      "name": "streaming-token-settle",
      "type": "registry:ui",
      "title": "Streaming Token Settle",
      "description": "Streaming text where provisional tokens sit off-baseline and a hair rotated like loose letterpress type, then snap into the chase with a stiff spring the instant they're confirmed — a correction slides the old word out sideways as its replacement slides in.",
      "files": [
        {
          "path": "registry/core/streaming-token-settle/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/streaming-token-settle.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "text",
          "streaming",
          "transcription",
          "state-machine",
          "accessibility"
        ],
        "instruction": "<LooseType tokens={LooseTypeToken[]}> renders a line of streaming text as a real provisional-vs-committed state machine, not a decorative flourish: pass the full ordered token snapshot every render, where each token is { id, text, committed? }. A provisional token (committed: false, the default) renders muted and sits off its baseline by a token-seeded ±1px translateY and ±0.6deg rotation — like loose type not yet locked into the chase — so a still frame reads at a glance as 'may still change' without any color-only signal to miss. The instant a token's committed flips to true it snaps to baseline, full --foreground, with a stiff, low-damping spring (a CSS back-out easing that overshoots slightly before settling, ~420ms) — no flash, the motion itself is the confirmation. To represent a correction (the model or ASR revising a word), give the replacement a fresh id at the same array position instead of editing the old token's text in place: the old id's span slides out sideways (translateX -0.4em, fades) while the new id's span slides in from the opposite side (translateX +0.4em to 0), and the space the old token occupied is width-tweened back to zero as it exits so the rest of the line reflows under it smoothly, like a compositor pulling and reseating a sort, rather than jumping. Reusing an existing id just updates that token's text/committed flag in place with no swap animation. Pure DOM + CSS transforms on real <span> elements, zero canvas, zero dependencies. Accessibility is a single channel by design: the entire visual row is aria-hidden, and a screen reader instead gets one aria-live=polite region holding only the committed text, joined with single spaces — provisional wording is never exposed to assistive tech, so a reader is never told something that might still be revised, and because the region's content is recomputed as one string on every commit or correction, a multi-token correction announces as one coalesced update rather than word-by-word churn. prefers-reduced-motion drops the jitter, the slide, and the spring entirely: provisional tokens are told apart from committed ones purely by --muted color plus a dotted underline, both applied and removed instantly with no transform or transition at all, so the component stays fully legible and non-distracting under reduced motion rather than just slowing down. Distinct from streaming-ink-dry (which encodes elapsed-time freshness via variable-font weight/opacity/blur ramping toward settled, with no revision concept and nothing ever un-arrives) and from text-decrypt (a one-shot monospace scramble-to-decode entrance on a fixed string, no ongoing state at all)."
      }
    },
    {
      "name": "surface-glass",
      "type": "registry:ui",
      "title": "Surface Glass",
      "description": "Container-level liquid glass: blur, saturation, noise grain, specular rim, graduated shadow ramp.",
      "files": [
        {
          "path": "registry/core/surface-glass/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/surface-glass.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "surface",
          "glass",
          "card",
          "container"
        ],
        "instruction": "A liquid-glass container surface built as a layer stack: backdrop blur with saturation boost, tiled SVG noise grain blended over the fill, an inset specular rim lit from above, and a four-step graduated shadow ramp for depth, with children rendered above the glass. Works over any moving or static backdrop, both themes."
      }
    },
    {
      "name": "swipe-row-detent",
      "type": "registry:ui",
      "title": "Swipe Row Detent",
      "description": "A swipeable list row that clicks through machined detent stops — archive, then flag — and strains against a hard stop before overtravel arms delete, with a 3-second undo bar before it commits.",
      "files": [
        {
          "path": "registry/core/swipe-row-detent/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/swipe-row-detent.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "swipe",
          "list",
          "row",
          "gesture",
          "drag",
          "undo",
          "accessibility"
        ],
        "instruction": "Build a swipeable list row, not a generic free-drag swipe-to-reveal — the identity here is MECHANICAL: the row clicks through fixed detent positions as you drag, rather than following the pointer 1:1. Layout: a relative, overflow-hidden row wrapper; an absolutely-positioned actions strip pinned to the right edge (Archive ~76px wide, Flag ~76px wide, Delete ~96px wide, each a real `<button>`); and a foreground content layer (avatar circle + title/subtitle text) that is the actual pointer-drag target, translated left over the actions strip via a CSS custom property (`--dtx-x`, applied once as `transform: translate(var(--dtx-x,0px), var(--dtx-y,0px))` so drag writes only ever touch that one custom property directly on the ref, never touching `transform` as a whole string — keeps a separate hover-lift `--dtx-y` from fighting the drag offset).\n\nDetents: D1 = 76px (archive revealed), D2 = 152px (archive+flag revealed, the hard stop). While dragging, track the raw pointer delta (initial-down-x minus current-x, offset by whatever detent it was already resting at if re-grabbed) and classify it into a zone (0 / 1 / 2) by which detent's midpoint threshold it has crossed. On a ZONE CHANGE ONLY, snap the content's offset to that detent's exact px value with a short (~140ms) eased settle transition — between zone changes the row does NOT continuously follow the finger, it holds at its current detent. This discrete jump-between-detents behavior, not smooth 1:1 dragging, is what makes it read as 'machined' rather than a rubber-band swipe. A newly-engaged action button's left border highlights (--border -> --foreground) as its detent engages, standing in for the brief's 'faint tick mark.'\n\nOvertravel: once the raw delta exceeds D2, switch to continuous tracking with a diminishing-returns resistance curve (`resisted = MAX * (1 - 1/(1 + extra/K))`, extra = raw-D2) so further dragging yields less and less additional reveal — this IS a direct per-pointermove ref/style write (the hot path), unlike the discrete detent jumps above. Once raw delta passes a further threshold beyond D2, arm delete: the Delete button becomes visible/hittable (`visibility: visible`), its border and text render in `var(--error)` (there is no Tailwind `error` utility registered in this repo's theme — reference the color via arbitrary-value classes or a small scoped CSS rule, never invent a hardcoded hex), and the row edge visibly compresses by ~2px (fold that into the same offset calculation rather than a separate transform) to read as strain. Releasing while armed does NOT commit immediately: it opens a 3-second undo bar (a full-width bar that burns down via a `width` transition from 100% to 0%, direct ref write, not React state) with a real 'Undo' button; if untouched, the timer fires the delete callback and resets the row. Clicking Undo cancels the timer and returns to rest. Releasing NOT armed always settles to the currently-engaged detent (0, 1, or 2) — there is no separate spring-back-to-zero-on-release behavior for non-overtravel drags, since the row is designed to rest open at a detent until an action is taken.\n\nActions are real `<button>` elements that exist in the DOM at all times but are `visibility: hidden` (never `display:none`, never conditionally unmounted) until their detent is reached — this is deliberate: `visibility:hidden` removes an element from both hit-testing and the accessibility tree exactly like being genuinely absent, with none of the risk of an invisible-but-still-hittable element silently intercepting clicks or confusing a generic 'first interactive element' test. Because of this, keyboard activation must NOT depend on focusing/clicking the (possibly hidden) button DOM node — Enter should call the archive/flag handler directly based on which detent is currently engaged.\n\nKeyboard: the row itself is a focusable (`tabIndex=0`, `role=\"group\"`, a descriptive `aria-label`) container. ArrowRight/ArrowLeft step the engaged detent index up/down through 0/1/2 with the same settle transition (keyboard never arms delete — that is pointer/touch overtravel only, by design). Enter activates whichever action is currently engaged (archive at index 1, flag at index 2; no-op at rest). Escape returns to rest from any engaged detent. Pointer AND touch both drive the same pointer-event handlers (use Pointer Events, not separate mouse/touch listeners, and `touch-action: pan-y` so vertical list scrolling still works while horizontal drag is captured).\n\nHover (rest state only, i.e. detent 0 and not armed/pending-delete): the row lifts 1px (via the `--dtx-y` custom property flipped by a plain CSS `:hover` rule scoped to a 'restable' class, never JS pointer-tracking) and small grip dots fade in near the left edge (opacity via the same scoped `:hover` rule) — purely CSS, no extra JS needed for either cue. A `cursor: grab` / `:active { cursor: grabbing }` pair on the content layer reinforces the drag affordance.\n\nReduced motion: skip the ~140ms detent-settle transition and the undo-bar's burn-down transition entirely — every position change (detent snap, overtravel offset, undo-bar reset) applies with `transition: none`, landing instantly at its target, while remaining fully functional (the undo bar still holds for the same 3 seconds, it just doesn't animate the width).\n\nAccessibility: a dedicated sr-only `role=status aria-live=polite aria-atomic=true` span announces each meaningful transition ('Archive revealed.', 'Flag revealed.', 'Delete armed. Release to confirm.', 'Delete cancelled for {title}.', '{title} deleted.') separately from any button's own label. No dependencies, no gradient backgrounds, no color outside the repo's CSS variables (including the semantic `--error` token for the destructive state, used via its custom property, not a hardcoded hex)."
      }
    },
    {
      "name": "switch-ascii-knife",
      "type": "registry:ui",
      "title": "Switch ASCII Knife",
      "description": "An accessible switch drawn entirely in box-drawing and block characters, in the register of a physical knife switch: the blade fills one cell at a time and the handle glyph spins through the throw.",
      "files": [
        {
          "path": "registry/core/switch-ascii-knife/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/switch-ascii-knife.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "switch",
          "toggle",
          "ascii",
          "mono",
          "accessibility"
        ],
        "instruction": "Build <ThrowSwitch checked? defaultChecked? onCheckedChange? disabled? className? aria-label?> — same controlled/uncontrolled contract as the repo's other switches. STRUCTURE: a single <button role=\"switch\" aria-checked aria-label> containing three font-mono spans in one row: a printed \"OFF\" legend, the bracketed track, a printed \"ON\" legend. Both legends are aria-hidden (the accessible name comes from aria-label) and brighten to text-foreground/font-semibold on whichever side is currently active, dimming to text-muted on the other — no other hue is used to indicate state. TRACK: 6 interior cells wrapped in literal `[` `]` characters. The blade is modeled as thrown from a fixed left-hand pivot: cells with index less than the handle's current position render the heavy box-drawing rule (━, text-foreground) representing blade already thrown across that cell; the cell at the handle's position renders the handle glyph; cells after it render the light rule (─, text-muted) representing untouched rail. OFF rests with the handle at position 0 (no heavy cells, all light); ON rests with the handle at position 5 (heavy cells 0-4, no light). THROW ANIMATION: on a state change, the handle steps one cell at a time toward the target position every 70ms (a plain timestamp-driven requestAnimationFrame loop, not CSS transition) — this is the character-quantised motion the whole ascii suite shares, not a slide. While a step is in flight the handle glyph itself is not the static solid circle; it cycles through a small rotation sequence (◐ ◓ ◑ ◒) driven by progress within the current 70ms beat, and the instant a step lands the glyph snaps back to the solid ● before starting (or finishing) the next step, so the settle reads as a distinct event separate from the spin. ACCESSIBILITY: role=\"switch\" with aria-checked kept in sync, a real <button> so Space/Enter activate it via native semantics with no extra keydown handler, and a visible focus ring built ONLY from focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-accent with no base outline-none on the same element — pairing outline-none with focus-visible:outline-* sets --tw-outline-style to none permanently in Tailwind v4 and the ring never paints even though every class looks correct. prefers-reduced-motion snaps directly to the target position with no rotation frames, and the very first mount never animates regardless of the initial checked value — only a later toggle drives the throw."
      }
    },
    {
      "name": "switch-eclipse",
      "type": "registry:ui",
      "title": "Switch Eclipse",
      "description": "A binary switch drawn as an eclipse — a dark occluding disc slides across a bright sun disc inside a 56x28 track, forming a computed crescent at the midpoint and a thin corona ring once fully on, while the track's ambient tint darkens smoothly with occlusion.",
      "files": [
        {
          "path": "registry/core/switch-eclipse/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/switch-eclipse.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "switch",
          "toggle",
          "theme-switcher",
          "binary",
          "svg",
          "drag",
          "accessibility"
        ],
        "instruction": "Build a binary switch (theme toggle or any on/off value) drawn as an eclipse rather than a sliding-pill iOS-style switch. A 56x28 track (a real <button role=\"switch\">, not a decorative div) contains a small SVG with two circles: a fixed bright 'sun' disc (fill var(--foreground), radius ~8, centered 6px from the left edge) and a dark 'occluder' disc (radius ~9, slightly larger than the sun so it can fully cover it) that slides horizontally across the track between the sun's rest position (off) and the far side (on). The track must clip its own overflow (`overflow-hidden` alongside its `rounded-full`) — the occluder's resting position sits close enough to the track's edge that its own curvature (radius ~9) doesn't match the track's cap curvature (radius = half the track height, ~14); left unclipped, the occluder paints straight through the rounded cap and visibly bevels/notches the pill. This is load-bearing, not cosmetic. The occluder's fill is NOT a hardcoded color — set a CSS custom property (e.g. --umbra-tint) on the track button element itself, computed as `color-mix(in srgb, var(--border) X%, #000 Y%)` where Y grows with the occlusion fraction (0 at off, up to ~55% black-mixed at full on), and have the occluder circle's fill read that tint by default so the crescent shape that appears as the sun peeks out from behind it is pure geometry (two overlapping circles), never a sprite or a mask. Because the occluder is concentric with and larger than the sun, at full occlusion the sun's own fill is never visible again — legibility of the eclipsed disc rests entirely on whatever distinguishes the occluder from the track background around it, so an occluder that matches the tint with zero deviation reads as a hole, not a disc, once the corona's contrast runs out (see below). Above the same ~0.72 fraction threshold the corona fades in on, linearly blend the occluder's fill away from pure tint toward a second custom property, e.g. --umbra-moon = `color-mix(in srgb, tint (100-mix)%, --umbra-bright mix%)` where mix ramps 0% to ~22% by fraction=1; below the threshold mix stays 0% and the occluder is byte-identical to the tint (unchanged resting/mid-drag behavior). --umbra-bright is themed the same way the corona stroke is (below): var(--background) in light theme, var(--foreground) in dark theme — always the 'light' token for the current theme, since the tint always darkens toward black in both themes. The track's own background-color is set to that same base tint, so as occlusion increases the whole track visibly darkens in sync with the disc's motion. Near full occlusion (fraction above ~0.72, ramping linearly to 1 at fraction=1), fade in a corona: a third circle behind the sun, stroked 1px, no fill, with a couple of pixels of CSS blur — restrained, not a big glow. The stroke color is themed rather than a single var(--foreground): var(--background) (white) in light theme and var(--foreground) in dark theme, set via a `.dark` CSS override rather than the bare custom property, because --foreground flips polarity between themes while the occluder's ambient tint always darkens toward black — a foreground-stroked ring is high-contrast in dark theme but low-contrast (dark ring on a mid-gray tint) in light theme if left un-themed; even themed, a 1px blurred stroke alone is not enough headroom in light theme, which is why the occluder fill also needs the --umbra-moon blend above. Drive everything from a single 'occlusion fraction' in [0,1]: on discrete changes (click, keyboard) write the occluder's cx attribute and fill, the track's background-color/custom-properties, and the corona's opacity through refs with a short (~220ms) CSS transition; while actively pointer-dragging, write the same three ref properties on every pointermove with transitions disabled so the disc tracks the pointer 1:1 with zero lag, then on release snap to whichever side the fraction ended up closer to (>=0.5 rounds to on) and re-enable the transition for the settle. Distinguish a plain click from a drag by pointer movement: arm on pointerdown without moving anything, and only start continuously updating the fraction once movement exceeds a small threshold (~3px) — a release with no real movement always just flips the current value outright (ignoring where exactly the click landed), while a release after real dragging snaps based on final position; this keeps click and drag as two coherent, non-conflicting gestures. Full switch semantics: role=switch, aria-checked mirrors the boolean value, an aria-label (or accept one from the caller) since there's no visible <label>, and Space/Enter toggles via a keydown handler. A visible 'Light / Dark' label pair in font-mono flanks the track, with whichever word matches the current state rendered at full --foreground and the other at --muted (never conveying the state through color alone — the switch position and aria-checked both carry it too). Hovering the track does two things: it brightens the track's border from --border to --foreground (a plain CSS hover class — this is the one guaranteed-visible hover cue regardless of on/off state, so hover always differs from default even before the drag/click model is used at all), and if the switch is currently ON it also plays the corona 'breathing' once — a single keyframe animation (opacity 1 -> 0.35 -> 1 over ~900ms, not a repeating loop, restarted by clearing and re-setting the animation property) rather than a continuous pulse. prefers-reduced-motion: the occluder disc teleports (transitions disabled entirely) and the ambient tint applies in one discrete step rather than easing; the corona breathe animation and every CSS transition are also suppressed globally for this component via a scoped @media rule. Support both controlled (`checked`/`onCheckedChange`) and uncontrolled (`defaultChecked`) usage, matching the shape of a native form control. Distinct from switch-frost (frost creep, particles, canvas) — this is pure vector geometry, no canvas, no seeded randomness, no accretion effect. Zero dependencies."
      }
    },
    {
      "name": "switch-frost",
      "type": "registry:ui",
      "title": "Switch Frost",
      "description": "iOS-style switch whose OFF state freezes over: seeded dendritic frost feathers creep in from the track edges over a frosted-glass film, with sparkle grain that glistens at idle; switching ON drives a melt front ahead of the sliding thumb with droplet run-off.",
      "files": [
        {
          "path": "registry/core/switch-frost/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/switch-frost.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "switch",
          "toggle",
          "canvas",
          "particles",
          "micro-interaction",
          "form"
        ],
        "instruction": "Build an iOS-style toggle (real <button role=switch aria-checked>, 52x30 track, 26px thumb, rounded-full) with three overlay layers above the thumb, all clipped to the track: a frosted-glass span (backdrop-filter blur+brightness, translucent white film) and two canvas 2D layers (crystal + sparkle, DPR clamp 2, explicit style width/height since canvas is a replaced element). On switch-to-OFF, mulberry32-seeded dendritic spines creep in from the right/top/bottom track edges (2px steps at 140 px/s, gentle curvature, some fingers stall early), throwing alternating side needles at ~60 deg every 2-3 steps; longer needles fork sub-barbs (depth 0-2, ~700ms total, deterministic per state-change so each freeze traces a fresh pattern). Crystals render in three passes so ice reads on both themes: wide translucent white haze, a cold shade stroke (definition on light surfaces and the thumb), and a bright cold-cast ice body, widths tapering 1.15 to 0.5px by depth, plus a rim-frost stroke around the pill edge; the glass layer fades in with growth so the thumb blurs beneath the ice. The sparkle canvas carries seeded grain dots and four-point glints; idle OFF glisten mutates only that layer's style opacity via a slow sine (no redraw), its period and phase re-seeded (3.4-4.6s, random offset) each freeze cycle so the breathing never locks into an exact metronome; paused offscreen via IntersectionObserver and when the document is hidden; rAF fully sleeps at end states. On switch-to-ON a spatial melt front sweeps left-to-right over 450ms, always a few px ahead of the thumb's leading edge: canvases clip to the frozen side, a white wet-gleam gradient marks the front, the glass layer recedes via clip-path inset, and 3-5 droplets detach when the front passes them (gravity 1200 px/s^2, 400ms lifetime, elongated body + white highlight); the thumb slide starts 120ms into the melt on a spring (k=170 s^-2, zeta=0.85, one small overshoot). All ink derives from --foreground/--background via getComputedStyle at mount (cold-cast toward blue: r*0.93, b*1.06+6) and re-derives on a MutationObserver watching documentElement class changes; theme is detected by background luminance. Accent focus-visible ring, token-relative hover border, disabled renders all layers at 40% opacity with no glisten, prefers-reduced-motion swaps to two static frames (full crystal OFF / clear ON) with a snapping thumb. Guard zero-size tracks before seeding; tear down every observer, listener, and rAF on unmount and free the crystal arrays."
      }
    },
    {
      "name": "switch-solder-bead",
      "type": "registry:ui",
      "title": "Switch Solder Bead",
      "description": "A boolean switch rendered as two liquid solder beads on a hairline rail, trading mass through a gooey neck that bulges, stretches and pinches off on toggle — with a ratio prop for rendering any partial allocation, not just on/off.",
      "files": [
        {
          "path": "registry/core/switch-solder-bead/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/switch-solder-bead.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "switch",
          "toggle",
          "control",
          "svg",
          "gooey",
          "filter",
          "animation",
          "accessibility"
        ],
        "instruction": "Build a boolean switch (role=\"switch\", aria-checked, native <button> so click and Space both toggle for free) that renders as two liquid solder beads on a hairline horizontal rail instead of a sliding thumb, sized to a 64x24 viewBox. Both beads sit at FIXED x anchors (18 and 46 out of 64, y centered at 12) — only their radius (mass) and a connecting neck's thickness ever change; nothing travels along the rail. Radius is derived from a mass fraction via radius = 3.5 + (8.2-3.5) * mass, so a bead ranges roughly 3.5-8.2px and never fully vanishes (real solder doesn't disappear). A `ratio` prop (0-1) is the fraction of total mass in the RIGHT bead; when omitted it defaults to 0.9 when the switch is checked and 0.1 when unchecked, but can be passed explicitly to render any partial split (e.g. 0.35) independent of the boolean switch semantics — the control stays a switch regardless of what ratio is currently drawn. The gooey look comes from an SVG filter applied to a <g> that wraps both bead circles plus a connecting <rect> neck (toc-minimap-mercury's exact recipe: feGaussianBlur stdDeviation ~2.6-4 on SourceGraphic, then a feColorMatrix contrast/alpha threshold matrix `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -9` applied to the blur) — when the neck rect's height (drawn as a capsule spanning between the two bead edges) is nonzero and the beads are close, the blur+threshold naturally fuses everything into one blob; as thickness goes to zero they snap cleanly apart, which reads as bulge/stretch/pinch-off without any manual blob-path geometry. On every toggle (or ratio change), a requestAnimationFrame loop that runs only for the ~450ms transition (not persistently) eases the ratio from its previous value to the new target with easeInOutCubic, and derives the neck's thickness from a sine curve that is 0 for the first ~8% and last ~20% of the transition and peaks at 9px around the midpoint — so the neck appears after a beat, bulges, then pinches off before the transition fully settles. All of this — both bead radii, the neck rect's height/y/rx — is written directly to the SVG elements via refs (setAttribute), never through React state per frame; the ONLY value ever set through JSX is each bead's radius at mount (frozen in a lazy useState initializer) so a later re-render can never snap the shape ahead of the animation and cause a one-frame flash. Hover (or focus-visible) reveals a subtle specular dot inside each bead (a small var(--background)-filled circle offset toward the upper-left, opacity 0 to ~0.5 over a 200ms CSS transition) — the sheen is the one purely decorative, non-hot-path animation and is fine as a CSS transition rather than rAF. A dedicated sr-only span (role=\"status\", aria-live=\"polite\", aria-atomic=\"true\") announces \"On\"/\"Off\" on a boolean toggle, or \"N% allocated\" when a ratio prop is explicitly supplied. prefers-reduced-motion applies the target ratio instantly with no rAF tween and the neck never appears — no bulge, no stretch, just an immediate mass swap. Props: checked/defaultChecked/onCheckedChange (controlled or uncontrolled boolean), ratio (0-1, optional), disabled, aria-label (default \"Toggle allocation\"), className. Zero dependencies."
      }
    },
    {
      "name": "table-heat-shimmer",
      "type": "registry:ui",
      "title": "Table Heat Shimmer",
      "description": "Dense ops table where rows above a heat threshold shimmer like air over asphalt — live DOM text refracts through an animated SVG displacement filter while cold rows sit dead-still behind hairline borders.",
      "files": [
        {
          "path": "registry/core/table-heat-shimmer/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/table-heat-shimmer.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "table",
          "data-viz",
          "svg-filter",
          "displacement",
          "heat-haze",
          "canvas",
          "sortable",
          "dashboard"
        ],
        "instruction": "Dense data table where rows above a heat threshold visibly shimmer like air over asphalt: real content refracts via an animated SVG displacement filter while cold rows sit dead-still with 1px hairline borders — the hot/cold contrast IS the piece; warmth is implied by motion, never by hue (no orange anywhere). RENDERING: semantic DOM <table> (sortable headers with real client-side comparators, selectable checkbox rows) inside a padded rounded-md surface card, plus (a) one inline SVG <filter> per hot-row slot with instance-unique ids (useId): feTurbulence type=fractalNoise baseFrequency='0.008 0.02' numOctaves=2 feeding feDisplacementMap; each hot <tr> gets style.filter=url(#heat-<id>) so its actual text/cells refract; and (b) a pointer-events:none overlay canvas over the tbody (explicit style.width/height, dpr-scaled backing store, clipped to the tbody band) drawing per-hot-row edge haze: two 24px vertical-fade gradient strips off each hot row's top and bottom edges, ink = --accent mixed 20% into --muted at alpha 0.08. MOTION: displacement scale oscillates 1.5→3.0px on a per-row sine (period 2.2s, phase offset 0.4s per sorted row index — resorting re-phases the field); turbulence seed re-randomized every 4s for non-looping shimmer; SVG attribute writes throttled to 30fps inside one shared rAF that walks ONLY the hot-row list and fully clears/redraws the haze canvas each frame (no accumulation). Hover or focus-within on a hot row eases its displacement to 0 over 120ms (legibility snap), back over 400ms on leave, with a forced completion deadline at 500ms so fast hover-scrubs never strand rows mid-wobble. INTERACTION: header sort (sorting by heat re-phases shimmer), checkbox selection with token-relative fill (surface step + 1px foreground/muted mix ring via color-mix), hover raises row background one surface step (bg-foreground/[0.04]). TOKENS: haze ink derived from getComputedStyle(--accent, --muted) at mount, re-derived live via MutationObserver on documentElement class. SLEEP/TEARDOWN: rAF stops entirely when no row exceeds threshold or all hot rows are hover-frozen (scales pinned to 0, canvas cleared); IntersectionObserver pauses offscreen; ResizeObserver with zero-size tbody guard; all listeners/observers/rAF torn down on unmount. REDUCED MOTION: no filter, no canvas — hot rows get a static 2px --accent left rule and slightly elevated surface fill. DEMO: dashboard card 'Server load — last 24h' with mono timestamp, 10 believable ops rows (service, region, req/s, p95 ms, heat score), 3 hot at default threshold 70, footer row count + pagination stub, demo-level threshold segmented control."
      }
    },
    {
      "name": "tabs-carriage",
      "type": "registry:ui",
      "title": "Tabs Carriage",
      "description": "Tabs whose underline is a typewriter carriage on a rail — forward moves glide on a spring and stretch with speed, backward moves snap home faster with a ding-bounce, and the panel line-feeds in from the travel direction.",
      "files": [
        {
          "path": "registry/core/tabs-carriage/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tabs-carriage.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tabs",
          "navigation",
          "spring",
          "physics",
          "indicator",
          "keyboard"
        ],
        "instruction": "Build a tab component whose active-tab underline behaves like a typewriter carriage riding the tablist's bottom border rail. The indicator's x-position and width are each driven by an underdamped spring (k = 380 s^-2, zeta = 0.72) toward the active tab's offsetLeft/offsetWidth, stepped in a single direct-DOM rAF loop with zero React state on the hot path; the loop sleeps when position, width, and velocity are all under epsilon and wakes on retarget. While traveling, the carriage stretches horizontally in proportion to |velocity| (scaleX up to 1.45, transform-origin center) so speed is legible, and the squash on arrival falls out of the spring's overshoot rather than being keyframed. Direction matters: a move to a LOWER index is a carriage RETURN — the spring gets an extra leftward velocity kick proportional to the travel distance plus a small downward y impulse on its own stiffer spring (k = 900, zeta = 0.5), producing the typewriter ding dip-and-recover on arrival; rightward moves stay calm. The tab panel line-feeds on every change: content enters with a 220 ms translateX from the travel direction using an ease-out-expo Web Animations call. First paint and any ResizeObserver-detected layout change seat the carriage instantly with velocities zeroed — never animate a resize. Full tablist semantics: role=tablist/tab/tabpanel, aria-selected, aria-controls/labelledby via useId, roving tabindex with automatic activation on ArrowLeft/ArrowRight (wrapping) and Home/End, the panel itself focusable, selected tab in foreground weight-medium against muted siblings, token-relative accent focus-visible rings only. Under prefers-reduced-motion the carriage repositions instantly with no stretch, kick, or panel slide. Colors come only from theme tokens (the carriage is bg-foreground on the border rail)."
      }
    },
    {
      "name": "tabs-notch-tenon",
      "type": "registry:ui",
      "title": "Tabs Notch Tenon",
      "description": "Tabs whose bottom rule is a single SVG path with a notch missing under the active tab, and whose panel carries a matching raised tenon that slots into that gap — the strip and panel read as one interrupted border, legible at rest with zero motion.",
      "files": [
        {
          "path": "registry/core/tabs-notch-tenon/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tabs-notch-tenon.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tabs",
          "navigation",
          "svg",
          "joinery",
          "spring",
          "physics",
          "keyboard",
          "accessibility"
        ],
        "instruction": "A tabs component built around a physical joinery metaphor rather than a floating indicator. The tab strip's bottom border is rendered as a single <path> element (two M/L subpaths sharing one stroke, so it reads as one interrupted line, not two separate borders): a continuous stroke from the left edge to the active tab's left edge, a gap for the width of the active tab, then a continuous stroke to the right edge. The content panel directly below carries a 'tenon' — a 6px-radius rounded rectangle, stroked in --border and filled --background, positioned so it pokes 6px above the panel's top edge (into the gap left by the notch) and overlaps 1px below the boundary into the panel, fusing visually with the missing segment of the rule. Because the notch and tenon share the same x-position and width at all times, which panel belongs to which tab is physically legible from the joint alone with the component fully at rest — no hover or motion required to disambiguate, unlike a floating underline/pill indicator that only reads correctly once you already know which label is 'selected'. On tab switch, both the notch and tenon's x-position and width chase the newly active tab's measured getBoundingClientRect() on an underdamped spring (stiffness k=300 s^-2, damping c=26 s^-1 — zeta ≈ 0.75, so arrival carries a small overshoot that reads as the joint 'tapping home'), stepped in a single direct-DOM rAF loop with zero React state on the hot path; a ResizeObserver re-measures on layout change and the spring re-targets without a jump. Simultaneously the tab panel's content translates ±16px in the direction of index travel (rightward for a higher index, leftward for lower) with an ease-out-expo Web Animations call (~220ms, cubic-bezier(0.16,1,0.3,1)) while fading in, and the just-vacated panel is rendered as an aria-hidden, non-interactive overlay that cross-fades out on top of it over ~120ms before being removed — so old and new content genuinely overlap during the transition rather than one instantly replacing the other. Direction of slide always matches the sign of the tab-index delta, preserving spatial order regardless of how far apart the two tabs are. Full WAI-ARIA tabs semantics: role=tablist/tab/tabpanel, aria-selected, aria-controls/aria-labelledby via useId, roving tabindex with automatic activation on ArrowLeft/ArrowRight (wrapping) and Home/End, the visible tabpanel itself focusable. The notch/tenon SVG and DOM nodes are aria-hidden — selection is carried entirely by aria-selected plus the selected label's weight/color change (foreground vs muted), never by the decorative geometry. Under prefers-reduced-motion the notch and tenon snap straight to the target rect (spring disabled) and the panel transition drops its translate in favor of a plain ~120ms opacity cross-fade. Distinct from every underline/pill-indicator tab pattern (shadcn, Radix, and this registry's own tabs-carriage, whose carriage is a floating bar riding the border) because the border itself is the indicator — strip and panel read as two halves of one physical object, not a label with a bar drawn under it — and distinct from streaming-markdown-caret, which is an unrelated streaming-text caret, not a tab component. Colors are strictly token-relative (--border for strokes, --background for fills, --foreground/--muted for label state, --accent for focus rings only); no canvas, zero runtime dependencies."
      }
    },
    {
      "name": "tabs-rail-points",
      "type": "registry:ui",
      "title": "Tabs Rail Points",
      "description": "Tabs whose active indicator is one continuous SVG rail running under the entire row like railway track — selecting a tab throws the points, bending a raised siding segment off the base line so it travels to the new tab instead of a puck jumping between slots.",
      "files": [
        {
          "path": "registry/core/tabs-rail-points/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tabs-rail-points.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tabs",
          "navigation",
          "svg",
          "rail",
          "keyboard",
          "accessibility"
        ],
        "instruction": "A tabs component whose selection indicator is never a discrete object — it is one continuous rail. A thin polyline (1px, --border) spans the full width of the tab row at a fixed baseline and never moves; it is the track bed, always present regardless of selection. A second polyline (2px, --foreground, round joins/caps) rides directly on that baseline everywhere except under the active tab, where it is locally lifted into a flat plateau — a short diagonal ramp (10px horizontal run) rises off the baseline, holds flat under the tab's full measured width, then ramps back down to rejoin the baseline on the far side. Both polylines are measured against the tab row's real DOM geometry (getBoundingClientRect of each tab button relative to the row's positioned wrapper), so tabs of any width work. On selection change the four x-coordinates that describe ramp-in/plateau-start/plateau-end/ramp-out tween from their old values to the new tab's values in a single direct-DOM rAF loop (zero React state on the hot path) using an ease-out-expo curve (1 - 2^(-10t)) over 350ms — 'throwing the points' — so the bend is visibly travelling geometry, not a value snapping between two states. Simultaneously the tab panel does a 12px lateral slide-fade in the same direction as the index delta (translateX 12px→0 with an opacity fade, ease-out-expo Web Animations call, ~220ms), so content motion and rail motion agree on which way the selection moved. First paint and any ResizeObserver-detected layout change (a real resize, not a selection) reseat both polylines' coordinates instantly with the tween skipped entirely — a resize must never be mistaken for a throw. Full WAI-ARIA tabs semantics: role=tablist/tab/tabpanel, aria-selected, aria-controls/aria-labelledby via useId, roving tabindex with automatic activation on ArrowLeft/ArrowRight (wrapping) and Home/End, the tabpanel itself focusable, selected tab in foreground weight-medium against muted siblings. The rail SVG is aria-hidden decoration in full — selection state lives entirely in aria-selected, never in the geometry. Under prefers-reduced-motion the rail's coordinates jump straight to the new tab's target with no tween, and the panel drops its translate for a plain ~150ms opacity crossfade. Distinct from segmented-control-fling (a segmented control whose selection pill is a discrete, physically-flung, draggable object that coasts and rubber-bands between slots) and from this registry's other SVG-based tabs (tabs-notch-tenon, whose indicator is a gap cut into the border with a matching tenon on the panel, and tabs-carriage, whose indicator is a floating bar riding the border on an underdamped spring): here there is no gap, no floating bar, and no puck — one line runs the whole row at all times and only its local shape changes, so the bend itself is the only thing that travels. Colors are strictly token-relative (--border for the base rail, --foreground for the siding, --accent for focus rings only); no canvas, DOM+SVG+CSS only, zero runtime dependencies."
      }
    },
    {
      "name": "tabs-slack-cable",
      "type": "registry:ui",
      "title": "Tabs Slack Cable",
      "description": "Classic tabs whose active-indicator is a single SVG path drawn as a slack cable instead of a rigid bar: it sags mid-jump proportional to distance, tautens flat with a small spring snap, and previews the destination with a 3px tug when you hover a neighboring tab.",
      "files": [
        {
          "path": "registry/core/tabs-slack-cable/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tabs-slack-cable.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tabs",
          "navigation",
          "svg",
          "spring",
          "physics",
          "accessibility"
        ],
        "instruction": "A full WAI-ARIA tabs widget (role=tablist/tab/tabpanel, roving tabindex, automatic activation on ArrowLeft/ArrowRight/Home/End) whose selection indicator is one aria-hidden SVG <path> under the tab row instead of a CSS-transitioned bar. At rest the path is a flat 2px --foreground stroke spanning exactly the active tab's measured left/right edges. On selection change both endpoints are independently driven by a damped spring (stiffness 220 s^-2, damping 26.4, integrated semi-implicit-Euler on a single rAF loop with zero React state on the hot path) toward the new tab's edges; the spring's effective mass is 1 + travelDistance/260, where travelDistance is the old-to-new active-tab center jump measured once at transition start — so a short hop between adjacent tabs stays near-critically damped while a long jump across the row accelerates and settles more slowly and with a touch more overshoot, reading as heavier. Each frame the path is rebuilt as a quadratic Bezier: the control point's y drops below the flat baseline by min(travelDistance/8, 6)px scaled by how much of the total travel the slower endpoint still has left (1 at the instant the jump starts, 0 once both endpoints arrive), so the cable visibly pays out and sags mid-flight and flattens back to a straight line exactly as it arrives, with the spring's own slight overshoot read as the small snap into place. Hovering a non-active tab (pointerenter/pointerleave) nudges whichever endpoint sits spatially nearest that tab 3px in its direction, eased in with an exponential ease-toward (tau 90ms, the ease-out-expo family) and eased back to 0 on pointerleave — a preview of where the cable would travel, never committing selection. A ResizeObserver keeps the SVG's viewBox locked 1:1 to the tablist's pixel width and re-snaps the rail instantly (no animation) on layout change, e.g. font load or container resize. Tab labels are --muted at rest, --foreground with a hover transition when active or hovered; focus uses focus-visible:outline-accent, never paired with a base outline-none. Panels are real DOM nodes linked by aria-controls/aria-labelledby and kept mounted with the hidden attribute so ids always resolve. prefers-reduced-motion collapses the whole spring/sag mechanism: a selection change snaps both endpoints straight to the destination with zero sag, the hover tug is disabled outright, and the path instead plays a 120ms opacity crossfade so the reposition still reads as a change rather than a silent jump. Pure DOM + SVG + CSS — no canvas anywhere."
      }
    },
    {
      "name": "tag-input-backspace",
      "type": "registry:ui",
      "title": "Tag Input Backspace",
      "description": "Tag input where Backspace on an empty field arms the last tag with a depleting bar instead of deleting it — a second Backspace inside the window removes it.",
      "files": [
        {
          "path": "registry/core/tag-input-backspace/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tag-input-backspace.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "input",
          "tags",
          "form",
          "keyboard",
          "undo",
          "micro-interaction"
        ],
        "instruction": "A tag / chip multi-input — a bordered field of rounded pills each with an 18px circular remove button, a borderless flex-1 text input, and a muted hint line below that doubles as the rejection message. Its one deviation from every other tag field is what Backspace does to an empty input: it does not delete the last tag, it arms it. The armed pill takes an accent border and a 2px accent bar inside it depletes left-to-right over exactly armDuration (default 2000ms); a second Backspace inside that window removes it, and any other keystroke, any pointer press anywhere, a blur, or the bar running out disarms. That removes the classic accident of destroying three tags because you over-backspaced while typing fast, and the depleting bar makes the two-step rule discoverable rather than mysterious. The armed state is disarmed from four independent directions — the timer, the input's key/change/blur handlers, a capture-phase document pointerdown listener that exists only while something is armed, and a guard that drops the armed id the moment it stops matching a tag actually present at that position — because the reach-for-the-mouse case shifts the tag list under a pending timer and would otherwise strand a pill wearing the accent ring forever. Controlled or uncontrolled, resolved once from whether `value` is passed and never mirrored into state. Commit keys default to Enter and comma; entries pass through an optional validate that returns a normalized string or null to reject, with duplicates and a max cap rejected the same way and announced. Full keyboard model: clicking the field focuses the input, ArrowLeft from an empty input jumps to the last tag, remove buttons use roving tabindex with ArrowLeft/ArrowRight/Home/End, Enter, Space, Delete or Backspace on a focused tag removes it immediately (explicit focus is explicit intent, so no arming there), Escape returns to the input, and a visually-hidden polite live region announces additions, removals and the arming prompt. Hover is tracked in React rather than :hover so synthetic pointer events reach it. Zero dependencies, one useRef timer cleared on unmount, colors from --background, --foreground, --surface, --border, --muted and --accent only, so both themes read correctly. prefers-reduced-motion drops the tag entrance and the caret blink and freezes the depletion bar at full width, leaving the armed state unmistakable without motion while the live region carries the timing."
      }
    },
    {
      "name": "tag-input-cord",
      "type": "registry:ui",
      "title": "Tag Input Cord",
      "description": "Tag input as beads threaded onto a cord: committing a tag cinches a bead into place with one overshoot squeeze while its knot draws on; Backspace on an empty input unravels the last bead with a drop-and-twist. Duplicate commits shudder the existing bead instead of re-threading it.",
      "files": [
        {
          "path": "registry/core/tag-input-cord/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tag-input-cord.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tag-input",
          "chips",
          "input",
          "form",
          "svg",
          "micro-interaction"
        ],
        "instruction": "Build a tag input whose chips read as beads on a wire. STRUCTURE: a click-to-focus bg-surface field (cursor-text, focus-within border shift to muted) laid out as a wrapping flex row: a short cord stub (1px-tall bg-border span), then per tag a pill chip followed by a 12px cord segment, then the flex-1 text input — the h-px segments between pills are what makes the row read as one threaded cord, and they wrap naturally with the chips. Each chip is a rounded-full bg-background bordered pill holding a 10px SVG knot (circle, stroke var(--muted), pathLength 1), the truncated tag text, and a 16px remove button (inline SVG x, aria-label 'Remove {tag}', hover bg-border, focus-visible accent outline). MOTION, all CSS keyframes scoped in a <style> tag: entry 'cinch-in' slides the chip from translateX(18px) scaleX(1.1) through a -2px/0.97 overshoot at 62% into rest (280ms ease-out-back cubic-bezier(0.22,1,0.36,1), fill backwards) while the knot circle draws on via stroke-dashoffset 1->0 delayed 120ms; exit 'cinch-unravel' drops translateY(12px) rotate(8deg) to opacity 0 over 240ms ease-in (fill forwards) — the tag is parked in a 'leaving' list and removed by a setTimeout matching the animation, all timers tracked in a Set and cleared on unmount; duplicate commit fires 'cinch-shudder', a +-3px decaying x-shake on the EXISTING bead, instead of adding. INTERACTION: Enter or comma commits (preventDefault so the comma never lands in the value), trims, ignores empty; Backspace on an empty input unravels the newest non-leaving bead; each remove button unravels its own. onChange fires with the new tag array on every add/remove; a visually-hidden aria-live span announces the count. maxTags caps additions silently. Reduced motion: a media query kills all four animations and the leaving-park executes synchronously. INK: tokens only (background, surface, border, muted, foreground, accent for focus rings); no canvas, no observers."
      }
    },
    {
      "name": "tag-input-pull",
      "type": "registry:ui",
      "title": "Tag Input Pull",
      "description": "A tag/token input whose chips attach like burrs — hooked, not glued. Removing one takes a deliberate pull: it stretches at the trailing edge, pops free, and the row exhales closed behind it with a staggered ripple toward the caret.",
      "files": [
        {
          "path": "registry/core/tag-input-pull/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tag-input-pull.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tag-input",
          "token-input",
          "chip",
          "listbox",
          "form",
          "removal",
          "drag",
          "micro-interaction",
          "aria-live"
        ],
        "instruction": "A tag/token input (real DOM chips in a flex-wrap row, role=group over the whole cluster) whose removal mechanic is the whole point: chips are hooked, not glued, so taking one out costs a deliberate, visible pull rather than a flat delete. Three ways to remove a chip all funnel through the same two-phase animation: clicking its × button, focusing the chip's × button and pressing Backspace/Delete, or pointer-dragging the chip body itself past a 64px threshold. Phase one, stretch: the chip's trailing (right) edge is fixed as the transform-origin and it scaleX-stretches to 1.15 over 90ms (an eased-out curve, not a spring) — the hook visibly holding under load. Phase two, detach: the chip translates 14px and fades to opacity 0 over 140ms on a fast ease-in curve, reading as a quick pop-free rather than a fade-out. Only after both phases finish does the tag actually leave the array (state update + aria-live announcement), so the two phases are the felt cost of deletion, not cosmetic filler in front of an instant removal. Once the DOM updates, every surviving chip that shifted to close the gap is caught with FLIP (bounding rects captured before the removal, inverse transform applied instantly after, then eased to zero) on a 220ms ease-out-expo curve, with each chip's transition delayed 40ms per position past the vacated slot — the close reads as a ripple travelling from the gap toward the text input's caret, not a snap-together. Pointer-drag removal maps |dx| directly onto the same 0→1.15 stretch in real time (no easing while dragging, 1:1 tracking) anchored at the same trailing edge regardless of drag direction; releasing past the 64px threshold continues straight into the detach phase from whatever stretch the drag already reached, releasing under threshold eases the chip back to scaleX 1 over 140ms on a single curve — elastic, deliberately no spring overshoot or oscillation either way. Accessibility: chips are a roving-tabindex group — real DOM focus moves between each chip's × button (a genuine <button aria-label=\"Remove {tag}\">, never a synthetic listbox option) and the text input at either end via Left/Right (Home/End jump to the first/last chip); Backspace/Delete on a focused chip removes it and moves focus onto whichever chip took its slot, or back to the input if the list emptied; Backspace in an empty input is also a shorthand for removing the last chip, and Enter or comma commits the current input text as a new tag. Every removal — from any of the three trigger paths — is announced through a visually-hidden aria-live=\"polite\" status region as \"Removed tag {name}, {n} remaining\". prefers-reduced-motion drops straight to the end state: the tag list updates immediately with no stretch, no detach, no ripple, and no per-frame drag scaling, but the announcement and focus-management behavior are unchanged. Controlled/uncontrolled via value/defaultValue/onChange (string arrays; duplicate values are ignored on add). disabled greys the whole control and blocks add/remove/drag. Zero dependencies, pure DOM + CSS transforms — no canvas."
      }
    },
    {
      "name": "tag-input-tear",
      "type": "registry:ui",
      "title": "Tag Input Tear",
      "description": "Tag input whose chips hang from a perforated edge instead of an x icon: dragging a chip down snaps its dashes into torn stubs top-to-bottom as a 0-1 tear progress crosses each one, and past 80% releasing drops the chip with a 4deg rotate and gravity while siblings close the gap via FLIP; releasing early springs everything back and re-knits the perforation in reverse. Delete/Backspace on a focused chip removes it instantly through a plain 150ms fade, no tear theater, and Ctrl+Z undoes any removal.",
      "files": [
        {
          "path": "registry/core/tag-input-tear/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tag-input-tear.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tag-input",
          "chips",
          "input",
          "form",
          "svg",
          "drag",
          "listbox",
          "micro-interaction"
        ],
        "instruction": "Build a tag/chip input where every chip is attached by a perforated left edge instead of carrying a delete icon. STRUCTURE: a click-to-focus bg-surface field (cursor-text, focus-within border shift to muted) laid out as a wrapping flex row containing a <ul role=listbox aria-label={label}> of chips (className=contents so its <li> children share the outer flex row) followed by the free-text <input>. Each chip <li role=option> is a fixed h-7 (28px) pill: border on top/right/bottom only (border-l-0, the perforation stands in for the left edge), rounded-md, bg-background, holding an inline SVG strip (viewBox in real px so stroke width never scales) of 7 short horizontal dashes stroke var(--border) evenly spaced down the chip, then a spacer whose width grows with drag progress, then the truncated tag text (max 16ch). DRAG-TO-TEAR (pointer sugar, not a required flow): pointerdown on a chip captures the pointer and focuses it; pointermove maps clamped downward-only dy over a 64px throw to a 0-1 tear progress, mirrored through a ref for the hot path. Each dash's broken state is a pure function of progress (progress >= (index+1)/7) so re-render alone flips it from one intact line to two 1px-offset stubs — no separate reverse-animation path needed for re-knitting. The instant a dash's broken state flips (either direction) it gets a brief 1px vertical translate jitter (a short CSS keyframe class applied only to that dash for ~120ms). While held, the chip translates down and rotates up to 2deg proportional to progress (visual tension); once progress crosses 0.8 the dash stroke color swaps border->accent as an 'armed' cue. On release: progress >= 0.8 commits — the real chip is removed from state immediately (so siblings FLIP-close the gap) while a short-lived fixed-position ghost clone (same rect, captured via getBoundingClientRect before removal) plays a 340ms ease-in rotate-to-4deg + translateY(48px) + fade-out fall, then unmounts; progress < 0.8 (or pointercancel) runs an underdamped spring back to progress 0 (rAF integrator, ~260 s^-2 stiffness, zeta 0.5), which re-knits the dashes in reverse via the same pure-function render, then focus returns to a neighbor chip or the input. FLIP: any tags-array mutation (either removal path, add, undo) first snapshots every surviving chip's getBoundingClientRect via a ref map; a useEffect keyed on the tags array then offsets each surviving chip from its old position back to zero on a spring-approximating cubic-bezier(0.34,1.56,0.64,1) over 380ms, skipped entirely under reduced motion (chips just snap). KEYBOARD (the only required path, drag adds nothing to it): chips carry roving tabindex (one tabIndex=0 at a time, defaulting to the first chip) so Tab reaches the list; ArrowLeft/ArrowRight/Home/End move focus among chips (ArrowRight off the last chip moves into the text input); Delete or Backspace on a focused chip removes it via a fast 150ms opacity+scale fade (no rotate, no perforation theater) and moves focus to a neighbor or the input. Enter or comma in the text input commits the draft as a new tag (trimmed, ignored if empty or a duplicate). A visually-hidden aria-live=polite region announces 'removed {tag}, {n} tags remain' on every removal and 'restored {tag}' on undo. Ctrl+Z / Cmd+Z (checked at the root on keydown) pops a small undo stack of {tag, removalIndex} entries and re-inserts the tag at its original index, reusing the FLIP path so its neighbors slide over for it; new/restored chips play a 180ms fade+rise-in entrance. Reduced motion: the jitter, the fall-ghost, the FLIP replay and the release spring are all skipped; a drag that crosses 0.8 and releases routes straight through the same fast-fade removal keyboard deletion uses, so every removal is either instant-with-fade or nothing. INK: tokens only (background, surface, border, muted, foreground, accent for the armed-dash cue and focus rings); pure DOM + inline SVG + CSS, no canvas, no dependencies."
      }
    },
    {
      "name": "testimonial-wall-reflow",
      "type": "registry:ui",
      "title": "Testimonial Wall Reflow",
      "description": "A masonry testimonial wall that physically reflows as you read: expanding one card's quote via 'read more' re-packs every other card into its new shortest-column slot, animated with a FLIP transform so only the cards that actually move ever animate.",
      "files": [
        {
          "path": "registry/core/testimonial-wall-reflow/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/testimonial-wall-reflow.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "testimonial",
          "wall",
          "masonry",
          "layout",
          "flip",
          "reflow"
        ],
        "instruction": "Testimonials are packed into a shortest-column-first masonry (own arithmetic, not CSS `columns`, which flows top-to-bottom per column and can't be re-packed on demand): column count is derived from measured container width (1 below 560px, 2 below 900px, else 3), and for each item in order the next slot goes to whichever column currently has the least accumulated height. Each card is an absolutely positioned <figure> so its own height never affects siblings through normal document flow — position is entirely computed by the packing pass and written as inline left/top. Every card longer than ~150 characters gets a 'read more' button (aria-expanded, an accessible name naming both the action and the author); toggling it swaps the rendered excerpt for the full quote, which changes that one card's own height. THE REFLOW: a `useLayoutEffect` keyed on the expand/collapse state re-runs the packing pass with the new heights (read via `offsetHeight` after the content swap has already committed, so this always reads real post-toggle heights) and, for every card whose computed slot actually changed from the last pass, applies a FLIP transform: `before` is not read from a live `getBoundingClientRect` — it's the position this same packing function produced on the PREVIOUS pass, cached in a ref — the delta is applied as an instant untransitioned `translate`, then forced-reflowed and eased back to identity over 420ms (cubic-bezier(0.22,1,0.36,1)). Cards whose slot didn't move are left completely untouched, so a wall with one changed card only animates the ones that needed to. A `ResizeObserver` re-packs (unanimated — a viewport resize isn't 'reading') whenever the container width crosses a column-count breakpoint. `prefers-reduced-motion` still repacks and repositions correctly but skips the transform/transition entirely, snapping straight to the new layout. Each card exposes `data-expanded` for anyone probing its open state. Props: `items` (id/name/role/quote array, six built-in examples if omitted), className. Zero dependencies — refs, inline styles and CSS transitions only, matching this registry's direct-DOM-write convention for hot-path visual state."
      }
    },
    {
      "name": "text-card-flick",
      "type": "registry:ui",
      "title": "Text Card Flick",
      "description": "A hover or click flourish where every letter is an index card on a spindle — the face flicks back over its top edge and out of view while a duplicate flicks up from underneath into its place, staggered across the word from a configurable origin on a numerically integrated spring.",
      "files": [
        {
          "path": "registry/core/text-card-flick/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/text-card-flick.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "text",
          "hover",
          "spring",
          "micro-interaction",
          "typography"
        ],
        "instruction": "Build an inline text component that splits `text` into one wrapper span per character, each holding two stacked layers inside a small perspective (480px) container: a 'front' layer showing the letter normally, and an absolutely positioned 'echo' layer holding the same letter, pre-rotated rotateX(80deg) with translateY(6px), scale(0.85), blur(4px) and opacity 0 — tucked out of sight below. On trigger (pointerenter by default, or click when `trigger='click'`), every letter's front layer animates to rotateX(-100deg) translateY(-6px) opacity 0 blur(4px) while its echo layer simultaneously animates to rotateX(0) translateY(0) scale(1) blur(0) opacity 1, so the face flicks away over its top edge exactly as the duplicate flicks up into the vacated spot. The motion is NOT a cubic-bezier approximation of a spring: on each trigger the component numerically integrates a damped harmonic oscillator (x'' = -(stiffness/mass)(x-1) - (damping/mass)x', mass=1, dt=1/240s) from rest until it settles within tolerance, resamples the trajectory to a fixed 26-point keyframe list, and feeds those literal positions into two native Element.animate() calls (front and echo) with linear easing between keyframes — the physics lives in the keyframe values, not in the interpolation curve, so changing stiffness/damping changes the actual overshoot shape rather than just its timing. Each letter's delay is `stagger` seconds (default 0.035) times its distance from the stagger origin: `from='start'` counts left to right, `from='end'` right to left, `from='center'` fans out symmetrically from the middle letter. Once every letter's pair of animations finishes, both layers are snapped back to their rest state with a zero-duration `fill: forwards` animate() call so the component is idle and ready to re-trigger; a fresh trigger while one is still mid-flight is ignored until the whole staggered sequence (including its tail letter's delay) has elapsed, then `onSettle` fires. The whole word carries `role=\"text\"` and `aria-label={text}` on the outer span so assistive tech reads the real string once; every individual letter layer is `aria-hidden`. Props: text, className, letterClassName, trigger ('hover' | 'click'), stagger, from ('start' | 'end' | 'center'), stiffness, damping, onSettle. Display-only decoration — no controls are rendered, so it is exempt from keyboard-reachability, matching how a hover-triggered headline flourish is used elsewhere in this registry. prefers-reduced-motion (checked at mount and re-checked on change) skips the spring entirely on trigger: the letters stay put and `onSettle` fires immediately, rather than merely slowing the flip down. Zero dependencies."
      }
    },
    {
      "name": "text-decrypt",
      "type": "registry:ui",
      "title": "Text Decrypt",
      "description": "Scramble-to-decode text reveal with left-to-right character locking.",
      "files": [
        {
          "path": "registry/core/text-decrypt/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/text-decrypt.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "text",
          "reveal",
          "mono",
          "micro-interaction"
        ],
        "instruction": "A scramble-to-decode text reveal: monospace glyphs churn randomly and lock into the final string left to right with a per-character stagger, the currently resolving character brightened, unresolved characters dimmed. No layout jitter (mono charset), final text exposed via aria-label with churning spans aria-hidden, instant render under prefers-reduced-motion."
      }
    },
    {
      "name": "text-ekg-baseline",
      "type": "registry:ui",
      "title": "Text EKG Baseline",
      "description": "Text whose baseline is a live EKG trace: each beat fires a QRS spike that travels under the letters, throwing every glyph up the waveform before it spring-settles flat.",
      "files": [
        {
          "path": "registry/core/text-ekg-baseline/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/text-ekg-baseline.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "text",
          "ekg",
          "waveform",
          "svg",
          "spring",
          "beat",
          "raf",
          "imperative"
        ],
        "instruction": "A text component whose baseline is an EKG trace. Render one inline-block DOM span per glyph (measured once via offsetLeft/offsetWidth, remeasured on ResizeObserver and fonts.ready; baseline found with a zero-height inline-block marker span whose offsetTop is the baseline) plus one absolutely-positioned SVG holding a polyline trace and a sweep dot, all written by a single direct-DOM rAF loop with no React state on the hot path. The trace is a 1.5px #8f8f8f flatline at the text baseline; a 4px #ffffff sweep dot rides it at 320 px/s, wrapping at the right edge. Each beat injects a piecewise QRS-T kernel at the left edge — half-sine segments: Q dip +6px/40ms, R spike -46px/70ms, S dip +10px/50ms, T bump -8px/180ms — propagating right at 900 px/s as wave(x,t) = kernel(t - t0 - x/900), summed over active beats. While the kernel passes under a glyph's center the glyph is position-driven: translateY = wave(centerX) and rotation = atan(local slope, sampled ±4px) clamped ±9°, with velocity tracked so the handoff is continuous. Once the wave passes, the glyph rings down on an underdamped spring (k=140 s^-2, zeta=0.45 — two visible wobbles, then flat) while rotation decays exponentially. Polyline points sampled every 6px are rewritten in the same frame, and the dot's cy follows the wave under its x. Beats fire from a bpm prop timer (default 50, resting-clinical pace, each interval jittered +/-8% for a natural sinus-arrhythmia feel rather than a metronomic tick; timer lives in a separate effect so rate changes never reset glyph state) or imperatively via a ref handle beat() for real events (deploys, messages). The loop sleeps once every wave has exited the right edge and all glyph residuals are < 0.1px, waking on the timer or beat(). Under prefers-reduced-motion: static flatline plus typeset text, no dot, no timers, no listeners. Zero dependencies."
      }
    },
    {
      "name": "text-ligature-melt",
      "type": "registry:ui",
      "title": "Text Ligature Melt",
      "description": "Headline whose glyphs liquefy near the cursor — an SVG gooey filter fuses neighbors into temporary ligatures that spring apart on leave, with a faint field drifting the melt on its own at rest.",
      "files": [
        {
          "path": "registry/core/text-ligature-melt/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/text-ligature-melt.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "text",
          "cursor",
          "svg-filter",
          "goo",
          "micro-interaction"
        ],
        "instruction": "A headline where characters near the cursor liquefy into temporary ligatures. One span per glyph inside a container carrying CSS filter: url(#goo); the inline SVG filter is feGaussianBlur into an feColorMatrix with alpha row 0 0 0 19 -9 (blur + threshold), so overlapping glyph edges fuse into metaball necks. Text stays real DOM — selectable and SEO-safe, the filter is visual only. A Gaussian field (sigma 70px) around a field center scales affected glyphs to 1 + swell*g and translates them toward the field by up to `pull` px so edges overlap and goo; total line width stays constant by redistributing the induced width surplus as compression across out-of-field spans each frame with line start/end pinned. At rest — no pointer involved — a synthetic field sweeps back and forth across the line on its own at half the hover amplitude, so the melt is the default look, not a hover-only trick; a real pointer takes over the field immediately. The feGaussianBlur stdDeviation itself ramps off the same field's peak value (near-zero away from any activity, up to the `blur` prop at the field's peak) instead of sitting at a constant blobby value, so idle text reads crisp except where it's actually melting. Per-span state (scale x/y, dx) lives in plain arrays written as transforms by a direct-DOM rAF loop — no React state on the hot path. Approach eases via lerp (~0.15/frame on hover, ~0.035/frame ambient); on pointer leave (including pointerup/pointercancel) every span snaps back on an underdamped spring (k = 170 s^-2, zeta = 0.55) with visible surface-tension overshoot as the ligatures pinch apart, then ambient drift resumes. The root carries role=\"button\" and tabIndex=0 so the mechanic has a keyboard/focus equivalent too: tabbing in melts the line centered on itself (same field math as a parked cursor) with a token-colored focus-visible ring, and blur releases it on the same spring. The loop never fully sleeps — since the ambient field never settles, an IntersectionObserver pauses it offscreen instead. Spans are measured once via offsetLeft/offsetWidth and remeasured on ResizeObserver. Under prefers-reduced-motion the filter is removed and the text renders static and crisp with no listeners, no ambient loop. Zero dependencies."
      }
    },
    {
      "name": "text-slot-rotate",
      "type": "registry:ui",
      "title": "Text Slot Rotate",
      "description": "A rotating-word slot: each character column spins through a slot-machine reel of glyphs before landing on the next word, with the slot's own pixel width carried between words of different lengths so the layout never jump-cuts.",
      "files": [
        {
          "path": "registry/core/text-slot-rotate/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/text-slot-rotate.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "text",
          "slot-machine",
          "reel",
          "mono",
          "rotate",
          "headline",
          "micro-interaction"
        ],
        "instruction": "A rotating-word slot driven by a `words: string[]` prop, cycling automatically to the next word every `interval` ms (default 2400) and looping. The mechanic is a continuous SLOT-MACHINE REEL per character column, distinct from this registry's other character-swap components: split-flap-board hinges one fixed-width card 180deg on a change, and counter-carry-ripple diffs a number by place value with a 2-state vertical flip. Here, each column is an overflow-hidden cell containing a vertical strip of `reelSteps` (default 5) random glyphs from `charset` followed by the target character; a CSS transform transition (`translateY`, `cubic-bezier(0.2,0.85,0.1,1)`, ~90ms per step) scrolls the whole strip downward in one continuous motion so it decelerates onto the landing glyph like a real slot reel, never a discrete 2-state flip. Columns are staggered 45ms apart left to right so the reel-stop visibly ripples across the word. The slot's own pixel width (measured per-character from a hidden monospace probe span x the incoming word's length) eases via a `width` transition (320ms, `cubic-bezier(0.34,1.4,0.64,1)`) toward the new word's width at the same time the reels spin — the width-carry that keeps a shorter or longer incoming word from jump-cutting the layout. All of this is direct-DOM: reel strips are rebuilt and their `style.transform`/`style.transition` set imperatively via refs on each rotation; only the fully-settled word lives in React state. Two real, keyboard-reachable `<button aria-label=\"Previous/Next word\">` controls (‹ ›) let a visitor step the rotation manually at any time, which also resets the automatic interval; hovering or focusing the whole control pauses the automatic timer (manual stepping still works while paused). Accessibility: the entire visual reel is `aria-hidden`; the real, always-current word is exposed via a `role=status aria-live=polite aria-atomic=true` sr-only span that updates once the spin has fully settled, never mid-spin. `prefers-reduced-motion: reduce` skips the reel animation and the width transition entirely — a rotation jumps straight to the new word's final width and glyphs. Zero dependencies, no canvas, no SVG — pure DOM + CSS transforms."
      }
    },
    {
      "name": "text-variable-weight",
      "type": "registry:ui",
      "title": "Text Variable Weight",
      "description": "Letters morph variable-font weight by cursor proximity — pure typography interaction.",
      "files": [
        {
          "path": "registry/core/text-variable-weight/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/text-variable-weight.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "text",
          "typography",
          "cursor",
          "variable-font"
        ],
        "instruction": "A headline whose individual letters morph between a light and a heavy variable-font weight based on horizontal cursor proximity with a Gaussian falloff, easing smoothly as the pointer moves and settling back when it leaves. Pure typography — no color, no particles. Direct-DOM rAF loop, real text in aria-label, static under prefers-reduced-motion."
      }
    },
    {
      "name": "textarea-autosize-swell",
      "type": "registry:ui",
      "title": "Textarea Autosize Swell",
      "description": "An autosize textarea that grows like dough proofing instead of reflowing like a spreadsheet cell: ordinary typing raises it line by line with a soft 180ms swell, a large paste breathes open over 400ms in one continuous motion instead of popping, and deleting text back down is slower still (320ms) so the surface never snaps under the cursor.",
      "files": [
        {
          "path": "registry/core/textarea-autosize-swell/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/textarea-autosize-swell.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "textarea",
          "input",
          "form",
          "autosize",
          "comment-box",
          "chat-composer",
          "micro-interaction",
          "accessibility"
        ],
        "instruction": "Build a real, fully native <textarea> (focus, selection, IME composition, undo history, screen reader behavior all untouched — nothing is a fake contenteditable) wrapped in a div whose height is the animated surface. A visually hidden, aria-hidden mirror <div> sits in the same wrapper sharing the textarea's exact font, padding, width and box-sizing (white-space: pre-wrap, overflow-wrap: break-word, height: auto so its scrollHeight is never clipped by the wrapper's own overflow). On every native `input` event the mirror's innerHTML is rebuilt from the textarea's value split at the caret (selectionStart, with an end-of-value fallback for input types that refuse the selection API), each half HTML-escaped and newlines converted to <br/>, with a zero-width-space <span data-caret> marker spliced in at the caret position — this single rebuild yields both the total target content height (mirror.scrollHeight, clamped to a minRows/maxRows-derived min/max computed from one measured line height) and the caret's own line bottom (the marker's offsetTop + offsetHeight) in one DOM write. The live textarea's own height is set to the target instantly and unanimated, with overflow hidden — it never internally scrolls and the caret is never blocked by its own content clipping. The wrapper's height is driven by a `--proof-h` custom property registered via CSS.registerProperty (syntax '<length>') so the browser interpolates it directly as a length, with `transition-property: --proof-h, border-color` and `transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1)` (ease-out-expo shaped) fixed in a stylesheet, while `transition-duration` and the target value are both set imperatively per input event so a single declaration can carry three different weights: growing sets 180ms, shrinking (target below the wrapper's current live height, read via getComputedStyle so an in-flight transition is measured mid-flight rather than assumed settled) sets 320ms, and a paste-originated growth (armed by a `paste` listener consumed on the very next `input` event) whose delta exceeds 40px stretches to 400ms so a large paste reads as one continuous breath instead of a multi-line pop. Before committing a new rise, the caret's line bottom (from the mirror) is compared against the wrapper's current live height: if a fast edit or an interrupted prior transition has left the caret's own line below that clipped height by more than one line, the wrapper first snaps instantly (0ms, forced reflow via a synchronous offsetHeight read) to just behind the caret's line, then the real transition runs from there — this is what keeps the caret's line inside the animating viewport at the very start of every rise instead of trusting the prior animation to have already caught up. A hairline border (1px, --border at rest) brightens to --muted for the duration of a rise via a class toggle removed on a matching timeout, and separately shows --accent on :focus-within — three states, three tokens, no canvas, no SVG, DOM and CSS only. Reduced motion: the exact same measurement and instant-height-set logic runs every time (nothing about correctness depends on the animation), a media query simply zeroes the transition duration and the JS path also skips the rising-border class, so the field still resizes correctly, just as a snap rather than a swell — the ecosystem's standard instant-reflow autosize behavior, not a degraded version of this component. Zero dependencies; Geist tokens only (--background, --foreground, --muted, --border, --accent), radius 12px, font-sans body text."
      }
    },
    {
      "name": "ticker-tape-splice",
      "type": "registry:ui",
      "title": "Ticker Tape Splice",
      "description": "Horizontal ticker tape with mechanical feed physics: quotes stream right-to-left at constant speed, new data visibly splices in with a 1px seam that travels with the strip, and hover or keyboard focus brakes the feed with inertia instead of stopping it dead.",
      "files": [
        {
          "path": "registry/core/ticker-tape-splice/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ticker-tape-splice.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "ticker",
          "marquee",
          "finance",
          "feed",
          "keyboard",
          "accessibility",
          "rAF"
        ],
        "instruction": "Build <BourseTape quotes speed? paused? onPausedChange? className?> where `quotes: {id, symbol, price, changePct}[]` is an append-only array the caller grows over time (existing entries are never mutated or removed by the caller — the component tracks its own internal, prunable chip list derived from it). STRUCTURE: a hairline-bordered chrome row holds a transport button (play/pause glyph, aria-pressed, aria-label \"Pause feed\"/\"Resume feed\") on the left and the tape viewport (overflow:hidden) filling the rest. Inside the viewport sits ONE flex row (`display:flex`, natural per-item widths, no fixed-width guessing) holding every currently-tracked chip in order; a single requestAnimationFrame loop writes `transform: translateX(-offset)` on that row directly every frame (never through React state) — offset increases by `currentSpeed * dt`, and currentSpeed itself exponentially eases (rate 0.06/frame) toward a TARGET that is 0 whenever the tape is hovered, has focus-within, or is externally paused, else the base `speed` (default 46px/s) — that lerp toward a changing target is the whole of the brake-with-inertia behavior: no separate spring, no special-cased deceleration curve, just a target that flips and a smoothed follow. SPLICING: the component diffs `quotes.length` against its last-seen length every render; newly appended entries are adopted into internal chip state (each wrapped with a locally-generated chipId so repeated symbols still get distinct DOM identity/keys), and the FIRST chip of each newly-adopted batch carries a `spliced` flag that renders a 1px var(--accent) seam as that same chip's own `::before` pseudo-element — because the seam lives inside the chip's own DOM node rather than a separately-positioned overlay, it rides along with zero extra positioning code as the row's transform moves everything together, and it stays visible for as long as that chip remains on screen (not just at the instant of insertion). PRUNING: the same rAF loop measures the current first chip's real getBoundingClientRect() against the viewport's left edge each frame; once its right edge has scrolled more than a 40px margin past the edge, that chip is dropped from state AND its just-measured width is subtracted from `offset` in the same tick, so removing the DOM node and shortening the translate happen in visual lockstep — no jump. KEYBOARD: the viewport is role=\"list\", each chip role=\"listitem\" with a roving tabIndex (exactly one chip is 0, the rest -1) and aria-label reading the full quote (\"ACME up 2.3 percent\"); ArrowLeft/ArrowRight move the roving index to the previous/next chip and imperatively call .focus() on it — this call happens ONLY inside the keydown handler itself, never inside an effect reactive to the chip list or the index, because an effect-driven focus call would steal page focus the instant a new quote streams in, which is the actual bug this component must not have. Home/End jump to the first/last currently-tracked chip. Focus landing inside the tape (any chip focused) counts as one of the three \"stopped\" reasons alongside hover and the explicit pause button. VISUAL: symbol in var(--foreground) mono, price in var(--muted) tabular-nums, delta as a filled triangle (▲/▼) plus tabular-nums percentage colored var(--success) (>=0) or var(--error) (<0) — never any other hue for the delta. Each chip is separated by a hairline border-right (var(--border)). REDUCED MOTION: the rAF loop never starts at all (checked once against the cached matchMedia result before the loop's effect runs); the internal chip list is capped to the most recent 8 entries (older ones simply never get tracked, since there's no scroll to prune them naturally); a newly appended chip plays a plain 260ms opacity fade-in defined entirely in a `@media (prefers-reduced-motion: reduce)` CSS block, so there is no JS branching needed to gate it — the animation only exists in that media query in the first place. DEMO: a feed of plausible fake quotes appended on a 1.4s timer, with the pause state additionally toggled on its own every few seconds so an unattended screenshot can land on either a live-scrolling or a paused frame."
      }
    },
    {
      "name": "ticker-teleprinter",
      "type": "registry:ui",
      "title": "Ticker Teleprinter",
      "description": "Character-quantised teleprinter crawl: a fixed row of monospace cells advances one full cell per beat via content substitution, never a subpixel slide.",
      "files": [
        {
          "path": "registry/core/ticker-teleprinter/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ticker-teleprinter.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "ticker",
          "marquee",
          "ascii",
          "mono",
          "teleprinter"
        ],
        "instruction": "Build <RollCrawl items separator? beatMs? className?> where `items: string[]` is joined with ` ${separator} ` (default separator \"•\") and a trailing ` ${separator} ` appended so the loop seam reads identically to every other item boundary. IMPLEMENTATION: the row is NOT translated. A fixed set of DOM cell spans exists at fixed positions (count derived from container width divided by a measured 1-character cell width, recomputed via ResizeObserver), and every `beatMs` (default 110) the whole row is rewritten by walking the tape string one index further — this content substitution, not a CSS transform, is what makes the motion character-quantised rather than a sliding marquee. A cell's distance from the right edge is fixed per DOM position and doubles as that position's age since the character now shown there arrived: the rightmost cell is always the just-arrived character and is scrambled to a random glyph from a noise charset on every beat and again on an intermediate flicker tick roughly 2.4x per beat, so an incoming character visibly churns through 2-3 glyphs; once a position's age reaches 3 beats it always shows the true tape character. HOVER: pointerenter (and focus-within, via focusin/focusout) sets a paused flag that stops the beat/flicker timers, but also force-settles every cell to its true character immediately — this is what makes the pause land cleanly on a cell boundary instead of freezing mid-flicker on a scrambled glyph. Accessible name is the plain comma-joined item list on a role=\"text\" wrapper; every visual cell is aria-hidden. prefers-reduced-motion renders one static settled frame (no timers, no listeners beyond a resize observer) rather than a crash or a blank strip. Direct-DOM rAF loop, canceled on unmount; zero React state on the animation path."
      }
    },
    {
      "name": "time-ago-drift",
      "type": "registry:ui",
      "title": "Time Ago Drift",
      "description": "A relative timestamp whose type visibly ages — variable-font weight, letter-spacing and ink drift from --foreground toward --muted as the moment recedes; hover or focus crossfades to the exact ISO instant.",
      "files": [
        {
          "path": "registry/core/time-ago-drift/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/time-ago-drift.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "time",
          "typography",
          "variable-font",
          "data-display",
          "hover",
          "accessibility"
        ],
        "instruction": "A semantic <time datetime> that renders a moment's age typographically instead of just numerically: 'just now' sits at full variable-font weight (650) with zero letter-spacing in --foreground; as the moment recedes the weight steps down through four discrete buckets (~650 -> 500 -> 400 -> 300, matched to the minute/hour/day/date thresholds a relative-time label already crosses), letter-spacing opens from 0 to 0.04em, and ink steps from --foreground toward --muted via color-mix — the drift is felt in the glyphs before the digits are read. Label text itself follows the usual relative-time ladder: 'just now' under 45s, '<n>m' under an hour, '<n>h' under a day, then a short date ('Mar 3', or 'Mar 3, 2024' once the year differs) beyond that. A recursive setTimeout (not setInterval) re-evaluates the age and reschedules itself at a delay that grows with the age — 1s while fresh, 15s through the minutes bucket, 5min through the hours bucket, 1hr once it reads as a date — so a feed or table full of these costs almost nothing once its rows have aged. All ticking writes go straight to the DOM (textContent, style, attributes) with no React state on the hot path. Hovering or focusing the stamp crossfades (150ms, ease-out-expo) from the relative label to the exact ISO instant in Geist Mono ('2026-07-22 14:32:07Z'); both layers sit in the same CSS grid cell so the reveal never reflows the surrounding layout, and tabular-nums keeps every periodic digit swap at a fixed width too. The whole thing is one focusable node (tabIndex=0) — there is no separate button. Accessibility: the rendered relative text IS the accessible name (aria-label mirrors it exactly on every tick) and the absolute instant lives in aria-description, so both are always in the accessibility tree regardless of what's currently painted; the weight/tracking/color drift is presentational only and never carries information the text doesn't already carry. prefers-reduced-motion swaps the 150ms crossfade for an instant opacity flip and drops the weight/tracking/color transition entirely, staying fully legible either way. DOM+SVG+CSS only, zero dependencies, no canvas. Differs from date-picker-moon: that component is a date ENTRY control (a masked input plus a calendar popover with canvas moon-phase decoration) — DriftStamp has no input and no popover, it is a pure display where elapsed time itself is the typographic variable, meant for the 'updated 3m ago' spot in tables, feeds and cards."
      }
    },
    {
      "name": "time-picker-sundial",
      "type": "registry:ui",
      "title": "Time Picker Sundial",
      "description": "A time-of-day picker as a flat sundial — drag the outer ring to set the hour and the inner ring for 5-minute detents, while a soft blurred gnomon shadow points at the hour and lengthens from a short AM wedge into a long PM one as you keep dragging past noon.",
      "files": [
        {
          "path": "registry/core/time-picker-sundial/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/time-picker-sundial.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "time-picker",
          "slider",
          "dial",
          "svg",
          "drag",
          "keyboard-navigation",
          "accessibility",
          "sundial"
        ],
        "instruction": "Build a time-of-day picker shaped like a flat sundial: a 160x160 SVG dial with two concentric draggable rings. The OUTER ring (radius ~62, a wide invisible hit band using stroke=\"transparent\" strokeWidth=18 and pointer-events=\"stroke\" so only the ring band — not the interior — captures pointer events) sets the HOUR as an internal 0-23 value; 12 hairline tick marks plus Geist Mono numerals (12 at top, 1-11 clockwise) sit just inside it, matching standard clock-face placement (angle = -90 + hourOf12*30 degrees, hourOf12 = hour24 % 12, so 0/12/24-equivalent sits at the top). The INNER ring (radius ~34, hit band width 12) sets MINUTES in 5-minute detents via 12 finer tick marks at the same angular spacing, no numerals. A gnomon shadow — a soft, blurred (feGaussianBlur stdDeviation ~1.6) triangular wedge polygon from the dial center out to the current hour's angle, var(--foreground) at ~0.3 opacity — is the picker's headline feature: its length is SHORT (32 of the 80-unit radius) when hour24 < 12 (AM, 'high sun') and LONG (58) when hour24 >= 12 (PM), so continuing to drag the hour ring clockwise past the 12-o'clock mark doesn't just wrap the numeral back to 1 — it visibly lengthens the shadow, reading as morning turning into afternoon. A small Geist Mono 'AM'/'PM' caption sits just below the dial's center hub, updating live with the same condition. Both rings are role=\"slider\" (aria-valuemin/max/now/valuetext, e.g. \"3 o'clock PM\" / \"35 minutes\"), keyboard-operable with ArrowUp/ArrowRight to step +1 (hour or 5-minute unit) and ArrowLeft/ArrowDown to step -1, Home jumping to the reference minimum (hour ring: midnight, 00:00; minute ring: :00) and End jumping to the reference maximum (hour ring: noon, 12:00; minute ring: :55). Dragging is the animation hot path: pointerdown captures the pointer and starts accumulating a floating-point hour (or minute) value in a ref from the signed angular delta between successive pointermove events (delta/30 degrees per hour, or per 5 minutes for the inner ring) — NOT from an absolute snap-to-nearest-tick each event, which is what makes continuous multi-revolution dragging (and the AM/PM rollover) feel natural. Every pointermove writes the wedge polygon's `points`, both ring handles' cx/cy, the AM/PM caption's textContent, the big readout's textContent, and both rings' aria-valuenow/aria-valuetext directly via refs (setAttribute/textContent) — no React state during the drag. Only on pointerup does the final rounded value get committed through React state (or the controlled `onChange` callback) once. A separate hover affordance — independent of dragging, tracked via a lightweight pointermove-over-the-dial handler that also writes directly to a ref'd line element rather than React state — draws a faint short radial tick at the exact (unsnapped) angle under the pointer, and the shadow wedge's opacity rises slightly on hover via a plain CSS `:hover` rule (a legitimate CSS transition, not JS, since it's cosmetic and not per-drag-frame). The chosen time reads out large (text-3xl) in Geist Mono tabular-nums below the dial as 'HH:MM AM/PM'. A dedicated sr-only span (role=\"status\", aria-live=\"polite\", aria-atomic=\"true\") announces the same readout string whenever a value is committed (drag release or keyboard step) — not during the live drag itself, to avoid spamming. prefers-reduced-motion doesn't change the interaction model (there was never a continuous tween on hour/minute changes to begin with — the wedge always jumps directly to its new angle, whether via drag-follow or a keyboard step) but does drop the hover-opacity and hover-tick CSS transitions to instant. Props: hour/minute (controlled, 0-23 / 0-59), defaultHour/defaultMinute (uncontrolled default), onChange(hour, minute), className. Zero dependencies."
      }
    },
    {
      "name": "timeline-agent-lanes",
      "type": "registry:ui",
      "title": "Timeline Agent Lanes",
      "description": "Live multi-agent turn tracker — one hairline lane per agent, the lane holding the turn brightened to foreground, handoffs drawn as stepped connectors with mono duration labels.",
      "files": [
        {
          "path": "registry/core/timeline-agent-lanes/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/timeline-agent-lanes.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "agent",
          "timeline",
          "orchestration",
          "status",
          "aria-live",
          "concurrency",
          "dashboard",
          "mono"
        ],
        "instruction": "A live multi-agent turn tracker for orchestration UIs — 'who is doing what right now'. Renders one horizontal hairline lane per agent (label column left, track column right, laid out on a CSS grid so both share row geometry with zero manual sync). Idle lanes sit at --muted (dim dot, dim label, thin 1px hairline); the lane whose agent currently holds an open turn brightens its dot and label to --foreground and gets a breathing pulse, with a live font-mono tabular-nums elapsed readout (mm:ss) ticking beside the name. Data model: consumer owns `agents` ({id,label}[], lane order) and `turns` ({id, agentId, start, end?}[]) — a turn with no `end` is the one currently in flight; the component holds no orchestration logic of its own, only layout, a 1s wall clock (skipped entirely when a `now` prop is supplied, making the component fully controlled/deterministic), and announcement text. TIME AXIS: a rolling `windowMs` window (default 20000) with a 'now' line pinned to the right edge — this is the load-bearing design decision: timeline-agent-lanes shows the present moment, not a post-hoc trace, so turns older than the window silently age off the left exactly like a real monitor strip, and there is deliberately NO scrub/seek control — adding one would turn it back into the span-tree debugger this component exists to differ from. Past (finished) turns stay visible inside the window at reduced opacity (foreground/45) only so a handoff connector has two ends to draw between; the live turn's bar is full-opacity foreground with the pulse. CONCURRENCY: any number of turns with no `end` can coexist across different agents with no special-casing — each renders its own bar independently, which is exactly how a parallel fan-out (one planner spawning three subagents at once) reads: three lanes bright at the same x-range. HANDOFFS: a stepped (horizontal/vertical/horizontal) connector plus a Geist Mono tabular-nums duration chip is drawn from a finished turn to the next turn on a DIFFERENT agent that starts at or after it ends (400ms slack for real event-timestamp jitter) — a genuine baton pass, not two turns that happen to overlap; one finished turn can feed multiple connectors, so a fan-out reads as one source splitting into several stepped lines. Connector labels alternate above/below the rail by arrival order and a handoff greedily flips to the other band if the previous same-band label sits within 10% of track width — the answer to two handoffs landing close together in time never drawing label-on-label. LANE COUNT: readable from 2 up to 12 lanes; beyond 12 the extra agents collapse into one aggregate '+N more' row that brightens with a live count badge ('3 active') when any of them hold a turn, but never participates in handoff connectors since there's no single fixed row to draw a line to or from — a stated degrade, not a silent one. ACCESSIBILITY: role=region with an accessible name; a single aria-live=polite aria-atomic visually-hidden region always holds the CURRENT resting summary ('Planner has the turn.' / 'Search agent, Codegen agent are active concurrently.' / 'No agent currently has the turn.') recomputed every render, so the browser's own live-region diffing announces genuine turn changes and a screen reader landing mid-run still hears an accurate state rather than only a diff log. SCOPE: no interactive controls at all (no scrub, no pause, no expand) — a deliberate choice, not an omission for time; it's a passive live status board, so it renders no controls and is exempt from the tabbability rule. REDUCED MOTION: the breathing pulse and bar-position transition both drop out (motion-reduce:animate-none / transition-none equivalents) — the resting brightness contrast between active and idle lanes already carries the full signal without any motion. Pure DOM: every bar, hairline, and connector segment is a plain absolutely positioned div using independent left:%/top:px axes (never a scaled SVG viewBox, which would stretch the mono duration text). No canvas, no SVG. Zero dependencies. Props: agents, turns, label, windowMs, now (controlled clock), className."
      }
    },
    {
      "name": "timeline-changelog-wave",
      "type": "registry:ui",
      "title": "Timeline Changelog Wave",
      "description": "Changelog timeline drawn as a beach strand: each release arrives as a foam crest sweeping an arc in from the now edge, breaks at its chronological position, pops a marker in with a spring, and recedes leaving an etched tide-ring — later waves wash past older marks so ring density reads as event history.",
      "files": [
        {
          "path": "registry/core/timeline-changelog-wave/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/timeline-changelog-wave.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "timeline",
          "changelog",
          "wave",
          "particles",
          "scroll-story",
          "data-viz",
          "ambient"
        ],
        "instruction": "Timeline drawn as a beach strand via wave-deposition. RENDERING: a Canvas 2D layer sits behind DOM marker nodes inside a padded surface card; the horizontal timeline axis is stroked 1px --border at 62% of canvas height, with the 'now' edge at the right and chronological positions laid out so the OLDEST event breaks nearest the water — each later wave must reach further up-strand, which is exactly what lets it wash past earlier markers. WAVE: the active crest travels a quadratic-bezier arc from the right edge toward the target event x at 420px/s with ease-out-expo arrival (duration = arcLength/420, hard forced-settle deadline at dur+150ms, never epsilon-based); the crest front is 48 short stroked segments (3-9px long) jittered ±3px perpendicular to the arc tangent in --foreground at alpha 0.5 with falloff over a 60px trailing comb, and the crest's leading 1px line is stroked in --accent — the ONLY accent ink, the active incoming crest; a faint --foreground wetted-arc trail glows behind it. BREAK: on arrival, 24 foam flecks spawn with initial speed 60-140px/s, upward spray angles, gravity 300px/s², life 600ms, emission staggered over a 250ms burst window, held in a pruned splice-on-death array; the event's 24px DOM marker (border ring on surface, small commit icon) pops in with an underdamped spring (k=250, zeta=0.5) from scale 0.6 to 1.0 overshooting ~1.06, settling under 500ms with a forced-settle deadline of 800ms. RESIDUE: each wash appends one arc stroke (--muted, alpha 0.12, crescent bowed seaward with jittered rotation and span) to a per-marker segment list capped at 3 with the oldest shifted out; rings are re-stroked from the lists every frame after a FULL clearRect — no destination-in decay, avoiding the quantize-forever trap. Later waves passing an existing marker append a ring without touching the marker. After the break the wetted arc recedes back toward the now edge over 550ms. INTERACTION: a next/prev control pair (plus counter) advances or retreats 'now' — next queues the next event's wave (queue drains sequentially), prev pops the queue, cancels an in-flight wave into a non-depositing recede, or undeposits the last marker and prunes its rings; hover or keyboard focus on a stranded marker replays its swash once via a short 110px mini-arc (no new residue ring) and raises a DOM detail card (title, version+date, one-line body) positioned by offset transform from the container origin above the marker's settled coordinate, with a no-fly-in first show. On mount an autoplay intro queues the first 3 waves so the ambient tide is the default look. REDUCED MOTION: all markers render pre-stranded with synthesized ring histories (min(3, n-i) rings each), no waves, controls disabled, hover/focus still raises the card statically. ACCEPTANCE: all inks (--border axis, --foreground foam/flecks, --muted rings, --accent crest) derived via getComputedStyle at mount and re-derived by a MutationObserver on documentElement class that re-strokes the scene; rAF sleeps when there is no active or queued wave, the fleck array is empty, and all marker springs are settled; IntersectionObserver pauses offscreen; zero-size containers guard the loop; canvas sized with explicit style.width/height plus a dpr-clamped backing store; every listener, observer, and frame torn down on unmount. DOM markers and the detail card take offset transforms from the container origin, never absolute canvas coords."
      }
    },
    {
      "name": "timeline-reasoning-rail",
      "type": "registry:ui",
      "title": "Timeline Reasoning Rail",
      "description": "Collapsible agent-reasoning timeline — status lives entirely in the node glyph on a 1px vertical rail, steps stream in over time, and completed steps fold to a single line so a long run stays readable.",
      "files": [
        {
          "path": "registry/core/timeline-reasoning-rail/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/timeline-reasoning-rail.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "timeline",
          "stepper",
          "agent",
          "log",
          "disclosure",
          "accordion",
          "streaming",
          "accessibility"
        ],
        "instruction": "Build a collapsible agent-reasoning timeline for streaming, open-ended step lists (not a fixed known-step wizard — see stepper-needle's spinbutton and any fixed-step form stepper for that case; this one has no notion of a total step count and steps arrive one at a time). DATA: the consumer owns a `steps` array (oldest first) they append to and mutate in place over time — each step is {id, label, status: pending|running|done|failed, time?, detail?, evidence?: {label,value}[]}; the component holds no timer or generation logic of its own, only fold/expand and announcement state. RENDER: a self-contained rounded-md border-border bg-surface panel around a role=region (aria-label from the `label` prop) scrollable ol (max-h-[420px] overflow-y-auto) so a 40-step run reads as a compact scrollable log, not a wall — every row folds to a single flex line (glyph + truncated label + optional mono time stamp + chevron) once its step is no longer the active one. STATUS GLYPH — the ONLY place status appears, no colored badges or pills anywhere: pending is a hollow ring (stroke --border, fill --surface), running is a HALF-FILLED disc — a full --foreground ring (fill --surface) with exactly one half painted solid --foreground, i.e. a 50% pie — with a motion-safe Tailwind `animate-ping` halo layered over it; the half-filled form is deliberately the resting signal, because the ping halo is fully transparent for most of its cycle and a bare dot inside it is indistinguishable from done's solid dot in any still frame, whereas a half-filled disc reads as 'in progress' at any scale, done is a solid 8px --foreground dot, failed is a distinct FORM — a stroked --foreground diamond (rotated square) with a crossing X, deliberately shaped rather than colored so the failure reads even in grayscale. Glyphs sit on a continuous 1px --border rail drawn per-row as two absolutely centered segments (above and below the glyph, skipped for the first/last row) so the connector reads as one unbroken line threaded through every node. FOLD LOGIC: the step currently running is expanded by default; once nothing is running, the most recently arrived step stays expanded — that is 'the current step is what you should see, with history collapsed above it'. Every other step folds. Clicking a folded row's header (the whole header is the hit target, not just the chevron) expands it and that per-id choice is kept in a Record<id,boolean> override that survives new steps streaming in, so a user reviewing history doesn't get overridden by the next arrival; folding a currently-auto-expanded step likewise sticks. Steps with neither `detail` nor `evidence` render as a plain non-interactive line (nothing to disclose, so no button is rendered for them — an inert row is not a broken control). EXPAND ANIMATION: pure CSS grid-template-rows 0fr/1fr transition (300ms, cubic-bezier(0.22,1,0.36,1)) on a wrapper around an overflow-hidden div — no JS height measurement, no layout thrash; the chevron rotates 90deg over 200ms in sync. DETAIL: a max-w-[60ch] text-sm text-muted paragraph plus, if present, evidence rendered as flat bordered chips (rounded-sm border-border bg-background font-mono text-[11px], label in --foreground, value in --muted, separated by a middot) — never colored badges, evidence is data, not a verdict. STREAMING + A11Y: a diff effect compares the previous `steps` array to the next by id, emitting one polite (never assertive — aria-live=polite, aria-atomic=true, visually-hidden) announcement per arrival (\"Queued: <label>\" for a step arriving pending, else the bare label) and per status flip (\"<label> running/done/failed\"), joined into a single live-region update per render so a burst of simultaneous changes announces once; a companion effect scrolls the ol to bottom on every `steps` change (instant under prefers-reduced-motion, smooth otherwise) so the newest arrival stays in view, but NEVER fires on a manual expand/collapse — exploring folded history must not get yanked back down. KEYBOARD/A11Y: every fold toggle is a real <button aria-expanded aria-controls> with an accessible name (\"Expand/Collapse <label>\"), reachable and operable by Tab/Enter/Space with no custom key handling needed; focus-visible ring is --accent, --accent's only appearance in the whole component. REDUCED MOTION: the grid-rows transition and chevron rotation both go to duration 0 (still expands/collapses, just instantly) and the running glyph's ping halo is dropped, leaving the half-filled disc that already carries the meaning — every state stays fully legible and operable, nothing is hidden. Zero dependencies, pure DOM + SVG + CSS, no canvas."
      }
    },
    {
      "name": "toast-gravity-stack",
      "type": "registry:ui",
      "title": "Toast Gravity Stack",
      "description": "Toast stack with real gravity — notifications thud into a jostling heap, errors sink under their own mass, and the pile resettles when one is dismissed.",
      "files": [
        {
          "path": "registry/core/toast-gravity-stack/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/toast-gravity-stack.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "toast",
          "notification",
          "physics",
          "gravity",
          "rigid-body",
          "micro-interaction",
          "feedback"
        ],
        "instruction": "A toast system where notifications fall under real gravity and pile into a jostling heap at the base of a docked surface. RENDERING: real DOM toast cards (severity icon, title, mono message, dismiss button, role=status/alert) whose translate/rotate transforms are written per-frame by a rigid-body-lite 2D sim — refs only on the hot path, no React state, no canvas. Each card's collision proxy is a rounded box: a row of circles of half-card-height radius along the card midline (corner circles plus midline fill), so corners round off and nothing tunnels through edge gaps; container floor and walls are static planes. MOTION: gravity 1800 px/s^2 at fixed 120Hz substeps; restitution 0.15 applied only above an 80 px/s impact threshold; tangential Coulomb friction mu 1.2 at every contact (raised from an earlier 0.85, which let cards slide under >40° contacts forever); angular damping 0.92/frame framerate-normalized via pow(0.92, dt*60); mass by severity error=3 / warning=2 / info=1 so heavier bodies displace lighter ones on impact and errors migrate toward the pile floor. Toasts spawn from the top, x round-robined across up to 4 jittered tray-width slots (vs. a single fixed edge) so the pile heaps rather than sliding in flat from one side, with vx jitter +/-40 px/s and rotation jitter +/-4 deg; each arrival's impact impulse briefly jostles the pile. The physics floor sits a few px above the tray's visible bottom edge (a shorter inner sim frame inside the clipped outer box) so a rotated card's corner overhang never reads as clipped. SLEEP: the whole sim parks when every body holds |v| < 8 px/s and |omega| < 0.05 rad/s for 12 consecutive frames; spawn, dismiss, hover, drag, and resize wake it. Auto-dismiss default 6s per toast, paused on hover; oldest layers fade to opacity 0.55 as newer sediment lands. Dismissing removes the collision body instantly (150ms opacity fade on the card) and the sediment above resettles under gravity. INTERACTION: hover lifts the card 2px and applies a shadow tinted from a fixed dark ink (rgba(0,0,0,...), not a theme token, so it never inverts to a light halo in dark mode); drag right past 80px dismisses, under-threshold release springs back with k=180, zeta=0.9 while the held card goes kinematic so the pile leans on it. Severity color appears only as a 3px left rail using var(--error)/var(--warning)/var(--success); everything else is neutral tokens (bg-surface, border-border, hover:border-foreground/20, accent focus ring on dismiss buttons). REDUCED MOTION: classic static vertical list, newest on top, instant add/remove with 150ms opacity fades, no physics. Imperative handle exposes push/dismiss/clear. ResizeObserver re-derives floor and walls (zero-size container guarded, coincident-center collision normals fall back to vertical); every rAF, observer, listener, and timer is torn down on unmount."
      }
    },
    {
      "name": "toast-undo-fuse",
      "type": "registry:ui",
      "title": "Toast Undo Fuse",
      "description": "Undo toast whose remaining lifetime is a literal fuse — a hairline along the bottom edge burns right-to-left with an ember at the front, answering \"when does this go away\" without a numeric countdown.",
      "files": [
        {
          "path": "registry/core/toast-undo-fuse/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/toast-undo-fuse.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "toast",
          "notification",
          "undo",
          "timer",
          "svg",
          "spring",
          "micro-interaction",
          "feedback"
        ],
        "instruction": "A single undo toast (role=status, aria-live=polite, aria-atomic) whose remaining lifetime is rendered as its own bottom border rather than a digit. The border is a full-width SVG <line> with pathLength=100 so stroke-dasharray/stroke-dashoffset are resolution-independent; a Web Animations API animation drives stroke-dashoffset linearly from 0 to 100 over `duration` (default 6000ms), which shortens the visible (unburnt, --border colored) segment from the right edge toward the left as time elapses. A sibling absolutely-positioned span is the 3px ember — --foreground colored, 1:1 vertically centered on the line — animated via a mirrored `left` WAAPI animation (100% to 0%) so it always sits exactly at the current burn front. Both animations are created once at mount and never restarted by prop identity churn (onUndo/onDismiss are read through refs); pausing is `.pause()` on both Animation objects, which freezes `.currentTime` for free, so the accessible description (a visually-hidden span bound via aria-describedby) can read genuine remaining seconds off the animation clock instead of a separate drifting interval. Hovering the toast or focusing it (Tab reaches the toast itself before the Undo button, and it is a real focus target, not a visual-only affordance) pauses both animations and springs the ember's scale down to 0.45 with an underdamped 1D spring (k=300 s^-2, zeta=0.7, rAF-driven, sleeps once settled) — the pause is a physical pinch of the fuse, not an invisible timer stopping somewhere off-screen. Undo is a real <button> (--accent text, the only place --accent appears); clicking it reads the current burn progress off the forward animation's currentTime, cancels both forward animations, and plays a reverse pair back to the unburnt state at 4x speed (duration/4, floored at 140ms) with ease-out-expo, then runs a 180ms exit translate/fade before calling onUndo. Escape, or the fuse simply running out, skips the reverse and runs the same exit before calling onDismiss. The toast surface is --background with a 1px --border and 12px radius; overflow-hidden lets the corner radius clip the hairline's ends cleanly. prefers-reduced-motion: the line animation switches to stepped easing (steps(8, end)) so it depletes in discrete jumps instead of a continuous sweep, the ember is not rendered or animated at all, hover/focus pausing is disabled (nothing to pause), and Undo snaps back to unburnt in a single frame instead of replaying the reverse-burn — the toast stays fully usable, just without any of the physical motion. Props: message (required), actionLabel (default \"Undo\"), duration, onUndo, onDismiss. Zero dependencies beyond React."
      }
    },
    {
      "name": "toc-minimap-mercury",
      "type": "registry:ui",
      "title": "TOC Minimap Mercury",
      "description": "TOC minimap where scroll progress is a column of liquid mercury — a gooey SVG blob climbs the rail, absorbs section ticks, teardrops under fast scroll, and bulges toward hovered ticks.",
      "files": [
        {
          "path": "registry/core/toc-minimap-mercury/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/toc-minimap-mercury.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "nav",
          "scroll",
          "svg",
          "goo",
          "toc",
          "cursor",
          "micro-interaction"
        ],
        "instruction": "Build a fixed-right table-of-contents minimap where scroll progress renders as liquid mercury inside a 48px-wide, 60vh-tall SVG rail. Use one goo filter (feGaussianBlur stdDeviation 4 into an feColorMatrix with alpha row 0 0 0 19 -9) over a liquid group filled with the foreground token: a 4px rounded-rect column from rail top to the blob, a leading r=7 blob circle, and per-section r=4 circles that start hidden; outside the filter, draw crisp unfilled r=4 ticks stroked with the muted token at each section's evenly mapped y. Map window scroll through the sections' document offsets piecewise-linearly to a target y, and drive the blob with a semi-implicit Euler spring (stiffness 120, damping 20, slightly underdamped so stops land with a droplet wobble) integrated in a requestAnimationFrame loop that writes only via setAttribute, reads scroll from a ref set by a passive listener, and sleeps when velocity and error settle. Stretch the blob volume-preservingly by velocity — scaleY = 1.06 + clamp(|v|*0.004, 0, 0.74), scaleX = 1/sqrt(scaleY) — shifted a few px toward the travel direction, with a small constant bias baked into the 1.06 floor so the blob still reads as a teardrop (not a plain circle) at rest, not just while moving; fast scroll further stretches the leading edge. When the blob passes within 6px of a tick, reveal that tick's hidden circle inside the goo group (the filter renders the merge) and fade the outline tick; scrolling back up re-hides it so the goo neck snaps and releases the dot. On pointer within 24px of a tick, grow a helper circle in the goo group from r 0 to 5 positioned 60% of the way from the column toward the tick so the liquid bulges at the cursor; clicking a tick smooth-scrolls its section into view. Geist Mono text-xs labels slide in from translateX(-8px) to 0 over 150ms ease-out on rail hover, muted by default and foreground when active. Under prefers-reduced-motion swap the whole goo layer for a plain 2px filled line plus dots with instant active states and no animation loop. Auto-discover section[id] elements when no sections prop is given."
      }
    },
    {
      "name": "toggle-theme-ascii",
      "type": "registry:ui",
      "title": "Toggle Theme ASCII",
      "description": "A light/dark toggle whose chip reads the real resolved --background/--foreground values at mount and on every theme change and paints itself in their negative, so it previews almost exactly what the page will look like the instant you click it.",
      "files": [
        {
          "path": "registry/core/toggle-theme-ascii/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/toggle-theme-ascii.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "theme",
          "toggle",
          "dark-mode",
          "ascii",
          "mono",
          "accessibility"
        ],
        "instruction": "Build <ThemeToggleAscii dark? defaultDark? onDarkChange? syncDocument? storageKey? className?> — same controlled/uncontrolled contract as the repo's other toggles, defaulting syncDocument to true (clicking toggles document.documentElement's \"dark\" class and writes storageKey, default \"ns-ui-theme\", to localStorage; wrapped in try/catch since localStorage throws in locked-down contexts) and storageKey overridable so a consumer with a different theme key isn't locked to this one. STRUCTURE: a single real <button aria-pressed aria-label> containing an aria-hidden 8-unit chip and a visible uppercase 'light'/'dark' text label (aria-hidden false — it's part of the accessible picture alongside aria-label, but aria-label is authoritative and states the action: 'Switch to light theme'/'Switch to dark theme'). THE MECHANIC — the one only a theme control can have: on mount, and on every mutation of <html>'s class attribute (a MutationObserver, not just the toggle's own click — so it stays correct if some other control on the page changes the theme too), read the actual resolved custom-property values via getComputedStyle(document.documentElement).getPropertyValue('--background'/'--foreground') and paint the chip in their NEGATIVE: chip background = the current foreground token's resolved value, chip ink (text color) = the current background token's resolved value. Because foreground and background swap between the two themes, the chip is always showing, in real token colors read at runtime (never a hardcoded hex, satisfying the token rule even inside this imperative color-setting code), very nearly what the whole page will look like immediately after the next click — a live preview of the target state, not a static icon. Test for whether a mechanic belongs here: it would mean nothing on a control that doesn't change the palette it's drawn in. GLYPH: two absolutely-stacked <pre aria-hidden> blocks inside the chip — a small ascii sun (rays radiating from a parenthesized O) and a small ascii moon (a parenthesis-drawn crescent) — cross-fading via opacity over 200ms keyed off the current dark state, with prefers-reduced-motion dropping the transition to an instant swap. ACCESSIBILITY: a real <button>, aria-pressed kept in sync, an aria-label that names the action rather than merely describing current state, hover (chip scale-105 plus border brightening) and focus-visible (outline-2 outline-offset-2 outline-accent, with no outline-none on the same element — Tailwind v4 latches --tw-outline-style to none permanently if both classes are present, and the ring silently never paints even though the classes look correct) states that are visibly distinct from rest. `mounted` gates the label/aria-pressed text (client-decided theme, unknowable during SSR) exactly like the repo's existing app-level ThemeToggle, and suppressHydrationWarning covers the one-frame mismatch between the server's default render and the class the anti-flash script already applied before hydration."
      }
    },
    {
      "name": "tool-call-board",
      "type": "registry:ui",
      "title": "Tool Call Board",
      "description": "An agent's tools hung on a board like fishing tackle — slim capability chips at rest, each lifting and paying out a streaming-argument strip the instant its tool is called, with a recency underline that fades over 60 seconds.",
      "files": [
        {
          "path": "registry/core/tool-call-board/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tool-call-board.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "agent",
          "tool-calling",
          "status",
          "aria-live",
          "mono",
          "list",
          "activity"
        ],
        "instruction": "A live capability-and-activity board for a single agent's tools, answering both 'what can this agent even do' and 'what is it doing right now' with the SAME element, which is the point of difference from every other agent-status component in this registry. Takes `tools: {id,name,affordance}[]` (the static capability list) and `invocations: {id,toolId,args,status:'pending'|'success'|'error',startedAt,endedAt?,summary?}[]` (the call log) — the component holds no orchestration logic of its own, only layout, a light clock for streaming/decay pacing, and announcement text; the consumer owns when a call starts and resolves. Tools render as a wrapping flex row of slim 28px chips, each a real `<button>` inside an `<li role=group>` labeled with the tool's name AND its one-line affordance ('search_web: searches the live web'), so a screen reader gets the capability description without extra navigation; at rest a chip is just `name` in Geist Mono beside its affordance in `--muted` — literally an inventory list. The moment a tool has a `pending` invocation, ITS chip (and only its chip — simultaneous calls simply lift multiple chips independently, with zero shared choreography) lifts via `translateY(-2px) rotate(0.75deg)` on a spring-flavored cubic-bezier, gains a soft `color-mix(in srgb, var(--foreground) 6%, transparent)` shadow, and pays out a strip beneath itself: a `grid-template-rows 0fr->1fr` expansion (ease-out-expo) that streams the call's `args` string into view in 12px Geist Mono at a pace that fills a ~900ms window regardless of string length, with a blinking trailing caret while still in flight — this streaming region is `aria-hidden` throughout, since it is progress chrome, not content. The instant `status` moves off `pending`, the strip's content swaps to a one-line, NOT-aria-hidden summary: an inline check-mark SVG (`--foreground` stroke) for `success`, a hollow circle (also `--foreground` stroke — success/error is shape-coded, deliberately never color-coded, so it survives without `--accent` or any status hue) for `error`, plus the summary text and a relative timestamp; the chip itself settles back down since it is no longer in flight, but the strip and a 2px `--foreground` underline beneath it persist, the underline stepping opacity 100/66/33/0 across four ~15s beats over the full `decayMs` window (default 60000) — recency visibly cooling back to quiet inventory rather than vanishing or lingering forever. Once fully decayed the strip closes and the chip returns to a bare rest state, but the call is not forgotten: every chip's button, on click OR Enter/Space (it is a native `<button>`, so both are free), force-opens that tool's last invocation regardless of decay — and this open is a SET, not a toggle, so a second click (or an automated press pass that clicks the same control twice) cannot accidentally close it again; Escape is the one documented way back to ambient/decayed display, closing whichever tool was manually expanded. A single shared `aria-live=polite` region announces each new call once by tool name ('search_web called') the instant it appears in `invocations` — never per streamed token, never per render. Zero dependencies; DOM + CSS + inline SVG only, no canvas; every color is a token (`--background --foreground --muted --border --accent`, `--accent` reserved for the focus ring only — no status color exists here at all, which is itself the accessibility strategy). `prefers-reduced-motion` drops the chip's lift/tilt/shadow and the caret blink entirely (final states unaffected, just not eased into) — the strip still opens, because the strip's presence, not the tilt, is what actually signals 'this ran'. Distinct from timeline-agent-lanes, which choreographs turn HANDOFFS between multiple agents on a shared timeline with a rolling now-window and stepped connectors — there is no single tool binding capability to usage, because the agent isn't the subject, the relay is. Distinct from citation-grounding-hatch, which is a post-hoc grounding audit over an already-finished answer. Tackle-board is neither: it is the live, single-agent surface where a tool's icon-and-description chip and its usage indicator are literally the same object, and the lift-and-pay-out on invocation is the entire mechanism, not an animation layered on top of a separate status list."
      }
    },
    {
      "name": "tooltip-delay-group",
      "type": "registry:ui",
      "title": "Tooltip Delay Group",
      "description": "A tooltip whose 1px border traces itself outward from the trigger's own edge and whose delay-group lets adjacent triggers hand off instantly with no re-animation, like a real menu bar.",
      "files": [
        {
          "path": "registry/core/tooltip-delay-group/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tooltip-delay-group.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tooltip",
          "overlay",
          "hover",
          "delay-group",
          "placement",
          "portal",
          "accessibility",
          "keyboard"
        ],
        "instruction": "Build a tooltip that reads as something the trigger extrudes rather than a layer that fades in on top of the page. On open, the panel's 1px border traces itself out of the edge facing the trigger: that facing edge grows first from its own centre outward in both directions (a plain scaleX/scaleY from transform-origin center over 70ms, ease-out-expo), the two perpendicular edges that meet it are anchored at the corner touching the facing edge and shoot outward from that seam starting ~42ms later, and the far edge — anchored at its own centre like the facing edge — closes the loop last, finishing around 150ms. The label content is a separate node that starts at opacity 0 with a 3px nudge from the facing-edge direction and settles in over 130ms starting 80ms after the trace begins, so it visibly lags the border rather than arriving with it. All of this is direct-DOM inline-style writes on four 1px border-token divs plus the content node (no keyframes, no dependency), started from a rAF after the collapsed starting state is committed so the browser has something to transition from. Delay-group: PenumbraTipGroup wraps a row of triggers and shares an 'open delay' (default 500ms) and a 'close grace' (default 300ms) across them via refs, not React state, because a hover handoff has to be decided synchronously inside the pointerenter that starts it. The first tooltip to open in a cold group waits the full delay and plays the trace; while one is open, or within closeGrace of the last one closing, the next sibling entered opens instantly with the trace and content-settle both skipped outright (edges and content snap straight to their resolved state) — a real menu-bar handoff, not a fast version of the same animation. Leaving the group for longer than closeGrace re-arms the delay for the next open. Collision-aware placement: the panel is measured off-screen (visibility:hidden, not display:none, so layout size is real) against the trigger's rect and the viewport, flips to the opposite side when the preferred side doesn't fit, and is clamped on the cross axis so it never runs past the viewport edge; it portals to document.body specifically so an ancestor's overflow:hidden — a recurring failure mode in this registry — can never clip it, and the trace/settle geometry is recomputed from the resolved side every open rather than assumed. Semantics: role=tooltip on the panel plus aria-describedby on the trigger, applied only while open — the Radix convention, not the APG's always-mounted/visually-hidden alternative. That alternative keeps the id association stable for AT that only reads aria-describedby once on focus, but means paying real layout cost for content that is usually never seen; because this panel needs a fresh measure-and-place pass every open anyway for collision awareness, it is never a static always-present node regardless, so tying its DOM lifetime to the aria-describedby association is the smaller compromise, and the attribute lands synchronously in the same handler that opens it, well before any AT polling interval. Keyboard: focus opens the tooltip instantly, never delayed — a keyboard user is not 'hovering past' and gating them behind the same delay as a mouse would be a real accessibility regression, not a nicety — and Escape closes it from either hover or focus. Hover and focus are tracked as independent flags and the tooltip stays open while either is true, closing only once both release, so resting the mouse on a trigger you've also tabbed to doesn't flicker closed on mouseleave. Touch has no hover: a touch tap opens the tooltip as an isolated peek (bypassing the delay/group logic entirely), and a second tap on the trigger, a tap anywhere else, any scroll, Escape, or a 4-second safety timeout all close it, so nothing a touch user does can leave it stuck open. prefers-reduced-motion skips the trace and the content settle outright — same placement and delay/group timing, panel just appears fully drawn, fully legible, never animated."
      }
    },
    {
      "name": "tour-spotlight",
      "type": "registry:ui",
      "title": "Tour Spotlight",
      "description": "An onboarding tour rendered as a lighthouse — the current target gets a light ring and halo while the rest of the page drops to penumbra behind a single SVG-masked scrim, and advancing sweeps a feathered beam along the path from the old target to the new one as the hole morphs to match.",
      "files": [
        {
          "path": "registry/core/tour-spotlight/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tour-spotlight.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "onboarding",
          "tour",
          "coach-mark",
          "walkthrough",
          "spotlight",
          "svg-mask",
          "overlay",
          "accessibility"
        ],
        "instruction": "Build a step-by-step onboarding tour ('coach mark') themed as a lighthouse sweeping between UI targets. The core visual is a single full-viewport SVG overlay containing a <mask>: a full-size white rect (meaning 'show the dim scrim here') and a second rect — the hole — filled black (meaning 'clear here') with rounded corners and a feathered edge via an SVG feGaussianBlur filter applied to that rect. A third rect, filled with a translucent black scrim (about 62% alpha) and referencing that mask, is what actually paints the dimming — everywhere except a soft-edged rounded rectangle around the current target is darkened, and the underlying page needs zero per-element opacity changes to make this work. The hole rect's x/y/width/height are set directly (via ref, using setAttribute rather than React re-renders) from the current target's getBoundingClientRect() plus a small pad (~9px), with an inline `transition` on those four properties (~480ms, ease-out-expo) so changing them on step-advance animates a morph rather than a jump — SVG geometry properties are CSS-transitionable in Chromium-class browsers, so a plain attribute change under an active `transition` animates correctly. A separate absolutely-positioned div is the 'light ring': positioned to hug the target rect (small pad, matching corner radius), styled with a 1px --foreground border plus a soft --foreground-derived halo (use `color-mix(in srgb, var(--foreground) 35%, transparent)` for a token-correct, restrained glow rather than a hardcoded rgba white — this must look right in both themes), and it morphs its left/top/width/height on the same transition timing as the hole so ring and hole move together. Advancing between steps additionally sweeps a beam: a third absolutely-positioned div, a feathered wedge (a linear-gradient fill faded at both ends, clipped to a trapezoid via clip-path so it's a true wedge not a rectangle, plus a few px of CSS blur for softness), positioned at the OLD target's center, rotated via `transform: rotate()` to point at the NEW target's center (computed with Math.atan2/Math.hypot), sized to the distance between the two centers, and animated by transitioning `transform` from `scaleX(0)` to `scaleX(1)` (transform-origin: left center) over ~340ms so it visibly draws itself along the connecting path, then fades its opacity out over another ~180ms and disappears — this entire sequence is a one-shot ref-driven write per step change (compute once, let CSS interpolate), not a continuous rAF loop. A step card (a bordered, token-styled panel: 'N of M' in font-mono, a title, body copy, Back/Next/Skip buttons, Next reads 'Done' on the last step) docks near the target — default beneath it, flipping above if there isn't room, and always clamped horizontally/vertically to stay fully inside the viewport with a margin. Keyboard: on mount and on every step change, focus moves into the card (onto the Next button); a keydown handler on the card traps Tab (computing the card's own focusable descendants and wrapping from last back to first, and first back to last on Shift+Tab, so focus can never leave the card while the tour is open); ArrowRight/ArrowDown call the same handler as Next, ArrowLeft/ArrowUp call Back (a no-op at step 0); Escape calls onExit, and the component captures whatever had focus before it mounted and restores it there on unmount, so exiting the tour never strands focus. Hovering the Next button flickers the *next* beam sweep's starting opacity slightly (a one-off dimmer starting alpha on the following sweep) as a subtle 'preview' cue rather than anything continuous. Accessibility beyond the focus trap: the card is a labelled region (aria-labelledby the title) and carries a visually-hidden aria-live=polite status span announcing 'Step N of M: <title>' on every change, since the ring/beam/mask are all aria-hidden decoration that assistive tech never needs to parse. prefers-reduced-motion: skip both the hole/ring CSS transitions (apply the new rect instantly, `transition: none`) and the beam sweep entirely (the beam div stays at opacity 0) — the tour still fully works, it simply jumps between steps with no motion. The demo renders a small fake mini-app chrome (a nav bar, a toolbar with a New button, two content cards, a save-status pill) with four real elements carrying stable ids as the four tour targets, and self-drives: an internal timer advances through all four steps automatically (~2.6s per step), then pauses and restarts the loop, so the component demonstrates its full behavior — including the beam sweep between every pair of steps — with no pointer or keyboard input required. Zero dependencies, no canvas."
      }
    },
    {
      "name": "transfer-list-siphon",
      "type": "registry:ui",
      "title": "Transfer List Siphon",
      "description": "Multi-select a source list, drag just one selected item into the destination to prime the siphon, and the rest of the selection flows through a live SVG tube one bead at a time until it drains or you break the seal.",
      "files": [
        {
          "path": "registry/core/transfer-list-siphon/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/transfer-list-siphon.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "listbox",
          "multi-select",
          "drag-and-drop",
          "transfer-list",
          "bulk-action",
          "svg",
          "accessibility",
          "input"
        ],
        "instruction": "A bulk transfer control for moving many items between two lists (assign members, move files) as an inspectable, interruptible stream instead of N separate drags or an opaque 'Move 14 items' button. Props: `items: { id, label, hint? }[]` the full pool, `defaultDestinationIds?`/`defaultSelectedIds?` seed which start on the destination side and pre-selected, `sourceLabel?`/`destinationLabel?` panel headings, `onTransfer?(ids)` fires once when a flow drains successfully with the ids that made it across in order. STRUCTURE: the source panel is a real listbox (`role=listbox aria-multiselectable=true`, options `role=option aria-selected`, roving tabindex, Arrow/Home/End navigation, Space or Enter toggles the focused option) so ordinary click-to-toggle, Ctrl-less multi-select, and full keyboard operation all work before any drag happens; a 'Select all'/'Clear selection' toggle button sits in the source panel's header, above the listbox, for selecting a pool too long to click through row by row; the destination panel is a plain display list. MECHANISM: dragging any source row (auto-selecting it if it wasn't already) toward the destination panel tracks the pointer via native pointer capture on that row, so movement is followed even once the pointer leaves the row; dropping inside the destination panel's bounds PRIMES the flow — a cubic-bezier `d` string is built once from the dragged row's edge to the drop point (control points bowed upward, `stroke-width:1` var(--border)) and set on a single persistent `<path>`, and `path.getTotalLength()`/`getPointAtLength()` on that same path drive every subsequent bead, so the tube is a fixed physical conduit for the rest of the transfer, not recomputed per item. Dropping outside the destination silently cancels the drag — nothing was ever selected away, nothing to undo. An identical 'Move selected to {destination}' button primes the exact same state machine with no drag at all: keyboard users reach the identical primed flow, not a lesser substitute. FLOW: item ids queue in drag order (dragged item first, then the rest of the pre-existing selection); every ~100ms (44ms under reduced motion) the next item's source row gets a `data-departing` flag — CSS grid-template-rows springs its height 1fr->0fr on ease-out-expo while its own bead (a plain SVG `<circle r=4 class=sl-bead>` filled var(--foreground)) is created and driven every animation frame by `path.getPointAtLength(easeInOutCubic(t) * length)`, t running 0->1 over 640ms — ease-in off the source, ease-out into the destination, one continuous cubic. On arrival the bead is removed, the item is spliced from the source array and pushed onto the destination array with a `data-entering` flag that plays a forwards-filled `grid-template-rows:0fr->1fr` keyframe once, and the aria-live caption/announcement updates. INTERRUPTION: while flowing, a small real `<button aria-label=\"Stop transfer\">` rides at the tube's midpoint (computed from the same path at prime time) — clicking or Enter/Space-ing it (it is a genuine focusable button, reachable by Tab, not a click-only affordance) breaks the seal: no further items are dequeued, the path plays a one-shot stroke-width/opacity recoil keyframe, and every bead currently mid-flight reverses along the identical path at matching speed back to t=0, at which point its source row's `data-departing` flag simply clears (the item was never actually removed from the source array while in flight, so 'returning' is just clearing that flag) — items that hadn't yet had their turn were never touched and stay put. A11y: progress is a polite aria-live region throttled to at most one update per second while flowing, with the final 'Moved N of N' (or, if stopped, 'Transfer stopped, X moved, Y returned') always delivered regardless of throttle. ENGINE: one requestAnimationFrame loop owns bead creation/motion/removal via direct DOM attribute writes on the SVG circles (not React state) so the hot path never re-renders; React state only tracks which rows are mid-collapse/mid-grow, the delivered/total counters (polled off the engine every 120ms for the caption, not per frame), and the live-region string. prefers-reduced-motion: the same state machine runs but each bead resolves on its scheduled turn with no per-frame travel and every CSS transition/keyframe is disabled, so items still arrive in the same staggered sequence a screen reader's narration can follow, just instantly. Distinct from file-upload-thermal, which is a canvas-particle thermal dropzone reacting to a hover/drop point with no multi-select or sustained per-item stream at all, and from segmented-control-fling, whose drag produces one ballistic object's release-velocity coast to a single detent — transfer-list-siphon's drag only PRIMES a continuous, cancellable, many-item flow that keeps running with zero further pointer input, and the tube itself is the progress indicator for that whole run, not a settling animation for one thing. Zero dependencies; DOM + SVG + CSS only, no canvas."
      }
    },
    {
      "name": "tree-box-drawing",
      "type": "registry:ui",
      "title": "Tree Box Drawing",
      "description": "File/directory tree drawn with real box-drawing connectors, whose glyphs redraw themselves as folders expand and collapse instead of static per-level guides.",
      "files": [
        {
          "path": "registry/core/tree-box-drawing/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tree-box-drawing.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tree",
          "navigation",
          "file-tree",
          "hierarchy",
          "ascii",
          "keyboard",
          "sidebar"
        ],
        "instruction": "A file/directory tree rendered entirely in monospace box-drawing characters (├── └── │   ), the way the `tree` command prints, where expanding or collapsing a folder redraws the connectors rather than just toggling row visibility. Data is a plain recursive `{ id, label, children? }` array passed as `nodes`; expand state is uncontrolled via `defaultExpandedIds`. Every row's connector prefix is computed as a pure function of its ancestor chain (`│   ` for an ancestor that still has siblings below it in the visible list, four spaces for one that was last), with the row's own connector `├── ` or `└── ` depending on its position among its current siblings — nothing is ever hand-toggled per row. The collapse sequence is the differentiator: on collapse, the toggled folder's currently-visible descendant rows are snapshotted and retracted one at a time, bottom row first, via a single rAF loop stepping through the reversed list at a fixed ~42ms cadence and writing opacity/max-height directly onto each row's DOM node through a ref map (no per-frame React state, no re-render mid-sequence). The instant a direct child's own row is the one being hidden, the loop also reaches into the previous still-mounted sibling's connector `<span>` and rewrites its textContent from `├── ` to `└── ` in place — that sibling has just become the last visible child on screen, so the glyph above it changes to say so, live, mid-retraction. Expansion is the simpler direction: new rows mount at their final, already-correct connectors and reveal top to bottom over the same cadence, since there is no 'which child is last' ambiguity to fix up on the way in, only removal creates that moving target. React only commits the settled expanded/collapsed Set once a sequence finishes; the rendered shape during a collapse is the union of the settled Set and the in-flight node so the old rows stay mounted long enough to animate out. Full WAI-ARIA tree pattern: role=tree with an aria-label, role=treeitem rows carrying aria-expanded (only when the node has children), aria-selected and aria-level. The focusable, clickable surface is a real button spanning the row (not a decorative disclosure triangle) so the accessible name is the row's own label; a container-level roving tabindex plus a single keydown handler on the tree root drives ArrowDown/ArrowUp to move focus over the flattened visible order, ArrowRight to open a closed folder or dive into an already-open one's first child, ArrowLeft to close an open folder in place or climb to its parent, and Home/End to jump to the first/last visible row. Connector glyphs are `text-border`, the row label is `text-muted` at rest and `text-foreground` when selected against a `bg-surface` chip, so the tree reads correctly against either theme without a single hardcoded hex. prefers-reduced-motion (read live via matchMedia) skips the retract/reveal sequence entirely — toggles resolve to their settled row set on the next paint with no animation frame ever scheduled."
      }
    },
    {
      "name": "tree-hinge-fold",
      "type": "registry:ui",
      "title": "Tree Hinge Fold",
      "description": "Tree view whose branches unfold like a carpenter's folding rule — each child group is a hinged segment that swings open around a visible pivot, rows unfolding in sequence, and snaps shut faster than it opens.",
      "files": [
        {
          "path": "registry/core/tree-hinge-fold/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tree-hinge-fold.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tree",
          "navigation",
          "hierarchy",
          "fold",
          "hinge",
          "keyboard"
        ],
        "instruction": "Build a tree view where expanding a node reads as a carpenter's folding rule swinging open a hinged segment. Each child group sits inside a grid-template-rows 0fr/1fr wrapper (transitioned 360 ms open / 200 ms close with ease-out-quint) so layout height animates for free, and the group itself lives under a 700px perspective and rotates from rotateX(-58deg) at transform-origin top to flat, with an overshooting back-out cubic-bezier(0.34, 1.4, 0.64, 1) so the segment lands with a slight bounce; individual child rows carry their own smaller rotateX(-28deg) staggered ~45 ms apart down the segment, so the branch unfolds row by row rather than as one slab. Collapse is deliberately asymmetric: faster (200 ms), ease-in, no stagger — a rule folds shut quicker than it opens. Each open segment draws its hinge as a small bordered pivot dot at the joint plus a 1px vertical rule down the segment's spine, positioned per depth so nesting reads as jointed segments, and leaf rows carry a 1px dot bullet instead of a chevron; the chevron rotates 90 degrees on expand. Full tree semantics: role=tree with an aria-label, role=treeitem rows carrying aria-expanded (only when the node has children) and aria-selected, children inside role=group, roving tabindex with ArrowUp/ArrowDown over the visible flattened order, ArrowRight expands then dives to first child, ArrowLeft collapses then climbs to the parent, Home/End jump the ends, Enter/Space toggles and selects. Selection is a token bg-surface fill with foreground text; hover matches; focus is a token accent outline. All colors from theme tokens only. Under prefers-reduced-motion every rotation and height transition is disabled — groups simply appear and disappear."
      }
    },
    {
      "name": "tree-root-trace",
      "type": "registry:ui",
      "title": "Tree Root Trace",
      "description": "File/nav tree whose indent guides are a single living root system — an SVG path draws itself down from a parent's junction and elbows into each child row as it expands, retracting on collapse instead of toggling static border-left lines.",
      "files": [
        {
          "path": "registry/core/tree-root-trace/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tree-root-trace.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "tree",
          "navigation",
          "file-tree",
          "hierarchy",
          "svg",
          "keyboard",
          "sidebar"
        ],
        "instruction": "Build a file/nav/org tree where the indent guide is not a static per-level border-left rule but a single continuous SVG path that grows downward and branches every time a node expands. Data is a plain recursive `{ id, label, children? }` tree; expand state is uncontrolled (`defaultExpandedIds`), selection and focus are internal. Each node that has children and is expanded owns exactly one branch overlay — an absolutely-positioned, aria-hidden `<svg>` sitting behind its `role=group` children block, sized to that block, coordinates in raw pixels (no viewBox, so measured centers map 1:1). That overlay is a main vertical stem from the parent's own chevron (the 'junction') down to the last child, plus one small 6px-radius quarter-turn elbow per child branching right toward its row; every path uses the pathLength=1 / strokeDasharray=1 / strokeDashoffset 0-or-1 trick so `stroke-dashoffset` alone animates the draw with no pixel-length math needed at the CSS layer, transitioning over 240ms on an ease-out-expo curve. Row centers are measured, never assumed — a ResizeObserver on the children block re-reads each direct child row's `getBoundingClientRect()` (a descendant expanding further down changes the height of everything below it, so the block's own box resizing is exactly the signal to remeasure and rebuild the path). The differentiator is the choreography: each child row's opacity/translateY(-4px) reveal is delayed by exactly how far along the path its own elbow sits, converted through the true inverse of the ease-out-expo curve (`-log2(1-f)/10 * duration`), not a linear fraction of the duration and not a generic per-row stagger — ease-out-expo is heavily front-loaded, so a linear delay would put every row's fade-in behind where the tip visually already is. Collapsing reverses the same dashoffset back to 1 (ease-in, 200ms) while rows fade out fast and together (120ms, no delay) — the root retracting into the junction, not a mirrored draw. Expansion is per-node-recursive: a nested node's own branch draws from its own junction the instant it opens, composing into what reads as one root system rather than one global path redrawn on every toggle. Full WAI-ARIA tree pattern: role=tree with an aria-label, role=treeitem rows with aria-expanded (only when the node has children), aria-selected, aria-level, and role=group wrapping each children block. There is deliberately no nested `<button>` or `<a>` anywhere in a row — the chevron is a decorative aria-hidden mark and a leaf gets a small dot bullet instead; the treeitem itself is the whole interactive surface, click or Enter/Space both selects and (for a folder) toggles. Keyboard model: ArrowDown/ArrowUp move focus over the flattened visible order; ArrowRight opens a closed node without moving focus, or dives to its first child if already open; ArrowLeft closes an open node in place, or climbs to the parent if already closed or a leaf; Home/End jump to the first/last visible row; type-ahead buffers printable characters for 600ms and jumps focus to the next row (wrapping) whose label starts with the buffer. Roving tabindex: exactly one row is ever tab-stoppable, moved imperatively alongside the focus state on every keyboard or pointer interaction. Stroke color is --border at rest; the one branch whose direct children include the currently focused row switches to --muted, so as you arrow through the tree the guide segment you're inside of visibly reads differently from the rest — the mechanism itself answering 'what belongs to what', which a uniform gray indent rule can't. Zero dependencies, DOM+SVG+CSS only, no canvas, every color a token (--background --foreground --muted --border --accent). prefers-reduced-motion (read live via matchMedia, not just CSS) skips the entrance/retract frames entirely: every open path renders at full length immediately and every child row appears already in its resting opacity/position, fully usable and legible without any of the draw. Differs from tag-input-backspace (a tag field whose one deviation is what Backspace does to an empty input) in domain and mechanism entirely — the only thing they share is that both are keyboard-first, token-only DOM widgets in this registry."
      }
    },
    {
      "name": "treemap-ascii-partition",
      "type": "registry:ui",
      "title": "Treemap ASCII Partition",
      "description": "A recursive slice-and-dice treemap where every rectangle's interior is filled with an ASCII density ramp keyed to its value instead of a colour scale. Clicking a rectangle with children descends into it, recomputing the partition over just its children; a breadcrumb (or Escape) climbs back out to any ancestor.",
      "files": [
        {
          "path": "registry/core/treemap-ascii-partition/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/treemap-ascii-partition.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "treemap",
          "partition",
          "chart",
          "data-viz",
          "ascii",
          "hierarchy",
          "drill-down"
        ],
        "instruction": "Build a recursive slice-and-dice treemap from a `data` prop (TreemapNode[], each `{id, label, value, children?}`). At any moment the component renders exactly one level: the root's children, or — once the user has descended — the children of whichever node is currently open. Layout is computed on a fixed 34x15 character-cell grid: `layoutSlice(nodes, x, y, w, h, dir)` walks the sibling list once, accumulating each node's share of the total value and deriving every boundary from the RUNNING cumulative fraction (`start + round((acc/total)*axisSize)`), never from independently rounding one node's own share — that cumulative-rounding is what keeps adjacent cells' edges flush with no 1px gap or overlap. The split axis alternates with depth: horizontal at the root, vertical one level down, horizontal again below that (classic slice-and-dice). Each rectangle is a real, focusable `<button data-treemap-rect>` (never a div with a click handler) sized and positioned from its cell-grid rect, with a plain `border-border` CSS border (crisp rectangle edges) and an interior fill of `ASCII_RAMP = ' .:-=+*#%@'` — the shared dithered-chart-family ramp — repeated across `value/maxValue-at-this-level` many ramp positions as literal monospace text rows (never canvas), so bigger value reads as denser/darker ink fill, not a colour hue. A small label+value badge sits over the fill with a translucent background so it stays legible regardless of density. Clicking (or Enter/Space on) a rectangle whose node HAS children descends: the current path array gains that node's id and the whole grid re-lays-out over just its children, filling the same 34x15 area again — descending is never a shrinking inset of the old rectangle. A leaf rectangle (no children) is still focusable/hoverable but the click is a no-op. A breadcrumb row above the grid shows Root plus every ancestor label, each a real button that truncates the path back to that depth; a dedicated `data-treemap-up` button (aria-label naming the level it returns to) renders ONLY once the path is non-empty, giving one-click access back exactly one level, and Escape does the same. Roving tabindex across the current level's rectangles: ArrowRight/Down and ArrowLeft/Up move focus between siblings by index, both wired through the same underlying focus-index state so keyboard and pointer never fight over which rectangle is 'active'. Hover and keyboard focus are visibly distinct from rest and from each other: hover brightens the border toward `--accent` and the fill ink from `--muted` to `--foreground`; focus additionally gets a `--accent` focus-visible outline. Tokens only — `--background --foreground --muted --border --accent`, applied as Tailwind utility classes (`bg-background`, `border-border`/`border-accent`, `text-muted`/`text-foreground`) bound to the same CSS custom properties, so both themes repaint correctly via the cascade with no JS token reads and no remount. No dependencies, no canvas — pure DOM text + CSS."
      }
    },
    {
      "name": "truncation-taper-fade",
      "type": "registry:ui",
      "title": "Truncation Taper Fade",
      "description": "Truncation without the ellipsis — an overflowing line's trailing characters crowd tighter and dissolve toward the clip edge instead of getting cut to '...'; hover or focus decompresses it to read the tail.",
      "files": [
        {
          "path": "registry/core/truncation-taper-fade/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/truncation-taper-fade.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "text",
          "truncation",
          "table",
          "typography",
          "accessibility",
          "variable-font",
          "hover"
        ],
        "instruction": "A drop-in replacement for ellipsis truncation in table cells, breadcrumbs, and file paths: instead of hard-clipping overflow with '...', the trailing run of characters nearest the clip edge visibly crowds together and dissolves, so a still frame communicates both 'there is more' and roughly how much before hovering or focusing to read it. MECHANISM: the only required prop is `text` (the full string, rendered as real, complete DOM text — never aria-hidden decoration standing in for a label). On mount, and on every container resize, a hidden absolutely-positioned measurer clone (same text, no taper styling) is compared against the visible container's clientWidth to detect overflow and its magnitude in pixels. If the line overflows, the estimated visible boundary (natural width divided by character count, projected against the container width) locates roughly where the clip edge falls, and the last min(maxTailChars, length) characters counting back from that estimated edge — not the literal last characters of a string many times longer than the box — receive per-span negative letter-spacing (the property that actually reclaims layout width, stepping from ~0 down to as much as -0.18em), a matching horizontal scaleX squeeze (down to as low as 0.84, transform-origin: right, reinforcing the narrowing by eye without affecting layout) and font-stretch (100% down to ~62%, included for forward-compatibility with a variable font that ships a real wdth axis — Geist Sans, as shipped in this registry, has only a wght axis per its fvar table, so font-stretch is presently a harmless no-op and letter-spacing/scaleX carry the actual visual effect). All three ease in via t^1.6 across the tapering run, so most characters barely compress and only the last few crush hard. How aggressively they compress is proportional to `hiddenRatio` (overflow px over container width): a barely-clipped cell tapers subtly, a badly-clipped one visibly crowds at its edge, so the resting frame alone communicates overflow magnitude. A mask-image linear-gradient (alpha fade, not a background-colored overlay, so it's correct against any surface color without sampling one) fades the final 3ch of the container to transparent, dissolving the very edge instead of hard-cutting it. DECOMPRESS: focus or hover immediately springs every tapered span back to letter-spacing 0 / font-stretch 100% / scaleX 1 (a 220ms ease-out-expo transition) and removes the mask-image, so the visible run reads normally. If the line still doesn't fit even fully expanded (the same overflow-px value measured at rest — decompression can't change how much text there is), after that spring settles the whole line glides left on an ease-in-out 650ms transition to reveal the tail, holds ~900ms, and glides back — repeating for as long as hover/focus is held, resetting the instant it isn't. ACCESSIBILITY: the element is a tab stop and carries `title` with the full string; because the visible characters are real text nodes (mask and width-axis styling are paint-only, not display/visibility changes), a screen reader already reads the complete string regardless of visual state — `title`/tab-stop status exist for sighted mouse and keyboard users to trigger the same decompress a hover gives, not to compensate for a11y-hidden content. The admitted cost: a keyboard user must Tab to each cell individually to check for a hidden tail, the same cost native ellipsis-plus-tooltip already has. Under prefers-reduced-motion every transition (taper spring, mask fade, peek glide) is disabled — decompression on focus/hover still applies instantly and fully, just without animation; legibility never depends on motion running. Zero runtime dependencies, DOM + CSS only, no canvas."
      }
    },
    {
      "name": "truncation-word-count",
      "type": "registry:ui",
      "title": "Truncation Word Count",
      "description": "Line-clamp that tells the truth: the fold control states the exact measured word count it hides ('+ 42 words'), the cut edge is a dashed selvage rule with a soft fade, and unfolding is an in-place height ease.",
      "files": [
        {
          "path": "registry/core/truncation-word-count/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/truncation-word-count.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "typography",
          "truncation",
          "clamp",
          "expand",
          "read-more",
          "text",
          "accessibility"
        ],
        "instruction": "A truncation primitive for prose where the cut is measured, not guessed. Every word renders in its own span, and after layout the component finds the first span whose offsetTop lands at or past the fold line (lines × computed line-height, with a fontSize×1.5 fallback when line-height is 'normal') — everything from that word on is the hidden remainder, so the control can state exactly '+ 42 words' instead of an ellipsis that admits something was cut but not how much. The measurement re-runs through a ResizeObserver, so reflowing the container (resize, font swap) keeps the count honest rather than stale. THE FOLD EDGE: when folded, the clip is a max-height with a mask-image fade over the last 1.5em (alpha-only mask, theme-independent) so the final visible line visibly runs out rather than guillotines, and the control row draws a dashed hairline rule — the selvage, the finished edge of the cut, thread-ends showing — leading into a mono '+ N words' button with a chevron. UNFOLD: activating the button eases max-height from the clamp height to the measured full height over 400ms ease-out (an in-place reflow of the same paragraph, never a jump or a remount), flips the chevron, and swaps the label to 'fold'; folding back is the same motion reversed. The button carries aria-expanded and a full-sentence accessible name ('Unfold 42 more words' / 'Fold text back'). HONESTY GUARANTEES: text that fits its clamp renders with no mask, no rule, and no control at all — a read-more that appears on nothing-to-read is decoration; and the full text stays in the DOM in both states, so assistive tech, find-in-page and copy always see everything — the fold is visual, never informational. REDUCED MOTION: the height ease and chevron rotation drop via motion-reduce, folding and unfolding become instant, nothing else changes. Colors are tokens only (--border for the selvage rule, --muted/--foreground for the control, --accent only as the focus ring). Pure DOM/CSS, zero dependencies, no canvas."
      }
    },
    {
      "name": "typing-indicator-trace",
      "type": "registry:ui",
      "title": "Typing Indicator Trace",
      "description": "Multi-user typing/presence as a live seismograph strip — one hairline trace per participant, scrolling leftward and spiking with their real keystroke cadence, never a looping three-dot bubble.",
      "files": [
        {
          "path": "registry/core/typing-indicator-trace/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/typing-indicator-trace.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "presence",
          "typing-indicator",
          "collaboration",
          "chat",
          "svg",
          "aria-live",
          "cadence",
          "multiplayer"
        ],
        "instruction": "Multi-user typing and presence as a live seismograph strip: one horizontal hairline row per participant, each a 1px currentColor (var(--foreground), stepped opacity per row from a fixed [1, 0.72, 0.5, 0.34] cycle so several rows are told apart without color) SVG polyline scrolling slowly leftward, spiking with amplitude proportional to that person's REAL keystroke cadence and decaying flat on pause, ending in a small terminal tick when they disconnect. The component holds NO rhythm of its own — unlike text-ekg-baseline's internal bpm timer, every number on screen originates from a real event the consumer feeds in via a ref handle: pulse(userId) for each throttled keystroke (call it from a real input handler; the component further buckets calls into ~5Hz ticks itself, so upstream throttling is optional, not required) and a controlled `users` prop of {id, name, status} where status ('typing' | 'idle' | 'disconnected') comes from the consumer's own presence protocol (a WebSocket event), exactly the same separation a real chat backend already has between 'someone typed a key' and 'someone's connection state changed'. MECHANISM: each row keeps a 31-slot ring buffer (30 visible ticks plus one incoming) in refs, ticked every ~200ms. Each tick: the group snaps back to translateX(0) instantly (no transition), the buffer shifts (oldest sample dropped), the pulse counter accumulated since the last tick is read and reset, target cadence = min(1, count / 2) and the row's smoothed amplitude lerps toward it at 0.6 when pulses arrived this tick or decays by a 2^(-10*0.2) ease-out-expo factor per tick when none did (roughly flat within ~1s of the last keystroke) — the new sample (amplitude times an alternating sign times a small 0.65-1.0 wobble, for organic up/down texture rather than a smooth envelope) is appended, the polyline's `points` attribute is rewritten once, and then a CSS transition animates the group's transform to translateX(-step) over the same 200ms, a standard treadmill scroll so nothing recomputes every point every frame. On disconnect the next tick emits one zero-amplitude sample flagged 'terminal', drawn as a small separate perpendicular tick mark that then scrolls off with the rest of the buffer like any other sample, and the row stops accepting further amplitude (flatlines) and its name label dims to var(--muted) — signaling 'gone', not merely 'quiet'. The strip fades at both edges via a CSS mask-image alpha gradient, not an opaque overlay, so it's correct against any surface. Names render as real, always-visible Geist Mono text at each row's baseline (never hidden or motion-only); the SVG graphic itself is aria-hidden, and an adjacent role=status aria-live=polite region (visually sr-only) announces only coarse transitions — 'Ana started typing', 'Ana stopped', 'Ben left' — computed from status changes in the `users` prop, never once per spike, and skipping the very first mount so a room with existing participants doesn't narrate its own initial state. The component never sees or exposes keystroke content: pulse() takes only a user id, no character or key data crosses the boundary, and cadence is bucketed to a 5Hz tick rather than raw timestamps, coarse enough to avoid becoming a keystroke-timing side-channel. Under prefers-reduced-motion every row's interval is skipped entirely — traces render a flat baseline once and stay there, a disconnected row still gets its static terminal tick (no animation needed to show it), and a typing row shows a small Geist Mono 'typing' text tag beside its name in place of any spike, so legibility never depends on motion running. Props: `users` (array of {id, name, status}, required — status drives announcements, dimming and the reduced-motion tag, not the animation itself, which idles or spikes purely from pulse() cadence), `className`. Ref handle exposes `pulse(userId)`. Zero dependencies, DOM + SVG + CSS only, no canvas."
      }
    },
    {
      "name": "undo-drift-bar",
      "type": "registry:ui",
      "title": "Undo Drift Bar",
      "description": "Deleted list row collapses in place to a thin labeled bar that drifts toward the trailing edge over the grace window — distance traveled is the only clock, and pressing the bar pulls it back.",
      "files": [
        {
          "path": "registry/core/undo-drift-bar/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/undo-drift-bar.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "list",
          "undo",
          "destructive",
          "delete",
          "accessibility",
          "micro-interaction"
        ],
        "instruction": "A list where deleting a row spring-collapses it, in place, to a fixed 28px bar: 1px solid --border, the item's title in Geist Mono at --muted, and a small inline return-arrow SVG, all inside a rounded-sm pill anchored to the row's leading edge. The row wrapper's height is driven by the Web Animations API — from its measured natural height down to 28px over 250ms on a no-overshoot glide curve (cubic-bezier(0.22,1,0.36,1)) — and once that collapse finishes, a second WAAPI animation translates the bar horizontally, linearly, from its start position to the row's trailing edge over graceMs (default 6000ms): distance already covered is time already spent, distance left is time left, with no numeric countdown rendered anywhere in this default state. Reaching full travel fades the bar over 200ms and finalizes the delete, removing the row from the list (siblings reflow, plain document flow, no absolute overlay). Hovering the bar, or moving keyboard focus into it, calls Animation.pause() on whichever WAAPI animation is currently live (collapse or drift); the countdown only resumes once both hover and focus have cleared, so a screen-reader or keyboard user is never raced by the clock. Clicking the bar, or Enter (focus moves to the bar the instant it exists, so Enter is immediate undo), reads the bar's live translateX fraction and the wrapper's live height, cancels both running animations, and starts a matched pair of ease-out-back overshoot animations (cubic-bezier(0.34,1.56,0.64,1), 420ms) snapping the bar back to its start position while the row grows back to its original height; simultaneously the bar's border flashes --border -> --foreground -> --border once via a 150ms color transition, then the row is restored. Deleting fires one visually-hidden aria-live=polite announcement naming the item and the grace window; restoring announces the restoration the same way. prefers-reduced-motion keeps the collapse (a state change, not a decoration) but never drifts the bar horizontally — it stays put at its start position, and a static, visible tabular-nums 'Ns' readout next to the title ticks down instead, because time-as-distance is an enhancement layered over a countdown, never its sole channel. The bar's own aria-label ('Deleted <title>, undo, N seconds left') updates on a coarse 1-second tick in every mode, reading the live Animation.currentTime rather than a separate, possibly-racy clock. Every ink is a token (--background/--foreground/--muted/--border/--accent, plus --surface for the list card), --accent appears only as the focus ring, no gradients, no canvas — DOM + WAAPI + CSS only. Props: items (id/title), graceMs, onDelete/onRestore/onExpire callbacks receiving the affected item, className. Differs from a shrinking-height ghost (which reads its own vertical extent as the clock) by keeping a constant-height bar and reading horizontal position instead — the deleted thing stays exactly where it was, at exactly the height it always will be, and only slides."
      }
    },
    {
      "name": "undo-ghost-row",
      "type": "registry:ui",
      "title": "Undo Ghost Row",
      "description": "A deleted list row leaves a dashed retinal ghost in its exact place that slowly closes over 8 seconds — remaining height is remaining time, and clicking it any time before it vanishes restores the row with a spring.",
      "files": [
        {
          "path": "registry/core/undo-ghost-row/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/undo-ghost-row.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "list",
          "undo",
          "destructive",
          "delete",
          "accessibility",
          "micro-interaction"
        ],
        "instruction": "A list where deleting a row swaps it, in place, for a same-height ghost container: 1px dashed --border, the item's title at 40% opacity, and a real button labeled 'Undo delete: <title>'. The ghost's height is driven by the Web Animations API directly — a linear animation from its measured natural height down to 0 over `ghostMs` (default 8000ms) — so the remaining height is legible as the remaining time, with no separate countdown UI needed. Siblings below ride the shrink smoothly since it's plain document flow, not an absolutely-positioned overlay. Hovering the ghost, or moving keyboard focus into it, calls Animation.pause() on the running effect; the timer only resumes once both hover and focus have cleared, so a screen reader user tabbed onto the undo button is never raced by the clock. Clicking the ghost at any point before it collapses reads its current live height off getBoundingClientRect, cancels the linear collapse, and starts a second ~380ms animation back up to full height on an ease-out-expo curve (cubic-bezier(0.16,1,0.3,1), the same curve used elsewhere in this registry for collapse/expand), restoring the row's data on finish. If the collapse instead reaches 0 naturally, the item is finalized as deleted and removed from the list. Deleting fires a visually-hidden aria-live=polite announcement naming the item and the undo window ('Deleted X. Undo available for 8 seconds.'); restoring announces the same way. prefers-reduced-motion holds the ghost at its full height indefinitely — there is no clock to race, so instead of an ephemeral auto-collapse it grows an explicit 'Dismiss' icon-button next to the Undo control, and only that click finalizes the delete. Every ink is a token (--background/--foreground/--muted/--border/--accent, plus --surface for the list card), --accent appears only as the focus ring, no gradients, no canvas. Props: items (id/title/subtitle), ghostMs, onDelete/onRestore/onExpire callbacks receiving the affected item, className. Differs from a press-and-hold destructive confirm (which gates the action before it happens) by being purely a post-destruction recovery window: the deletion already fired, the ghost is the undo affordance, and at rest — even to a sighted user glancing at the dashed outline and dimmed title — it reads as 'something was here' rather than a detached snackbar bolted to a screen edge."
      }
    },
    {
      "name": "validation-error-summary",
      "type": "registry:ui",
      "title": "Validation Error Summary",
      "description": "Failed-submit error handling as a contractor's punch list: a numbered defect summary pins to the top of the form, a hairline SVG leader line links each entry to its field on selection, and fixing a field strikes it through and counts down to self-dismissal.",
      "files": [
        {
          "path": "registry/core/validation-error-summary/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/validation-error-summary.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "form",
          "validation",
          "error-summary",
          "svg",
          "accessibility",
          "checklist",
          "leader-line"
        ],
        "instruction": "Wraps a real <form> whose failed submit renders as a contractor's punch list rather than a scattering of red borders. Each PunchListField carries its own validate(value) function; submitting recomputes every field and, if any fail, pins a numbered Geist Mono defect summary (role=alert, receives focus) above the form listing only the invalid fields in field order, each rendered as a real <a href=\"#field-id\"> so Tab and Enter both work with zero JS dependency. Clicking or activating an item scrolls the form to that field (scrollend-aware, with a timeout fallback so a browser without the event still animates), then draws a temporary hairline SVG leader line — an absolutely positioned <path> spanning from the list item's right edge to the field's bounding box, stroke-dashoffset drawn over 300ms, held about 1.5s, then faded over 300ms and removed — while the field's wrapper plays a single box-shadow pulse in the semantic --error color to confirm which control the line points at. Typing a fix into a field re-runs that field's own validate live; the moment it passes, its list entry draws a scaleX(0→1) strike-through rule over the label (house ease-out-expo, cubic-bezier(0.16,1,0.3,1)), the open-count badge in the summary header decrements with a short number-settle transition, and a separate aria-live=polite region announces \"Email resolved, 2 remaining\" independent of the decorative strike. Once the last item resolves, the whole summary panel collapses itself away via a grid-template-rows transition and the form returns to its plain resting state — no dismiss button, no leftover chrome. A second failed submit re-arms the same summary with a fresh set of defects. Distinct from a stock WCAG error-summary (a static list of links into the form) by three things that pattern never renders: the spatial leader-line link from summary entry to field, live strike-through as each field resolves, and self-dismissal at zero remaining — this is a live progress artifact, not a one-shot dump. Distinct from approval-inline-diff: approval-inline-diff is a single tool-call approved/denied exactly once and permanently collapsed; validation-error-summary is per-field, revalidates continuously as the user types, and re-arms indefinitely across repeated failed submits. Every field carries aria-invalid and aria-describedby pointing at its own inline error paragraph regardless of whether the summary is open, so the underlying error semantics don't depend on the decorative overlay at all. Leader lines and the strike rule are aria-hidden decoration only; keyboard users operate entirely through the anchor list and native field focus. Under prefers-reduced-motion the leader line and strike render at their end state instantly and the summary collapse/settle animations are skipped, with no behavior gated behind a timer."
      }
    },
    {
      "name": "validation-inline-wick",
      "type": "registry:ui",
      "title": "Validation Inline Wick",
      "description": "Inline per-field validation where the bottom border diffuses an error tint in from the exact offending character, like litmus paper — slow and pale while an async check is still pending, fast and decisive once it's definitely wrong, and it wicks back out the moment the field is fixed.",
      "files": [
        {
          "path": "registry/core/validation-inline-wick/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/validation-inline-wick.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "input",
          "form",
          "validation",
          "accessibility",
          "aria-live",
          "micro-interaction"
        ],
        "instruction": "A form field primitive whose bottom border is the litmus paper. Render a real, fully native <input type=text> — plain keyboard flow, untouched selection/autofill, standard controlled/uncontrolled value handling — stacked with two absolutely positioned 2px-tall div overlays at its bottom edge: a permanent baseline strip painted in the border token (the field's resting look, brightening slightly to a foreground-tinted hover state) and, on top of it, a decorative aria-hidden 'wick' div painted solid in the error token whose visibility is entirely controlled by its own mask-image. Two CSS custom properties, --validation-inline-wick-left and --validation-inline-wick-right (percentages), are registered via @property with syntax '<percentage>' specifically so they are animatable — an unregistered custom property cannot be transitioned by the browser at all, it just snaps, which is why @property is load-bearing here rather than decorative. The wick div's mask-image is a linear-gradient with hard stops built from those two properties (transparent, then black between left and right, then transparent again), so a CSS transition on the two properties alone drives real eased motion of the diffusion front with zero JS animation loop. A consumer supplies a validate(value) function that either returns a LitmusOutcome synchronously ('definitely wrong', known instantly — e.g. a regex or character-class rule) or returns a Promise<LitmusOutcome> ('still checking' — e.g. an async uniqueness or server-side check); a request-id ref guards against a stale async result landing after a newer keystroke superseded it. On an invalid outcome, an off-screen mirror <span> that copies the input's real computed font (font shorthand, letter-spacing) measures the pixel width of the value up to the offending character index, converts that to a percent of the input's own box accounting for its left padding, and that becomes the diffusion's origin — both --validation-inline-wick-left and --validation-inline-wick-right start collapsed at that single point and animate outward from it, never from the field's edge or center, so the stain visibly originates under the specific character that failed. Three read states, told apart by speed and extent, not just color: checking pulls the two edges out to a modest +-16% band around the origin over 1100ms, at 60% opacity with a slow breathing pulse animation layered on top — tentative, still-working, deliberately not committing to full coverage; invalid snaps the edges to the full 0%-100% width over a comparably fast 420ms at full opacity, no pulse — decisive, this is the daily-driver replacement for an instant red border flash, so it is still an eased diffusion, just a quick one, never a hard cut; valid (or the field emptied) collapses both edges back to wherever the origin last was, over 480ms, so the stain visibly recedes and converges on the exact point it grew from rather than shrinking from the field's outer edges. The wick carries zero semantic weight — aria-hidden throughout. The real, assistive-tech-facing state lives entirely on the plain input: aria-invalid mirrors whether the current outcome is invalid, and aria-describedby always points at one paragraph that both names the fault by position in plain language ('Character 4: space not allowed', 1-indexed for a human reader) and is itself aria-live=polite, so an async result — valid or invalid — is announced the moment it resolves; a sync rule resolving valid on an ordinary correct keystroke deliberately stays silent rather than spamming 'looks good' on every character, while an async check that does resolve valid announces 'Looks good.' once, because that is a result the user was genuinely waiting on. Differs from approval-inline-diff on purpose: approval-inline-diff is a form-level, one-shot, irreversible approve/deny gate over a whole payload; validation-inline-wick is per-field, positional, and fully reversible — the same field keeps validating, soaking and healing, for as long as the user keeps typing in it, with no terminal state at all. prefers-reduced-motion sets --validation-inline-wick-left/-right with transition:none so every state (checking's partial band, invalid's full soak, valid's collapse) still lands at its correct final extent instantly, with no animated diffusion and no pulse keyframe, so the field stays fully legible and usable either way. No canvas, no SVG — pure DOM and CSS, colors drawn only from --border, --foreground (hover only, low alpha), --accent (the input's own focus-visible outline, interaction-only) and the semantic error token (var(--error, #ea001d), matching this registry's existing convention for status color — stepper-needle, toast-gravity-stack, input-focus-membrane, sparkline-automaton all resolve the same token the same way since it isn't in globals.css yet), never a gradient wash between border and error — the visible color at any point is always one or the other, solid, never blended."
      }
    },
    {
      "name": "view-toggle-rails",
      "type": "registry:ui",
      "title": "View Toggle Rails",
      "description": "List/grid/board view switcher that draws a faint curved rail from every item's old slot to its new one and lets it ride that exact rail, releasing cards in reading order like cars cut loose over a classification-yard hump.",
      "files": [
        {
          "path": "registry/core/view-toggle-rails/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/view-toggle-rails.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "layout",
          "transition",
          "flip",
          "view-toggle",
          "kanban",
          "svg",
          "radio-group"
        ],
        "instruction": "A list/grid/board view switcher for one shared data set, where the transition itself is a diagram rather than an ambiguous blur. All three views are one absolutely-positioned layer inside a single container: list stacks items in one full-width column, grid flows the same items into 2-4 responsive columns of fixed-height rows, and board groups them into three status columns (To do / In progress / Done) purely through left/top placement — no item ever changes DOM parent or source order, so tab order and screen-reader reading order are identical in every view. Selecting a view via a real ARIA radiogroup (roving tabindex, arrow keys move and commit, Home/End jump to the ends) captures each visible item's bounding rect, commits the new layout INSTANTLY via React state (so assistive tech never observes an in-between shuffle), then in a layout effect measures every item's new rect and, for each one that actually moved, builds a slightly sagging cubic bezier between the two centers — the sag is a perpendicular offset toward screen-space +y scaled to 14% of the path length, clamped 6-30px, so it reads as a cable drooping under its own weight rather than a straight ruled line. That curve is drawn once as a hairline path in a fixed SVG overlay (stroke var(--border), stroke-opacity 0.3, 1px) UNDER the cards, and an equivalent local-space version of the same curve is set as the item's own `offset-path` (anchor pinned to its top-left corner, offset-rotate 0deg so the card itself never rotates); `offset-distance` then animates 0% to 100% along that exact rail on a 560ms cubic-bezier(0.16,1,0.3,1) ease-out-expo transition. Items are released in the data's original order (not their visual row/column) at a 15ms stagger, the 'cars over the hump' cut-loose rhythm, and 150ms after an individual item's own transition ends its rail fades out and is removed — rails clear one at a time as their cargo arrives, not all together. Interrupting mid-transition (clicking again before a prior switch has finished) is safe: the next transition measures whatever rect is currently on screen, mid-flight or not, and rebuilds fresh rails from there. Where `CSS.supports('offset-path', ...)` is false the whole rail apparatus is skipped and items instead run a plain FLIP translate between the same two rects with the same stagger and easing, just without a visible path. `prefers-reduced-motion` skips rails and offset-path entirely: the new layout still commits instantly and items get a plain 120ms opacity crossfade, nothing else animates. A visually-hidden aria-live region announces '<View> view, N items' after each change (skipped on mount). Pure DOM + one small SVG overlay, zero canvas; every stroke and surface tint is token-relative (var(--border), bg-foreground/[0.03], text-muted, text-foreground, focus ring in --accent only). Distinct from avatar-stack-flock (a boids flock that mills and resolves into a row on hover — motion with no destination-tracing, one continuous simulation) and reveal-ripple-tiles (a radial wave-driven reveal, motion with no per-item path at all): view-toggle-rails's signature is that every item's journey between two arbitrary layouts is a specific, individually-traceable, transiently visible curve, not a choreographed field."
      }
    },
    {
      "name": "voice-recorder-meter",
      "type": "registry:ui",
      "title": "Voice Recorder Meter",
      "description": "Voice-capture chip whose amplitude strip is real Web Audio, not Math.random() bars — flat at rest, live spectrum while listening, an honest error if the mic is denied.",
      "files": [
        {
          "path": "registry/core/voice-recorder-meter/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/voice-recorder-meter.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "voice",
          "audio",
          "recorder",
          "microphone",
          "input",
          "svg",
          "accessibility"
        ],
        "instruction": "A voice-capture chip built around a real Web Audio AnalyserNode — no component in this registry (or the prior-art it was researched against) fakes its bars with Math.random(), and this one doesn't either. Four states, told apart by the amplitude strip's MOTION rather than color, because this repo's token set has no red/green: idle is a perfectly static flat hairline (nothing has happened yet, so nothing moves); listening reads one real frequency bin per SVG bar straight off the live microphone every animation frame (analyser.fftSize is set to 2× the bar count so frequencyBinCount lands exactly on one bin per bar, smoothingTimeConstant 0.75 for the browser's own damping rather than hand-rolled easing); processing plays after capture stops while a consumer-supplied onCapture(durationMs) promise is in flight — a deterministic travelling sine sweep across the bars, explicitly NOT audio data, signalling 'still working' honestly; error is flat like idle but the strip takes one denial-shake and a legible reason renders in visible text below it (permission denied, no device found, device busy, no Web Audio support, or a client-side 8s timeout as a safety net against a stuck permission flow), so 'never started' and 'tried and failed' never look identical. The microphone is requested ONLY from the capture button's click handler, never on mount or on any other lifecycle event — getUserMedia only ever runs in response to an explicit user gesture. A denied or unavailable microphone renders that same honest error state rather than leaving a dead control, and the component renders and behaves correctly with zero microphone hardware and zero granted permission, which is exactly the environment its own screenshot verifier runs in. The capture control is a single toggle <button> with a computed aria-label (Start capturing / Stop capturing / Connecting / Processing / Retry) and aria-pressed reflecting listening state — never role=switch, so there's no separate aria-checked contract to satisfy. All state transitions announce through a polite aria-live role=status region; the error reason additionally renders as visible on-page text (not screen-reader-only) linked to the button via aria-describedby, because a legible error a sighted user can actually read is the whole point of the honesty requirement. Every bar is a fixed-geometry SVG rect; the only thing a requestAnimationFrame loop ever touches is a CSS transform: scaleY() written straight to each rect's ref (transform-box: fill-box, origin centred on the pill), never a height/y attribute rewrite — React state changes only on discrete mode transitions (idle/requesting/listening/processing/error), never per audio frame. Stopping capture always releases the MediaStream tracks and closes the AudioContext, on unmount too. prefers-reduced-motion keeps every state fully legible and operable: the listening meter still updates live but throttled to ~4/sec with a hard snap instead of a 60fps redraw (it's data, not decoration), the processing sweep is replaced by one static distinct bar pattern set once, and the error shake is dropped entirely — nothing is hidden, only the decorative motion is. Pure DOM + SVG + CSS, zero dependencies, zero canvas."
      }
    },
    {
      "name": "waveform-ascii-scrub",
      "type": "registry:ui",
      "title": "Waveform ASCII Scrub",
      "description": "An audio-style waveform rendered as columns of ASCII density glyphs with a draggable playhead — dragging (or hovering, or holding keyboard focus) subdivides the glyph columns nearest the cursor into finer sub-columns that re-ink the region at higher resolution, while the rest of the strip stays coarse.",
      "files": [
        {
          "path": "registry/core/waveform-ascii-scrub/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/waveform-ascii-scrub.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "waveform",
          "ascii",
          "audio",
          "canvas",
          "scrub",
          "slider",
          "drag",
          "accessibility"
        ],
        "instruction": "<WaveformAsciiScrub value? defaultValue? duration? onValueChange? label? className?> draws a fixed-width canvas waveform from a locally synthesised (no audio API, no network) layered-sine amplitude function sampled peak-in-bucket across 64 base glyph columns, each rendered as a Geist Mono density character from ' .:-=+*#%@' stacked into a symmetric bar mirrored above and below the centerline. At rest every column is exactly that one coarse sample. Dragging the accent playhead (a role=slider div overlaying the canvas, pointer and keyboard driven), hovering anywhere over the strip, or holding it in keyboard focus subdivides the ~6 base columns nearest that position into 3 finer sub-columns apiece, each independently re-sampling its own narrower time span — genuinely finer detail, not an interpolated blur — while columns outside that radius stay at base resolution; the radius eases in and out over roughly 250ms rather than snapping, and collapses back to zero (fully coarse) the instant nothing is hovered, dragged or focused. Keyboard: ArrowLeft/Right step 1%, PageUp/PageDown step 5%, Home/End jump to the ends; aria-valuenow/aria-valuetext (an M:SS derived from the duration prop) update on every commit. Colors read live from --foreground/--border/--accent via getComputedStyle, resynced on a documentElement class MutationObserver — no hardcoded hex. Under prefers-reduced-motion the resolution radius snaps instantly with no eased rAF loop. Zero dependencies."
      }
    },
    {
      "name": "wizard-canal-lock",
      "type": "registry:ui",
      "title": "Wizard Canal Lock",
      "description": "A wizard stepper built as a flight of canal locks: the next chamber's water level climbs to meet the current one, and only once they equalize does the shared gate split open for the active highlight to glide through — an invalid step's level visibly stalls short of the gate line instead of popping a toast.",
      "files": [
        {
          "path": "registry/core/wizard-canal-lock/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/wizard-canal-lock.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "stepper",
          "wizard",
          "nav",
          "form",
          "validation",
          "onboarding",
          "micro-interaction"
        ],
        "instruction": "Build a multi-step wizard stepper (LockFlight) whose steps are chambers in a flight of canal locks. RENDER: a real <nav aria-label> wrapping an <ol> of chambers, one per step — each chamber is a flex-1 <button> with overflow-hidden, height ~64px, containing an absolutely-positioned water layer (a tinted body plus a 1px --foreground-at-50%-opacity meniscus line) whose ONLY driven property is translateY, mapped from a 0-1 level: reached steps (index <= current) sit at level 1 (translateY 0, meniscus at the chamber's top edge); everything ahead sits at level 0 (translateY 100%, meniscus below the fold) except a transient 'stall' chamber. Each chamber past the first also renders the shared GATE — its left border split into two 1px halves (top half, bottom half) that sit flush (closed) until the boundary is reached, then translate -4px/+4px apart with a spring overshoot easing (cubic-bezier(0.34,1.56,0.64,1), ~260ms) delayed ~480ms so the split visibly waits for the levels to finish equalizing first. The active highlight is not a bar but a route: a single SVG path (in its own <svg>, never a direct child of the <ol>) runs the full width of the row, hugging the bottom of the chambers, dipping into a shallow rounded notch at every internal gate boundary and curling up around the outer border at the flight's own two ends (the first chamber's left edge, the last chamber's right edge) instead of terminating in a flat clipped stub — the shape is bounded by the svg's own viewBox plus an explicit overflow-hidden on the svg itself, not by a wrapper clipping an over-wide rectangle. That path is drawn twice: once in --border, always fully visible, as the flight's permanent conduit; once in --accent, windowed via a normalized (pathLength=1) stroke-dasharray/dashoffset to roughly one chamber's length of that same route (padded ~20% so a curl or notch is never cut off mid-curve, the pad extending the window's start earlier rather than its end past the path's full length so the last chamber's window never wraps the dasharray pattern back onto the first chamber), as the actual \"you are here\" marker — its dashoffset moves exactly one window per step, transitioning ~460ms with its own ~560ms delay ON A FORWARD ADVANCE ONLY so it visibly glides through the just-opened gate; back-navigation and direct jumps to already-reached steps use no delay. MECHANISM: clicking/Entering the chamber exactly one ahead of the current step (or the built-in Continue/Finish footer button) attempts to advance — if the current step's `valid` is not false, the target chamber's level animates 0->1 over 600ms ease-out-expo (cubic-bezier(0.16,1,0.3,1)), the gate then splits, the highlight glides through, and the step commits; if the current step IS invalid, the target chamber's level still climbs on the SAME ease-out-expo curve but only to that step's `progress` (default 0.35) and holds there — the visible stall below the gate line is the primary explanation, not a toast — while a lightweight one-shot 'deny' flash (a remounted key={nonce} span running a 420ms keyframe: rises 3px, fades) retriggers on every repeated attempt so the denial reads as motion, not just a static frame. Clicking any chamber more than one step ahead of current (or beyond the highest step ever reached) is flatly refused — the gate mechanism only ever operates on the single adjacent boundary, one lock at a time — and produces no water animation, only the status line. Clicking any already-reached chamber (index <= the highest index ever committed) jumps directly; if that jump moves current BACKWARD, every chamber ahead of the new current position drains from level 1 back to 0 over a heavier 950ms ease-in (cubic-bezier(0.7,0,0.84,0)) and its gate reseals with a quick 200ms ease-in (no spring, no delay) — going back is a slower, heavier motion than advancing, deliberately. A11Y: current step's button carries aria-current='step'; a step is 'locked' (clickable but aria-disabled='true', with aria-describedby pointing at a per-step sr-only span reading e.g. 'Step 3 of 5, blocked: email required' for the immediate-next step, or 'Step N of M is locked until step K is reached' for anything further out) whenever it hasn't been reached and isn't current; a permanently-mounted <p role='status' aria-live='polite'> (kept in the DOM at all times for reliable announcement, invisible via opacity:0 until it has content) mirrors every attempt/jump/refusal in plain language for anyone who can't see the water. Arrow Left/Right (and Up/Down as aliases) move FOCUS between chamber buttons via refs, Home/End jump to the first/last chamber; Enter/Space activate through the browser's native button behavior, no custom handling needed. REDUCED MOTION: every transition duration collapses to 0 (levels snap instantly to their resting position), the gate halves stay permanently at translateY(0) regardless of open/closed state (the split is skipped entirely, reading as a plain divider), the highlight jumps with no delay, and the deny keyframe is neutralized to a static, fully-transparent state via a scoped @media query — the component stays fully usable, just instant. TOKENS: every visible ink is --foreground/--border/--accent/--background/--surface/--muted, several at low fractional opacity (bg-foreground/[0.06], /50, /90) — no hex, no rgb()/hsl(), no Tailwind palette classes; both themes render correctly since nothing is baked in. Pure DOM + CSS transitions driven by React state, no canvas, no rAF loop — every animated value is a plain inline style recomputed on render, and direction-dependent duration/easing (forward-fill vs back-drain vs stall-fill) is chosen per render by comparing the new index against a ref of the previous one."
      }
    },
    {
      "name": "wizard-dovetail",
      "type": "registry:ui",
      "title": "Wizard Dovetail",
      "description": "A multi-step form whose completed steps physically interlock: each step contributes an SVG dovetail chip to a rail, sliding in to mate on a spring when valid, or bouncing off the joint and returning when it isn't.",
      "files": [
        {
          "path": "registry/core/wizard-dovetail/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/wizard-dovetail.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "form",
          "wizard",
          "multi-step",
          "checkout",
          "onboarding",
          "validation",
          "svg",
          "accessibility"
        ],
        "instruction": "Build a multi-step form (`steps`: {id, title, fields}[], each field {id, label, type, required, placeholder, autoComplete, validate}) where only one step's fields are interactive at a time — a real `<fieldset>`/`<legend>` region per step — while a rail above it accumulates a physical summary of every completed step. Each rail slot is a fixed-size SVG shape (104x34) cut with dovetail edges: a flared trapezoid tail protrudes past its trailing (right) edge, and an identical trapezoid notch is cut into its leading (left) edge as a socket, both drawn from one shared path-generator so an adjacent slot's tail would nest exactly into this one's socket — the socket is DEEPER-wide than its mouth (the classic dovetail profile), which is what makes the joint read as something that resists being pulled straight apart rather than a plain rounded chip. Every slot always renders its socket outline (1px --border stroke, dashed while not yet joined) as a constant, visible 'not yet joined' gap; a step's fields validating on Next/Submit fills that slot with a solid --surface chip animated in via a CSS keyframe (translateX from 40px to 0 plus a fade-in) — ease-out-expo for the bulk of the travel, then the final 12px settles on a spring-shaped cubic-bezier(0.34,1.56,0.64,1) with a small overshoot past the seat before resting, so the joint visibly mates; on failed validation the same chip flies in, decelerates to the joint, then recoils off it — a decaying two-swing 4px bounce — and fades back out to its start offset, leaving the socket empty/dashed exactly where the outstanding field is. A step's status can regress: editing an already-seated step's fields back into invalidity and resubmitting un-seats its chip (back to the dashed socket) before the same reject animation plays — the rail never shows a joint that isn't currently true. The step currently being worked has its rail slot's pin (left/socket) edge redrawn on top in --accent, but ONLY while focus is genuinely inside the form (a `:focus-within` rule scoped to a shared wrapper around both the rail and the fieldset) — moving focus elsewhere reverts it to --border, so the accent never reads as a static label. On invalid submit, a `role=\"alert\" aria-live=\"assertive\"` summary above the fields lists every failing field's message, each field also gets its own error paragraph directly below it via `aria-describedby` plus `aria-invalid`, and focus moves straight to the first invalid field — the live summary and the moved focus fire together, not gated behind the ~700ms bounce animation finishing. Next and Back are real buttons (`type=\"submit\"` — the form has `noValidate` and does its own validation on submit so Enter in any field submits the step honestly; Back is `type=\"button\"` and only shown past the first step, never validates, never un-seats a step just from viewing it). The rail is one `<ol aria-label=\"Steps\">`; each `<li>` carries `aria-current=\"step\"` only for the active index and a visually-hidden 'Step N, {title}, {complete|current|not started}' string — the SVG itself is `aria-hidden`, so the rail's only tab stops are none: it contributes zero interactive elements and tab order flows straight from one field to the next to Back/Next, never detouring into the decorative rail. The last step's button label is the `submitLabel` prop (default 'Submit'); completing it replaces the fieldset with a plain resting confirmation panel and calls `onComplete(values)`, while the rail keeps showing every seated chip. Under prefers-reduced-motion the seat/reject keyframes are stripped entirely (checked via matchMedia with a change listener, plus a CSS media-query belt-and-suspenders) — a step's chip appears already seated or the socket stays empty, immediately, with no travel, bounce, or fade, fully legible and usable either way. Every color is `var(--background|--surface|--border|--foreground|--muted|--accent)`, zero hex, both themes render; zero dependencies, DOM + SVG + CSS only, no canvas. Differs from progress-narrated (a determinate progress bar whose fill narrates continuous phase completion with a typed caption and a milestone ledger docking below the track) by being validity-gated and discrete rather than continuous: wizard-dovetail's steps don't advance a percentage, they either mechanically join the rail or get physically rejected back to the form with the gap left exactly where the invalid field is. Differs from drill-down-spines (drill-down navigation where a pushed level compresses into a permanently clickable spine you can pop back into) by having no navigation history to click back into — the rail is a read-only, non-interactive summary of validity, not a set of live route targets."
      }
    },
    {
      "name": "ascii-engraving-contour",
      "type": "registry:ui",
      "title": "ASCII Engraving Contour",
      "description": "ASCII rendered as a real engraving — glyph density traces concentric contour bands through an orbiting metaball field instead of mapping brightness straight to weight, so the plate reads as ridged hatch linework over a soft rounded subject, with a pointer-driven polish trail and a self-healing burin-slip glitch.",
      "files": [
        {
          "path": "registry/loud/ascii-engraving-contour/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ascii-engraving-contour.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "canvas",
          "generative",
          "engraving",
          "cursor",
          "glitch",
          "monochrome"
        ],
        "instruction": "Build a full-bleed Canvas 2D engraving effect. A scalar field is the sum of four metaballs (v = r^2/(dist^2+1) per ball) whose centers orbit slowly around fixed anchors near the canvas center at different radii, speeds and phases, sized relative to min(width,height) so the composition scales with the container. Density is NOT luminance — for each grid cell (default 11px), compute the field value v, fade cells below v=0.16 to fully transparent and reach full opacity by v=0.42 (a soft plate edge), then take cycle = v * ringDensity (default 0.09) and phase = fract(cycle): if phase exceeds ringWidth (default 0.4) the cell is a gap between hatch lines and draws nothing, otherwise it draws a glyph from a 9-step density ramp ' ·-+=*#%@' chosen by how close phase sits to the ring's center, at alpha scaled by both the plate-edge fade and the ring-center closeness. This produces true concentric contour hatching across the metaball surface rather than a brightness photograph rendered in text. A per-cell polish buffer (Float32Array) decays by 0.965 every frame and gets set to max(current, 1 - distance/polishRadius) for any cell within polishRadius (default 130px) of the live pointer position; that value multiplies into ringDensity locally (up to +85% by default), so a cell the cursor has recently crossed hatches measurably finer for about a second before relaxing back to the ambient coarseness — a burnished trail, not an instant halo. Independently, on a jittered interval (default ~2600ms), a burin-slip glitch picks one or two random rows and offsets only the x position glyphs are drawn at (not the field sample) by a random few cells for roughly 90-200ms before self-healing, reading as the plate momentarily slipping under the tool. Direct-DOM rAF loop, zero React state on the hot path; the metaball centers still animate under prefers-reduced-motion in the sense that a single frame is drawn at t=0 with no orbit, no polish decay, and no glitch scheduling. Glyph ink is `canvas.style.color` (the live --foreground token via a `text-foreground` class), re-read on every <html> class mutation through a MutationObserver so a theme toggle repaints the static reduced-motion frame correctly without a remount. A ResizeObserver drives the resize/regrid path. Props: cellSize, ringDensity, ringWidth, polishRadius, polishStrength, glitchIntervalMs (0 disables the slip). Decorative canvas, aria-hidden, no dependencies."
      }
    },
    {
      "name": "ascii-globe-spin",
      "type": "registry:ui",
      "title": "ASCII Globe Spin",
      "description": "A rotating ASCII globe with recognizable continent landmasses and a day/night terminator sweeping across it as it spins, drag-rotated with inertia and framed by a box-drawing HUD printing the lat/lon under the cursor.",
      "files": [
        {
          "path": "registry/loud/ascii-globe-spin/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ascii-globe-spin.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "3d",
          "canvas",
          "cursor",
          "globe",
          "physics"
        ],
        "instruction": "A unit sphere is swept every frame over a lat/lon grid (sample counts scaled to render-area pixel size, clamped 70-140 lat steps and 200-360 lon steps), each sample rotated by a single yaw angle (lon + rotY) and projected ORTHOGRAPHICALLY into isotropic pixel space (both axes scaled by one K1) around the render area's center — the character-grid quantization (col = round(px/cellW), row = round(py/cellH)) happens only after that projection, the same aspect-correction discipline as ascii-torus-donut, so the globe reads as a circle rather than an ellipse against the non-square mono cell. Samples with z<=0 (back hemisphere) are skipped before any projection work; surviving samples compete for their screen cell on z (bigger = nearer) in a Float32Array reset to -2 every frame. Continents are a coarse hand-authored lat/lon RECTANGLE mask (11 boxes approximating North America, a Central American land bridge, Greenland, South America split into a wide upper and a tapering southern box, Africa, Europe/western Asia, main Asia, the Russian far east, Australia, and an Antarctic lat band) — not traced coastlines; a `ponytail:` comment in the source names this fidelity ceiling explicitly (recognizable silhouettes, not an accurate map) and explains why: no geo dependency, no network fetch, and no resolution in a character grid this coarse would reward tracing real coastlines anyway. Illumination is a Lambertian dot product between the sample's unit-sphere position and a light direction FIXED in world space (independent of the spin angle), producing a day factor; the night side is forced toward the sparse end of a 12-step density ramp ('.,-~:;=!*#$@') regardless of land or water (with a small ambient floor so the terminator reads as a gradient, not a hard clip) — since illumination is computed in world space and the sample's own longitude is offset by the live rotation angle, spinning the globe visibly sweeps the terminator across the surface rather than dragging a static shading pattern with it. DRAG: pointerdown/move/up (mouse and touch via Pointer Events, touch-action:none, setPointerCapture wrapped in try/catch) maps horizontal drag only to the yaw angle (0.011rad/px), tracking a smoothed (EMA 0.35) release velocity that becomes spin momentum on release (clamped to 10rad/s) and relaxes every frame toward a fixed idle omega (0.22rad/s) via one exponential decay (rate 1.5/s) — it never comes to a dead stop. READOUT: hovering the render area inverts the same orthographic projection and rotation to solve the lat/lon under the cursor (asin/atan2 against the pointer's normalized position, un-rotated by the live spin angle), printed live into a box-drawing HUD frame's reserved readout row as individual canvas glyphs — like ascii-torus-donut, no trig-derived value ever reaches DOM text, so the SVG/trig SSR hydration-mismatch class of bug never applies. Direct-DOM rAF loop, zero React state on the hot path; depth and char buffers are typed arrays allocated once per resize and reset (not reallocated) every frame. The mono cell is measured via an offscreen canvas after document.fonts.ready with cellH set explicitly to font size. Ink reads getComputedStyle(canvas).color for the globe and the --muted token for the frame/readout, re-derived on a documentElement class MutationObserver. prefers-reduced-motion renders one static frame at a non-degenerate rotation (0.4rad) with the rAF loop and drag listeners skipped entirely, but keeps hover tracking live so the lat/lon readout still updates against the still frame. Props: cellSize (grid cell px, default 13), className."
      }
    },
    {
      "name": "ascii-knot-volumetric",
      "type": "registry:ui",
      "title": "ASCII Knot Volumetric",
      "description": "A genuine volumetric (3,2) torus knot rendered in ASCII: a solid tube swept along a self-crossing parametric centerline via a Frenet frame, with real per-character depth buffering resolving the strand crossings, Lambertian shading, and drag-rotate inertia.",
      "files": [
        {
          "path": "registry/loud/ascii-knot-volumetric/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ascii-knot-volumetric.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "3d",
          "canvas",
          "cursor",
          "knot",
          "physics"
        ],
        "instruction": "A (p,q) = (3,2) torus knot's CENTERLINE is evaluated analytically every resize — C(t) = ((R + r*cos(qt))*cos(pt), (R + r*cos(qt))*sin(pt), r*sin(qt)) for t across [0, 2*pi) — which, unlike a torus's independent (theta,phi) sweep, is a single closed curve that winds 3 times around the ring axis and 2 times around the tube before closing, and for coprime p,q with |p-q|>=2 that curve GENUINELY CROSSES OVER AND UNDER ITSELF when projected to 2D: that self-crossing is the entire visual difference between a knot and a donut, not a change of constants on the same surface. At each sampled t a Frenet frame (tangent, normal, binormal) is built from finite differences (central difference for tangent, the acceleration component perpendicular to the tangent for the normal, cross product for the binormal), with a fallback to an arbitrary perpendicular vector at near-zero curvature — this is what lets a small TUBE of fixed radius be swept around the centerline with a second angle phi, producing a genuinely VOLUMETRIC solid surface rather than a wireframe curve. Frame construction happens once per resize into typed-array caches (not per animation frame), so the hot draw loop only pays for rotating and projecting already-built samples. Projection reuses the same isotropic pixel-space technique as this registry's other ASCII-3D pieces — project to pixel space with a single scale K1 for both axes, THEN quantize to the (roughly 2:1 tall/narrow) monospace cell grid, never the reverse, which is what keeps circular cross-sections circular. Depth resolution is a per-cell 1/z competition in a Float32Array reset every frame; critically, this SAME mechanism is what resolves the knot's self-crossings for free — wherever two different (t,phi) samples project to the same screen cell, the nearer one simply wins, so the near strand correctly occludes the far one from any rotation without any extra crossing-detection logic. A Lambertian term (tube surface normal, rotated identically to the point, dotted against a light direction fixed in world space) indexes a 12-step density ramp ('.,-~:;=!*#$@') in a parallel Uint8Array, with continuous alpha layered on top for tonal depth beyond the 12 discrete glyphs. Sample counts are capped low on purpose (160-260 steps around the knot, 10-16 around the tube, both scaled to render-area size) since a torus knot's crossings read clearly at a fraction of a torus's own sample density, keeping the per-frame cost small under headless/software rendering. DRAG: pointerdown/move/up (Pointer Events, touch-action:none) map horizontal drag to yaw and vertical drag to pitch, tracking a smoothed (EMA 0.35) angular velocity that becomes release inertia (clamped 13rad/s), relaxing via one exponential decay (rate 1.6/s) toward a fixed idle spin (0.14rad/s pitch, 0.36rad/s yaw) — it never comes to a dead stop, it settles back into the same idle spin it started at. A box-drawing HUD frame with a centered '(3,2) torus knot  a _°  b _°' readout is drawn straight into the canvas grid, never DOM text, so no trig result ever reaches SSR'd HTML. Direct-DOM rAF loop, zero React state on the hot path. Glyph ink reads getComputedStyle(canvas).color for the knot and the --muted token for the HUD, both re-derived on a documentElement class MutationObserver. prefers-reduced-motion renders one static frame at a non-degenerate angle and skips the rAF loop and pointer listeners entirely. Props: cellSize (grid cell px, default 13), className. Zero dependencies."
      }
    },
    {
      "name": "ascii-torus-donut",
      "type": "registry:ui",
      "title": "ASCII Torus Donut",
      "description": "The donut.c homage done properly — a rotating 3D torus with real per-character depth buffering and Lambertian shading, spinning on two axes, drag-rotated with inertia, framed by a box-drawing HUD printing a live rotation readout.",
      "files": [
        {
          "path": "registry/loud/ascii-torus-donut/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ascii-torus-donut.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "3d",
          "canvas",
          "cursor",
          "torus",
          "physics"
        ],
        "instruction": "A parametric torus (tube radius R1=1, center radius R2=2.15, viewer distance K2=5, world units) is swept every frame by two nested loops over theta (around the tube) and phi (around the ring), sample counts scaled to the render area's pixel size (clamped 80-150 theta steps, 220-420 phi steps) so the surface stays solid without gaps at both small cards and large windows. Each sample is rotated by two Euler angles A (pitch) and B (yaw) with the classic donut.c rotation formulas, then projected into ISOTROPIC PIXEL space around the render area's center using a single scale K1 for both axes — critically, the character-grid quantization (col = round(px/cellW), row = round(py/cellH)) happens only AFTER that pixel-space projection, never before, which is what keeps the torus circular instead of an ellipse given the mono cell is roughly 2:1 tall/narrow. Depth resolution is a per-cell competition on 1/z (bigger = nearer) written into a Float32Array reset every frame; a Lambertian term (surface normal at (theta,phi), rotated identically to the point, dotted against a light direction fixed in world space, independent of A/B) indexes a 12-step density ramp ('.,-~:;=!*#$@') stored in a parallel Uint8Array, and only samples with a positive dot product ever write. The final blit pass walks the render-area cells once, applying continuous alpha keyed to the ramp index for tonal depth beyond the 12 discrete glyphs. DRAG: pointerdown/move/up (mouse and touch via Pointer Events, touch-action:none, setPointerCapture wrapped in try/catch for synthetic pointers) map horizontal drag to yaw and vertical drag to pitch directly (angle = base + delta*0.012rad/px), tracking a smoothed (EMA 0.35) angular velocity from the last move's delta/dt; on release that velocity (clamped to 14rad/s) becomes the spin, and every frame thereafter it relaxes toward a fixed idle omega (0.16rad/s pitch, 0.42rad/s yaw) via one exponential decay (rate 1.6/s) — so the torus never comes to a dead stop, it settles into the same idle spin it started at. FRAME/HUD: a box-drawing border (┌─┐│└─┘) is drawn straight into the canvas grid around the render area, with row 1 reserved for a live centered readout ('a 083.4°  b 271.9°') printed as individual glyphs — the rotation angles never reach DOM text or SSR'd HTML, sidestepping the SVG/trig hydration-mismatch class of bug entirely by construction, not by rounding. Direct-DOM rAF loop, zero React state on the hot path; depth and char buffers are typed arrays allocated once per resize and reset (not reallocated) every frame. The mono cell is measured via an offscreen canvas after document.fonts.ready with cellH set explicitly to font size, matching the aspect-correction discipline above. Glyph ink reads getComputedStyle(canvas).color for the torus and the --muted token for the frame/readout, both re-derived on a documentElement class MutationObserver. prefers-reduced-motion renders one static frame at a non-degenerate angle (0.6, 0.9 rad — never (0,0), which reads ambiguously edge-on) and skips the rAF loop and all pointer listeners entirely. Props: cellSize (grid cell px, default 13), className."
      }
    },
    {
      "name": "background-ascii-plasma",
      "type": "registry:ui",
      "title": "Background ASCII Plasma",
      "description": "A full-bleed animated ASCII plasma field — three octaves of traveling value noise mapped through a density ramp to a monospace glyph grid, with the pointer warping the field outward and letting it relax back like dragging a finger through liquid.",
      "files": [
        {
          "path": "registry/loud/background-ascii-plasma/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-ascii-plasma.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "background",
          "canvas",
          "cursor",
          "noise",
          "plasma"
        ],
        "instruction": "Build a full-bleed Canvas 2D ASCII field: a scalar noise field is sampled per grid cell from three summed sine octaves — octave A is slow and low-frequency/isotropic (the big drifting bands, ~0.045-0.05 spatial frequency, ~0.1-0.16 rad/s), octave B travels at an angle to A at roughly 2.5x the frequency and creates the visible interference bands where the two sets cross, and octave C is a fast, low-amplitude fine ripple that keeps the surface alive up close. The weighted sum is rough-normalized to 0..1 and gamma-shaped (pow 1.6) so the noise floor deepens to empty space and only crests read as dense glyphs — real tonal depth rather than mush. The field maps to a density ramp (space, period, apostrophe, backtick, comma, colon, dash, equals, plus, asterisk, hash, percent, at) indexed by luminance, with per-frame alpha additionally quantized into 6 buckets and drawn in 6 passes (one ctx.globalAlpha set per pass, not per glyph) to bound per-frame canvas state changes at full-viewport cell counts. THE POINTER DOES NOT PAINT, IT WARPS: instead of brightening nearby cells, each cell's noise-sample coordinate is displaced away from the cursor by an amount that falls off with distance (gaussian, sigma 9 grid cells) and scales with a decaying 'energy' scalar that rises with pointer movement speed and relaxes exponentially with a 1.3s time constant — so a cell samples noise from further out in the direction away from the cursor, reading as the field being physically pushed outward, and it eases back to its resting pattern over roughly a second or two once the cursor stops, exactly like dragging a finger through a liquid surface. The eased cursor position itself lerps toward the raw pointer at 0.15/frame for a trailing feel. Direct-DOM rAF loop, zero React state on the hot path; the char-index and alpha-bucket buffers are Uint8Arrays allocated once per resize and overwritten every frame, never reallocated. The mono cell is measured via an offscreen canvas's measureText AFTER document.fonts.ready (a fallback-font measurement bakes in the wrong grid aspect until reload) with cellH set explicitly to the font size rather than derived from any bounding box, so the grid ratio is a number the component controls outright. Glyph color reads getComputedStyle(canvas).color live via a font-mono class on the canvas and re-derives on a documentElement class MutationObserver for theme flips. prefers-reduced-motion renders exactly one static field frame at t=0 with no pointer listeners bound. Props: cellSize (grid cell px, default 12), className."
      }
    },
    {
      "name": "background-gradient-shader",
      "type": "registry:ui",
      "title": "Background Gradient Shader",
      "description": "Full-bleed animated gradient background — a WebGL shader drifts three theme-derived colors through a domain-warped noise flow field into a simple 3-stop ramp, reading as a calm moving current of color rather than a shader demo.",
      "files": [
        {
          "path": "registry/loud/background-gradient-shader/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-gradient-shader.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "webgl",
          "shader",
          "gradient",
          "background",
          "hero",
          "ambient",
          "decorative"
        ],
        "instruction": "A full-bleed, aria-hidden, pointer-events-transparent WebGL background (children are not wrapped — callers overlay their own content, matching a hero-background pattern). A single fragment shader samples 4-octave value-noise fbm at a slowly drifting, domain-warped coordinate (the sample point is displaced by a second fbm evaluation before the final lookup) and maps the scalar result through a linear 3-stop color ramp. Default colors, re-derived on every theme flip, are --background lightened 12% toward --foreground (stop 1), --accent (stop 2), and --background (stop 3) — read via getComputedStyle(document.documentElement) at mount and re-read on a MutationObserver watching documentElement's class attribute. An optional `colors` prop (2-4 hex strings) overrides the token-derived palette entirely for a fixed look independent of theme. `speed` (default 1) scales the flow's internal clock; `scale` (default 1) is the noise zoom — smaller values read as broader, slower-moving fields. The rAF loop pauses on document visibilitychange and under prefers-reduced-motion, drawing one static frame instead (still theme-reactive: a MutationObserver-triggered redraw still fires while paused). Backing store is dpr-clamped to 2, resizes via ResizeObserver with a zero-size guard, and the canvas listens for webglcontextlost (cancels the loop, prevents default so the browser doesn't drop the tab context permanently) and webglcontextrestored (recreates the program and resumes). On GL init failure the container simply stays transparent rather than crashing. Program, both shaders and the vertex buffer are deleted on unmount and on context loss."
      }
    },
    {
      "name": "border-chrome-ring",
      "type": "registry:ui",
      "title": "Border Chrome Ring",
      "description": "Wraps any element in a thick tube of molten chrome — a WebGL shader fakes a 3D torus cross-section across the band width (not a flat gradient), lights it with a rotating banded environment reflection so bright/dark stripes sweep around the collar, and perturbs the band's own boundary with noise so it wobbles like liquid metal instead of holding a fixed shape. Hovering biases a specular hit toward the cursor; pressing bulges the metal outward and pools it toward the pointer; releasing sends a ripple once around the ring.",
      "files": [
        {
          "path": "registry/loud/border-chrome-ring/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/border-chrome-ring.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "webgl",
          "shader",
          "metal",
          "chrome",
          "ring",
          "border",
          "decorative",
          "avatar",
          "button"
        ],
        "instruction": "A wrapper component (children prop) that surrounds its content with an animated liquid-metal tube roughly `ringWidth` (default 14) CSS px wide. One WebGL canvas per instance is sized to the wrapper's full box (child + `ringWidth` padding on every side); the fragment shader computes a rounded-rect signed distance field whose outer corner radius is `radius` (default 16, css px — set this to match the child's own border-radius) plus `ringWidth` for variant='pill', or min(w,h)/2 for variant='circle'. That field is perturbed by a domain-warped value-noise fbm (own hash constants) before the band mask is derived from it, so the boundary itself wobbles. Inside the band, the cross-section position (0 at the outer edge, 1 at the inner edge) is remapped to [-1,1] and used to fake a 3D torus normal (a semicircular profile across the band width, further perturbed by a second noise term for anisotropic brushed-metal streaking); a numeric central difference of the same noise-perturbed field supplies the in-plane component of that normal, so shading and boundary distortion move together. The normal is lit by a rotating banded ramp through four achromatic stops (dark/mid/light/hot) chosen at mount by the background's own luminance — dark theme leans on bright specular bands, light theme leans on the dark occlusion crease at each edge of the tube — plus two rotating pow(cos(angle-time), n) specular lobes tinted 14% with --accent. Pointer interaction: hovering eases in a specular hit locked to the cursor's angle around the ring; pressing eases in a bulge that pushes the boundary outward and locally thickens the band toward the pointer (surface-tension pooling); releasing fires a decaying pulse that travels once around the ring from the release point. All easing is exponential (physics-style), computed once per frame from real elapsed time. `strength` (0..1, default 1) scales final alpha and is read from a ref so it updates without recreating the GL context; `paused` freezes the shader on its current frame via a lightweight 120ms poll rather than a full context teardown. Colors are read via getComputedStyle(document.documentElement) at mount from --background, --foreground, --muted, --border and --accent, and re-read on a MutationObserver watching documentElement's class attribute, repainting one frame immediately so a theme flip is never stale. The rAF loop pauses on document visibilitychange and under prefers-reduced-motion (draws one static frame at a fixed non-zero time offset chosen so a specular hit is visible, instead of a possibly-flat t=0 frame). Backing store is dpr-clamped to 2, resizes via ResizeObserver with a zero-size guard, and the canvas listens for webglcontextlost (cancels the loop, prevents default) and webglcontextrestored (recreates the program and resumes) so a lost context never leaves a dead loop or a permanently blank ring. On GL init failure the component still renders its children with no ring rather than crashing. Program, both shaders and the vertex buffer are deleted on unmount and on context loss. variant='pill' uses `radius` for its own corner rounding (the caller should give the child the same `radius` so the tube reads as constant-thickness padding); variant='circle' forces a true disc, intended for wrapping an avatar or icon button."
      }
    },
    {
      "name": "border-electric-arc",
      "type": "registry:ui",
      "title": "Border Electric Arc",
      "description": "An electric border for a CTA — the outline crackles like a live wire via feTurbulence displacement, micro-arcs spark off the corners, and near the cursor the stroke opens a gap that tracks the pointer with an arc jumping across it. Pressing discharges the wire: a bright flash, then two seconds spent and calm.",
      "files": [
        {
          "path": "registry/loud/border-electric-arc/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/border-electric-arc.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "cta",
          "button",
          "border",
          "electric",
          "svg",
          "feTurbulence",
          "cursor",
          "hover",
          "glow"
        ],
        "instruction": "Build an electric-border CTA where a real <button> (accessible name intact, default label 'Get Started') sits inside a position:relative wrapper and every effect layer is an absolutely-positioned aria-hidden pointer-events-none SVG sibling — nothing ever intercepts a click, hover or Tab meant for the button, and the button keeps its own hover fill and focus-visible outline. The SVG extends ~19px past the wrapper on all sides (5px standoff for the border rect plus 14px of glow/arc headroom) and is sized in raw CSS pixels by a ResizeObserver: width/height attributes equal to the on-screen size, no viewBox scaling, no CSS transform — that is what makes raw-unit dash math exact. The border is an explicit rounded-rect <path> (r=10) stroked twice: a 5px --accent copy blurred 3.5px underneath at 0.3 idle opacity (0.55 when the pointer is near, 0.12 when spent) for the electric glow, and a crisp 1.5px --foreground core on top carrying the crackle. The crackle is an SVG filter — feTurbulence type=fractalNoise numOctaves=2 into feDisplacementMap scale 2.8 — whose jitter is animated by rewriting the feTurbulence element's seed and baseFrequency attributes directly via refs on a throttled 150ms setInterval, never per frame and never through React state; ticks no-op while document.hidden. CRITICAL dash trap: do NOT set pathLength on the element and do NOT use vector-effect:non-scaling-stroke anywhere — combining them makes Chromium compute the dash window in screen space so it lands wrong while every attribute reads correct. Instead measure the real perimeter P with getTotalLength() on the path and compute everything in raw user units. Gap-follows-cursor: a window pointermove/mousemove listener (so synthetic MouseEvents drive the same path) converts the pointer to SVG coords, projects it onto the nearest of the four straight edges (clamped dot-product projection per edge, corners approximated as the nearer adjacent edge — clamping to a segment end IS that approximation), converts the analytic arc-length to measured units via a k = P/analytic correction, and when the pointer is within 30px of the border opens a 16px gap centered there: stroke-dasharray '(P-g) g' with stroke-dashoffset P - s - g/2 written to both stroke layers. A short rAF tween (only alive while opening/closing, ~150ms feel) eases the gap width; position updates ride the move events directly. A separate 4-point jagged <path> in --foreground with a double --accent drop-shadow spans exactly the gap lips (endpoints from getPointAtLength at s±g/2, two mid vertices jittered ±2.5px along the normal, re-jittered every move so it crackles) — the arc that jumps the gap and follows the cursor along the edge. Pointer leaves the proximity band: gap target 0, dash attributes removed once under 0.6px. Micro-arcs: a recursive setTimeout re-rolling a 1-3s delay each round picks a random corner and flashes a 3-segment jagged branch (3-7px steps outward along the corner diagonal, ±2.5px perpendicular jitter) on one of two pooled paths — opacity snapped to 0.95 with transition:none, then eased to 0 over 220ms on the next frame; 40% of strikes fork a second shorter branch rotated ~0.7rad on the other pooled path. Press = discharge: on the button's pointerdown (and keyboard activation, detected as click with detail 0) the whole stroke floods — core to opacity 1 / 2.6px width, glow to opacity 1 / blur 6px over an 80ms ease-out — then settles over 260ms into a 'spent' state held for exactly 2000ms: core at 0.65 opacity, glow at 0.12, displacement scale dropped to 1.1 and every other jitter tick skipped, micro-arcs suppressed; then idle parameters restore over 300ms. An IntersectionObserver on the wrapper stops the jitter interval and the micro-arc timeout entirely off-viewport and restarts them on re-entry. prefers-reduced-motion (checked via matchMedia with a live change listener): the displacement filter attribute is removed so the border renders as a steady solid stroke, no gap, no arcs, no discharge animation, and the glow appears only as a plain CSS box-shadow (color-mix of --accent) on the button's :hover/:focus-visible, enforced belt-and-braces by a media-query style block zeroing the effect layers. All color from theme tokens — --foreground core and arc, --accent glow — as live var() references in the SVG so both themes render without any JS re-derivation. Zero dependencies, no canvas."
      }
    },
    {
      "name": "chart-area-aurora",
      "type": "registry:ui",
      "title": "Chart Area Aurora",
      "description": "Area chart whose fill is a live aurora curtain — series values drive curtain height per column, a cool-to-warm hue band tracks local trend direction (falling cool, rising warm), the noise-warped top edge drifts even at rest, and data updates glide the curve to its new shape with a staggered ease.",
      "files": [
        {
          "path": "registry/loud/chart-area-aurora/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chart-area-aurora.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "chart",
          "data-viz",
          "canvas",
          "noise",
          "ambient",
          "tooltip",
          "dashboard"
        ],
        "instruction": "Build an area chart component where the fill IS an aurora curtain, rendered on a DPR-clamped (max 2) Canvas 2D with a DOM layer for axis captions, legend, and tooltip (font-mono, text-muted). Curtain: for each 2px x-column, interpolate the normalized series value to a column height, warp the top edge with deterministic 2-octave value noise (0.65/0.35 octave weights, smoothstep interpolation) at amplitude 6% of column height drifting 0.05 noise-u/s, and fill the strip with a vertical alpha falloff pow(1 - y/h, 1.6) from the warped top edge down to the baseline. Implement the falloff as two pre-baked 1x256 alpha-strip offscreen canvases tinted at the cool and warm ramp endpoints, blended per column via globalAlpha by a mix value 0.5 + localSlope*14 clamped 0..1 (slope measured over a 4-index window: falling = cooler teal, rising = warmer magenta), plus a 1.5px luminous cap in the hsl-interpolated hue riding the warped edge. Every drawn color derives from theme tokens read via getComputedStyle on documentElement (--background, --foreground, --border, --accent), with the aurora ramp endpoints keyed to background luminance (bright HSL L=60 on dark, deep L=37 on light so the curtain reads on white), re-derived live by a MutationObserver watching documentElement class changes that rebakes the strips. Motion: data updates glide each column start-to-target with cubic-bezier(0.22,1,0.36,1) over 600ms, staggered 4ms per column left to right; ambient drift runs whenever visible via a direct-DOM rAF loop with zero React state on the hot path. Interaction: pointermove shows a DOM tooltip (label, value, delta) snapped to the nearest data point with a crosshair that spring-follows pointer x at k=200 s^-2 zeta=1, and a 3px accent-token dot rides the warped top edge at the active point (accent reserved for this interaction only); arrow keys step the active point when the wrapper is focused (token-relative accent focus ring). Performance: full stop offscreen via IntersectionObserver (drift time accumulator pauses), paint rate capped to 30fps when the pointer has been idle over 5s and no glide or spring is in flight, wake on pointer or re-entry; prefers-reduced-motion renders a single static frame with noise at t=0, no drift or glide (data changes repaint instantly, tooltip still works). Guard zero-size containers and empty series before normalizing (nice-ceil the max, never divide by zero), and tear down the rAF, static rAF, IntersectionObserver, ResizeObserver, MutationObserver, and all pointer/keyboard listeners on unmount. Y-gridlines use the border token at reduced alpha with mono DOM captions; a small legend maps the two ramp endpoints to falling/rising."
      }
    },
    {
      "name": "command-palette-orbit",
      "type": "registry:ui",
      "title": "Command Palette Orbit",
      "description": "Cmd-K palette where fuzzy-match results orbit the input as label pills — orbital radius is inverse match score, so the gravity sim is the ranking: an empty query leaves every command in calm staggered orbits, typing pulls strong matches into tight fast orbits while weak ones destabilize, fling off and despawn, and Enter consumes the winner into the horizon glow.",
      "files": [
        {
          "path": "registry/loud/command-palette-orbit/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/command-palette-orbit.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "command-palette",
          "cmd-k",
          "orbit",
          "physics",
          "fuzzy-search",
          "canvas",
          "keyboard"
        ],
        "instruction": "Build a Cmd-K command palette where fuzzy-match results orbit the input as pills and the gravity sim IS the ranking visualization. Rendering is hybrid: a real <input> (role=combobox, aria-expanded, aria-controls, aria-activedescendant, focus trapped while open, toggled by Cmd/Ctrl+K, closed by Esc) plus an sr-only listbox (role=listbox/option) for a11y; each result is a DOM label pill positioned per-frame via style.transform from the sim so text stays crisp, over a single Canvas 2D layer (DPR clamped to 2) that draws orbit trails and the horizon glow ring. Fuzzy matching is a self-contained scored subsequence (consecutive-run bonus, word-start bonus, gap penalty, query-length confidence — no fuse.js). An EMPTY query is neutral, never unstable: every command gets a staggered pseudo-score across 0.62..0.28 so all of them ride calm distinct orbits (distinct radii mean distinct Kepler speeds, so initial clusters shear apart into even spacing), and clearing the query returns everything to that calm band. Motion: each result gets target radius r = 70 + (1 - score) * 220 px around the input center (plus small deterministic per-id jitter, radii squashed to fit the container with zero-size guards); a radial spring pulls toward it with k = 40 s^-2, zeta = 0.8, while tangential speed omega = 1.6 * sqrt(120 / r) rad/s keeps tighter orbits faster (Kepler-flavored). Every keystroke re-scores and retargets radii, glided with cubic-bezier(0.22,1,0.36,1) over 450 ms. Scores below 0.25 destabilize: 400 px/s^2 outward acceleration plus a 500 ms fade, then despawn — the despawned pill is set opacity 0 AND visibility hidden so it can never be clicked at a stale position. CRITICAL coordinate frame: pills are DOM nodes anchored at left-1/2 top-1/2, so their per-frame transform must receive OFFSETS from the field center (cos/sin * r), while the canvas draws at absolute coords (center + offset); feeding pills absolute coords doubles the center and piles everything into the bottom-right corner. Labels also get a keep-out nudge: since low scores/empty-query orbits pass close to the input, a pill whose position would overlap the input's measured footprint is pushed vertically just clear of it (measured once per resize, not per-frame) so a label is never clipped behind the input at rest — the trail/physics position is unaffected, only the rendered label offset. Enter consumes the highlighted winner: radius decays exp(-t/120ms) into the horizon, scale 1 -> 0.6 and opacity -> 0 over 260 ms, onSelect fires at consumption with a brief accent flash in the glow. Trails are a pruned segment list redrawn onto the main canvas each frame with alpha = 0.3 * exp(-age / 0.24 s) and a hard 1 s max age, so ink provably dissolves within ~1 s wherever motion stops — never an accumulating offscreen canvas with destination-in fades, because 8-bit alpha quantization floors out and leaves a permanent scribble residue. Query confidence (mean of the top-3 scores) breathes the horizon glow radius between 24 and 48 px. Keyboard: ArrowUp/Down cycle the highlight (accent ring on the highlighted pill via token classes, never hex), Enter selects, Esc closes, clicking a pill selects. The rAF loop is ambient while open but fully pauses when the palette is closed or offscreen (IntersectionObserver), and every listener, observer, and the demo auto-type timer is torn down on unmount. All drawn colors (glow, trails, horizon ring) are derived from getComputedStyle CSS tokens (--foreground/--accent/--border) at mount and re-derived live via a MutationObserver on documentElement class changes so both themes render correctly. Under prefers-reduced-motion render a plain static ranked DOM listbox with standard styling and zero canvas. Include an optional autoTypeQuery prop that scripts a character-by-character query on a loop for demos: the first pass starts promptly (first character ~400 ms after mount) so an early default screenshot catches a mid-query state, later cycles clear the query for a brief calm-orbit beat before retyping, and any real user keystroke permanently stops the script."
      }
    },
    {
      "name": "command-palette-rotary",
      "type": "registry:ui",
      "title": "Command Palette Rotary",
      "description": "A jump switcher navigated as a 1D rotary space instead of a ranked list — destinations sit at fixed compass bearings on an invisible ring, and you sweep past them; only the reticle item resolves fully while neighbors compress toward the edges of a periscope slit, with a Geist Mono bearing tape scrolling along the top.",
      "files": [
        {
          "path": "registry/loud/command-palette-rotary/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/command-palette-rotary.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "command-palette",
          "navigation",
          "rotary",
          "compass",
          "physics",
          "keyboard",
          "listbox",
          "spatial-memory"
        ],
        "instruction": "Build a jump switcher for a bounded set of destinations (workspaces, projects, rooms) that abandons ranked-list navigation for a 1D rotary space: every destination has a fixed compass bearing in degrees (0-359), explicit via a `bearing` prop or auto-assigned once (evenly spaced plus small deterministic jitter) and persisted to localStorage keyed by destination id under a `storageKey` namespace, so positions never reshuffle across mounts and spatial memory stays valid. Render is pure DOM/CSS (no canvas): a bordered panel containing a Geist Mono bearing tape strip on top and, beneath it, a ~90-degree-wide viewport slit onto the ring. Each destination is a DOM node anchored at the slit's center via left:50%/top:50% plus a per-frame transform; its horizontal offset is FOCAL * tan(delta in radians), where delta is the shortest signed difference between the current center bearing and that destination's bearing and FOCAL is derived from the measured slit width so the ~45-degree half-window maps to roughly the slit's half-width — this tan projection is what makes neighbors telescope outward and compress near the slit edges exactly like a real periscope sweep, never a plain translateX. Scale falls from 1.06 at dead-center to about 0.56 at a 54-degree hard cutoff (quadratic ease) and opacity falls to 0 over the same range (cubic ease), with pointer-events disabled once opacity drops under ~0.04. The bearing tape is a separate, deliberately LINEAR mapping (px-per-degree, no tangent) of the same center bearing, ticked every 10 degrees and relabeled live as it scrolls — its motion visibly disagreeing with the ring's nonlinear compression is the point, not a bug to reconcile. INTERACTION: dragging the slit (pointer capture, direct-DOM, no React state on the hot path) rotates the center bearing 1:1 with pointer movement and tracks velocity; release hands off to a friction-decayed coast (exp decay, no bounce) which, once slow enough, hands off again to a critically-damped spring (k=170 s^-2, zeta=0.92, ~650ms forced-settle deadline) that locks onto whichever destination is nearest — a magnetic detent. Wheel/trackpad input adds a clamped velocity impulse and rides the identical coast-then-detent pipeline. The destination that a coast/detent locks onto is committed (onValueChange fires) the instant the target is decided, not when the settle animation finishes — the spring is cosmetic follow-through on an already-decided outcome. ArrowLeft/ArrowRight step the reticle to the adjacent bearing in sorted order and commit immediately via a quick spring, no coasting. Typing a letter buffers a query (500-900ms debounce) and spring-rotates the reticle to the nearest label match cyclically from the current position WITHOUT committing — this is a live preview, exactly like a combobox typeahead that hasn't been confirmed yet. Enter commits whatever destination is currently under the reticle (the buffered match if one is active, otherwise wherever the reticle already sits) and clears the buffer. Escape is the one genuinely distinct gesture: it only acts while something is uncommitted — an in-flight drag, an active coast, or a live typeahead preview — canceling it and spring-returning the reticle to the last committed destination; once a coast has already locked onto and committed a detent, Escape has nothing left to cancel and is a no-op. Clicking any visible (non-fully-faded) destination frames and commits it in one gesture — implemented as a pointerdown/up pair measured for total travel (under ~5px reads as a tap, exactly mirroring the drag-vs-tap threshold already used elsewhere in this registry for coverflow-style surfaces) rather than a plain onClick, because the same element also sits under the ring's drag-capturing pointerdown listener and a native click can be retargeted unpredictably once pointer capture is in play. Two small round icon buttons (Previous/Next bearing, real `<button>` elements, positioned over the ring's left/right edges but siblings of it in the DOM, never children) give a mouse-only, no-drag, no-keyboard path to the exact same detent step ArrowLeft/ArrowRight perform — keeping them outside the ring's own subtree means a button press can never also start a phantom zero-distance drag underneath it. A11Y: the panel is `role=listbox` with `tabIndex=0` carrying `aria-activedescendant` (set imperatively by the engine, never by React state, so it can update every animation frame without a re-render), each destination is a `role=option` in fixed bearing order with `aria-selected` on the committed one; screen readers get each option's plain label text — bearings and the tape are `aria-hidden` flavor, not information. An `aria-live=\"polite\"` region announces \"Now at {label}.\" whenever a commit actually happens. Colors are Tailwind classes bound only to --background/--foreground/--muted/--border, with --accent reserved for exactly one thing: the reticle crosshair and the reticled label's text color turn accent only while the listbox itself has keyboard focus, a genuine interaction-state signal, never an ambient decoration. Under prefers-reduced-motion, every rotation is instant (no coast, no spring, no easing) but the same commit semantics apply verbatim: drag release, wheel, arrow keys, typeahead-confirm-by-Enter, and click all behave identically minus the animation. Must read as clearly distinct from command-palette-orbit (an unbounded fuzzy-search command palette where a gravity sim visualizes ranking) and dropdown-drape (an ordinary hierarchical dropdown) — command-palette-rotary has no search ranking and no dropdown chrome at all, just a bounded ring of permanent addresses you sweep past."
      }
    },
    {
      "name": "confirm-hold-wax",
      "type": "registry:ui",
      "title": "Confirm Hold Wax",
      "description": "Press-and-hold confirm as a molten wax seal — holding pours a wobbling gooey blob of deep crimson wax onto the document line, hold-complete drops a signet stamp that squashes it into a scalloped, monogrammed seal that cools, darkens and micro-cracks over 2s, while early release slumps the blob and drains it back.",
      "files": [
        {
          "path": "registry/loud/confirm-hold-wax/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/confirm-hold-wax.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "confirm",
          "hold",
          "press-and-hold",
          "wax-seal",
          "svg",
          "gooey",
          "destructive",
          "aria-live",
          "accessibility"
        ],
        "instruction": "Build a press-and-hold confirmation as a molten wax seal on a document. The stage is a fixed 280x170 SVG: a --border document rule runs across at y=120 (plus two shorter rules above and below suggesting the tail of a letter), with the empty seal spot centered on it and a 'Seal' button below (standard chrome: --surface fill, --border border, --foreground text, an always-present plain CSS :hover border/background change independent of the wax visuals, and a visible focus-visible outline in --accent). HOLD GRAMMAR (confirm-hold-ink's exact grammar, molten visuals): pointerdown on the button (with setPointerCapture) or non-repeat keydown Space/Enter while focused starts a tracked hold; a rAF loop advances progress 0-1 over ~1100ms of unclamped wall time written to a ref, never React state; pointerup/pointercancel/pointerleave/lostpointercapture/keyup/blur before 1.0 is the cancel path; the instant progress reaches 1.0 the completion sequence fires regardless of any subsequent release. POURING: an SVG gooey filter (feGaussianBlur stdDeviation 5 piped into an feColorMatrix alpha threshold '... 18 -7') fuses three overlapping circles sitting on the rule into one liquid silhouette in molten crimson (component-local constants around #b31b30 — the repo has no crimson token and the loud collection is color-exempt; everything that is not wax stays on the theme tokens). Circle radii grow from ~0 to target (22/14/13u) on an ease-out of hold progress, and while liquid their cx/cy jitter a few px on sin/cos of an internal ms clock (per-circle phase offsets, amplitude scaled by progress) — all direct setAttribute writes on circle refs each frame. STAMPING (~690ms total): a signet stamp (handle + base rects in --surface/--muted, parked 92u above, opacity 0) drops with a fast ease-in translateY over 190ms onto the blob; at impact the goo hides and a STATIC scalloped seal disc appears — a precomputed path whose perimeter is a circle radius-modulated by a low-frequency cosine (10 scallops, amp 2.6 on r=29) — while both stamp and disc play a squash-and-rebound (stamp scaleY 0.76 -> 1.05 -> 1, disc 0.84 -> 1.02 -> 1, each anchored at its base via translate-scale-translate transform attributes, x widening inversely) and a squish ring of 8 small wax circles puffs outward from the rim (distance +17u ease-out, radius and opacity to 0 over 380ms). The disc carries an embossed monogram faked with exactly two overlapping copies of the glyph offset ~0.9u in opposite directions — a lighter highlight up-left, a darker shadow down-right — plus a scalloped inner rim stroke and a faint highlight ring for bevel depth. The stamp then retracts upward and fades over 240ms. COOLING (exactly 2000ms, JS-interpolated in the same rAF loop): a white specular sheen ellipse fades from 0.4 to 0, the disc fill lerps per-channel from hot crimson (#9c1526) to deep cooled crimson (#570d18), and three hairline micro-crack paths etch in via stroke-dasharray/stroke-dashoffset animating from fully hidden to revealed, staggered 420ms apart over 700ms each. CRITICAL: the crack lengths come from getTotalLength() on the authored geometry and the dash values are written in RAW SVG user units — never combine an SVG pathLength attribute with vector-effect non-scaling-stroke, and use neither here. EARLY RELEASE: the blob slumps — the goo group scales anchored at the document line (scaleY toward 0.15, scaleX widening ~1.3, radii shrinking, opacity easing to 0 over ~520ms, like losing surface tension) — then the stage returns fully to idle and the button re-arms. TERMINAL STATE: sealed is permanent — no reset, no replay. The disc stays exactly as cooled, the button keeps its visible 'Seal' text (accessible name intact throughout the lifecycle) but becomes aria-disabled='true', visually dimmed, with all handlers guarded, and a visible mono 'Sealed' caption appears beside it. ACCESSIBILITY: a dedicated sr-only span (role=status, aria-live=polite, aria-atomic=true), separate from the button, announces 'Poured' when a hold starts, 'Cancelled' on early release and 'Sealed' at the terminal state, appending a zero-width-space parity toggle so a repeated identical message (two cancels in a row) still forces a text-node change and re-announces. The SVG stage is aria-hidden and pointer-events-none; the button is the only interactive element. REDUCED MOTION (matchMedia prefers-reduced-motion): the hold requirement is preserved — intent still takes the full hold — but every intermediate visual is skipped: no goo growth or wobble, no stamp drop, no cooling transition; the moment progress reaches 1.0 the fully cooled, cracked seal appears in one discrete step, and a cancel snaps straight back to idle. DEMO MODE: a demo prop runs a scripted setTimeout timeline through the exact same internal hold/release functions the real handlers use — two short early-release holds (~620ms and ~560ms of a 1100ms threshold) inside the first ~3.5s so automated hover/focus screenshots see a normal idle button while still showing the molten wobble and slump, then one full hold at ~5.7s that completes, stamps, cools and stays sealed permanently; synthetic starts no-op unless the component is idle with no active hold, and a synthetic release can never cancel a real hold (hold-source tagging), so scripted playback never fights real input. Zero dependencies, no canvas."
      }
    },
    {
      "name": "event-stream-vapor",
      "type": "registry:ui",
      "title": "Event Stream Vapor",
      "description": "A Wilson cloud chamber for your event stream — each real event condenses into a soft vapor trail (angle by category, length/brightness by magnitude) that diffuses away over seconds, with a paired Geist Mono log so the same stream reads in words.",
      "files": [
        {
          "path": "registry/loud/event-stream-vapor/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/event-stream-vapor.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "monitoring",
          "event-stream",
          "status",
          "canvas",
          "ambient",
          "dashboard",
          "aria-live",
          "mono"
        ],
        "instruction": "A Wilson cloud chamber styled ambient-awareness widget for a live event stream, laid out as a canvas chamber (flex-1) beside a fixed-width Geist Mono legend. Consumer owns the data: pass `events` ({id, name, category, magnitude?, timestamp?}[]) as an append-only array — the component diffs by id every render and only ever reacts to ids it has not seen before, so re-renders with the same array are inert. MECHANISM: each new event immediately seeds a short track — origin is a random point inside the inner 64% of the box, direction comes from `angleForCategory` (a small canonical table: deploy/release ~-50deg, error/crash ~100deg, purchase/payment ~15deg, warning ~135deg, signal ~-125deg; any other category string falls back to a deterministic hash bucket, so nothing is ever unhandled), and distance covers speed*~400ms where speed and the per-particle brightness/radius/lifetime all scale with `magnitude` (0..1, default 0.4). Positions are stored as box-relative fractions and converted to pixels at DRAW time using the CURRENT box size, so an in-flight resize never distorts an already-spawned track. magnitude >= 0.68 also forks 1-2 short branch tracks off a random point partway along the primary path at a randomized angle offset (2 branches once magnitude >= 0.88) — the 'heavier events ionize harder' read. RENDERING: there is deliberately no persistent destination-out accumulation buffer. Every event pushes a small batch of stamps (fractional position, birth time, per-particle tau, peak alpha) into a plain capped array (700 max, oldest dropped first); every animation frame fully clears the canvas and redraws every live stamp from scratch at alpha = peak * exp(-age/tau) with 'lighter' compositing (so overlapping fresh particles brighten into a hot streak core while tails stay soft), pruning anything past ~5*tau. This is the fix for the destination-out fade this brief calls out: repeated low-alpha destination-out on an 8-bit backing store stalls once dst*alpha drops below the rounding floor, leaving a permanent low haze instead of returning to empty; redrawing from an explicit, analytically-decaying list has no such floor, so a quiet stream provably returns to a fully transparent, quiet chamber. Per-particle tau is 2.2-3.4s (branches 2.2-3.4s scaled off a lower magnitude), so a track's mist is visibly gone within roughly 6-10s of its last stamp. Brightness is the ONLY intensity channel — strictly monochrome, derived from --foreground via getComputedStyle at mount and re-baked into a small radial-gradient sprite on every documentElement class change, so both themes render correctly (dark ink on a light chamber in light mode is correct, not a bug). ACCESSIBILITY: the canvas is aria-hidden — the real interface for assistive tech is the paired legend, a role=log aria-live=polite region carrying event name/category/magnitude as plain Geist Mono text, labelled via the `label` prop. New arrivals are diffed against the same seen-id set but batched into ONE flush every `flushMs` (default 350) so a burst of events produces a single legend update and a single live-region announcement rather than one per event; this throttle only affects the legend/announcement, never the canvas, which reacts to a genuinely new event immediately. REDUCED MOTION: prefers-reduced-motion (checked live via a matchMedia change listener, not just once at mount) replaces the whole chamber — no canvas node is rendered at all — with the legend taking the full width plus a row of small static per-event tick marks (rotated to the event's category angle, sized/brightened by magnitude, zero motion, built from the same throttled row list) above it, so the same information stays legible without any animation. ENGINEERING: rAF loop pauses via IntersectionObserver while the chamber is scrolled offscreen; ResizeObserver keeps the canvas backing store correct (DPR clamped to 2); every observer, timer and rAF handle is torn down on unmount. Zero dependencies. Props: events, label, categoryAngles (override/extend the category->angle degree table), maxLegendItems (default 30), flushMs (default 350), className (default 'h-96', sets the widget's height)."
      }
    },
    {
      "name": "gallery-gantry-track",
      "type": "registry:ui",
      "title": "Gallery Gantry Track",
      "description": "A scroll-scrubbed, infinitely-looping 3D card gallery — cards hang off a receding gantry track using real CSS perspective/preserve-3d, the whole tray tilts toward the pointer like a specimen table, and whichever card sits nearest the camera (by depth, not mouse distance) gets a procedural focus highlight and its title pushed to a live readout.",
      "files": [
        {
          "path": "registry/loud/gallery-gantry-track/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/gallery-gantry-track.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "scroll",
          "3d",
          "gallery",
          "gantry",
          "perspective",
          "parallax",
          "cursor"
        ],
        "instruction": "Build a 400vh section with a sticky full-viewport inner div. Cards are laid out along a 'gantry track': item i sits at world position (i*34px lateral, i*-16px vertical rise, i*260px depth), and the item list is rendered TWICE back to back (a loop buffer) so a duplicate of the first card is already approaching as the last real card recedes out of frame. A real CSS perspective (1400px, origin 50% 45%) on the viewport wrapper plus transform-style: preserve-3d on the track and every card lets the browser do the actual projection — each card's transform (translate3d + a fixed rotateY(-6deg) + a small in-focus scale bump) is written directly to its ref every frame, not through React state. Scroll progress is read from the section's getBoundingClientRect in a passive scroll listener (0 at the section's top edge reaching the viewport, 1 once its bottom edge does), mapped to camera depth camZ = progress * (item count * 260px) — since that span equals exactly the depth of ONE set of the duplicated pair, the pass reads as a single continuous, closed loop: at progress 1 the camera is back at the seam. Independently of scroll, the pointer tilts the whole track (not any individual card) toward itself: pointer offset from viewport center maps to a target rotateX/rotateY on the track wrapper, eased in at a fixed 0.1 lerp factor per frame, so the tray reads as a tilting specimen table rather than N cards each springing at the mouse. Every frame the card whose depth (|world Z - camZ|) is smallest is the 'focus' card: it gets `data-focused=\"true\"\" (styled via a plain Tailwind data-attribute variant into an --accent border and a raised shadow, both transitioning over 200ms) and its title is written directly into a small mono readout above the scroll hint via textContent, so attention follows camera depth, not cursor proximity. Cards more than 1.5 track-steps behind the camera or past the loop's far edge are set to opacity 0 to bound the DOM work; visible cards fade out gradually as they approach either edge of the visible window. Default items render as token-styled specimen cards (id, a diagonal-hatch swatch built from a `repeating-linear-gradient` of `var(--border)`, title, caption) rather than photographic images, so the component has no network dependency and stays theme-reactive by construction. prefers-reduced-motion replaces the entire scroll-scrubbed 3D experience with a plain static grid of the same cards and tokens — no scroll listener, no tilt, no receding track — rather than merely slowing the scrub down. Props: items (id/title/caption), className. Zero dependencies."
      }
    },
    {
      "name": "heatmap-calendar-tide",
      "type": "registry:ui",
      "title": "Heatmap Calendar Tide",
      "description": "Calendar heatmap read as a tide table — one column per week, depth carried by a single-hue ramp mixed from the accent token, with arrow-key navigation.",
      "files": [
        {
          "path": "registry/loud/heatmap-calendar-tide/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/heatmap-calendar-tide.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "heatmap",
          "calendar",
          "data-viz",
          "chart",
          "grid",
          "keyboard-navigation",
          "colour"
        ],
        "instruction": "A calendar heatmap laid out column-major — one column per week, seven rows for weekdays — where magnitude is carried by a five-step sequential ramp of a single hue rather than a rainbow or a red-to-green scale. The ramp is not two hardcoded palettes: each step is mixed at runtime with color-mix(in oklab, var(--accent) N%, var(--background)) at 18/34/52/74/100, so the same five stops resolve to ink-on-paper depths in light and to lit water in dark, and a consumer who re-tokenises the accent gets a re-stepped ramp for free. A day with no reading is deliberately NOT step zero of that ramp — it is drawn in a border-derived slack colour, because a bottom-step cell and an empty cell mean different things and a viewer cannot be asked to tell them apart by lightness alone. The legend runs slack → five depths → flood, and the only numeric readout is the caption, which reports the hovered or focused day and otherwise reports the peak: one direct label rather than a number stamped on 182 cells. Interaction is a real grid, not a wall of tooltips — the whole heatmap is one tab stop via roving tabindex, arrow keys walk it (left/right by week, up/down by weekday) with focus moved imperatively so the browser scrolls the cell into view, every cell carries an aria-label of its date and value, and the caption is aria-live so a screen-reader user hears the value the sighted user reads. The hovered or focused cell lifts 35% and takes a two-ring highlight — a background-coloured ring first, then an accent ring — so it separates from its neighbours instead of bleeding into one continuous field. Under prefers-reduced-motion the lift and its transition are dropped; the ring, the readout and the keyboard grid are untouched."
      }
    },
    {
      "name": "hero-ascii-eclipse",
      "type": "registry:ui",
      "title": "Hero ASCII Eclipse",
      "description": "A full-bleed ASCII hero built around an occlusion event — a dark glyph disc transits a bright sun disc over a fixed starfield, the pointer dragging the transit, and a corona blooms at the rim only as the two centers nearly align, the way a real solar corona is only visible near totality.",
      "files": [
        {
          "path": "registry/loud/hero-ascii-eclipse/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-ascii-eclipse.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "hero",
          "background",
          "canvas",
          "cursor",
          "eclipse",
          "occlusion"
        ],
        "instruction": "Build a full-bleed Canvas 2D hero: a fixed sun disc (radius ~15% of the shorter canvas dimension, centered slightly above screen-center) sits over a sparse fixed breathing starfield (identical technique to hero-ascii-terrain's sky: seeded via mulberry32, alpha twinkling on a per-star sine phase), and a moon disc (radius ~1.04x the sun's, so it can fully cover it) transits across it. COMPOSITING, per cell, in strict occlusion order: if the cell is within the moon's radius, it is ALWAYS blank, unconditionally occluding sun or star beneath it — there is no partial-transparency moon. Otherwise, if within the sun's radius, draw the photosphere: the shared ' .:-=+*#%@' ramp indexed by a radial falloff (brighter toward the sun's own center), in the --foreground token. Otherwise, if within a corona ring band just outside the sun's radius (width ~65% of the sun's radius) AND the scalar 'corona intensity' is above a small threshold, draw a corona glyph whose density is the product of a radial falloff and an angular 'streamer' term — sin(angle * 7 + t * 1.4), a smooth per-angle flicker, not noise — scaled by that intensity, rendered in the --accent token (the one place in this hero color leaves pure ink, deliberately, since a corona is the visual payoff). CORONA INTENSITY is a single scalar, pow(max(0, 1 - centerDistance / (sunRadius * 0.85)), 2): near zero for an ordinary partial transit and blooming only as the moon's center nearly coincides with the sun's — real eclipse astronomy, not a cosmetic pulse. Otherwise the cell falls through to the starfield (occluded by the moon check above already handled) or stays blank. The moon's position is the pointer's, inside the container, eased 0.08/frame toward the raw position; with no active pointer, an idle sweep (9s period, sine-driven horizontal traverse of ~2.6 moon-radii either side of the sun plus a small vertical bob) keeps the transit alive on its own. Direct-DOM rAF, zero React state on the hot path; grid buffers/typed arrays sized once per resize (cols/rows via Math.ceil, not floor). Ink is read via getComputedStyle(canvas).color plus the --muted and --accent custom properties at mount, re-derived on a documentElement class MutationObserver. Mono cell measured via an offscreen canvas's measureText. prefers-reduced-motion renders exactly one static frame with the moon parked clear of the sun (an ordinary partial-transit moment, corona at rest) and skips the rAF loop and pointer listeners entirely. Loop pauses on document.hidden, resumes on visibilitychange. Optional children render over the field, bottom-left anchored with padding. Props: cellSize (grid cell px, default 13), children, className."
      }
    },
    {
      "name": "hero-ascii-rainfall",
      "type": "registry:ui",
      "title": "Hero ASCII Rainfall",
      "description": "A full-bleed ASCII precipitation hero — every column runs its own independent falling drop with a fixed rows/sec speed and trail length, and the pointer bends nearby streams sideways like wind gusting through rain instead of painting or warping cells directly.",
      "files": [
        {
          "path": "registry/loud/hero-ascii-rainfall/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-ascii-rainfall.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "hero",
          "background",
          "canvas",
          "cursor",
          "rain",
          "particles"
        ],
        "instruction": "Build a full-bleed Canvas 2D hero: one independent falling drop per grid column, each a float head-row position advancing at its own fixed rows/sec speed (9-22) with its own trail length (6-18 rows), seeded via a deterministic mulberry32 PRNG (never Math.random) so a session's rainfall is reproducible. A column's glyph buffer only gets a fresh random character (from '|:.\\'\\`,;') the instant the head crosses into a NEW row — never every frame — so a settled trail holds still instead of flickering; luminance is purely distance-from-head, gamma-shaped (pow 1.5) so the head reads bright and the tail fades toward the ink floor, alpha never fully to zero (0.08 floor) so the tail dissolves rather than hard-cutting. When a column's head clears rows + its trail length past the bottom, it restarts above the top at a randomized negative offset (a random multiple of its own new trail length) with freshly randomized speed and trail — this staggering is what keeps drops from ever resetting in visible unison. WIND, not paint: the pointer maintains a rising/decaying 0..1 'energy' scalar (gains on pointer speed, relaxes over ~1s once it stops moving, exactly the plasma/wake decay pattern but applied to a lateral offset instead of a stamp or sample warp) and every column computes a target horizontal pixel offset as a gaussian falloff of its distance (in columns, sigma 10) from the pointer's column, times that energy, times a per-column sine gust (each column has its own frequency 0.6-1.4 rad/s and phase so nearby streams don't bend in lockstep) capped at 14px — the whole column's rendered x is offset uniformly by this eased value, so streams bend like a curtain in a gust and glide back straight once the pointer stops and energy decays to zero. Direct-DOM rAF, zero React state on the hot path; all typed arrays sized once per resize (cols/rows computed with Math.ceil, never floor, since flooring leaves an unpainted strip along the clipped container's bottom/right edge). Ink is read once via getComputedStyle(canvas).color and re-derived on a documentElement class MutationObserver for live theme flips. Mono cell dimensions are measured via an offscreen canvas's measureText after document.fonts.ready. prefers-reduced-motion fills every column full-height with one static randomized frame (no head/trail concept, no animation, no pointer listeners attached at all) so the effect's silhouette is legible without motion. Loop pauses on document.hidden and resumes on visibilitychange. Optional children render over the field, bottom-left anchored with padding. Props: cellSize (grid cell px, default 13), children, className."
      }
    },
    {
      "name": "hero-ascii-terrain",
      "type": "registry:ui",
      "title": "Hero ASCII Terrain",
      "description": "A full-bleed ASCII landscape hero — five ridgelines of deterministic value noise recede toward a horizon, each layer stepping up in frequency, height and ink density the nearer it sits, with the pointer driving parallax so the terrain slides past like a window on a moving vehicle.",
      "files": [
        {
          "path": "registry/loud/hero-ascii-terrain/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-ascii-terrain.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "hero",
          "background",
          "canvas",
          "cursor",
          "noise",
          "terrain"
        ],
        "instruction": "Build a full-bleed Canvas 2D hero: five ridgeline layers, far to near, each a deterministic 1D value-noise curve (two octaves at 0.7/0.3 weight, hashed with Math.sin-based fixed seeds per layer — never Math.random, so the terrain is identical every mount) sampled once per column at a per-layer spatial frequency, base depth and amplitude that all step up together with proximity (0.02 to 0.13 frequency, 0.04 to 0.7 base fraction of the terrain band, 0.05 to 0.16 amplitude fraction) so the farthest ridge is a smooth low haze near the horizon and the nearest is tall and jagged, reaching furthest into the frame. Layers are resolved far-to-near into one Int8Array 'which layer owns this cell' buffer and one Uint8Array ramp-index buffer, cleared and rebuilt every frame — later (nearer) layers simply overwrite the cells they cover, which is what gives clean occlusion instead of two glyphs stacked translucently in one cell. A render pass then walks the terrain band once per layer (one ctx.globalAlpha set per layer, stepping 0.26 to 1.0 opacity from haze to ink) drawing only the cells that layer owns, with the ramp character (' .:-=+*#%@' index 2,3,5,7,9) also stepping denser with proximity. Above the horizon row (fixed at 34% of grid height, ridges clamped so no peak ever crosses into the sky), a fixed-seed sparse star field (~3.5% of sky cells, capped at 240) is drawn directly per star — not through the buffer, since stars need no occlusion — each with an independent alpha 'twinkle' on a slow sine so the sky isn't a dead flat void. PARALLAX: the pointer's position inside the container, normalized to -0.5..0.5 and eased (0.08/frame lerp), maps to a world-column and world-row offset (max 46 columns, 5 rows at full travel) that each layer's noise sample is displaced by, scaled by that layer's own 0..1 proximity factor (0.05 farthest to 0.92 nearest) — so nearer ridges visibly slide faster than the horizon as the cursor moves, exactly like looking out of a moving window. A slow ambient drift (0.045 world-units/s, same per-layer scaling) keeps the terrain gently alive even with the pointer at rest. Direct-DOM rAF loop, zero React state on the hot path; the ramp/group buffers and star typed arrays are allocated once per resize and reused every frame. The mono cell is measured via an offscreen canvas's measureText after document.fonts.ready (a fallback-font measurement bakes in the wrong grid aspect until reload). Glyph ink reads getComputedStyle(canvas).color for terrain and the --muted token for stars, both re-derived on a documentElement class MutationObserver for live theme flips. prefers-reduced-motion renders exactly one static frame at t=0 with the pointer offset and idle drift both zeroed (the full ridge shape, not an edge-on or empty state) and skips the rAF loop and pointer listeners entirely. Optional children render over the field, bottom-left anchored with padding, so the hero can carry a real headline and CTA. Props: cellSize (grid cell px, default 13), children, className."
      }
    },
    {
      "name": "hero-ascii-tunnel",
      "type": "registry:ui",
      "title": "Hero ASCII Tunnel",
      "description": "A full-bleed ASCII perspective-corridor hero — sixteen squircle rings advance toward the viewer on a continuous loop, shaded through a density ramp so depth reads from character weight alone, with the pointer steering the shared vanishing point and the whole corridor tilting to follow it.",
      "files": [
        {
          "path": "registry/loud/hero-ascii-tunnel/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-ascii-tunnel.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "hero",
          "background",
          "canvas",
          "cursor",
          "3d",
          "tunnel"
        ],
        "instruction": "Build a full-bleed Canvas 2D hero: sixteen superellipse ('squircle', exponent 2/n with n=4) rings sit at world depths evenly spaced across [Z_NEAR 0.55, Z_FAR 6.2] and advance toward the viewer every frame (z -= speed*dt, speed = (Z_FAR-Z_NEAR)/3.4s), wrapping back to the far plane by adding back the full depth span the instant a ring passes Z_NEAR — the classic starfield treadmill, which keeps the ring count and relative spacing constant forever so the loop reads as continuous forward motion rather than a visible reset. Each ring is walked by point count scaled to the render area (48 to 220, clamped) around its perimeter; every point projects into ISOTROPIC pixel space (both axes scaled by one constant K1, exactly ascii-torus-donut's discipline) around a SHARED vanishing point, and only then quantizes to the mono cell grid via col/row division — projecting before quantizing is what keeps the rings true squircles instead of stretched ellipses, since the cell is roughly 2:1 tall/narrow but device pixels are square. Nearer points win a per-cell Float32Array depth competition on 1/z (bigger = nearer), reset every frame, because ring draw order does not track depth order once rings have wrapped at different times — without a real depth buffer a far ring could paint over cells a near ring already claimed. The winning cell's ramp index (density ramp ' .:-=+*#%@', a gamma of 0.85 applied to the depth fraction so mid-depth rings don't crowd the top of the ramp) is stored in a parallel Uint8Array, and the render pass applies continuous alpha (0.3 to 1.0) keyed to that index for tonal range beyond the ramp's ten discrete glyphs. STEERING: the pointer's position inside the container, normalized to -0.5..0.5, sets a target for the vanishing point (max travel 34% of the render area's shorter side) that eases toward it at 0.07/frame — every ring projects around this same shared point, so the whole corridor visibly tilts to follow the cursor with a trailing, never-instant feel; releasing the pointer eases the vanishing point back to center. Direct-DOM rAF loop, zero React state on the hot path; depth and char buffers are typed arrays allocated once per resize and reset (not reallocated) every frame. The mono cell is measured via an offscreen canvas's measureText after document.fonts.ready (a fallback-font measurement bakes in the wrong grid aspect until reload). Glyph ink reads getComputedStyle(canvas).color, re-derived on a documentElement class MutationObserver for live theme flips. prefers-reduced-motion renders exactly one static frame with the rings at their initial evenly-spaced depth distribution and the vanishing point centered (a full composed tunnel, never an edge-on or empty state), and skips the rAF loop and pointer listeners entirely. Optional children render centered, re-transformed to track the vanishing point every frame via a direct style.transform write on the content ref (never React state), so a headline and CTA can sit exactly where the corridor converges. Props: cellSize (grid cell px, default 13), children, className."
      }
    },
    {
      "name": "hero-chart-recorder",
      "type": "registry:ui",
      "title": "Hero Chart Recorder",
      "description": "A chart-recorder hero: ruled paper feeds left at fixed px/s while a mechanical pen chases the live value through a deliberately underdamped spring, so a spike overshoots, quivers, and settles — and that tremor is stamped permanently into the trace.",
      "files": [
        {
          "path": "registry/loud/hero-chart-recorder/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-chart-recorder.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "canvas",
          "data-viz",
          "chart-recorder",
          "live",
          "physics",
          "spring",
          "mono",
          "aria-live",
          "hero",
          "status-page"
        ],
        "instruction": "`<PenLag value unit label min max speed rangeMs formatValue className />` renders a wide chart-recorder strip: ruled horizontal lines (fixed, translation-invariant) and scrolling vertical time ticks (5s apart by default) drawn on a DPR-aware Canvas 2D, with a live ink trace that is the component's real subject. `value` is the consumer-supplied true telemetry reading, updated as often as the consumer likes; the pen never jumps to it. Every animation frame the pen's drawn position integrates one step of an underdamped spring toward `value` — stiffness 120, damping 8 (zeta ≈ 0.365, hardcoded, not props: this specific mechanical character is the component's identity) — so a step change overshoots past the target, rings through a couple of visibly decaying oscillations, and settles, exactly like a real needle recorder with real inertia. Each frame's pen position is appended once to a plain trace buffer (`{t, v}[]`, capped to a little over the `rangeMs` window, default 5 minutes) and NEVER recomputed afterward — the canvas redraws the buffer every frame purely by re-deriving each point's screen x from its age (`x = writeX - age/1000 * speed`), so the ink itself, including every quiver a spike ever produced, is permanent; only its position on screen slides left as the paper (implicitly) feeds under a fixed writing point at the strip's right margin. The pen arm and needle carriage are NOT canvas pixels — they're two absolutely-positioned DOM divs pinned to that fixed writing point (a pivoting arm whose length/angle are recomputed from trigonometry each frame, and a small carriage dot riding a vertical rail) so the moving mechanical parts stay crisp at any zoom while the historical trace stays raster. All draw colors (`--foreground` for ink, `--border` for rules/carriage-margin, `--muted` for time ticks) are read via `getComputedStyle` at mount and re-derived on both a `MutationObserver` watching `documentElement`'s class/data-theme attributes AND a `prefers-color-scheme` `matchMedia` change listener, so the strip repaints correctly however the theme actually flips. `--accent` appears exactly once: the vertical hairline and time/value label of the hovered or arrow-key-scrubbed timestamp cursor, never as decoration. ACCESSIBILITY: the canvas itself is `aria-hidden`; the strip wrapper carries `role=\"img\"` with an `aria-label` that stays a stable description at rest and switches to the live scrub reading (`\"-8s: 242ms\"`) while a cursor is active, plus a separate visually-hidden `role=\"status\" aria-live=\"polite\"` region that emits a threshold-debounced ambient summary (`\"Response time, currently 220ms, rising, 5-minute range 180–460ms\"`) only when the rounded value or trend word actually changes and the user isn't mid-scrub, so the region never spams. A real, always-visible Geist Mono readout (current value, trend word, min–max over the window) sits above the strip in plain DOM text — the number is never canvas-only. The strip is `tabIndex=0`; hovering OR focusing it with Left/Right arrows moves a fixed-screen-position probe that reads back whatever sample is currently passing beneath that column (the same DOM cursor serves both input modes) and announces its age and value. REDUCED MOTION: the continuous spring/scroll rAF loop is replaced by a throttled ~2Hz timer that snaps the pen directly to the true value (no overshoot — the spring's decorative ring is exactly the kind of motion `prefers-reduced-motion` users are opting out of) and does one full static redraw per tick instead of animating between them — live and legible, never smoothly sliding. The rAF loop also pauses on `document.hidden` and via an `IntersectionObserver` while the strip is scrolled offscreen, and every observer/listener/timer is torn down on unmount. Zero dependencies, DOM+Canvas 2D only, no WebGL."
      }
    },
    {
      "name": "hero-cloth-type",
      "type": "registry:ui",
      "title": "Hero Cloth Type",
      "description": "Cloth-like kinetic type — a headline rasterized once to an offscreen canvas is re-drawn every frame through a warped mesh of spring-loaded nodes, so the cursor drags the type like fabric and release springs it back with damping.",
      "files": [
        {
          "path": "registry/loud/hero-cloth-type/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-cloth-type.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "text",
          "hero",
          "cursor",
          "canvas",
          "physics",
          "mesh",
          "kinetic-type",
          "drag"
        ],
        "instruction": "Build a headline rendered as a warped mesh on a single DPR-aware Canvas 2D, positioned over a visually hidden real text node (role=\"text\", aria-label=the string, opacity-0 so semantics/SEO survive; under prefers-reduced-motion the canvas is hidden entirely, the real text shows at full opacity, and a 6s ease-in-out infinite opacity keyframe between 0.88 and 1 stands in for the warp as a static-safe ripple). On mount, after document.fonts.ready, measure the container box and rasterize the headline once onto an OFFSCREEN canvas at the same DPR (Geist Sans bold, size via clamp(2.5rem,8vw,6rem), fillStyle the --foreground token, centered) — this offscreen bitmap is the texture that gets warped, never redrawn per frame. Build a rest grid of (GRID_COLS+1) x (GRID_ROWS+1) nodes (12x4 cells is enough resolution to read as cloth) evenly spaced over the box; each node tracks home (hx,hy), current position (x,y) and velocity (vx,vy) in closure-scoped arrays, no React state on the animation hot path. Every rAF tick: if the pointer is within a capture radius (~150px) of a node's HOME position, that node's spring target is displaced toward the pointer by a falloff-weighted fraction (smoothstep falloff, ~0.6 pull strength at the pointer itself, 0 past the radius) — nodes outside the radius simply target their home. Integrate every node on an underdamped spring (k~170 s^-2, zeta~0.72, so release visibly overshoots before settling) each frame, then render: for every grid cell, split it into two triangles and texture-map the offscreen bitmap onto the CURRENT (warped) corner positions via a solved 2D affine transform — three point correspondences (home-in-source-pixels -> current-position-in-destination) exactly determine an affine matrix (solve via basis vectors: u=p1-p0, v=p2-p0 in source space, U=P1-P0, V=P2-P0 in dest space, M = [U V]·inverse([u v]), translation from the point-0 correspondence), then clip to the destination triangle path and drawImage the offscreen bitmap through ctx.transform(...matrix) — this is the standard three-point affine texture-mapping technique canvas 2D needs since it has no native quad/homography draw. Loud flourish: for any cell whose four corner nodes average above a velocity threshold (~90px/s), set ctx.shadowColor to the --accent token and a small ctx.shadowBlur before drawing that cell's two triangles (reset via the per-triangle save/restore, so it never bleeds onto neighboring cells) — a soft accent glow that only appears on the fastest-moving fabric, not a global filter. Sleep the rAF loop once every node is within ~0.15px of its target and under ~0.5px/s AND the pointer has left; a MutationObserver on documentElement's class re-reads --foreground/--accent and rebuilds the offscreen bitmap on theme flip so colors never go stale; a ResizeObserver rebuilds the grid and bitmap on container resize; render one flat resting frame immediately after the initial rasterize so there is no blank paint before the first pointer event. Interaction lives entirely on pointermove/pointerleave against the container — no button, no click semantics, matching the cursor-driven display-component shape used elsewhere in this registry (hero-gravity-well, text-prism-split): the effect is passive and readable purely from hover, not a control that needs activating. Guard a null 2d context and a zero-size container before ever touching the canvas; cancel the rAF and disconnect both observers on unmount."
      }
    },
    {
      "name": "hero-gravity-well",
      "type": "registry:ui",
      "title": "Hero Gravity Well",
      "description": "Headline as a monochrome particle cloud — the cursor is a gravity well that eats letters into an orbital accretion ring, then releases them to spring back into typeset.",
      "files": [
        {
          "path": "registry/loud/hero-gravity-well/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-gravity-well.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "text",
          "particles",
          "cursor",
          "canvas",
          "physics"
        ],
        "instruction": "Build a hero headline rendered as a monochrome particle cloud on a single DPR-aware Canvas 2D positioned over a visually hidden real h1 (opacity-0 so semantics and a11y survive; under prefers-reduced-motion the canvas is hidden and the static h1 shows instead). On mount, after document.fonts.ready, draw the headline (Geist Sans 600, clamp(3rem,9vw,7rem)) to an offscreen canvas, getImageData, and sample alpha>128 on a 3px stride into one Float32Array of (x,y,vx,vy,hx,hy) capped near 5k particles — no React state anywhere on the hot path, pointer position lives in closure variables on a direct-DOM rAF loop. Physics integrates with dt clamped to 32ms: the cursor is a gravity well with a 180px capture radius; inside it each particle gets radial acceleration a = G/max(d², 24²) with G around 4e6 px³/s² plus a tangential component 0.6x the radial so particles spiral into orbit instead of beelining; particles within 48px of the cursor render 1.6x brighter (via globalAlpha, clamped to 1) as 2-4px velocity-aligned streaks — that band is the accretion ring — over a thin guide circle at r=36 (alpha 0.15) colored from the --muted token. Outside the well or on pointerleave, particles spring home with k=90 s⁻² and ζ=0.55 (underdamped, so letters visibly overshoot as they reform) with 0.92/frame velocity drag. Fill and stroke resolve from the --foreground CSS custom property (read via getComputedStyle(document.documentElement), re-read on a MutationObserver watching <html class> so a theme flip repaints particles in the correct color instead of going invisible on light) with per-particle alpha 0.5-1 keyed to speed, and sleep the rAF loop when every particle is within 0.5px of home and the cursor has left. Pointermove engages the well, pointerleave releases everything, no click behavior."
      }
    },
    {
      "name": "hero-isobar-contours",
      "type": "registry:ui",
      "title": "Hero Isobar Contours",
      "description": "Hero background of drifting, breathing isobar-like contour lines whose density bunches tightly around the primary CTA and whose whole field leans toward the pointer, so the layout physically reads as a live pressure system centered on the one action that matters.",
      "files": [
        {
          "path": "registry/loud/hero-isobar-contours/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-isobar-contours.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "svg",
          "hero",
          "contour",
          "isobar",
          "cta",
          "hierarchy",
          "ambient",
          "drift",
          "pointer-reactive",
          "signup"
        ],
        "instruction": "A hero whose background is ~30 closed isobar-like contour <path> rings, each one a plain SVG element whose stroke reads --border/--muted/--foreground (and color-mix(in srgb, var(--foreground) 25%, transparent) for the innermost two) directly as CSS custom properties on the `style` attribute — no getComputedStyle, no canvas, no color parsing at all, so both themes repaint for free. Ring radius follows radius(t) = minR + (maxR - minR) * t^2.15 for t in [0,1] across the ring index, a convex power curve that packs the inner rings tightly and spreads the outer ones — this radial compression toward the CTA's measured center is the entire hierarchy device; color only steps twice (border to muted to foreground@25%) as secondary reinforcement, never a continuous gradient. Each ring is additionally perturbed by a deterministic low-frequency harmonic sum (three sine terms at ascending integer frequencies with descending amplitude, a closed-form stand-in for 2D noise) sampled directly in theta, which is exactly periodic so every ring closes without a stitching seam; each ring also carries a small per-index phase offset and a slightly orbiting center (a few px, cosine/sine of the drift phase) so the rings read as a layered weather-map field rather than N identical concentric circles. The CTA's on-screen center is measured via getBoundingClientRect relative to the hero's own bounding rect and re-measured on a ResizeObserver watching both the hero container and the CTA element itself, so layout reflow (font load, viewport resize, content wrap) never leaves the field anchored to a stale position. Geometry is recomputed directly every throttled tick (~20fps via a rAF accumulator, no interpolation between cached keyframes — the recompute itself is cheap closed-form trig, on the order of a couple thousand sin/cos calls, so no Web Worker is warranted) and the phase advances (elapsed / 20s) * 2*pi in a continuous loop that never settles, giving the field constant drift (tightened from an original 40s once that read as too subtle to register as alive at rest); the loop pauses on IntersectionObserver (offscreen) and document visibilitychange (backgrounded tab). A second, independent phase clock (7s period) drives a breathing pulse by modulating minR itself as a single global +/-22% scalar (never per-ring), so the low-pressure centre visibly swells and contracts on its own faster rhythm instead of the field reading as a slow uniform rotation; folding the pulse into minR rather than perturbing each ring's radius independently keeps every ring's gap to its neighbor at (maxR - minR_breathing) * (t_i^2.15 - t_(i-1)^2.15), always positive, so the pulse cannot invert ring order even where the innermost rings are packed within a fraction of a pixel of each other, and it tapers to zero at the outer edge for free via the same (1 - t^2.15) the compression curve already has. Hovering or focusing the primary CTA raises a target inward pull of 8px, integrated every animation frame (not just the throttled draw ticks) through a damped spring (k=90, zeta=0.8) and applied per ring scaled by (1 - t), so inner rings pull hardest and the effect reads as the low visibly deepening; releasing focus/hover springs it back. Separately, the whole hero tracks the pointer (mouse/pen, not touch) and every ring leans a bounded outward swell toward the cursor's bearing — one-sided via max(0, cos(theta - angleToPointer))^2, gaussian-weighted by how close the pointer's distance from that ring's own center is to that ring's own radius, and clamped to 50% of its real base gap to the neighboring ring, deliberately well under 100% because the pre-existing wobble term already consumes part of that same gap on its own — so the pointer term alone is always a parallel, non-convergent lean (never a point-attractor that would pull rings across each other), and in practice adjacent contours stay clearly separated on the cursor-facing arc even directly over the densely-packed centre; a spring-eased 0..1 strength ramps the effect in/out on pointer enter/leave rather than snapping. The SVG layer is aria-hidden and pointer-events-none, so it never interferes with the real interactive CTA underneath — pointer tracking is bound to the hero section itself, not the SVG. The breathing pulse and pointer-lean are both full-motion-only: under reduced motion neither the faster breathing clock nor root-wide pointer tracking ever starts (a continuous redraw on every pointermove is exactly the motion that preference opts out of), leaving only the CTA's existing single-toggle hover/focus pull. The primary CTA is a solid bg-foreground/text-background button — deliberately neutral, since --accent is reserved for exactly one appearance in this whole component: the CTA's focus-visible outline, the same outline-2/outline-offset-2/outline-accent pattern used registry-wide. The secondary CTA is a bordered ghost button with the same focus treatment (in --border/--foreground, not accent, keeping accent scarce). Under prefers-reduced-motion the phase is frozen at a single fixed instant and the continuous spring/rAF loop never starts at all — the static ring spacing alone still does the complete hierarchy job, and hover/focus on the CTA instantly toggles between the resting and pulled-in ring layout (a single redraw, not an animation) so the interaction stays legible without motion. Zero dependencies."
      }
    },
    {
      "name": "hero-letterpress-lockup",
      "type": "registry:ui",
      "title": "Hero Letterpress Lockup",
      "description": "A letterpress hero whose identity is a terminal lock-up event — headline glyphs slide in as metal sorts on individually sprung composing rails, then the quoin tightens in one mechanical clunk: letter-spacing compresses, a hairline chase snaps to fit, and an impression flash prints the subhead beneath.",
      "files": [
        {
          "path": "registry/loud/hero-letterpress-lockup/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-letterpress-lockup.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "text",
          "typography",
          "letterpress",
          "spring",
          "entrance",
          "print"
        ],
        "instruction": "Build a hero headline whose identity is a terminal lock-up event, not an ongoing interaction. Split the headline into per-glyph spans (aria-hidden, direct-DOM only, no React state on the animated path) inside a whitespace-pre line; a real, full headline string is always present as a visually-hidden (sr-only) h1 so screen readers get one ordered string immediately regardless of animation state, and glyph spans are never focusable. On mount (unless prefers-reduced-motion), each non-whitespace glyph gets a deterministic (seeded off the headline text) starting offset of +/-40-120px translateX along its own baseline and its own randomized critically-ish damped spring (stiffness 90-180 s^-2, damping ratio 0.5-0.8, staggered start delay up to 200ms so sorts arrive in a loose sequence rather than all at once); each glyph's spring integrates every frame (accel = -k*p - c*v, c = 2*zeta*sqrt(k)) toward its rail's zero point, with overshoot past zero hard-clamped to 4px so no glyph ever travels backward through its neighbors by more than a hairline. Because translateX is paint-only, the line's natural bounding box is already the final assembled size from frame one, so a hairline (1px, --border) chase rect can be sized to it immediately (with padding) without waiting for the animation, and sits at opacity 0 / scale(1.01) until lock. The instant every glyph's position and velocity settle under a small epsilon (checked once per frame, no per-glyph independent lock), the quoin visibly tightens in one clunk: the headline's letter-spacing tweens from a resting -0.01em to a locked -0.03em (the -0.02em compression) over 90ms ease-in, the chase rect's opacity goes to 1 and its transform eases from scale(1.01) to scale(1) over 160ms on a cubic-bezier(0.16,1,0.3,1) snap, the headline's text-shadow jumps instantly to 0 1px 0 var(--border) simulating a fresh press impression, and the subhead (real paragraph text, opacity 0 until this instant, never aria-hidden — no content is ever gated behind the animation since a screen reader can read it regardless of its visual opacity) pops to visible with its color set to --foreground. After a ~90ms dwell, the impression eases off: the headline's text-shadow fades to transparent over 80ms and the subhead's color eases from --foreground to --muted over 320ms, like fresh ink drying. After that the hero is completely static forever — no ambient loop, no hover state, no re-trigger; assembly is only the setup for the single mechanical tightening moment. Colors read only from --background/--foreground/--muted/--border tokens (border and text-shadow both reference --border via a CSS var() so no hex ever appears). Under prefers-reduced-motion, skip every spring and the stamp flash entirely: glyphs render at their final translateX(0), letter-spacing is already -0.03em, the chase is already visible at scale(1), and the subhead is already --muted — the whole hero simply fades in once over 200ms, the only motion reduced-motion users see. No canvas is used; the whole effect is DOM transforms and CSS transitions, ResizeObserver keeps the chase sized to the line across reflow and webfont swap (document.fonts.ready), and every timer/observer/rAF is torn down on unmount."
      }
    },
    {
      "name": "hero-oscilloscope",
      "type": "registry:ui",
      "title": "Hero Oscilloscope",
      "description": "A full-width oscilloscope hero: a summed three-harmonic waveform drawn as one continuous box-drawing trace inside a box-drawing frame with a live amplitude/frequency readout, where the pointer injects energy into a real damped wave simulation that rings outward from the touched column and settles back over about a second.",
      "files": [
        {
          "path": "registry/loud/hero-oscilloscope/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-oscilloscope.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "hero",
          "canvas",
          "cursor",
          "waveform",
          "instrument"
        ],
        "instruction": "Build a full-bleed Canvas 2D oscilloscope: a monospace glyph grid (cell measured via an offscreen canvas's measureText, gated on document.fonts.ready so a fallback-font measurement never bakes in the wrong aspect ratio) is framed with a box-drawing border (┌┐└┘ corners, ─ top/bottom, │ sides) reserving row 1 inside the frame for a live readout string ('AMP x.xx  FREQ x.xxHz', centered) and the remaining interior rows/columns for the trace. The trace value per interior column is the sum of three harmonics at different spatial frequencies and phase speeds (a dominant fundamental plus two weaker, faster-moving overtones), scaled by the frequency prop's ratio to its default and by the amplitude prop, so the waveform has real structure rather than a bare sine. Consecutive columns are connected with box-drawing corner glyphs (╮ ╭ ╰ ╯) and vertical rule (│) rather than a single glyph per column: for each column, the transition from the previous column's row to this column's row fills every intermediate row with │ and caps the two ends with the corner glyph matching the direction of travel, so the trace reads as one continuous stroke at any slope, never a scatter of disconnected points. THE POINTER EXCITES THE LINE: pointer movement over the canvas is converted to a column index and an instantaneous speed, and injects velocity (gaussian-weighted across a couple of neighboring columns, clamped to a ceiling) into a real 1D wave-equation simulation running as a separate displacement layer summed on top of the base waveform — explicit finite-difference (tension coefficient ~620 col^2/s^2, fixed fixed-point ends so energy reflects rather than escaping as NaN, damping ~3.1/s so a disturbance rings out to a few percent of its amplitude in about a second), integrated in fixed ~1/120s substeps per frame regardless of the variable frame delta so the explicit integrator stays numerically stable even after a large gap (e.g. a hidden tab resuming). The displacement layer is additionally hard-clamped to a ceiling (with velocity zeroed on contact) so even an extreme, fast drag rings visibly against the frame's rails rather than pinning flat against them. The displacement and velocity buffers, the per-column row buffer, and the character grid are Float32Array/Uint8Array allocated once per resize and reused every frame — never reallocated in the hot loop, which matters especially here since this is exactly the propagation buffer the whole effect rests on. Ink for both the trace and the muted frame/readout is read via getComputedStyle at mount and re-derived on a MutationObserver watching <html>'s class attribute, since the site's theme toggle flips .dark live with no remount. prefers-reduced-motion renders exactly one static frame: the three-harmonic waveform evaluated at t=0 with zero displacement — real structure, correctly settled, no rAF scheduled, no pointer listeners bound. Optional `children` render as a headline centered over the field in a pointer-events-none DOM overlay, so the canvas stays interactive underneath. Props: amplitude (0..1, default 0.6), frequency (default 2.4, also shown on the readout and scales the waveform's spatial frequency), cellSize (px, default 14), className, children."
      }
    },
    {
      "name": "hero-recursive-type",
      "type": "registry:ui",
      "title": "Hero Recursive Type",
      "description": "A recursive-type hero: a giant wordmark rasterized through a block font, with every lit letterform cell subdivided and filled with real readable filler text, so the word is legible from across the room and made of smaller words up close — the pointer acts as a lens, scaling and brightening the interior text where it hovers.",
      "files": [
        {
          "path": "registry/loud/hero-recursive-type/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-recursive-type.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "hero",
          "type",
          "canvas",
          "cursor"
        ],
        "instruction": "Build a hero wordmark with two nested grids. The outer grid rasterizes the `headline` prop (uppercased; unsupported characters render blank) through a hand-authored 5x7 block font (A-Z, 0-9, space, and basic punctuation including , : ? ') into a coarse boolean letterform mask, exactly like hero-ascii-wordmark. Each lit coarse cell is then subdivided into an NxN grid of small cells, where N is derived per resize by measuring the mono font's advance width via an offscreen canvas (post document.fonts.ready — a fallback-font measurement bakes in the wrong subdivision count, and this measurement is load-bearing twice over since one grid nests inside the other) and snapping the coarse cell's pixel size to the nearest integer multiple of a ~15px target, clamped to 2-7 subdivisions. The `filler` string is then poured through the fine grid in raster order (row by row, left to right): each LIT fine cell consumes the next character of the filler string (wrapping when it runs out), while OFF fine cells are skipped without consuming a character, so word breaks and spacing in the filler text survive intact as it flows through the letterform's interior — this is computed once per resize into a reused Uint16Array of char codes (0 = blank), never per frame. UNDERNEATH the small text sits a second, static layer: a flat fill over every lit COARSE cell (one rect per letterform pixel, not per fine cell), rendered once per resize/theme onto an offscreen canvas at a low, constant alpha. This wash is what actually carries the letterform's silhouette — real filler text varies wildly in ink-weight and has its own word-gaps, so leaning on fine-cell glyph coverage alone to read as a clean shape from a distance doesn't work; the wash guarantees the word is legible at a glance regardless of what filler string is passed, while the small text drawn on top at a much higher, bold-weight alpha supplies the close-up 'made of words' read. The pointer is a lens: an eased cursor position (0.18 lerp/frame) and an eased 0..1 engagement scalar (0.12 lerp/frame, rising on pointer-enter, decaying on pointer-leave) drive a Gaussian falloff by distance; cells within roughly the lens radius are redrawn — in a small bounded box around the cursor recomputed each frame, never a full-grid rescan — scaled up via ctx.save/translate/scale/fillText/restore (up to ~1.9x, so magnified glyphs visibly overlap their neighbors) and at boosted alpha (up to fully opaque), drawn in three concentric distance bands from farthest to nearest so the cell closest to the cursor always paints on top. Outside the lens the field stays a legible but even texture over the wash. Direct-DOM rAF loop mutating refs only; glyph color read via getComputedStyle on mount and re-read through a MutationObserver on <html>'s class attribute (the theme toggle flips .dark live with no remount, and the observer also rebuilds the wash canvas so its tint follows). prefers-reduced-motion renders exactly one static frame at zero engagement — the full wordmark at its normal resting alpha over the wash, no lens, no pointer listeners bound. The canvas measures its own height from the wrapping div's width via ResizeObserver, so it never needs an explicit container height. aria-hidden canvas with the literal headline exposed via a sr-only sibling span. Props: headline (string), filler (string), lensRadius (px, default 130), className."
      }
    },
    {
      "name": "hero-vortex-street",
      "type": "registry:ui",
      "title": "Hero Vortex Street",
      "description": "Cursor fluid — moving the pointer through a full-bleed dark field sheds a von Karman vortex street of alternating-sign vortices that drift downstream and decay, revealed by ~600 velocity-aligned tracer streaks; clicking drops a counter-rotating vortex pair that stirs the field for exactly 3 seconds.",
      "files": [
        {
          "path": "registry/loud/hero-vortex-street/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-vortex-street.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "fluid",
          "vortex",
          "cursor",
          "canvas",
          "particles",
          "physics",
          "hero",
          "decorative"
        ],
        "instruction": "Build a full-bleed decorative Canvas 2D fluid field with zero interactive controls (canvas aria-hidden, pointer-events-none on the canvas itself with pointer handlers on its container — the direct analogue of a cursor-reactive particle hero). Backing store is dpr-clamped to 2 with a ResizeObserver-driven resize and a zero-size guard (skip when the container is under 4px). SIMULATION: 600 tracer particles live in two Float32Arrays (x and y) mutated in place — no allocation, .map, .filter, or object creation anywhere in the per-frame path. Each frame every tracer is advected by dt (clamped to 1/30 s) under the SUM of (a) a constant gentle ambient drift of (22, −7) px/s so the field is never static even with zero vortices, and (b) the tangential Lamb-Oseen velocity of every active vortex: v_theta = (Γ / 2πr)(1 − exp(−(r/r_core)²)) with r_core = 34 px, applied as the multiplier Γ(1 − exp(−r²/r_core²))/(2πr²) on the perpendicular vector (−dy, dx) from vortex center to tracer (regular at r→0, guard r² with 1e-6). Tracers wrap at the edges with a 6 px margin. VORTEX STORE: fixed capacity 24 in parallel typed arrays (x, y, signed initial Γ, birth time, kind flag, active flag, plus a per-frame effective-Γ scratch array and a compacted active-index Int32Array); slot allocation reuses any inactive slot else evicts the oldest. Shed vortices decay as Γ·exp(−age/2.2 s) and are pruned when |Γ_eff| < 1500; click vortices decay as Γ·(1 − age/3)² and are removed at exactly age = 3 s. SHEDDING DRIVER: a driver position eases exponentially (rate 6 s⁻¹, factor 1 − exp(−6·dt)) toward the real pointer position if the pointer moved within the last 1.5 s, otherwise toward an internal slow Lissajous orbit x = w(0.5 + 0.36·sin(0.5t + 1.3)), y = h(0.5 + 0.34·sin(0.34t)) — this single mechanism gives idle ambient shedding (self-driving demo, no synthetic events) and seamless pointer takeover. When driver speed exceeds 90 px/s, accumulate distance traveled; every 44 px spawn one vortex with ALTERNATING sign, Γ = ±140 × speed (speed clamped to 120–800 px/s), positioned 12 px behind the driver along −velocity plus 10 px perpendicular offset to the alternating side, producing the characteristic two-row zig-zag street. CLICK: a real pointerdown spawns a standing counter-rotating pair at the click point, ±16 px apart horizontally, Γ = ±90000 (elevated), gone after exactly 3 s. RENDER: each frame first fill the whole canvas with the --background token at alpha 0.32 (short-lived motion trails), then draw faint rotating glyph rings on each active vortex (a 1px --muted circle of radius 0.85·r_core plus a 5 px radial tick rotating at 1.4 rad/s signed by rotation direction, alpha 0.1 scaled by |Γ_eff|/40000, capped), then draw every tracer as a streak from (x − vx·k, y − vy·k) to (x, y) with k = 0.055 s in --foreground, alpha = 0.08 + speed·0.003 capped at 0.85; tracers faster than 150 px/s first get a glow under-stroke 3 px wide in a blue-white derived by mixing the --accent token 50% toward white, alpha 0.12 + (speed − 150)·0.0016 capped at 0.4. ALL canvas ink comes from CSS custom properties read via getComputedStyle(document.documentElement) at mount and re-read through a MutationObserver on documentElement's class attribute (repaint an opaque background flush on theme flip so old-theme trail pixels vanish). The rAF loop pauses on visibilitychange. REDUCED MOTION (matchMedia, tracked live): no rAF loop and no pointer listeners at all — render ONE static frame: 26 seed points spaced down the left edge, each integrated 300 Euler steps of 0.02 s through a frozen field of four alternating ±52000 vortices at x = 0.30/0.44/0.58/0.72 of width alternating between 0.44 h and 0.56 h (ambient boosted 2.4×), stroked as thin 1 px --foreground curves at alpha 0.32 with --muted ring hints at the four centers. No SVG, no dependencies."
      }
    },
    {
      "name": "loader-loom-weave",
      "type": "registry:ui",
      "title": "Loader Loom Weave",
      "description": "Loader as a loom — warp threads span the frame, a shuttle carries the weft back and forth alternating direction per row like real weaving, and progress is the woven cloth accumulating row by row, each pass beaten up against the fell with a small compression bounce.",
      "files": [
        {
          "path": "registry/loud/loader-loom-weave/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/loader-loom-weave.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "loader",
          "progress",
          "canvas",
          "weaving",
          "shuttle",
          "ambient"
        ],
        "instruction": "Build a progress loader rendered as cloth being woven on a loom. A Canvas 2D frame (~160px tall) draws vertical warp threads at 7px pitch in --muted with per-thread alpha variance (0.22–0.32) so the warp reads as fiber, not a grid. Progress maps to woven rows: rowCount = floor(height/5px), current = progress * rowCount. Completed rows live on an offscreen fabric canvas, appended incrementally one row per completion (full rebuild only on resize, theme change, or a backwards/jumping progress value): each weft row is a 1.6px --foreground line with a tiny per-row sine offset, then short --muted warp stubs are redrawn over it at alternating crossings ((warpIndex + row) % 2) so the over-under weave texture is real, not implied. The current row renders live on the main canvas: a partial weft from the row's start edge to the shuttle, and the shuttle itself — a pointed lozenge (~18px, quadratic-curve outline, --foreground fill, mirrored by direction) riding the row. Direction alternates per row exactly like a real shuttle: even rows weave left-to-right, odd rows right-to-left. When a row completes, the fabric is beaten up: the cloth blit offsets ~1.5px toward the fell and relaxes over 140 ms. A 1px --border fell line marks the woven edge. Uncontrolled, the loader self-runs a staged loop (fast to 35%, stall, to 72%, stall, finish, hold, restart) with ease-out-cubic between stages; a `value` prop (0–100) takes over when provided, flowing through a ref so the rAF loop never re-subscribes. Semantics: role=progressbar with aria-label and aria-valuenow updated only on whole-percent changes, a mono percent readout (aria-hidden, tabular-nums), and an sr-only aria-live=polite region that announces completion once. All canvas ink is read from getComputedStyle CSS custom properties at mount and re-read via a MutationObserver on documentElement class; dpr-clamped(2) backing store, zero-size guard, loop paused on visibilitychange, everything torn down on unmount. REDUCED MOTION: no rAF loop — a static frame at the current value (72% when uncontrolled) redrawn only on value/theme/resize change, no beat-up bounce."
      }
    },
    {
      "name": "not-found-knockout",
      "type": "registry:ui",
      "title": "Not Found Knockout",
      "description": "404 numerals rendered as literal absence — destination-out punches the glyphs through a token surface plane to reveal a deep grain void with drifting motes, defined by a bright stencil bevel rim and an inner wall-light glow so the digits read instantly in both themes, edges springing outward near the cursor.",
      "files": [
        {
          "path": "registry/loud/not-found-knockout/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/not-found-knockout.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "404",
          "typography",
          "negative-space",
          "canvas",
          "cursor",
          "page"
        ],
        "instruction": "Build a full 404 page where the numerals are literal absence: a single full-viewport Canvas 2D layer redrawn with an EXPLICIT FULL CLEAR every frame in this order — (a) void backdrop: a 128px seamless value-noise grain tile precomputed once per theme (recolored in --muted, alpha 0.2, tiled at device resolution) over a deep token base (--background crushed 60% toward black in dark, background mixed 16% toward foreground in light so the void reads CLEARLY dimmer than the surface in BOTH themes — the interior/surface gap is the legibility floor), plus 12 drifting motes (1-2px squares, --muted alpha 0.3, 4px/s on wrapped paths inside the glyph bounding box); (b) surface plane: an offscreen plane canvas refilled solid --surface; (c) carve: destination-out drawImage of an offscreen glyph canvas ('404', Geist Sans 600, font-size min(38vw, 62vh), rasterized after document.fonts.ready, centered at 46% height) so the numerals become holes showing layer (a) — because the plane is refilled fresh each frame, destination-out alpha can never accumulate or quantize; (c2) inner wall-light: a static offscreen glow layer — the undisplaced outline stroked in muted-mixed-45%-toward-foreground with layered shadowBlur (max(14px, 7% of font-size) and 2.4x that) then clipped to the holes via destination-in against the glyph mask, rebuilt per resize/theme — so the cut walls catch light and the digit interiors read as carved depth; (d) rim: the glyph outline restroked at 1.5px in border-mixed-55%-toward-foreground as the crisp definition stroke, plus two ±0.75px offset strokes in border-mixed-80%-toward-foreground (catch-light) and border-mixed-55%-toward-background (inner shadow) tones for an engraved stencil bevel bright enough that 404 reads at a glance. EDGE INTERACTION: threshold a half-res alpha mask of the glyph, Moore-neighbor trace its contours (outer loops and the 0's counter), arc-length resample to ~400 points with two smoothing passes and outward normals (oriented away from ink via mask probe); cursor proximity with gaussian falloff sigma 120px displaces points outward up to 6px along their normals; each point returns on a spring (k=520 s^-2, zeta~0.72, one ~3% overshoot, settle < 400ms) with a forced-settle deadline: cursor influence dies at 600ms idle and a hard deadline at 1400ms snaps displacement and velocity to zero; the displaced rim is restroked from the point list every frame. INTERACTION MODEL: pointermove/pointerdown nudge edges; DOM copy ('This page drifted into the void.') and two CTAs — 'Take me home' (accent primary, rounded-sm, hover accent-hover) and 'Contact support' (ghost bordered, hover border-foreground/40) — float below the punched area, positioned by an OFFSET transform from the container center (never absolute canvas coords), both keyboard-focusable with token-relative accent focus rings; an sr-only h1 keeps semantics. HOUSEKEEPING: all inks parsed from getComputedStyle tokens at mount with a MutationObserver on documentElement class that re-derives colors AND regenerates the grain tile + glyph canvas per theme; canvas sized via explicit style.width/style.height plus a dpr-clamped (2) backing store and setTransform; rAF is direct-DOM with zero React state on the hot path and sleeps when cursor idle > 500ms and all edge springs are under epsilon (motes freeze on sleep, redraw resumes on wake) after a 2.2s ambient intro; IntersectionObserver and document.hidden pause the loop; zero-size containers guarded; every listener, observer, and rAF torn down. REDUCED MOTION: one static carved frame, motes frozen, no edge displacement, no pointer listeners. Demo composes the complete page: top strip with mono logotype and one nav link, carved 404 centered, muted copy line, the two CTAs, and a hairline footer with mono status text 'ERR 404 / route unresolved'."
      }
    },
    {
      "name": "not-found-postmark",
      "type": "registry:ui",
      "title": "Not Found Postmark",
      "description": "A 404 rendered as returned mail — the failed URL addressed on an envelope that slides in, its routing history stamped hop by hop (edge, origin, router) as sequential ink postmarks, the last hop knocked down with a squash-and-settle NO SUCH ADDRESS hand-stamp and a 1px shake, then a forwarding-address label (search, home, recent pages) peels on as the real recovery page.",
      "files": [
        {
          "path": "registry/loud/not-found-postmark/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/not-found-postmark.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "404",
          "error-page",
          "svg",
          "postal",
          "sequence",
          "recovery",
          "page"
        ],
        "instruction": "Build a full 404 page whose graphic is a single piece of returned mail, not an illustration of an error. An envelope card (--surface fill, --border rules) slides in from the left (~150ms, ease-out-expo) holding a Geist Mono 'To:' address block with the real bad path (prop `path`, defaulting to `window.location.pathname + search`). Below the address, a row of routing-history postmarks — one per entry in prop `hops` (default Edge / Origin / Router, each `{ label, detail? }`), joined by a dashed connector rule — stamp in sequentially 250ms apart: each is an inline SVG cancellation mark (concentric circles plus two wavy cancel bars) that animates scale(1.3→1) with a small per-hop rotation scatter (deterministic from the label, ±6deg) on an ease-out-expo curve, and every postmark plus the final stamp shares one SVG `<filter>` (feTurbulence fractalNoise + feDisplacementMap against SourceGraphic, geometry-only — no baked color) so the ink reads grained rather than vector-clean. The last hop is where the trail dies: a larger NO SUCH ADDRESS rectangular hand-stamp (double-rule border, rotated -8deg) drops onto it right after that hop lands, playing a two-frame squash (scaleY down then a slight overshoot before settling to 1) while the whole envelope card gets a 1px horizontal shake — the one moment color escalates from postmark to verdict. A forwarding-address label card peels on immediately after (slight skewY + translateY easing to flat, ease-out-expo) and IS the real 404 page: a labeled search input plus a Home link plus `recentPages` links, all real, focusable, in source order, sitting entirely outside the aria-hidden envelope so nothing in the theatrical region is ever reachable by keyboard while invisible to assistive tech. The document title and an always-present h1 read exactly '404 — page not found' on mount, and a plain-text mono line above the envelope states the same story in words ('Route attempted: <path> — failed at every hop (Edge → Origin → Router) with no such address') so the failure is legible with zero motion. Reduced motion renders every postmark, the final stamp, and the forwarding label already landed in their resting transforms — no drop-in, no squash, no shake, no peel, and document.title/h1 are unaffected either way."
      }
    },
    {
      "name": "reveal-cloth-unfurl",
      "type": "registry:ui",
      "title": "Reveal Cloth Unfurl",
      "description": "Media reveal that unrolls an image like a bolt of cloth: ~24 vertical strips fan open left-to-right off a rotateY hinge, curled strips converge into a shaded cylinder at the roll's leading edge, and one staggered settle ripple (plus a sheen sweep) crosses the surface once fully unrolled.",
      "files": [
        {
          "path": "registry/loud/reveal-cloth-unfurl/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/reveal-cloth-unfurl.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "image",
          "reveal",
          "scroll",
          "3d",
          "hero",
          "media",
          "intersection-observer"
        ],
        "instruction": "Build <BoltUnfurl src alt trigger strips? aspectRatio? className?> where `trigger: number` is a counter the caller bumps to (re)play the unroll from scratch (an IntersectionObserver firing once on first view, and/or a Replay button) — the component diffs `trigger` against the last value it saw in a ref and starts a fresh 0->1 tween any time it changes, including while already mid-animation. STRUCTURE: a real <img src alt> fills the box (object-cover) underneath everything, both for accessibility (the caller's real alt text) and as network-free progressive enhancement; an aria-hidden overlay of exactly `strips` (default 24) absolutely-positioned divs sits on top and is what's actually seen once painted, since each strip is opaque. SLICING: strip i has `left: (i/strips)*100%`, `width: 100/strips%`, and shows its slice of the same image via the classic CSS sprite technique — `backgroundImage: url(src)`, `backgroundSize: (strips*100)% 100%`, `backgroundPosition: (i/(strips-1))*100% 0%` — no canvas, no cropping math beyond that one percentage formula. MOTION: one requestAnimationFrame loop tweens a single `progress` 0->1 over 1300ms with an ease-out-expo curve; every frame it computes `rollX = progress * containerWidth` and, for each strip, `curl` (0 = flat/unrolled, 1 = fully rolled) as a smoothstep of the strip's rest-center distance from rollX across a curl band ~16% of the container width — strips left of the band are curl 0, right of it are curl 1, inside it interpolate. Each strip's actual style.transform is `translateX(tx) rotateY(angle)` where `angle = -curl * 86deg` and, critically, `tx = (rollX - centerX) * curl` — the translate is what converges every still-rolled strip's center onto the roll cursor as curl approaches 1, bundling them into a dense, foreshortened, shaded cylinder sitting at the leading edge rather than 24 independent slivers spread across the whole width (that translate term is the difference between reading as \"a rolled bolt of cloth\" and reading as \"broken image\"). `filter: brightness(1 - curl*0.45)` darkens curled strips for the shaded side of the roll. transform-origin is `left center` on every strip so the hinge is physically at its own left edge. A single absolutely-positioned shadow div (linear-gradient transparent -> rgba(0,0,0,0.35), ~24px wide) tracks `rollX` via the same per-frame writes, giving the roll's leading edge a soft cast shadow on the already-flat part; it's opacity-hidden outside 0<progress<1. SETTLE: the instant progress reaches 1, a CSS class toggle (removed then re-added after a forced reflow, so replays restart it cleanly) plays one 260ms per-strip `rotateX(-6deg) -> rotateX(0deg)` keyframe with a 14ms stagger (via an inline `animationDelay` set just before the class is added) — the fabric settle ripple — plus, being `loud`, a diagonal sheen sweep (a `::after` layer, `background-position` animated across a wide gradient band) that plays once over the same beat; both are pure CSS keyframes, no JS per-frame work. REDUCED MOTION: the whole strip/tween machinery is skipped — on trigger, `progress` jumps straight to 1 (paint once, no rAF loop) and the strips wrapper gets one opacity fade-in class, so the caller still sees a state change but nothing rolls, rotates, or ripples. A11Y: the real <img>'s alt text is the only accessible description; every strip and the shadow overlay are aria-hidden; the Replay button (owned by the demo, not the component) is a real, labeled, focusable <button>. DEMO: the media box's src is generated once on an offscreen canvas (diagonal bands + a couple of rings in the live --foreground/--border/--accent tokens) precisely so the bare demo never depends on a network image URL; an IntersectionObserver on the box bumps `trigger` the first time it scrolls into view, a visible Replay button bumps it again on demand, and a self-driving interval also bumps it every few seconds so the reveal plays unattended for screenshots."
      }
    },
    {
      "name": "scroll-defrost",
      "type": "registry:ui",
      "title": "Scroll Defrost",
      "description": "Scroll-scrubbed defroster — a pinned full-bleed image behind rippled shower glass anneals from heavy frost with chromatic fringing to optical clarity, sealed by a specular sweep.",
      "files": [
        {
          "path": "registry/loud/scroll-defrost/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/scroll-defrost.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "scroll",
          "webgl",
          "shader",
          "image",
          "refraction",
          "glass",
          "hero"
        ],
        "instruction": "Build a pinned scroll-scrub frosted-glass reveal: a 300vh section holds a position:sticky h-screen pane; behind a rippled shower-glass shader sits a full-bleed image (prop src, else a monochrome studio still — sphere, wall glow, ground shadow, film grain — generated once on an offscreen canvas so the bare component is network-free). Raw WebGL 1, zero deps: one fullscreen triangle-strip quad and a single fragment shader. A 256x256 seamlessly-tiling RGBA noise texture is generated once on the CPU from two octaves of periodic sin-hash value noise (house noise2 on a wrapped lattice); central-difference gradients pack a normal map into RG with ripple height in B, sampled in the shader at gl_FragCoord in CSS px over REPEAT wrap. Refraction offset = normal.xy * pow(1-p, 1.6) * 42px; chromatic dispersion samples R/G/B at offset scales 1.0 / 1.012 / 1.024 so fringing lives on every ripple edge; frost scatter = 5-tap Poisson blur at radius (1-p)*10px; frost also lifts the pane toward white (mix 0.22 at full frost) with ripple-height shading; a white specular band (smoothstep width 0.08 along a diagonal UV axis) crosses the pane exactly once as p runs 0.92 to 1.0. Scroll model: a passive window scroll listener writes target progress (-rect.top / (sectionHeight - viewportHeight), clamped 0-1) to a closure variable; a direct-DOM rAF loop lerps rendered progress toward it at 0.12/frame, snaps and sleeps when |target - current| < 0.001, and wakes on scroll only — no React state anywhere on the hot path, fully reversible in both directions. Overlay side rail in font-mono: vertical-rl caption SCROLL TO ANNEAL, a 1px track filled via scaleY transform, and a zero-padded live percent, all updated by textContent/style writes inside the same loop (plus an optional onProgress callback). If WebGL or shader compile is unavailable, fall back to a plain object-cover img with CSS blur(12px * (1-p)) driven by the same lerp. Under prefers-reduced-motion render a single shader frame at p=1 — clear image with a faint frost vignette confined to the pane edges via an edge-frost uniform — and collapse the section to one viewport with no listeners."
      }
    },
    {
      "name": "scroll-story-strata",
      "type": "registry:ui",
      "title": "Scroll Story Strata",
      "description": "Pinned scroll story — the viewport becomes a geological core cross-section and scroll drives a drill bit descending through procedurally banded strata, with content panels crossfading per band and a mono HUD ticking depth.",
      "files": [
        {
          "path": "registry/loud/scroll-story-strata/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/scroll-story-strata.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "scroll",
          "story",
          "canvas",
          "hero",
          "section",
          "parallax",
          "noise"
        ],
        "instruction": "Build a pinned scroll story: a 250vh scroll track wrapping a position:sticky 100vh stage. One full-stage Canvas 2D layer draws a geological core cross-section: 5 strata bands (thickness 12-28% of stage height each), each filled with 2-octave value-noise grain (3px cells) quantized to 3 gray levels interpolated between --border and --muted, with slightly different quantization thresholds per band so bands read distinct in monochrome; exactly one accent vein — a 1.5px --accent polyline at a fixed depth inside band 4. Grain is rendered ONCE per resize/theme into an offscreen band atlas and blitted per frame — never per-frame noise. DRILL: center-left column with a mono-weight chevron bit and drill string stroked in --foreground plus faint depth ticks; depth target = scrollProgress * totalDepth and the rendered depth eases with depth += (target-depth)*(1-exp(-dt*8)) (~120ms lag), with a forced-settle deadline that snaps to target 250ms after the last scroll event so the HUD never ticks forever. While |depth velocity| > 2px/s the bit tip emits 12-particle chip-spray batches (life 400ms, gravity-integrated, pruned array with an explicit full clear per frame — no destination-in accumulation). CAMERA: the scene is overscanned ~1.22x and the canvas translates at 0.15x scroll delta plus a scale 1.00→1.06 across full progress; a DOM label layer rides the identical camera via an offset CSS transform (never absolute canvas/page coords). CONTENT: 5 DOM panels stacked in a right column (45% width, padded surface card rounded-md); when the eased depth crosses a band threshold the outgoing panel fades/slides -12px and the incoming fades/slides in from +12px over 350ms cubic-bezier(0.22,1,0.36,1). HUD: stage-corner font-mono depth readout in meters with 2 decimals updated via direct DOM every frame while moving; the band label gains --accent only while inside the vein band. INTERACTION: band labels along the left edge are focusable buttons that scroll the track to that band; hovering/focusing a label draws a 1px --foreground top-rule on its band in the canvas. REDUCED MOTION: no pin choreography — a static labeled cross-section rendered at full depth, panels as a normal stacked list, HUD frozen. ENGINEERING: all strata/grain/vein/HUD inks derived from getComputedStyle tokens at mount with a MutationObserver on documentElement class re-deriving live and regenerating the band atlas on theme flip; canvas sized via explicit style.width/height with a dpr-scaled backing store (sticky inset does not size a replaced element); rAF sleeps when |depth - target| < 0.1px and the chip list is empty; IntersectionObserver pauses the loop offscreen; zero-size stage guard; scroll listener passive and removed with all observers/rAF on unmount; no React state on the hot path."
      }
    },
    {
      "name": "slider-chladni-tune",
      "type": "registry:ui",
      "title": "Slider Chladni Tune",
      "description": "Precision-tuning slider whose readout is a plate of a few thousand sand grains: off target they churn in a formless haze, on target they lock into a crisp symmetric Chladni figure and hold still — distance-to-correct read as pattern coherence, not a number.",
      "files": [
        {
          "path": "registry/loud/slider-chladni-tune/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/slider-chladni-tune.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "slider",
          "canvas",
          "calibration",
          "physics",
          "particles",
          "tuning",
          "input",
          "form",
          "aria-live"
        ],
        "instruction": "A tuning slider paired with a square Canvas 2D sand plate above it. `target` (required) is the value the plate resolves at and is never rendered as a number anywhere in the UI — only proximity is communicated, like tuning by ear. MECHANISM: a few thousand grains (Float32Array x/y pairs, `grainCount` default 2200) each propose a small random step every frame; the step is judged by a Metropolis-style accept/reject against |f(x,y)| where f(x,y) = sin(n*pi*x)*sin(m*pi*y) - sin(m*pi*x)*sin(n*pi*y) is the Chladni mode function for a mode (n, m) — steps that shrink |f| are always taken, steps that grow it are taken anyway with probability normalizedDetune^1.4. That acceptance probability IS 'attraction strength = 1 - normalizedDetune' expressed as a rejection rate rather than a force: near detune 0 worse moves are almost never kept, so grains that land on the zero set (the nodal lines) stay there, locked and nearly motionless; near detune 1 worse moves are accepted almost always, so the walk is unbiased and no pattern ever accumulates. Jitter step size also scales linearly with detune (0.0016 to 0.03 in plate-normalized units), so off-target grains visibly churn harder as well as failing to organize. detune = clamp(|value - target| / detuneSpan, 0, 1) where detuneSpan defaults to 25% of the min/max range (override via `detuneSpan`); the plate is considered LOCKED when |value - target| <= lockEpsilon (default max(step, 0.5% of the range)). Mode (n, m) is picked deterministically from `target` via a cheap sine hash into a curated table of ten asymmetric pairs (n === m is rejected as degenerate, since sin(n*pi*x)*sin(n*pi*y) - sin(n*pi*x)*sin(n*pi*y) is identically zero) so every distinct target gets its own figure; overridable via `mode`. RENDER: the canvas is fully cleared and redrawn from the grain array every frame (fillRect plate fill + one fillRect per grain, no alpha accumulation, no destination-out) so a locked plate is provably a clean figure, never residue. Plate fill is --background and grain ink is --foreground at 0.5 alpha, both read via getComputedStyle at mount and re-derived on a MutationObserver watching documentElement's class attribute, so a theme flip repaints correctly instead of baking in stale colors. DPR clamped to 2, ResizeObserver keeps the backing store correct, IntersectionObserver plus a visibilitychange listener pause the rAF loop offscreen/hidden; the loop otherwise never fully sleeps because even a locked plate keeps a small residual jitter (real sand vibrates, it doesn't teleport to a stop). SLIDER: a plain core-styled track (bg-border rail, bg-foreground fill, a bg-foreground thumb whose position is a left percent) beneath the plate, driven by a pointerdown/pointermove handler on the track itself that maps clientX to value directly — deliberately not left to a native range input's own drag handling, since a synthetic pointerdown/pointermove dispatched at a native thumb is not guaranteed to move it (that click-drag-to-set-value behavior is UA-internal to trusted input), which would silently break the autoplay demo. A real native input type=range sits sr-only (not display:none) inside the same track for keyboard: Tab reaches it and arrow/Home/End/PageUp/PageDown come free from native semantics; its focus state is mirrored onto the visible thumb as an accent ring (ring utilities, never focus-visible:outline-* alongside a bare outline-none, which renders invisible in this Tailwind v4 setup). Controlled (`value`/`onValueChange`) or uncontrolled (`defaultValue`, default 50). ACCESSIBILITY: aria-valuetext reports both the number and a categorical proximity phrase derived from detune — 'on target' when locked, else 'very close to target' / 'close to target' / 'off target' / 'far from target' (e.g. '42.1, very close to target') — so a screen reader user gets the same convergence signal sighted users read off the sand. A Geist Mono readout beneath the slider (VALUE / COHERENCE percent, tabular-nums) duplicates that same information as visible text, plus a plain-language state word (scattered / converging / locked), so the canvas is never the only channel. A dedicated aria-live=polite region (sr-only) holds 'On target.' exactly while locked and is empty otherwise, so it announces once on the transition into lock rather than spamming every drag tick. REDUCED MOTION: prefers-reduced-motion (checked live via a matchMedia change listener) swaps the canvas for a static, non-canvas SVG of 220 points, computed once per mode as three fixed sets — 'scattered' (a deterministic pseudo-random spread), 'locked' (the 220 grid points, from a 44x44 sample, with the smallest |f|, i.e. genuinely the nodal lines), and 'forming' (each scattered point lerped 55% toward its paired locked point) — and the live detune/lock state just SELECTS which of the three renders; there is no interpolation or motion between them, an instant swap on every value change, so the same convergence signal stays legible with zero animation. Zero dependencies."
      }
    },
    {
      "name": "sticker-peel",
      "type": "registry:ui",
      "title": "Sticker Peel",
      "description": "A vinyl sticker you can actually peel — drag a corner and the printed face lifts on a moving fold line, exposing a pale adhesive underside with a sweeping specular sheen; release early and it re-sticks with one slappy flap, drag past 70% and it tears free, flutters under air resistance, and re-adheres where dropped.",
      "files": [
        {
          "path": "registry/loud/sticker-peel/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/sticker-peel.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "sticker",
          "peel",
          "drag",
          "3d",
          "clip-path",
          "physics",
          "spring",
          "micro-interaction",
          "accessibility"
        ],
        "instruction": "Build a peelable vinyl sticker as a 320x320 rounded (16px) panel that is itself the interactive element: a div with role=button, tabIndex=0, aria-label 'Peel sticker — drag a corner, or press and hold Space', cursor grab at rest and grabbing while actively dragging, touch-action none, and a perspective of ~900px so the peel reads in 3D. The panel must have a real visible :hover change (a -3px translateY lift, brighter border via color-mix of --foreground into --border, and a deeper box-shadow) and a visible :focus-visible ring (2px solid --accent outline, 4px offset). Centered inside sits a 220px-square decal built entirely from CSS and inline SVG — no image assets: an --accent-colored face carrying a bold lightning-bolt monogram (inline SVG path, fill var(--background) at ~0.92 opacity, faint dark stroke), a diagonal white gloss gradient, a halftone print texture (repeating radial-gradient dots, 1px circles on a 7px grid at ~0.35 opacity), a 7px 'kiss-cut' pale border (color-mix of --foreground 90% into --background), and 16px rounded corners. The peel is the classic CSS 3D two-layer trick, no canvas/WebGL. Two stacked layers inside a preserve-3d wrapper: (1) the FRONT face, clipped by a clip-path polygon to the still-stuck region, and (2) an adhesive-pale UNDERSIDE flap (a linear-gradient from color-mix(--foreground 62%, --accent) near the fold to color-mix(--foreground 94%, --background) at the tip, angle chosen per corner so shading always darkens toward the fold), clipped to the inverse region. Geometry, in a corner-local frame where the grabbed corner maps to (S,S) with S=220: peel progress p in [0,1] places the fold line at x+y=c with c=2S(1-p); the front keeps the u<=c region (a pentagon while c>S — [(0,0),(S,0),(S,c-S),(c-S,S),(0,S)] — collapsing to the triangle [(0,0),(c,0),(0,c)] once c<=S), and the flap is clipped to the inverse u>=c polygon in its own untransformed coordinates, then folded exactly over the fold line by a 2D reflection matrix — for the bottom-right corner matrix(0,-1,-1,0,c,c), generalized to any corner by sign flips ex/ey in {1,-1} and offsets ox/oy in {0,S}: matrix(0, m, m, 0, ex*k, ey*k) with m=-ex*ey and k=c-ox-oy, with every polygon vertex mapped through (x,y)->(kx?x:S-x, ky?y:S-y). After the reflection, append rotate3d(ex,-ey,0, lift) — an axis parallel to the fold line — with transform-origin at the fold midpoint, where lift = ex*ey*(10 + 42p) degrees: the curl tightens (steeper lift) as progress grows, and the perspective parent makes the flap tip visibly rise toward the viewer, overflowing past the decal footprint at high p. While any peel is in progress a specular sheen band (diagonal white linear-gradient at 240% background-size) sweeps across the flap via a 1.7s background-position keyframe loop, and a cast shadow is painted on the still-stuck face as a radial-gradient ring emanating from the grabbed corner with radius sqrt(2)*S*p, so darkness pools just beyond the fold. A separate inset shadow div under the decal carries the drop shadow (written per-frame as a box-shadow string). INTERACTION — all continuous per-frame numbers (progress, tilt, flutter position, spring state) live in refs/locals inside one requestAnimationFrame loop writing styles directly; React state holds only discrete facts (grab cursor class, live-region text). Pointerdown (with setPointerCapture) selects the decal corner nearest the pointer, records that corner's page position, and starts a drag; per-frame progress = clamp(distance from pointer to corner / (S*1.2), 0, 1). Pointer velocity is smoothed on move (exponential mix, decaying toward zero at ~e^-5t when the pointer rests) and tilts the whole decal via wrapper rotateY (clamped ±12°, ~0.018 deg per px/s) and rotateZ (clamped ±8°), lerped toward target at ~9/s, plus a small progress-proportional base rotateY (~-4° * p, signed by corner) so the sticker leans as it lifts. RELEASE below 0.7: re-stick on an underdamped spring on the progress value itself (k=210 s^-2, zeta=0.42, starting from the release progress toward 0) — it overshoots below zero exactly once; negative q renders as p=0 plus a squash scale pulse (1 + min(0.05, -q*0.35)) on the wrapper, which is the corner 'flapping once' as the adhesive slaps down; settle at |q|<0.004. RELEASE at/after 0.7 (or a Space-hold ramp completing at 1.0): tear free — enter a flutter phase (~850ms) where the wrapper translates from its current home offset to the drop point (pointer position relative to the panel center for drags, a small downward drift for keyboard, both clamped to ±44px so the decal stays inside the panel) on an ease-out-cubic with a decaying sinusoidal horizontal sway (9px * sin(9t) * (1-u)), while rotateZ rocks under air resistance as 16° * e^(-2.2t) * sin(9.5t) and peel progress decays back to 0 so the sticker flattens as it falls; the drop shadow floats larger and softer during flight. Then a resettle phase (~380ms): the home offset commits to the drop point, the drop shadow BLOOMS (blur +30px, opacity +0.12, both shaped by sin(pi*s)) then flattens back to rest, with a matching tiny squash pulse. KEYBOARD: Space keydown (ignore repeats; preventDefault so the page doesn't scroll) starts a hold that ramps progress to 1.0 over 1.2s through the same progress variable; Space keyup before 0.7 triggers the same spring re-stick, keyup at/after 0.7 or natural ramp completion triggers the same tear sequence; blur while holding releases. Enter/click alone do nothing. ACCESSIBILITY: an sr-only span (role=status, aria-live=polite, aria-atomic=true) announces 'Peeling' on drag/hold start, 'Re-stuck' when the spring settles, 'Torn free' when the tear triggers, and 'Re-adhered' when resettle completes; append an alternating zero-width-space suffix (parity toggle) so repeated identical messages still change the text node and re-announce. All decal visuals are aria-hidden; the panel is the only focusable element. REDUCED MOTION (window.matchMedia prefers-reduced-motion): drag and hold still track progress directly so the interaction works, but there is no velocity tilt, no sheen sweep (animation disabled in the media query), and every release lands instantly at its resolved state — re-stick snaps straight to p=0 (announce 'Re-stuck'), tear places the decal at the drop point immediately with 'Torn free' then 'Re-adhered' ~350ms later, no spring, no flutter, no bloom. DEMO MODE: a demo?: boolean prop runs an internal script through the exact same progress/release code paths — peel to 0.42 over 800ms, brief apex hold, release (spring re-stick), wait ~2.4s, peel to 0.92 over 900ms, release (tear + flutter + resettle cycling through a few preset drop offsets), wait ~3.4s, repeat — refusing to start (and retrying ~900ms later) whenever a real pointer or keyboard interaction is active or any phase other than fully-stuck is in flight; scripted runs skip the live-region announcements so the demo loop never spams screen readers. The rAF loop sleeps whenever nothing is active and wakes on visibilitychange; all timeouts, listeners, and the frame are torn down on unmount. Colors come only from the theme tokens (--background, --foreground, --muted, --border, --accent) via color-mix, plus neutral white/black rgba overlays for gloss, sheen, halftone, and shadows. Zero dependencies."
      }
    },
    {
      "name": "success-iron-filings",
      "type": "registry:ui",
      "title": "Success Iron Filings",
      "description": "Success moment as magnetism — a field of drifting iron filings snaps to attention when the action confirms, migrating and aligning to draw the checkmark as revealed field lines, with a halo of off-path filings rotating to the local field direction.",
      "files": [
        {
          "path": "registry/loud/success-iron-filings/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/success-iron-filings.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "success",
          "celebration",
          "confirmation",
          "canvas",
          "particles",
          "checkmark"
        ],
        "instruction": "Build a payment-confirmation card whose success state is rendered as iron filings revealing a magnetic field. A Canvas 2D layer over the card's upper field region holds ~520 short line-segment filings (1.1px stroke, ~6px long). IDLE: filings drift aimlessly on per-particle velocities (a few px/s) with slow random spin, wrapped at the edges, drawn in --muted at alpha 0.4 — an aimless field with no organizing force. ON CONFIRM (after a short 350 ms processing beat, button disabled meanwhile): the field switches on. 68% of filings are assigned arc-length-parameterized targets along a two-segment checkmark polyline (normalized points (0.30,0.55) (0.45,0.72) (0.72,0.30) of the field box) with a soft gaussian-ish normal offset up to ~7px so the stroke has filing-built thickness, target angle = local tangent plus jitter; the remaining 32% are HALO filings that creep only 12% toward their nearest point on the polyline and rotate to that point's tangent at low alpha 0.28 — the check emerges from the whole field aligning, not a stamped glyph. Each filing starts after a delay proportional to its arc-length parameter (plus jitter), so alignment sweeps along the stroke; position moves on an underdamped spring (k = 130 s^-2, zeta = 0.82), angle eases along the shortest half-turn (filings are unsigned — fold angle deltas into ±90°). Path filings draw in --foreground alpha 0.95, halo in --muted. All ink is read from getComputedStyle CSS custom properties at mount and re-read via a MutationObserver on the documentElement class attribute so both themes render. The rAF loop is refs-only, sleeps when every filing settles under epsilon, pauses on visibilitychange, and the canvas uses a dpr-clamped(2) backing store with a zero-size guard; a 'Payment confirmed' mono label appears in an aria-live=polite region under the check, the order summary dims, and the button becomes Replay — replaying kicks every filing with a burst velocity (coasting under drag until its stagger delay elapses) and reassigns targets, so the check explodes outward and the field pulls it back together. REDUCED MOTION: idle drift is a single static frame and confirm jumps straight to the fully-aligned final frame with no migration. Colors from theme tokens only; --accent appears solely on the interactive button."
      }
    },
    {
      "name": "success-nucleation",
      "type": "registry:ui",
      "title": "Success Nucleation",
      "description": "A payment/deploy/publish success moment built on supercooling: pending is a canvas particle field in faint restless Brownian shimmer, and the instant it resolves a nucleation flash fires at the exact point pressed and dendritic crystal growth races outward, freezing the shimmer solid in ~700ms under a now-calm confirmation line.",
      "files": [
        {
          "path": "registry/loud/success-nucleation/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/success-nucleation.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "confirmation",
          "success",
          "payment",
          "canvas",
          "particles",
          "phase-change",
          "micro-interaction",
          "aria-live"
        ],
        "instruction": "A self-contained success-moment card: a canvas layer sits behind a small panel's content, transparent over --background, and a real <button> (default label 'Confirm payment') drives a three-state machine — idle, pending, success — that is never re-enterable once past idle (a status guard, not a disabled attribute, so the button stays clickable and focusable throughout and never blocks anything). Clicking calls the optional onConfirm callback; if it returns a Promise, the pending shimmer holds until that Promise settles (success on resolve, a silent revert to idle on reject), otherwise a fallback pendingMs (default 900) simulates the wait — this is how a consumer wires a real payment/deploy/publish call into the moment. PENDING: ~800 points scattered across the panel each do a damped Ornstein-Uhlenbeck random walk (spring pulling back toward its own origin, velocity damping, small continuous noise) around a roughly 1px amplitude, drawn as sub-pixel dots in --muted — a faint, restless shimmer that reads as holding more energy than a settled surface should. SUCCESS: the moment the pending action resolves, React state flips to 'success' synchronously (independent of anything canvas-side) and a role=status aria-live=assertive line mounts immediately announcing confirmedText (default 'Payment confirmed') — the announcement is never gated behind the animation. On the canvas, the exact clientX/clientY of the triggering click (or, for a keyboard-fired click reporting (0,0), the button's own centre) becomes the nucleation point: a radial flash blooms there over ~180ms in --foreground, and a whole dendritic pattern — 5-6 primary arms radiating outward with gentle per-step curvature, throwing side branches at a randomized ~55-65deg with depth-decaying probability (mulberry32-seeded fresh per success) — is precomputed synchronously as a flat list of needle segments, each carrying a baked-in birth time. Every frame from then on simply draws every segment whose birth has passed, in --foreground, tapering by branch depth; the whole pattern finishes within growMs (default 700). In the same one-time build, every ambient particle is matched to its nearest needle endpoint (within a small lock radius) and inherits that segment's birth as its own lock time; a particle whose crystal front hasn't reached it keeps shimmering right up until it does, then eases onto the needle over a short spring-eased settle window, its color interpolating from --muted to --foreground as it commits — the visible read of shimmer 'locking solid' as the front passes. A particle the crystal never reaches simply stops moving in place once the pane freezes. Once every particle has finished settling the animation loop cancels itself outright — a real stopped rAF, not merely a slow one — and a MutationObserver watching documentElement class changes repaints that exact frozen frame with fresh colors on every theme flip, whether mid-growth or long settled. The confirmation line fades in gently (a 320ms entrance) below the button, calm on the now-still crystal. Colors are read once at mount via getComputedStyle(--muted, --foreground) and re-derived on the same MutationObserver; the canvas paints no background of its own. Reduced motion swaps the entire canvas for one static, seeded-once dendritic texture at low opacity — no shimmer, no growth, the same status text and state machine, just no motion. IntersectionObserver pauses the rAF loop offscreen and ResizeObserver keeps the backing store correct; canvas is aria-hidden + role=presentation throughout. Props: label, pendingLabel, confirmedText, onConfirm, pendingMs, growMs, className."
      }
    },
    {
      "name": "success-plumb-bob",
      "type": "registry:ui",
      "title": "Success Plumb Bob",
      "description": "A success moment built on a plumb bob instead of confetti or a checkmark — the bob drops, overshoots into a damped pendulum swing, and the instant it settles collinear with a fixed datum line, a level line draws outward from its tip and the success text rises onto it.",
      "files": [
        {
          "path": "registry/loud/success-plumb-bob/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/success-plumb-bob.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "success",
          "confirmation",
          "svg",
          "physics",
          "instrument",
          "status"
        ],
        "instruction": "A self-contained success card whose identity is a plumb bob coming to rest, not a confetti burst or a Lottie checkmark draw. It renders a pending state (order copy + a single button) and owns its own irreversible confirm/settle state machine — one press decides it, later presses on the same button (while mid-swing) are inert, and once settled the same button's label and handler swap to a real next action. The instant the button is pressed: the accessible event fires in full and immediately — a role=status/aria-live=polite region (always mounted, sr-only, text set on press) announces the success message and an onConfirm callback runs — neither is ever gated on the animation that follows. Visually: an SVG instrument (aria-hidden, all colors read as css var(--background|--foreground|--muted|--border|--accent) tokens directly in SVG attributes, nothing to re-derive on theme change since no canvas is used) shows a bob dropping straight down a fine 1px --muted string from a fixed anchor via an ease-in gravity curve with a hard stop at full string length (no bounce in the length itself); the sudden stop kicks it into a pendulum swing — rotation about the anchor computed every frame from a closed-form damped cosine (amplitude 15deg at the stop, ~330ms period, ~260ms decay time-constant, no keyframe list) that runs for slightly under a second, giving a handful of honestly diminishing oscillations before settling dead vertical. A second, fixed vertical datum line (etched: 1px --border, fine dash, tick marks at both ends) sits at the same x as the anchor for the whole card's life; while swinging, the rotating string visibly diverges from it, and the moment the decay settles to zero the two are collinear again — that coincidence is the entire proof, there is no separate checkmark glyph. Two faint ghost copies of the string+bob (color-mix(in srgb, var(--foreground|--muted) 20%, transparent), never a hex literal) trail the motion at a small time-lag and fade out over the first ~500ms of the swing as an optional motion-blur read; they carry no meaning on their own. Only once settled: a level line (var(--foreground), 1px) draws outward from the bob's tip in both directions at once via stroke-dashoffset on cubic-bezier(0.16,1,0.3,1) (~260ms, an ease-out-expo shape), and real (non-decorative, never aria-hidden) success text fades in and rises 8px to sit visually just above that line in Geist Sans 600 (~300ms). Focus moves onto the same button (now the next action) once settled, so a keyboard user lands exactly where the flow expects. prefers-reduced-motion skips the drop, swing and ghost echoes entirely and snaps straight to the settled pose in one paint — level line already drawn, text already in place, focus still moves — the component stays fully usable, it just stops narrating how it got there."
      }
    },
    {
      "name": "terrain-erosion-carve",
      "type": "registry:ui",
      "title": "Terrain Erosion Carve",
      "description": "The cursor is a river — dragging across a live topographic map carves a channel into the heightfield, marching-squares isolines reflowing into canyon walls with a faint waterline; release and sediment heals the terrain back over ~6 seconds. At rest the sheet stays alive: contours drift in a slow traveling ripple and sediment specks trickle downhill continuously.",
      "files": [
        {
          "path": "registry/loud/terrain-erosion-carve/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/terrain-erosion-carve.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "terrain",
          "topographic",
          "cursor",
          "canvas",
          "marching-squares",
          "drag",
          "physics",
          "ambient"
        ],
        "instruction": "Build a full-bleed Canvas 2D topographic map where the cursor is a river that carves persistent channels. Keep a 96x96 Float32Array heightfield: a fixed base from deterministic 2-octave value noise (hash-sin lattice, 0.65/0.35 octave weights, ~3.4 features across the sheet) minus a separate carve-delta layer, plus a low-frequency ambient drift term — a static per-node noise phase map sampled as sin(phase − elapsed·speed) at amplitude ≈0.42× the contour step, one full cycle ≈10s — rebuilt into a field array per frame so the isolines read as slowly traveling ripples even with no input. Render isolines via marching squares on the coarse grid every 0.08 elevation between the frame's min and max, batched into two Path2D strokes: hairline 0.75px #2e2e2e minors with every 5th contour index (k % 5 === 0) at 1px #8f8f8f — handle both saddle cases (5 and 10) with double segments and lazily interpolate only crossed edges. Layer ~42 ambient sediment specks (1.4px dots, #8f8f8f, fading in/out over a 3-7s randomized lifespan) that trickle continuously along the downhill gradient of the static base heightfield, independent of drag, with a slow noise-driven nudge when the local slope is near flat so nothing stalls; respawn at a random point on exiting the canvas or expiring. Carving: on pointer-down drag (setPointerCapture, touch-none), a Gaussian brush with sigma = 28px subtracts carve delta along the segment between velocity-smoothed pointer samples (head lerps 0.25/frame toward the raw pointer to prevent scalloping) at 0.9 elevation/s, scaled by inverse speed — full depth below 80 px/s tapering linearly to 0.25x at 400 px/s so dwelling digs canyons — stamped every half-sigma along the segment with the per-frame deposit split across stamps, delta clamped to 1. Waterline: while the pointer is down only, nodes whose carve delta exceeds 0.12 within 40px of the recent trail (ring buffer of the last 10 smoothed points) fill as cell rects in rgba(0,107,255,0.35) — the single accent, earned by interaction. Healing: every frame each node's delta decays exponentially toward zero with tau = 2.2s (95% healed around 6.5s) so contours visibly breathe back. Direct-DOM rAF loop with no React state on the hot path: the ambient drift and sediment keep it running continuously while the sheet is on screen, paused via IntersectionObserver once scrolled offscreen and resumed on re-entry; pointerdown/up layer the carve state on top without changing the scheduling. Include a font-mono corner readout of cursor grid coords updated via textContent in the pointermove handler, DPR-aware sizing capped at 2 with a ResizeObserver redraw, and a prefers-reduced-motion fallback that renders one static contour frame with no drift, no sediment, no carving, and no waterline. Zero dependencies, house tokens only."
      }
    },
    {
      "name": "text-ascii-cascade",
      "type": "registry:ui",
      "title": "Text ASCII Cascade",
      "description": "A headline that ambiently collapses character by character into a scattered field of noise glyphs below it, then climbs back into place — hovering forces an immediate, amplified cascade.",
      "files": [
        {
          "path": "registry/loud/text-ascii-cascade/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/text-ascii-cascade.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "text",
          "cascade",
          "glyph-field",
          "mono",
          "headline",
          "showpiece",
          "physics"
        ],
        "instruction": "A headline built from a `text` prop, one `<span aria-hidden>` per character inside an outer `<span aria-label={text} role=\"text\">` (the real string always exposed to assistive tech; every visual glyph is aria-hidden, matching text-decrypt's pattern). The motion axis is what makes this a distinct mechanic in the registry: characters are free bodies that FALL, DRIFT and FADE into a scattered field of noise glyphs below their home row, then climb back — not an in-place identity churn (text-decrypt) and not a 2-state hinge/flip on a fixed grid (split-flap-board, counter-carry-ripple). A state machine per character (settled -> falling -> field -> reforming -> settled) runs on one shared direct-DOM requestAnimationFrame loop: each character's home slot is measured once via getBoundingClientRect and cached; falling eases each character down by an amplitude (46px ambient, 96px while hovered) plus a small per-character random horizontal drift, fading opacity down to 28% over ~620ms (cubic ease-out cubed) while occasionally re-rolling its visible glyph from a noise charset (`#%&@*+=-:.░▒▓`); field holds that scattered, dimly re-rolling state briefly; reforming eases every character back to its exact home slot and opacity 1 over ~720ms with a small per-character index-based stagger so the reassembly visibly ripples left to right, snapping to the real target glyph only in each character's final frame. All transform/opacity/textContent writes are imperative on refs — no React state on the hot path. Every character's colour is recomputed each frame as `color-mix(in oklab, var(--accent) <stray%>, var(--foreground))`, where `<stray%>` tracks how far into the field that character currently is (0% settled, up to 70% at full scatter) — a loud, colour-coded readout of displacement, no hardcoded hex anywhere. The cycle runs ambiently forever at low amplitude (holds ~2.6s between cycles); hovering the headline interrupts the hold immediately and switches to the amplified fall for as long as the pointer stays over it, so hover and rest are never the same frame — releasing hover lets the current cycle finish and returns to the slow ambient breathe. `prefers-reduced-motion: reduce` disables the whole rAF loop: the headline renders once, fully settled, with no fall/reform ever running. Monospace charset throughout so no character's cell width changes as its glyph substitutes, keeping layout stable start to finish. Zero dependencies."
      }
    },
    {
      "name": "text-prism-split",
      "type": "registry:ui",
      "title": "Text Prism Split",
      "description": "A frosted prism strip rides the cursor across a headline, magnifying the text beneath it and tearing it into velocity-driven RGB channel slices that snap back with spring overshoot.",
      "files": [
        {
          "path": "registry/loud/text-prism-split/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/text-prism-split.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "cursor",
          "text",
          "rgb-split",
          "chromatic-aberration",
          "glass",
          "spring",
          "headline"
        ],
        "instruction": "A frosted prism strip that rides the cursor across a headline and refracts the text beneath it — spatial, physical refraction, not a global hover filter. Pure DOM, zero deps. Base headline (Geist Sans 600) split into per-char spans with kerning and ligatures disabled so split and continuous layouts register exactly. The prism is an absolutely-positioned 120px strip: overflow-hidden, backdrop-blur-md, bg-background/85 occluding fill, a 1px border (black/10 in light, white/10 in dark) and inset specular shadow (dark or light highlight per theme) per the glass recipe, and a 12px mask fade on both vertical content edges. The refracted-content layer inside the strip carries a fixed near-black tint (bg-[#0a0a0a]/90, independent of the page theme) so the three full-width clones of the headline in pure #ff0000/#00ff00/#0000ff with mix-blend-screen recombine to near-white where aligned in both light and dark mode — screen blending onto a near-white light-mode surface would otherwise wash the channel text to invisible. Each clone is scaled 1.06 about the strip's current center and counter-translated by -stripX so glyphs stay registered with the base — a lens, not a sticker. The strip follows the cursor via a manual spring integrator (stiffness 300, damping 24, mass 1, semi-implicit Euler) in a rAF loop with position and velocity held in refs and styles written via el.style.transform — no React state on the hot path, loop sleeps when settled. Channel dispersion is velocity-proportional: red dx = clamp(v × 0.018, ±14px), blue mirrored, green fixed, so a stationary strip shows converged text and dragging tears it into RGB slices. When the strip clears a glyph's center the rAF loop toggles a class on that span directly, springing it from a 6px offset (signed by travel direction) back to rest over 350ms with cubic-bezier(0.34,1.56,0.64,1) overshoot. On pointerleave the strip springs back to center rest. Under prefers-reduced-motion there is no rAF: the strip pins statically at 38% of the headline with fixed ±6px channel offsets so the frozen dispersion still reads as the concept."
      }
    },
    {
      "name": "text-stitch-unpick",
      "type": "registry:ui",
      "title": "Text Stitch Unpick",
      "description": "Headline rendered as running-stitch embroidery: the cursor is a seam ripper that picks a letter's stitches loose into a gravity-swayed dangling thread, lingering unravels it into a static pile, and a click (or the Re-sew button) sews it back in.",
      "files": [
        {
          "path": "registry/loud/text-stitch-unpick/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/text-stitch-unpick.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "text",
          "headline",
          "svg",
          "physics",
          "hover",
          "micro-interaction",
          "typography"
        ],
        "instruction": "Build <StitchPick text? size? dwellMs? className?>, a headline rendered as dashed-stroke, fill:none SVG <text> glyphs (\"running stitch\") that a passing cursor picks apart letter by letter. LAYOUT: lay every glyph on a monospace grid (fontFamily var(--font-mono), fontWeight 700) so per-letter hit-testing is index math, not proportional-width measurement: render one throwaway offscreen <text>M</text> once, read its getComputedTextLength() in an effect (re-run once more after document.fonts.ready resolves, in case the mono face swaps in after first paint) to get the true per-glyph advance in px, then position glyph i at x = i * advance. THREAD RENDERING: each glyph is TWO offset <text> elements, both fill=\"none\" stroke-dasharray=\"3 2.4\" (the running-stitch dash), one full-strength stroke=var(--foreground) strokeWidth 1.6 slightly on-baseline, one stroke=var(--accent) strokeOpacity ~0.22 strokeWidth 1 offset +0.6/-0.6px — the second, low-alpha, off-hue pass is the loud collection's subtle two-tone shimmer, not a wash. STATE MACHINE, per letter, independent of every other letter: stitched (default: both dashed strokes visible) -> pointer enters that letter's horizontal zone (glyph index = floor((clientX - svgLeft) / advance), computed once from a single onPointerMove on the SVG root rather than per-glyph listeners) -> picked: the stitched <g> fades to opacity 0 over 200ms and a dangling thread <path> appears, driven by a 4-point verlet chain (point 0 pinned at the glyph's top-stitch anchor; points 1-3 integrate gravity 0.055 + a small per-letter-seeded sin sway + velocity damping 0.96, then 3 iterations of a distance constraint at segment length 4.2, all inside a SHARED requestAnimationFrame loop that starts when the first letter is picked and stops itself the frame no letter is left in the picked state — every frame writes the path's `d` attribute directly via a ref, never through React state). If the pointer leaves that letter's zone before `dwellMs` (default 650) elapses, the letter returns straight to stitched (dashed <g> fades back in, a `transition-delay: index*18ms` stagger so a multi-letter re-stitch reads as sewn back in left-to-right order rather than snapping at once) and the dwell timer is cleared. If the pointer stays past dwellMs, the letter advances to unraveled: physics stops, the dangling path is replaced by a small static wavy \"pile\" shape (a couple of quadratic-bezier loops near the glyph baseline, same dashed thread styling) that plays one 220ms settle-in (opacity+translateY) and then just sits there — it does NOT auto re-stitch on pointer leave anymore. UNRAVELING RESOLUTION: clicking directly on an unraveled letter (same index math, on a root onClick) resews just that letter; a real, always-rendered <button> below the headline, labeled \"Re-sew\" (or \"Re-sew (nothing loose)\" when nothing is unraveled, so its accessible name always reflects real state and it is never a no-op the user can't discover) resews every unraveled letter at once — this button is also the component's one required real interactive control for keyboard/screen-reader users, since the letter-hover/click interactions are a mouse-only bonus layer on top of it, not a replacement for it. A11Y: the SVG itself is role=\"img\" aria-label={text} (the individual glyph strokes are aria-hidden, so a screen reader gets the plain headline text once, not per-letter noise); the Re-sew button is a real focusable <button> with a visible focus-visible ring. REDUCED MOTION: the entire picked/unraveled state machine is bypassed — onPointerMove instead just tracks a single \"brightened\" letter index and bumps that letter's stroke-opacity (foreground 0.85->1, accent 0.22->0.4) over a fast 120ms linear color transition, no dangling, no physics, no pile, so hover still visibly differs from rest with zero motion. TOKENS: var(--foreground) for the primary thread, var(--accent) at low opacity for the shimmer pass, var(--border) for the Re-sew button's chrome — no fill color anywhere on the glyphs themselves (stroke only is the whole point of the embroidery look). Not SVG <path> pathLength/dash-window tricks anywhere — the dasharray here is a static texture, not a normalized progress indicator, so the pathLength+non-scaling-stroke screen-space trap does not apply, but is avoided regardless as a matter of course. DEMO: a centered headline (\"UNRAVEL\") self-driving through the whole state space unattended — a quick multi-letter sweep (pick+auto-restitch), a long dwell on one letter (unravels, then a synthetic click resews it), dwells on two more letters followed by a synthetic click on the Re-sew button — all via real dispatched PointerEvent/click sequences at the SVG's DOM node, not simulated state."
      }
    },
    {
      "name": "toast-newton-cradle",
      "type": "registry:ui",
      "title": "Toast Newton Cradle",
      "description": "A toast stack that behaves like a Newton's cradle: a new toast swings in and strikes the queue, a fast domino of shunts carries the impact down the stack, and past capacity that momentum visibly ejects the oldest toast off the far end.",
      "files": [
        {
          "path": "registry/loud/toast-newton-cradle/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/toast-newton-cradle.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "toast",
          "notification",
          "queue",
          "micro-interaction",
          "aria-live",
          "feedback",
          "physics"
        ],
        "instruction": "A toast stack, capped at `maxVisible` (default 4), choreographed as a Newton's cradle rather than a generic slide/scale stack. RENDERING: plain DOM cards in a vertical flex column (severity icon, truncated title, optional mono message, dismiss button), newest on top; every transform is written directly via the Web Animations API on refs, not React state, so no canvas and no per-frame re-render. MOTION: a pushed toast enters at the top on an accelerating ease-in curve (cubic-bezier(.55,.06,.68,.19), 200ms, translateY -26px to 0 plus a 0.97-to-1 scale) reading as a released cradle ball picking up speed as it swings in and 'strikes' the queue. The instant it lands, every OTHER currently-visible toast (every toast except the new arrival) plays a 60ms shunt: translateY 0 to 3px and back, staggered 35ms deeper per row via the animation's own delay — a fast domino of tiny nudges that visibly propagates from the impact point to the far end, letting a glance at the stagger read the stack's depth without counting cards. Under capacity that impulse simply dissipates: the deepest toast shunts and returns exactly like every other row. At capacity, the same event instead ejects the oldest (bottommost) toast: it is pulled out of the visible list into a brief inert overlay and, at the exact stagger slot it would have shunted at, plays a 340ms departure on the ease-out curve cubic-bezier(0.16,1,0.3,1) — translateY 0 to 10px (the 'arc') with a 2deg rotation at the 35% mark, continuing to translateY 46px, rotate 9deg, opacity 0 — matching the inherited momentum with a decelerating exit rather than an abrupt cut. Manual dismiss and auto-expiry both play a plain 150ms opacity fade (no shunt, no eject arc) before the toast leaves the array, since those departures were never caused by an incoming impulse. AUTO-DISMISS: duration prop, default 5000ms per toast (0 disables); a hover OR a keyboard focus on the card pauses its own timer, and the timer only resumes once both are clear, so a screen-reader user tabbed onto a toast is never raced by the clock. ACCESSIBILITY: a single shared aria-live announcer (visually hidden) is the only thing that speaks — remounted (via a React key on an internal sequence number) each time so a screen reader re-registers its live-ness, aria-live='assertive' for error toasts and 'polite' for info, announcing each toast's title and message exactly once on arrival; the physics-only reflow of existing toasts (shunt, eject, reposition) never touches the announcer, so nothing already announced gets re-announced. Each toast card itself is role=alert (error) or role=status (info), keyboard-focusable, with an aria-label naming its severity and title; Escape while a toast has focus dismisses that toast. A global F6 keydown sends focus straight into the region — to the newest toast if one is visible, otherwise the region landmark itself — a pane-jump convention for reaching the stack without tabbing through the whole page. Every evicted, expired, or manually dismissed toast is appended to a capped (12-entry) history list surfaced behind a 'History (n)' disclosure button (aria-expanded/aria-controls) below the stack, so an ejected toast — the whole point of the eviction policy — stays reachable and readable, tagged with why it left ('evicted' / 'expired' / 'dismissed'). REDUCED MOTION: every custom animation call above is swapped for a plain 150-200ms opacity fade in the same Web Animations calls (checked once via a matchMedia('(prefers-reduced-motion: reduce)') listener) — no shunt, no eject arc, no translateY at all — while the queue, capacity, eviction, timer, and history logic all run identically, so the cap and the eviction reason are still fully legible, just without the choreography. Colors are exclusively --background/--foreground/--muted/--border/--accent (--accent only as the focus ring); severity is distinguished by icon shape and title weight, never by hue. Imperative handle exposes push/dismiss/clear; props: maxVisible, duration, initial (toasts seeded at mount), className, aria-label. Every timer, animation, and the window keydown listener is torn down on unmount."
      }
    },
    {
      "name": "transition-ascii-dissolve",
      "type": "registry:ui",
      "title": "Transition ASCII Dissolve",
      "description": "A layout transition swept by a user-driven, reversible dissolve front: outgoing content frays into a band of ASCII glyph noise ahead of the front, incoming content resolves out of that same noise behind it, and dragging the front back un-happens the transition exactly as far as you pull it.",
      "files": [
        {
          "path": "registry/loud/transition-ascii-dissolve/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/transition-ascii-dissolve.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "transition",
          "ascii",
          "canvas",
          "dissolve",
          "drag",
          "compare",
          "layout"
        ],
        "instruction": "<AsciiDissolveTransition from to value? defaultValue? onValueChange? label? className?> stacks two full-size panels: `to` underneath, always rendered; `from` above it, hard-clipped via clip-path to the region right of a front position (0..1, 0 = all `from`, 1 = all `to`). A canvas veil above both draws a band of Geist Mono ASCII noise glyphs (from a density ramp '░▒▓█#%@*+=-:. ') centered exactly on the front, density falling off smoothly with distance so the outgoing panel visibly frays into noise on the unswept side and the incoming panel visibly resolves out of the same noise on the swept side — a spatial dissolve, not a per-character identity churn. The front is a role=slider div spanning the whole surface (aria-valuenow/aria-valuetext as a percentage), draggable with the mouse or touch and steppable with ArrowLeft/Right (2%), PageUp/PageDown (10%), Home/End; because both the clip-path and the noise band are recomputed purely from the current front value, dragging it back genuinely un-sweeps the transition — there is no play-once state machine underneath. The noise churns (re-rolled every animation frame) only while the front is actively being dragged or holds keyboard focus; at rest it freezes on one frame, and the whole churn loop is skipped entirely under prefers-reduced-motion, leaving a single static band. Colors read live via getComputedStyle from --foreground/--muted/--accent, resynced on a documentElement class MutationObserver — no hardcoded hex, matching the token rule even in `loud`. Whichever of `from`/`to` currently covers less than half the surface is marked aria-hidden so assistive tech only ever encounters the panel that's actually dominant. Zero dependencies, one canvas, no WebGL."
      }
    },
    {
      "name": "transition-panel-crumble",
      "type": "registry:ui",
      "title": "Transition Panel Crumble",
      "description": "A layout transition where the outgoing panel crumbles into a few thousand token-colored grains that fall under gravity, funnel toward the incoming panel's matching regions, and settle there while the crisp new DOM (already fully mounted and focused) shows through — conservation of matter made visible, an hourglass turn between a dashboard overview and its detail pivot.",
      "files": [
        {
          "path": "registry/loud/transition-panel-crumble/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/transition-panel-crumble.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "transition",
          "canvas",
          "particles",
          "physics",
          "dashboard",
          "layout",
          "verlet",
          "gravity"
        ],
        "instruction": "Build a two-panel dashboard (Overview / Sessions-detail) whose swap is driven by a canvas grain-pour overlay layered over an otherwise-instant DOM transition. Every element that participates in the pour is tagged data-scree-id (shared ids between the two panels mark matched regions) and data-scree-tone (foreground/muted/border/accent, deciding which house token the grains sample). On trigger: the currently-active panel's marked elements are measured via getBoundingClientRect relative to the container (the OLD rects), React state swaps which panel is mounted (the old DOM is genuinely gone, not hidden — this is the 'navigation is instant' half of the contract), the new panel's heading receives focus immediately via a tabIndex=-1 ref, and an aria-live=polite region announces 'Now showing: X' — none of that waits on the animation. A useLayoutEffect fired by the swap then measures the NEW panel's marked elements (the NEW rects) and spawns grainBudget (default 2400, cap 4000) 2-3px canvas squares: for every old-rect id that also exists in the new rects, grains are seeded inside the old rect and targeted at a random point inside the matching new rect (a funnel-attractor whose spring constant ramps from 4 to 150 via smoothstep over the grain's own 900ms window while a companion gravity term of the same shape fades out, so early motion reads as a gravity-driven fall and late motion reads as a soft landing); for an old-rect id with no counterpart in the new layout, grains instead fall straight down and out of the container under constant gravity and fade — the visible read for content that simply didn't survive the pivot (the Overview view's two side stat cards when pivoting into Detail; the Detail view's description paragraph when pivoting back). New regions with no old counterpart (Detail's description on the way in) simply appear with the rest of the already-mounted, already-accessible DOM — there is deliberately no separate materialize shower, keeping the grain budget spent on departure and arrival of real matter rather than every pixel. Integration is verlet (previous-position implied velocity times a damping factor, plus acceleration times dt^2, no explicit velocity field) so grains carry momentum through the funnel instead of snapping. Grain color is baked once per trigger from --foreground/--muted/--border/--accent read via getComputedStyle on a MutationObserver watching <html>'s class attribute — --accent is reserved for the one region seeded from the interactive control (the View details / Back to overview action), never used decoratively elsewhere. A per-grain release delay up to 140ms staggers the crumble so it reads as a trickle rather than a single synchronized pop; each grain fades to zero over the last 40-50% of its own window so the canvas is provably empty well before the simulation's 900ms mark. Because the new DOM is correct and fully opaque from the instant of the swap, the canvas is purely a pointer-events:none, aria-hidden decorative layer riding on top — an audit of the resting or mid-transition frame always finds the real, accessible interface underneath, and elementFromPoint resolves straight through the canvas to it. prefers-reduced-motion, and a one-shot frame-budget probe (a fixed arithmetic loop timed synchronously at mount; taking meaningfully longer than budget flags the device as low-power), both skip the whole simulation for a plain 150ms opacity cross-fade on the new panel instead — no canvas, no grains, no verlet. Zero dependencies beyond React."
      }
    }
  ]
}
