{
  "$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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": "adhesive-squeeze-bead",
      "type": "registry:ui",
      "title": "Adhesive Squeeze Bead",
      "description": "A merge/dock affordance modeled on structural adhesive bonding: two panels compress a bead of adhesive to a fixed bond-line stop, forcing an uneven bead of squeeze-out along the seam that visibly settles into a continuous, even fillet.",
      "files": [
        {
          "path": "registry/core/adhesive-squeeze-bead/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/adhesive-squeeze-bead.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "merge",
          "dock",
          "attach",
          "combine",
          "svg",
          "drag",
          "physical-metaphor",
          "aria-live"
        ],
        "instruction": "`<AdhesiveSqueezeBead topContent? bottomContent? draggable? onBond? ariaLabel? className? />` renders a card-scale `role=\"group\"` region with two flat panel `<div>`s (bg-surface, border-foreground/15) stacked with a gap between them, plus an absolutely-positioned inset-0 SVG whose single `<path fill=\"var(--foreground)\">` is the squeeze-out bead. Panel geometry is DERIVED, never hardcoded: `layout()` measures the container and sets each panel's own top/bottom offset and height in JS so the two panels' inner edges are always exactly 14px apart at rest — the panel gap and the 3px bond-line stop below are only meaningful numbers if the resting geometry actually matches them, so no Tailwind arbitrary-height class is trusted to get that right on its own. The whole thing runs an UNFORCED, UNBOUNDED ~3900ms loop with zero input: panels ease-IN (accelerating close — `t*t`, matching two rigid parts under applied pressure, not a soft cushion-stop) from that 14px starting gap to a fixed 3px bond-line stop over 260ms (panels translateY symmetrically toward the seam's fixed midpoint, never past the 3px stop — that stop is the joint's physical spacer thickness, not a spring target); the bead THEN grows: across the FIXED 260-600ms window each of 24 control points spaced evenly along the seam's x-axis (same edge inset as the panels, so the bead reaches all the way to each panel's end) grows from radius 0 toward its OWN randomized target via an ease-out curve applied identically to every point, so only the per-point TARGET varies and the bead visibly builds unevenly. The target range is `[4, 12]px` at a 240px reference container minimum-dimension, then multiplied by `clamp(minDim/240, 0.75, 2.5)` — the binding 'derive geometry from the container's smaller dimension' rule, applied because the spec's literal 2-5px range reads as a sub-pixel wobble at typical card scale and the whole point of this component is that the settle motion stays legible. Across 600-1300ms every point's radius interpolates LINEARLY (deliberately not eased — an eased curve front-loads the change and leaves too little residual unevenness at the resting-loop's t5s check-frame, 1.1s into the next cycle / 71% through this window, to read as distinct from the fully-settled hold frame at t2.5s) from that lumpy target toward a value produced by applying a 3-point moving average (edges clamped to their nearest real neighbor, never wrapped — this is a straight seam, not a loop) TWICE to the target array, so the bead visibly relaxes from lumpy to a continuous, even fillet — this settle motion is the one thing a viewer is asked to follow, and 700ms at card scale is timed to be watchable, not a blink. The settled shape then holds dead still for 2100ms (this is the 't2.5s' resting-loop frame: solidly mid-hold, bead fully even) before a 500ms phase fades the bead's opacity 1->0 while the panels ease back apart to the 14px start, and the whole 3900ms cycle repeats forever. The bead path itself is built fresh every animation frame from the live per-point radius array via `buildBeadPath`: a top edge (seamY - r_i) and a mirrored bottom edge (seamY + r_i) each smoothed through a clamped Catmull-Rom-to-cubic-Bezier pass (no wraparound — both edges terminate as flat vertical caps at the seam's visible extent, like a bead of caulk run the width of a frame, not a closed loop), joined into one filled path with no stroke. INTERACTION: when `draggable` (default true), pointerdown on the lower panel reads its own current on-screen offset back out of its live `transform` (via `getComputedStyle` + `DOMMatrixReadOnly`, never assumed) so a drag started mid-cycle picks up exactly where the ambient loop left off, then pointermove drives the panel gap directly off `clientY` delta (clamped [3,14]px) with NO bead rendered during the drag itself — the growth+settle sequence is a committed sequence, never scrubbed live. On pointerup, if the live gap is at or below the 6px squeeze-out threshold, any growth/settle state left over from a previous ambient cycle is cleared and the shared animation clock is jumped straight to the start of the growth phase (already at the 3px stop), so a genuinely fresh growth->settle->hold->reset sequence plays out — identical in mechanism to the ambient loop's — and the loop simply continues from there afterward; releasing above 6px snaps the panel back to the 14px start and restarts the ambient cycle from t0 — this is a real merge action with a real spring-back, never a state that latches irreversibly, which is why `autoplay.mode` is `\"none\"`: the resting loop already moves without any input, dragging is an accent on top of it, not the only way to see motion. A visually-hidden `role=\"status\" aria-live=\"polite\"` paragraph announces 'Bonded — squeeze-out even along the seam' once per completed settle (ambient or committed drag) and 'Released before bond line — no squeeze-out' on a sprung-back release; ordinary ambient cycling otherwise announces nothing extra per pass beyond that one bonded line, so a screen reader isn't spammed every 3.9s. REDUCED MOTION (`matchMedia`, read once at mount) disables the ambient rAF loop entirely and renders directly into the settled state described by `STATIC_PHASE = \"settled\"`: panels already at the 3px bond-line stop, bead already built from one randomized-then-twice-smoothed target set, no growth or hold timers running — this is deliberately NOT the t0 (gap-open, no bead) frame, because an unfinished or gap-open frame reads as 'not bonded', where the settled fillet reads as 'a good bond' at a glance. Dragging still works under reduced motion (it's a real interaction, not decorative motion) but every state change it produces — committing a bond, springing back — applies instantly with no eased tween. Sizing: the SVG's `viewBox`, panel geometry and target-radius scale are all set from the container's own measured `getBoundingClientRect()` inside a `ResizeObserver` callback (never assumed, never before the first successful measurement — the root renders `opacity-0` until that first layout read completes, then fades in over 200ms), and the resize path resumes the rAF loop itself if the container was unmeasurable at mount (a lazily-mounted preview iframe) the same way an `IntersectionObserver` resumes it when the card scrolls back into view. Colors are `--background --foreground --ns-muted --border --surface` only, all via CSS custom properties in JSX/Tailwind classes — no JS color reads are needed because nothing here draws to canvas, and `--ns-accent` never appears: the bead's building-to-even motion is geometry/luminance only, never accent-tinted, per the brief's 'never the climactic moment' rule. DOM + SVG + CSS, no canvas, zero dependencies."
      }
    },
    {
      "name": "airlift-slug-flow",
      "type": "registry:ui",
      "title": "Airlift Slug Flow",
      "description": "A background file-sync/upload activity rail: a slim vertical conduit where discrete air slugs, injected at the foot every 1.8s, rise at a constant 140px/s and drag a liquid plug immediately ahead of them, depositing into an accumulator basin on arrival. A real airlift-pump slug-flow regime standing in for a generic spinner or looping percentage.",
      "files": [
        {
          "path": "registry/core/airlift-slug-flow/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/airlift-slug-flow.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "sync",
          "upload",
          "progress",
          "rail",
          "svg",
          "dom",
          "ambient",
          "fluid",
          "pump",
          "activity"
        ],
        "instruction": "A vertical activity rail, card-scale, meant to sit beside a real file list or sync panel. Root is a flex column anchored to the bottom (justify-end) so the rail's foot sits at the container's bottom edge and a small accumulator basin sits above its head. On mount, and on every debounced (100ms) ResizeObserver callback on the root, the container's own bounding box is measured: rail width = 8% of the container's smaller dimension, clamped 5-22px; the basin is a bordered square whose side is railWidth * 2.4 (itself clamped against the same 5-22px band before multiplying), separated from the rail by a 3px gap; rail height = container height minus the basin's rendered height minus that 3px gap, so the rail always fills whatever vertical space remains. The static liquid column is a single pill-shaped div spanning the full rail height, background var(--ns-muted) at opacity 0.3 in light theme and 0.16 under a `.dark` ancestor class (the muted token washes out against light backgrounds at low opacity, so light theme gets a deliberately higher floor; the moving slug/plug pair is var(--foreground) unmodified across themes since it is already the primary legibility carrier). Every 1800ms (real airlift small-riser slugging sits in the 0.5-3Hz band; 1.8s is inside it) a new slug+plug pair is injected at the rail's foot: raw DOM elements appended directly to a layer div and driven every animation frame by imperative style writes (bottom/height), not React state, so multiple concurrent pairs in flight cost nothing extra. The slug is a full-width pill (border-radius 999px) at 40% opacity var(--foreground) — the bubble read; the liquid plug it drags is a solid 100%-opacity var(--foreground) band with a 2px corner radius, riding flush against the slug's leading (upper) edge at the identical rise speed, so the two always touch and never separate or overlap. Both rise at a constant 140px/s (buoyancy-driven rise is near-constant, not accelerating) computed off a virtual clock that only advances while an IntersectionObserver reports the root as on-screen, so backgrounding the tab never produces a catch-up burst of slugs on return; the same virtual clock (not wall time) drives the 1800ms injection accumulator. Slug height is 9% of the current rail height, plug height 14%, both re-derived from railHeightPx every frame so a mid-flight resize reflows the pair onto the new geometry without restarting the cycle. The moment the pair's combined top edge reaches the rail's head, both elements get a 220ms linear opacity transition to 0 (removed from the DOM once that transition's virtual-clock duration elapses) and the arrival is recorded: an arrival counter increments mod 14 and the basin's fill div (absolutely positioned, bottom-anchored, height set as a percentage with its own 220ms linear transition so the deposit reads as landing in sync with the fade) is set to count/14 — a full 14-arrival cycle is roughly 25s and self-contained, no external data required. An optional `onSlugArrival` callback fires once per arrival, exactly at the moment the fade begins, meant to be wired to a real adjacent file row's status text for a single discrete pulse — the callback and the demo's row-cycling handler are the only sanctioned use of the arrival event; nothing in the component itself ever mixes var(--ns-accent) into the slug, plug, or basin fill, since accent is interaction chrome only and this is an ambient, non-interactive rail. Under `prefers-reduced-motion: reduce` (checked via matchMedia with a live change listener) no rAF loop or injection accumulator ever starts: exactly one slug/plug pair is laid out frozen with its combined vertical center at 50% of rail height — the single frame where both parts are visible, coupled, and clearly separated from the static column above and below — and the basin fill is set to a fixed 5/14, a deliberately mid-cycle, non-zero, non-full level rather than either boundary. Cleanup on unmount cancels the animation frame, clears the resize debounce timer, and disconnects both the ResizeObserver and the IntersectionObserver. Zero dependencies, DOM + CSS only, no canvas, every colour a token (--background --foreground --ns-muted --border), autoplay is 'none' because the rail runs on its own internal clock with nothing for a synthetic pointer/scroll/press driver to trigger."
      }
    },
    {
      "name": "announcement-bar-relay",
      "type": "registry:ui",
      "title": "Announcement Bar Relay",
      "description": "A queued announcement bar that hands off honestly: dismissing one notice shears it upward while the next arrives from below AND the bar's own height eases between the two measured heights, so the page reflows with the message instead of snapping under it, and the queue pips physically shorten by one.",
      "files": [
        {
          "path": "registry/core/announcement-bar-relay/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/announcement-bar-relay.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "announcement",
          "banner",
          "queue",
          "dismiss",
          "page-furniture",
          "marketing"
        ],
        "instruction": "Build <AnnouncementBarRelay items storageKeyPrefix=\"ns-announcement-bar-relay:\" resetOnMount=false onExhausted? className?> where items is ({ id, message, action?: { label, href }, tone?: 'muted' | 'accent' })[]. STRUCTURE: a <div role=region> labelled 'Announcements' by an sr-only span, carrying the current message and its optional inline action link on the left, and on the right a queue readout plus a dismiss control — a <button data-announcement-dismiss aria-label=\"Dismiss: {message}\"> with an SVG cross. The readout is a row of 6px pips, one per remaining item, the head one filled with --foreground and the rest hollow 1px --border rings, followed by a Geist Mono tabular-nums counter reading position/total (`1/3`), with an sr-only 'Announcement 1 of 3' carrying the same fact to a screen reader while the pips and counter are aria-hidden — that sr-only line lives INSIDE the aria-live=polite message column, so the new position is spoken with the new message instead of changing silently. THE MECHANIC — the reason this exists: an ordinary banner queue swaps the DOM and the page below jumps by whatever the height delta happens to be. Here a hidden mirror row (aria-hidden, visibility:hidden, position:absolute, inset-x-0) renders the NEXT message at the bar's exact content width, so its height h1 is a measured fact before the handoff begins. On dismiss the live height h0 is read from the bar's border box, the chrome delta (bar.offsetHeight − content.offsetHeight, i.e. the hairline rule) is added to the mirror's offsetHeight to get h1 in the same box model, h0 is written to the bar as an explicit px height, and then in one requestAnimationFrame — after the start poses have actually painted, otherwise the browser coalesces start and end and nothing moves — three clocks start together: (a) the outgoing message transitions to translateY(-100%) skewX(-3deg) opacity 0 over 240ms cubic-bezier(0.4,0,0.2,1); (b) the incoming message, rendered at translateY(100%) skewX(3deg) opacity 0 with transition:none, releases to identity over 240ms with a 60ms lead-in delay; (c) the BAR ITSELF eases height h0 → h1 over 300ms on the same curve, deliberately outlasting the swap so the content below the bar travels continuously rather than snapping at the instant the DOM changes. The 3deg shear is the whole point of the pairing — it makes the two messages read as one carriage travelling past a window rather than a crossfade. On settle the explicit height is cleared back to auto so the bar is fluid again. Simultaneously the leaving pip collapses from 6px to 0 width with its 6px right margin and its border going with it over 200ms, so the queue is visibly one dot shorter rather than silently re-rendering. LAST ITEM: the same outgoing shear, but h1 is 0, the bar eases to zero height and the region unmounts on the height transitionend, guarded by a 400ms fallback timer so a transitionend that never fires (tab hidden mid-flight) can't strand a zero-height region in the document; onExhausted fires there. PERSISTENCE: each dismissed id is written as `${storageKeyPrefix}${id}` = '1', and storage is read in a useEffect and never during render, so the server markup and the first client paint agree — item 0 renders optimistically and is swapped after hydration if it turns out to be already dismissed. Every storage access is try/caught, so private mode or a denied origin degrades to a session-only bar instead of throwing. resetOnMount clears this queue's own keys at mount, which is what a demo or preview should pass (with its own namespaced prefix) so a replaying page cannot permanently empty itself or collide with any key the host reads. Reduced motion: no shear, no height easing, no pip collapse — the message swaps in a single paint and the bar height changes instantly, and the dismissal still persists. Pure DOM/CSS/SVG, zero dependencies, no canvas. Tokens only — --border, --foreground, --ns-muted, --surface, --ns-accent — with accent restricted to the action link and the focus rings; tone:'accent' shifts only the 2px leading rule down the bar's left edge, never the bar's background, so a whole row of notices never turns into a colour-coded traffic light. The dismiss button and the action link both carry a visible focus-visible ring in --ns-accent, and the dismiss button is keyboard-operable as an ordinary button, so the entire queue can be walked with Tab and Enter alone."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 --ns-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 anticipation outright (pure flourish), but ack and failure keep a short 140ms linear settle instead of jumping straight to their resolved angle/pose — the wheel's position is the only passive signal a save just landed, so it still has to visibly travel there. Zero dependencies, no canvas."
      }
    },
    {
      "name": "auxin-canal",
      "type": "registry:ui",
      "title": "Auxin Canal",
      "description": "Full-bleed hero whose vein network grows by space colonisation through whatever negative space the copy leaves empty: sources scattered around measured content boxes, tips advancing toward and consuming them, widths thickened by Murray's law, then a slow drain to background and a fresh scatter begins.",
      "files": [
        {
          "path": "registry/core/auxin-canal/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/auxin-canal.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "hero",
          "svg",
          "vein",
          "space-colonisation",
          "generative",
          "quadtree",
          "murray-law",
          "ambient",
          "api",
          "docs"
        ],
        "instruction": "A full-bleed hero whose SVG vein network grows by Runions-style space colonisation through whatever area the DOM copy does not occupy. On mount and on every debounced ResizeObserver/webfont-ready event, the container is measured and every descendant carrying `data-auxin-box` (the headline, the subcopy paragraph, the CTA row, the stat strip) is measured too via getBoundingClientRect; a jittered grid of roughly `sourceCount` (default 1200) auxin sources is scattered across the container and any source that falls within a 22px margin of a content box is simply never added to the set — avoidance is a property of where sources exist, not a clip-path applied to the result. Mean source spacing is the grid's own cell size (sqrt(area / sourceCount)); kill distance dk = 1.7x that spacing (the single governing scalar between a sparse fan and a dense reticulate mesh), attraction radius = 5x dk, and step length = 0.6x dk so a single step can never leap clean over a source without killing it. Seven fixed seed points enter from the container's edges (three along the bottom, two per side) as the network's roots. Growth runs on a fixed 30Hz tick (a rAF loop accumulating real elapsed time into 33.3ms steps, so speed never depends on frame rate): each tick builds a quadtree of current tip nodes, finds every live source within reach of some tip, advances each attracting tip one step toward the mean unit-vector direction of the sources that picked it as nearest, then builds a second quadtree of remaining sources to kill every source within dk of a node that was just created. Growth halts after three consecutive ticks that create no new node (unreachable sources are left alive and simply never drawn toward — veins terminate exactly where open space runs out, never mid-void) or a 1600-node safety cap. Width is Murray's law maintained incrementally rather than solved as a post-pass: every node carries a leafCount, starting at 1; extending an existing tip in place costs nothing (the tip's count just moves to its new endpoint), but the instant a node that already has one child grows a second — an actual fork — its count rises by one and that rise is walked straight up its own ancestor chain, so a trunk's width (leafCount^(1/3)) is always exactly the terminal count still draining through it. Each tick, every segment except the ones grown that same tick is bucketed by width into one of eight `<path>` elements (four rendered inside a `<g>` whose `color` is var(--ns-muted) for the thinner half, four inside a `<g>` whose `color` is var(--foreground) for the thicker half, both using `stroke=\"currentColor\"` so a theme flip repaints for free with zero JS); the current tick's freshly grown segments live in one extra active-tip path until the following tick folds them into the buckets. Once growth stalls, the lamina rests for 3.2s, then both wrapping `<g>` elements' `color` transitions to var(--background) over 1.5s (a genuine colour drain into the page, not an opacity fade), after which every path is cleared and the whole cycle — fresh jittered scatter, fresh seeds, fresh growth — begins again. The svg is `aria-hidden` with `pointer-events-none`; the headline, subcopy and both CTAs are ordinary focusable DOM, untouched by any of this. `autoplay: none` because the network animates on its own internal clock regardless of pointer/scroll/press input — there is nothing for the site's synthetic-input driver to do. Under `prefers-reduced-motion` the entire colonisation-to-completion loop runs once, synchronously, at mount (same tick function, called in a tight loop instead of on rAF) and renders exactly one finished, fully usable venation with no rest/drain/rescatter cycle ever starting. Zero dependencies, DOM+SVG+CSS only, no canvas, every colour a token (--background --foreground --ns-muted --border --ns-accent)."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": "back-bearing",
      "type": "registry:ui",
      "title": "Back Bearing",
      "description": "A sighting-compass bearing picker: the graduated card rotates under a fixed lubber line and the instrument always shows the fore reading plus its reciprocal, the way a surveyor verifies a shot.",
      "files": [
        {
          "path": "registry/core/back-bearing/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/back-bearing.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "compass",
          "bearing",
          "dial",
          "slider",
          "physics",
          "svg",
          "azimuth",
          "accessibility"
        ],
        "instruction": "A bearing picker built like a real sighting compass rather than a generic rotary knob: a 240px SVG instrument where the graduated CARD is the only thing that rotates (transform rotate(-theta) applied via the SVG transform attribute on a <g>, never a CSS needle-over-static-rose), under a FIXED lubber line drawn as a static triangle at 12 o'clock outside the rotating group. One governing scalar, theta (degrees true, continuous float internally so 22.5deg intercardinal detents are exactly representable, displayed and reported rounded to an integer 0-359): card rotation, the fore readout, the back readout ((theta+180) mod 360, smaller, --ns-muted, rendered at a fixed position diametrically opposite the fore readout across the instrument), and the tick weighting are all pure derivations of theta, never independent state. The card is one SVG ring: 1px ticks every 2deg (180 of them), 2px ticks every 10deg, and N/E/S/W cardinal letters at the quadrants in Geist Mono, all inside the rotating group; the bezel circle, lubber-line triangle, a smaller muted back-sight index triangle at 6 o'clock, and the center pivot dot sit outside it and never move. Dragging anywhere on the card computes pointer atan2 about the instrument's center each move and accumulates the SHORTEST signed delta from the previous pointer angle into a continuous unbounded raw angle (never unwinding the long way around); the card tracks the pointer 1:1 while dragging. On release, the residual angular velocity from the last few pointer deltas hands off to a second-order damped spring (k=260 deg/s^2, zeta=0.85 -- just under critical, the same tuning confirm-dial-align uses for its one-small-overshoot snap) that lets the card coast past its resting angle by a hair and settle back, the signature of a liquid-damped marine card rather than a springy double-bounce UI dial. Holding Shift while dragging snaps theta live to the nearest 22.5deg intercardinal detent. Keyboard: role=slider on the draggable region, min 0 max 359, aria-valuenow the rounded fore bearing, aria-valuetext formatted exactly as 'bearing 042 degrees, back bearing 222'; arrow keys step 1deg, Shift+arrow steps to the next 22.5deg detent in that direction, both routed through the same settle-spring so keyboard nudges get the identical single-overshoot feel with zero initial velocity (a step response of an underdamped spring still overshoots once on its own). The fore readout is not a popup: it is an always-present 3-digit Geist Mono <input> that free-types digits and commits theta on every valid keystroke, kept in sync with drag/keyboard commits except while it holds focus so typing is never fought. A single direct-DOM rAF loop (SVG transform attribute writes only) is the sole animator; React state holds just the committed theta. Under prefers-reduced-motion the settle spring is skipped entirely (instant snap) while the Shift detents and drag/keyboard/typing behavior are unchanged. Zero dependencies, no canvas."
      }
    },
    {
      "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-domain-walls",
      "type": "registry:ui",
      "title": "Background ASCII Domain Walls",
      "description": "An ambient ASCII background running a real Ising model just below its critical temperature and inking only the DOMAIN WALLS (the boundaries between up and down spin regions), so a slowly reorganising network of rough hairlines sits on empty ground, with the pointer applying a local magnetic field that clears walls out of its own neighbourhood.",
      "files": [
        {
          "path": "registry/core/background-ascii-domain-walls/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-ascii-domain-walls.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "ascii",
          "canvas",
          "cursor",
          "ising",
          "domain-wall",
          "magnetism"
        ],
        "instruction": "Build <DomainWalls cellSize? temperature? className?> as a full-bleed <canvas>. THE SPIN LATTICE IS THE GLYPH GRID: one Int8Array of +-1 sized cols x rows at cellSize (default 12px), seeded 50/50 by a mulberry32 PRNG (seed 0x15196e) on every resize. DYNAMICS: Metropolis single-spin-flip Monte Carlo with ferromagnetic coupling J = 1 at temperature T = 2.09, deliberately 0.92 * Tc where Tc = 2/ln(1+sqrt(2)) = 2.269 — this constant is the whole piece: below it walls freeze straight and stop moving, at Tc the lattice becomes fractal mush, and only at 0.92 * Tc are walls simultaneously sharp AND mobile, which is what makes the frame calm-but-alive. Each frame performs 0.9 * (cols*rows) flip attempts; per attempt pick a random site, compute dE = 2*J*s*(sum of its 4 neighbours with periodic wrap) + 2*H_local*s, accept if dE <= 0 else accept with probability exp(-dE/T). At H = 0 the neighbour sum is one of -4,-2,0,2,4 so only five dE values are reachable — the Boltzmann factor is a precomputed 5-entry Float64Array keyed on (dE+8)>>2 and Math.exp never runs in the hot loop except for the few hundred cells actually inside the pointer's field. RE-SEEDING is mandatory, not decoration: curvature-driven coarsening monotonically shrinks total wall length and would end in one uniform domain and a blank frame, so every 3.0s +- 0.6s of jitter a random disc of radius 6 cells is flipped to a single sign, nucleating a fresh domain and holding total wall length roughly stationary forever. The lattice is pre-equilibrated with 120 sweeps (400 under reduced motion) at mount so the first painted frame is already coarse rather than salt-and-pepper noise. RENDER / SPARSITY: a cell inks ONLY if its spin differs from its right neighbour or its down neighbour. Domain interiors — the overwhelming majority — draw absolutely nothing, and measured coverage at T = 2.09 is ~19% of cells straight out of equilibration and settles to 13-15% under the re-seed-versus-coarsening balance, so the frame stays dark and the boundary is the whole subject. Which bond broke picks the glyph: right-bond only draws '|', down-bond only draws '-', both draw '+', which yields genuine line-work along the wall rather than a stipple, so no ' .:-=+*#%@' density ramp is used here. LUMINANCE IS WALL CURVATURE: count how many of the 8 surrounding cells are also inked, lum = 0.35 + 0.09 * count clamped to 1 — kinked, high-energy stretches of wall read brighter than straight ones, which is physically what costs energy. POINTER APPLIES A LOCAL MAGNETIC FIELD, not a brush and not an erase: while the pointer is over the canvas, H_local(cell) = 1.4 * strength * exp(-d^2 / (2 * 7^2)) in cell units, with strength eased 0 -> 1 on a 0.6s time constant and the gaussian cut off beyond 24 cells. Because energy carries a -H*s term, flipping costs +2*H*s, so a positive field makes flipping a down spin up cheap and the reverse expensive — a uniform field suppresses the minority phase, so walls under the cursor retract and annihilate on their own and a clean circular clearing tracks the pointer. Walls are never pushed or deleted; they are thermodynamically driven out. On pointerleave strength eases back to 0 over the same 0.6s and thermal fluctuation plus the next re-seed lets new domains nucleate back into the cleared patch over a couple of seconds — a physical relaxation, not a fade-in. Direct-DOM rAF, zero React state on the hot path. Rendering is two-pass: pass one fills a Uint8Array glyph-index buffer (0 = none, 1 = '|', 2 = '-', 3 = '+') and the curvature pass buckets each inked cell into one of 6 luminance buckets; pass two sets ctx.globalAlpha once per bucket and draws only that bucket's cells — never one globalAlpha write per cell. Ink is read once via getComputedStyle(canvas).color and re-derived on a documentElement class MutationObserver so theme flips are live in both light and dark; no colour literal anywhere. Mono cell width is measured from an offscreen canvas's measureText('MMMMMMMMMM')/10, and resize is debounced 150ms with the lattice reseeded and re-equilibrated. prefers-reduced-motion runs 400 equilibration sweeps at mount, draws exactly one static frame with the field at 0, and skips the rAF loop and the pointer listeners entirely. The loop pauses on document.hidden and resumes on visibilitychange. The canvas is aria-hidden — it is decoration and carries no content a screen reader should announce. Props: cellSize (grid cell px, which is also the Ising lattice spacing, default 12), temperature (in units of J/k_B, default 2.09 = 0.92 Tc), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 --ns-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-force-chains",
      "type": "registry:ui",
      "title": "Background ASCII Force Chains",
      "description": "An ambient ASCII background of a granular packing under load: a static disc packing's contact network is relaxed each frame and only the contacts carrying well above the mean force are inked, so the few percent of grains bearing the load stand out as thin branching chains while the rest of the pile stays dark.",
      "files": [
        {
          "path": "registry/core/background-ascii-force-chains/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-ascii-force-chains.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "ascii",
          "canvas",
          "cursor",
          "granular",
          "force-chains",
          "stress"
        ],
        "instruction": "Build <ForceChains cellSize? grainCount? className?> as a full-bleed <canvas>. PACKING, built ONCE per resize and never per frame: a seeded mulberry32 Poisson-disk (Bridson) sampling of grainCount discs (default 900) at a minimum spacing R solved from the target count so the packing is viewport-independent, R = sqrt(width * height / (grainCount * 1.57)), with grainR = R / 1.9 and per-grain radii jittered into [0.85, 1.15] * grainR — that polydispersity is what stops the packing crystallising into a boring hex lattice. Bridson's candidate annulus is narrowed to R..1.3R (30 candidates), which packs jammed rather than loose. Grains are then SORTED top-to-bottom and stored in Float32Arrays px, py, pr. CONTACT GRAPH, built once alongside it via a uniform-grid neighbour search: an edge wherever centre distance < (r_i + r_j) * 1.35, stored as flat Int32Array ea/eb plus a CSR offsets array; because the grains are depth-sorted every edge is directed from the upper grain to the lower one and its normal's vertical component n.y is >= 0 by construction. The 1.35 tolerance is calibrated to a coordination number z ~= 4, the correct value for a 2D frictional packing; the naive 2 * grainR contact test against 1.9 * grainR Poisson spacing yields z ~= 1.0, which is not a network at all. There is NO physics engine, no per-frame repacking and no collision solving — the only thing that changes per frame is how load distributes through that fixed network. FORCE RELAXATION per frame: each grain's outgoing force is its incoming force plus its own weight (TOTAL_WEIGHT 5.9 split across every grain), distributed across its below-neighbours in proportion to the vertical component of the contact normal, f_ij = total * n_ij.y / (sum of that grain's n.y). This is the q-model of granular stress transmission — a multiplicative cascade down a disordered network, whose steady state is a heavy-tailed force distribution. It is evaluated as a single ORDERED top-down sweep rather than N Gauss-Seidel iterations: because the grains are depth-sorted the contact graph is a DAG, so one in-order sweep IS the converged fixed point and further iterations reproduce it byte-for-byte at N times the cost. Load is injected at the top boundary row (grains within 1.6 * R of the top edge) as a per-column value that breathes calmly, unit * (1 + 0.22*sin(t*0.5 + col*0.4) + 0.08*sin(t*1.7)) with col = floor(x / R) and unit = 3.6 / (number of top grains) so total injected flux is the same at every viewport width — that breathe is the ambient jitter, and it is what makes chains slowly switch on and off at a resting, non-busy pace. SPARSITY, which is the whole point and the real physics: compute the mean contact force fbar over every edge each frame and INK ONLY CONTACTS WITH f > 1.9 * fbar. Roughly 12% of contacts survive, and they form the branching load-bearing skeleton; the other ~88% are 'spectator' grains and draw absolutely nothing. That is what a photoelastic sand experiment actually looks like — bright chains through dark grains — and the empty regions are meaningful rather than decorative. Each surviving contact is rasterised onto the glyph grid by walking the segment between the two centres at one sample per glyph cell and setting the glyph by segment slope ('-' below 22.5 deg, '|' between 67.5 and 112.5, '\\\\' and '/' either side), with luminance = pow(min(1, (f/fbar - 1.9) / 2.6), 0.8) so the most-loaded chains are the brightest; when two chains cross a cell the brighter one wins. Contacts below luminance 0.34 draw from the shared ' .:-=+*#%@' density ramp instead of a slope glyph, clamped to its '.' and ':' levels, so faint load reads as texture and only real chains read as line — letting the faint ramp reach '-' put 145 horizontal marks on screen against 36 genuinely horizontal chain cells (measured), which reads as streaking in a field whose subject is vertical chains. POINTER: the pointer is an INDENTER pressing into the packing. Its position adds a point load of 3.2 * strength to the NEAREST grain THAT HAS AT LEAST ONE BELOW-NEIGHBOUR (each grain's summed outgoing n.y is precomputed with the packing, so this is one comparison per grain; a grain with nothing beneath it terminates the load, and seating the indenter on one would make roughly 4% of pointer positions do nothing at all — measured), with strength eased 0 -> 1 over a 0.5s time constant; 3.2 is deliberately comparable to the entire 3.6 of ambient top flux, so the relaxation redistributes something the network can feel. A bright cone of new chains fans DOWNWARD and outward from the cursor at roughly the packing's 45-degree stress-cone angle, and chains elsewhere DIM — not because they lost force, but because fbar rises and they fall back under the 1.9 * fbar threshold, which is the correct and physically motivated response rather than a radial highlight. On pointerleave the load eases back to 0 over the same time constant and the cone dissolves as the network returns to its gravity-only skeleton. Direct-DOM rAF, zero React state on the hot path. Rendering is two-pass: pass one rasterises surviving contacts into a Uint8Array glyph-index buffer plus a Float32Array luminance buffer and buckets every non-empty cell into one of 6 alpha buckets; pass two sets ctx.globalAlpha once per bucket and draws only that bucket's cells — never one globalAlpha write per cell. Ink is read once via getComputedStyle(canvas).color and re-derived on a documentElement class MutationObserver so theme flips are live in both light and dark; no colour literal anywhere. Mono cell measured via an offscreen canvas's measureText, resize debounced 150ms and rebuilding the packing (the seed is fixed, so geometry is deterministic and identical across themes). prefers-reduced-motion builds the packing, runs one relaxation at t = 0 with the indenter at strength 0, draws exactly one static frame, and skips the rAF loop and pointer listeners entirely. The loop pauses on document.hidden and resumes on visibilitychange. Props: cellSize (glyph grid cell px, default 12), grainCount (target grains in the packing, clamped 200..2000, default 900), className."
      }
    },
    {
      "name": "background-ascii-nodal-lines",
      "type": "registry:ui",
      "title": "Background ASCII Nodal Lines",
      "description": "An ambient ASCII background of the destructive-interference nodal set of four point emitters: ink appears only where the summed radial waves cancel to near zero, tracing the hyperbolic dead-fringe families across an otherwise empty field, with the pointer as a fifth, phase-shifted emitter that redraws the geometry.",
      "files": [
        {
          "path": "registry/core/background-ascii-nodal-lines/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-ascii-nodal-lines.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "ascii",
          "canvas",
          "cursor",
          "interference",
          "wave",
          "nodal"
        ],
        "instruction": "Build <NodalLines cellSize? sourceCount? className?> as a full-bleed <canvas>. FIELD: four fixed point emitters on a slightly irregular quadrilateral at normalized [0.24,0.30], [0.76,0.26], [0.30,0.74], [0.72,0.78] of the canvas box, with amplitudes a_i of 1.00, 0.88, 1.12, 0.95 (all inside [0.85,1.15]) and fixed phase offsets phi_i of 0, 0.6, 1.9, 3.1 rad. Per grid cell the four RADIAL waves are SUMMED — A = sum_i a_i * cos(k*r_i - omega*t + phi_i) / sqrt(max(r_i, 8)), k = 0.042 rad/px, omega = 1.15 rad/s — which is deliberately neither background-ascii-caustics' operator (it MULTIPLIES rotated PLANE waves and sharpens with a power curve) nor background-ascii-plasma's (it sums octaves of noise and maps luminance directly). The 1/sqrt(r) term is the correct 2D cylindrical-wave amplitude decay and is what keeps the fringes close to an emitter from washing out. NODAL SET — this is the sparsity mechanism: ink marks only the ZERO SET of A, the cells where the four waves cancel each other out, and the roughly 90% of the frame (measured 90.5% at the default cell size) that is oscillating with any amplitude at all draws absolutely nothing. Point-sampling |A|/Amax against a fixed amplitude threshold aliases into speckle at glyph resolution (the cancellation band is about a pixel wide, a mono cell is seven), so the distance to the nodal curve is resolved analytically instead: with grad A accumulated in the same loop as A, d = |A| / |grad A| is the screen-space distance in px to the nearest nodal curve, and a cell inks when d < 4.0 px with luminance pow(1 - d/4.0, 1.3). The normalized depth |A| / Amax, where Amax = sum_i a_i/sqrt(max(r_i,8)) is the local maximum possible amplitude, still gates the result at 0.4 so a cell sitting on a stationary point of an otherwise loud field never inks. Because the nodal set of two sources is a hyperbola family and of four is their intersection lattice, the resulting ink is several families of smooth curved hairlines fanning between the four focal points — structure, never a wash. The emitter positions are never drawn as dots; only the nodal lines, which is what keeps the read honest. AMBIENT MOTION: all four emitters share the same omega, so the field swings as A = |S(x)| * cos(arg S(x) - omega*t) and the inked set is the INSTANTANEOUS zero-crossing family, which therefore sweeps on its own at a resting pace (about half a wavelength, ~75px, every pi/omega = 2.7s, i.e. ~28 px/s) without any extra animation. Layered on top of that, each emitter's phase ALSO drifts at its own much slower rate (0.05, -0.037, 0.061, -0.044 rad/s, ~20x below omega), which slowly re-shapes which family the sweep is walking through so the pattern never settles into a short repeating cycle. POINTER: the pointer is a FIFTH EMITTER, amplitude eased 0 -> 1.0 with a 0.5s time constant on enter, phase locked at pi so it is in antiphase with emitter 0. Because interference depends on the path-length difference to EVERY source, adding an antiphase one shifts every difference at once: the whole nodal family visibly reorganises around the cursor and a fresh set of dead fringes fans out from it. On pointerleave the amplitude eases back to 0 over the same time constant and the four-source geometry returns exactly. Direct-DOM rAF, zero React state on the hot path. Rendering is two-pass: pass one evaluates every cell into a Uint8Array ramp-index buffer and buckets it 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 — never one globalAlpha write per cell. Ink is read once via getComputedStyle(canvas).color and re-derived on a documentElement class MutationObserver so theme flips are live in both light and dark; no colour literal anywhere. Mono cell measured via an offscreen canvas's measureText, resize debounced 150ms. prefers-reduced-motion renders exactly one static frame at t = 0 with every phase at its base offset and the pointer emitter at amplitude 0, and skips the rAF loop and pointer listeners entirely. The loop pauses on document.hidden and resumes on visibilitychange. Props: cellSize (grid cell px, default 12), sourceCount (how many of the four emitters to use, clamped 2..4, default 4), className."
      }
    },
    {
      "name": "background-ascii-voronoi-walls",
      "type": "registry:ui",
      "title": "Background ASCII Voronoi Walls",
      "description": "An ambient ASCII background of Voronoi cell WALLS only: ink appears exactly where the first and second nearest sites are equidistant, so a sparse, slowly drifting network of hairlines and clean three-way junctions sits on an otherwise empty field, and the pointer is a weighted site whose territory visibly swells.",
      "files": [
        {
          "path": "registry/core/background-ascii-voronoi-walls/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-ascii-voronoi-walls.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "ascii",
          "canvas",
          "cursor",
          "voronoi",
          "tessellation",
          "boundary"
        ],
        "instruction": "Build <VoronoiWalls cellSize? siteCount? className?> as a full-bleed <canvas>. SITES: siteCount sites (default 22) scaled by sqrt(area / (1280*720)) and clamped to 14..34, positioned once per resize by mulberry32-seeded (seed 0x5eed1a) Poisson-disk rejection sampling at a minimum separation of 0.16 * min(W,H) so no two walls ever crowd each other; the rejection radius relaxes by 10% after 220 consecutive failures so a narrow frame terminates instead of spinning. Each site then drifts on its own slow Lissajous, p = p0 + A * [sin(t*wx + px), cos(t*wy + py)], with A = 0.045 * min(W,H) and wx, wy drawn per site from [0.06, 0.17] rad/s — a calm resting drift whose only ambient event is the topology occasionally flipping as two sites pass each other and a wall reconnects. FIELD: per grid cell the nearest, second-nearest and third-nearest site distances d1 <= d2 <= d3 are found by a plain linear scan over the site list (22 sites x cols*rows is trivial at cellSize 12) — no Delaunay, no library, no dependency; ordering is done on the squared quantity and only the three winners are square-rooted. WALL FUNCTION, which is the sparsity mechanism and must be the NORMALIZED difference rather than raw d2 - d1 (raw would make walls near a site hairline and walls far away fat, so thickness would vary wildly across the frame): w = (d2 - d1) / (d2 + d1), ink only where w < 0.055, luminance = pow(1 - w/0.055, 1.5). Cell interiors sit at w near 1 and draw absolutely nothing, so roughly 90% of the frame is empty by construction (measured 10.1% inked at 1280x720, stable across drift and across frame sizes from a 420x300 card to 2560x1400) and what remains is a network of hairlines meeting at clean triple junctions — three sites equidistant is exactly a Y-junction, which is why foam, cracked mud and a territory map all look like this. JUNCTION EMPHASIS: the third-nearest distance is carried through the same scan, and if (d3 - d1) / (d3 + d1) is also below 0.055 the luminance is multiplied by 1.6 and clamped to 1, so triple points read a touch brighter exactly as they do in a real foam. The sites themselves are never drawn as dots; only the walls, which is what keeps the read honest. POINTER: the pointer joins the diagram as an EXTRA SITE WITH A POWER WEIGHT — a Laguerre (power) Voronoi cell, which is the physically correct way for one site to own more territory than its neighbours. Its distance is measured as the tangent length sqrt(d^2 - r^2) instead of d, with r^2 eased from 0 toward (0.10 * min(W,H))^2 with a 0.5s time constant while the pointer is over the canvas. A positive weight moves every bisector it touches from a perpendicular line to a radical axis, so the surrounding walls bow convexly AWAY from the cursor and the neighbouring cells are visibly squeezed — a bubble inflating in a foam, not a stamp or a local recolor. On pointerleave r^2 eases back to 0 over the same time constant, the pointer's cell collapses to nothing, and the walls spring back to the undisturbed diagram. Direct-DOM rAF, zero React state on the hot path. Rendering is two-pass: pass one evaluates every cell into a Uint8Array ramp-index buffer and buckets each inked cell 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 — never one globalAlpha write per cell. Ink is read once via getComputedStyle(canvas).color and re-derived on a documentElement class MutationObserver so theme flips are live in both light and dark; no colour literal anywhere. Mono cell width is measured from an offscreen canvas's measureText('MMMMMMMMMM')/10, and resize is debounced 150ms with the site layout reseeded deterministically. prefers-reduced-motion renders exactly one static frame at t = 0 with the pointer weight at 0, and skips the rAF loop and the pointer listeners entirely. The loop pauses on document.hidden and resumes on visibilitychange. The canvas is aria-hidden — it is decoration, and carries no content a screen reader should announce. Props: cellSize (grid cell px, default 12), siteCount (base site count before area scaling and the 14..34 clamp, default 22), 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": "background-lloyd-relax",
      "type": "registry:ui",
      "title": "Background Lloyd Relax",
      "description": "A blue-noise dot field that never stops settling: points run one weighted Lloyd relaxation iteration per frame against a drifting density function with genuine zero regions, so ink continually migrates into the dense lobes and leaves the rest of the frame bare paper; the pointer opens a density well the dots crowd into and drain out of.",
      "files": [
        {
          "path": "registry/core/background-lloyd-relax/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-lloyd-relax.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "canvas",
          "stipple",
          "voronoi",
          "lloyd",
          "blue-noise",
          "generative",
          "cursor"
        ],
        "instruction": "Build <LloydRelax count? className?> as a full-bleed <canvas> drawn with Canvas 2D on a direct-DOM rAF loop, with every point's state held in two Float32Arrays (x, y) mutated in place so the hot path allocates nothing and React state is never touched. DENSITY — this is the sparsity control and it must have real zeros or the field degenerates into a uniform wash: n(x,y,t) = 0.5 + (sin(0.0091*x + 0.21*t) + sin(0.0067*y - 0.15*t) + sin(0.0053*(x+y) + 0.11*t)) / 6, then rho = pow(clamp((n - 0.35) / 0.65, 0, 1), 2). The 0.35 floor leaves roughly a quarter of the frame at rho = 0 exactly, and the square steepens the falloff, so ink concentrates into a few lobes with hard-edged empty ground between them. The three periods (~30s, ~42s, ~57s) are mutually incommensurate, so the lobes drift and reshape forever and the relaxation never converges — that non-convergence IS the calm resting pulse, not an animation layered on top. POINTS: count defaults to 1400, seeded by rejection sampling against rho at t=0 (propose a uniform position, accept with probability rho, 40 tries max then place anyway). RELAXATION, exactly one weighted Lloyd iteration per frame: (1) bin every site into a uniform hash grid of 48px cells — an Int32Array of bin heads plus a per-site next-link array, rebuilt in place each frame; (2) walk a quarter-resolution sample lattice of 6px cells, and for each sample find its nearest site by scanning only the 3x3 neighbouring hash bins, accumulating w = rho(sample) into that site's sumW, sumWX, sumWY accumulators — this is the discrete weighted Voronoi centroid, i.e. the Delaunay dual computed without ever building a triangulation, and samples where rho is zero are skipped so the dead regions cost nothing; (3) move each site 0.55 of the way toward its centroid, and teleport any site whose sumW is below 1e-4 by rejection-sampling a fresh position, which is what keeps points from stranding in the dead zones as the lobes drift away from under them. The fixed point of this iteration is a centroidal Voronoi tessellation whose local spacing goes as rho^(-1/2), which is exactly why the field reads as a genuine stipple screen — locally equidistant, globally tonal — rather than scattered noise. INTERACTION: while the pointer is over the canvas the density is multiplied by 1 + 2.6*exp(-d2 / (2*90^2)) with the multiplier's strength eased 0 -> 1 on a 0.5s time constant, so over about a second the dots physically migrate inward and pack tighter under the cursor; nothing pulls them directly, the relaxation does all the work because their cells' centroids moved. On pointerleave the well eases shut on the same constant and the packing loosens back out to the ambient density. RENDER: each point is a filled dot of radius 1.5px; alpha is bucketed by the local rho into five buckets (0.22, 0.40, 0.58, 0.76, 0.95) — pass one assigns buckets, pass two sets ctx.globalAlpha ONCE per bucket and fills only that bucket's dots as a single path, never one globalAlpha write per dot. Points where rho < 0.02 are culled and not drawn at all. TOKENS AND THEME: ink is getComputedStyle(canvas).color read at mount and re-derived on a documentElement class MutationObserver so a theme flip is live; the canvas is cleared with clearRect so the page's --background shows through; no color literal appears anywhere in the draw code. dpr is clamped to 2, resize is observed with a ResizeObserver that ignores deltas under 4px, dt is clamped to 1/30s, and the rAF loop pauses on document.hidden and resumes on visibilitychange. At mount and on every accepted resize the field is warmed by running 12 relaxation iterations synchronously at t=0 before the first paint, so frame one is already a settled stipple screen rather than the raw rejection-sampled scatter. prefers-reduced-motion: reduce runs 24 relaxation iterations synchronously at t=0, draws exactly ONE frame, and binds no rAF and no pointer listeners. Props: count (site count, default 1400), className."
      }
    },
    {
      "name": "background-truchet-weave",
      "type": "registry:ui",
      "title": "Background Truchet Weave",
      "description": "An ambient Truchet arc weave whose tile orientation is the SIGN of a slow harmonic field, so the frame reads as interlaced domains of continuous arcs separated by pale seams along the domain walls, and the pointer bends the field so the walls re-route outward from the cursor and retreat when it leaves.",
      "files": [
        {
          "path": "registry/core/background-truchet-weave/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-truchet-weave.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "canvas",
          "truchet",
          "tiling",
          "generative",
          "cursor",
          "weave",
          "ambient"
        ],
        "instruction": "Build <TruchetWeave tileSize? className?> as a full-bleed <canvas>. GRID: square cells of TILE px (prop tileSize, default 28), cols = ceil(w / TILE), rows = ceil(h / TILE). FIELD: for each cell take its centre in cell units (cx = gx + 0.5, cy = gy + 0.5) and evaluate a three-term harmonic sum at two fixed rotation angles, 0.42 and 1.93 rad — u = cx*cos(0.42) + cy*sin(0.42), v = -cx*sin(1.93) + cy*cos(1.93), f = sin(u*0.26 + 0.11*t) + 0.70*sin(v*0.19 - 0.09*t) + 0.45*sin((u+v)*0.11 + 0.05*t). ORIENTATION IS THE SIGN, NOT A COIN FLIP: bit = (f + bias) > 0, so consistent orientation extends over large soft domains instead of dissolving into per-cell noise. Tile A draws two quarter-circle arcs of radius TILE/2 centred on the cell's top-left and bottom-right corners; tile B centres them on the top-right and bottom-left. Both tilings cross every cell edge at exactly its midpoint, so the two are edge-compatible and the arcs join across every boundary into one continuous interlaced curve — the classic Truchet 'maze of loops'. That continuity IS the mechanism: a single flipped tile visibly re-routes the whole curve threading through it, which is why the pointer's effect reads as structural rather than cosmetic. SPARSITY (the negative-space control): stroke alpha = pow(min(1, abs(f + bias) / 1.2), 1.6), so cells near the zero set — the domain walls, exactly where the orientation is about to flip — fade to nothing. Cells whose alpha falls under 0.06 are skipped entirely — that is |f + bias| < ~0.21, measured at ~11% of the frame — and the bucket below them strokes at 0.10 alpha, so a band roughly two cells wide either side of each zero crossing is blank-to-ghost and the result is a weave torn open along pale seams rather than a uniform mat. RENDER: 5 alpha buckets. Pass one classifies every cell into a bucket and pushes its packed index (tile orientation encoded in the sign) into a per-bucket array; pass two does ctx.beginPath() once per bucket, arcs every cell of that bucket into the single path, sets ctx.globalAlpha ONCE and ctx.stroke()s — 5 stroke calls per frame total, never one globalAlpha write per cell. lineWidth 1.25, lineCap 'butt'. POINTER: bias(cell) = P * exp(-d^2 / (2 * 5.5^2)) with d the cell-space distance to the pointer and P = 1.35 * S, where S is a 0..1 scalar eased toward 1 while the pointer is over the canvas and back to 0 on leave via S += (target - S) * min(1, dt / 0.5), a 0.5s time constant; the gaussian is cut off beyond 22 cells (4 sigma). At full strength that flips on the order of 40 tiles in a ring around the cursor — small in count, large in effect, because every flip re-routes the continuous curve threading through it. Because the bias is added BEFORE the sign test, cells whose |f| is already small flip first, so a fresh domain wall opens as a ring around the cursor and spreads outward, then retreats and the original weave re-forms on the same time constant. No click, no discrete re-roll, no React state on the hot path — direct-DOM rAF only. TOKENS: the canvas is the drawing medium, so ink is read once via getComputedStyle(canvas).color and re-derived on a MutationObserver watching documentElement's class attribute, making theme flips live in both light and dark; there is no colour literal anywhere in the source. dpr is clamped to 2, a ResizeObserver re-sizes with a <4px zero-size guard, the rAF loop pauses on document.hidden and resumes on visibilitychange, and dt is clamped to 1/30 s so a backgrounded tab cannot jump the field. prefers-reduced-motion draws exactly one frame at t = 0 with P = 0 and binds no pointer listeners and no rAF loop. The canvas is aria-hidden — it is decoration and carries no content a screen reader should announce. Props: tileSize (square tile px, default 28), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-muted border with --ns-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, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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, --ns-muted and --ns-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": "barograph-drum-week",
      "type": "registry:ui",
      "title": "Barograph Drum Week",
      "description": "A card-scale status widget modelled on the aneroid barograph: a pen arm inches rightward across pre-ruled seven-day chart paper, its ink trail lengthening continuously while faint day/hour gridlines (printed on the paper, never drawn by the pen) pass beneath it, reading as \"this has been running, unattended, for a long time.\"",
      "files": [
        {
          "path": "registry/core/barograph-drum-week/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/barograph-drum-week.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "chart",
          "canvas",
          "dom",
          "barograph",
          "trace",
          "status",
          "ambient",
          "uptime",
          "instrument",
          "weekly",
          "dashboard"
        ],
        "instruction": "A card-scale DOM+canvas hybrid modelling an aneroid barograph: a clockwork drum turns once per week under a pen arm, wrapped in chart paper whose day/hour ruling is printed in advance — the pen only ever adds ink, never the grid. Real drum period 604,800s (7 days) is compressed into a 45s loop (documented ratio ~1 app-second : 3.7 real hours). Two stacked canvases stand in for the two physical layers, sized from chartWrap's getBoundingClientRect (the card's own smaller dimension governs derived sizes like tick length, pen-arm length and ink stroke width, e.g. armLen = max(10, min(chartW,chartH) * 0.16)): a grid canvas draws 7 full-height day-divider lines plus, per day, 23 hour ticks rendered as SHORT marks at the top and bottom edges only (never full-height) so the 7x24 = 168-tick mesh stays a faint structural ruling instead of a visible wash — both drawn with plain var(--border) at its designed near-invisible separator alpha, redrawn only on resize or theme flip, never per frame. An ink canvas accumulates the trace by drawing exactly one new line segment per animation frame from the previous point to the current one (never cleared mid-cycle); every point is also kept in a small history array (x/y as 0..1 fractions of the chart) purely so a resize — which invalidates a canvas's raster — can losslessly replot the whole trace at the new pixel size rather than losing it, not as a mechanic in its own right. Pen speed is constant: chartWidth / 45s (~6.7px/s for a 300px-wide card). The trace value is two incommensurate sine terms — a primary 'weather front' swing with a period randomized 8-14s per cycle (amplitude +/-30% of chart height, 66% of swing weight) and a secondary term at ~0.32-0.46x that period for texture (34% weight) — summed and clamped to +/-1, plus a bounded per-frame random-walk micro-jitter (+/-0.6px step, clamped to +/-0.3px) standing in for aneroid-capsule response noise; the two together give the day-gridline crossing its own 6.4s beat (45s / 7 days) independent of the swing. The pen arm is a real DOM div (never canvas), 2px tall, origin-right, translated and rotated by trigonometry each frame from the EXACT SAME (xFrac, yFrac) point that frame just pushed into the shared point history and drew to canvas — a single source of truth so the DOM overlay can never drift from the canvas ink's endpoint; its rotation is the atan2 of the delta to the previous point, clamped to +/-0.6rad so a single noisy frame can't over-rotate it. At the 45s mark the ink wrapper div (canvas + pen, NOT the grid canvas) transitions opacity 1->0 over 500ms via a CSS transition set imperatively, then once elapsed the point history and ink canvas are cleared, a fresh randomized front is drawn, the wrapper's transition is set to none and opacity snaps back to 1 against an already-empty canvas, and the cycle resumes at day-0 — a fade back to the start, never a hard cut. Colour is read once via getComputedStyle(document.documentElement).getPropertyValue for --foreground and --border with no literal fallback of any kind; if either is empty (stylesheet not yet applied) the mount loop retries on the next rAF and paints nothing until both resolve. A MutationObserver on document.documentElement's class attribute re-reads tokens and forces a full grid+ink repaint on every theme flip; a ResizeObserver on the chart wrapper resizes both canvas backing stores (capped devicePixelRatio 2) and replots from the point history; an IntersectionObserver stops driving new frames while the card is off-screen and, on re-entering view, re-reads tokens before resuming — no paint happens on any of these three paths before a token read succeeds. Under prefers-reduced-motion the component runs zero rAF loops, zero timers and sets up no crossfade at all: it deterministically builds (via a fixed-seed mulberry32 PRNG, not Math.random) a single static trace from day 0 to STATIC_DAY = 4 of 7 (~57% across, chosen as a structured mid-week frame with at least one full weather-front swing already visible), draws it once, and positions the pen arm at its leading edge — the exact same freeze frame on every load. Cleanup on unmount cancels both the animation and token-wait rAF handles and disconnects all three observers. Zero dependencies, no color literal anywhere including fallbacks."
      }
    },
    {
      "name": "bias-hysteresis",
      "type": "registry:ui",
      "title": "Bias Hysteresis",
      "description": "A saturation/headroom gauge rendered as an analog tape deck's own AC-bias hysteresis loop instead of a bar or dial: a bright marker rides a closed H/B loop that is narrow and sharp at low drive and widens with rounded corners toward saturation at high drive, its own envelope breathing on a slower independent cycle so the loop's shape visibly changes across a five-second sample, not just the marker's position on a fixed track.",
      "files": [
        {
          "path": "registry/core/bias-hysteresis/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/bias-hysteresis.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "meter",
          "gauge",
          "saturation",
          "hysteresis",
          "tape",
          "analog",
          "canvas",
          "monochrome"
        ],
        "instruction": "Build <BiasHysteresis label? height? className?> as a card-scale <canvas> panel, a drop-in saturation/headroom gauge that plots a closed H (drive field, x) vs. B (flux density, y) hysteresis loop instead of a bar or dial. SOURCE: AC-bias magnetic tape recording linearizes an inherently nonlinear medium by superimposing a high-frequency bias signal on the audio; the tape's flux density B vs. drive field H traces a hysteresis loop, not a straight line (Jiles-Atherton hysteresis model; documented on any pro reel-to-reel service manual, e.g. Studer A80 / Ampex ATR bias alignment procedures) — as input climbs toward and past 0dB the loop widens and its corners round off toward saturation, the mechanism behind analog tape's 'soft clip' character, colloquially 'tape saturation'. MATH: each branch is B = Bsat*tanh(k*(H -+ Hc)) with Bsat=0.92, Hc=0.18 (coercivity), k=2.4 (steepness constant controlling corner rounding) — the ascending branch (H rising) uses B = Bsat*tanh(k*(H-Hc)), the descending branch (H falling) uses B = Bsat*tanh(k*(H+Hc)); using the opposite sign per sweep direction is what opens the two tanh curves into a single closed lobed loop rather than one line. The full loop is rebuilt every frame at LOOP_POINTS=240 resolution (120 ascending-branch samples spanning H from -amp to +amp, 120 descending-branch samples spanning +amp back to -amp, closed into one path), never cached, because its own envelope amplitude changes continuously. DRIVE: H oscillates as a slow LFO, period 8.3s (0.12Hz, DRIVE_PERIOD_S), H = amp*sin(2*PI*(t/8.3 - 0.12)) — the 0.12-cycle phase offset exists purely so the reduced-motion freeze frame (below) lands on a specific, chosen state, not an arbitrary one. ENVELOPE: the loop's own saturation amount breathes on an independent 21s cycle (ENVELOPE_PERIOD_S), amp = 1.0 + 0.4*sin(2*PI*t/21), modulating peak H amplitude between 0.6 and 1.4 — this is what makes the loop's overall width/roundedness visibly different across a five-second sample, not just the marker's position on a fixed loop: narrow and sharp near the 0.6 trough, wide and rounded near the 1.4 peak. MARKER: computed directly from the same branch formulas at the drive's current H and sweep direction (ascending if cos(angle) >= 0, else descending) rather than searched out of the loop's point array — one full lobe traversal every 8.3s, ~29 points/second equivalent smoothness, nowhere near paint-rate aliasing risk; this is the ONE thing a viewer should follow, and 8.3s per lap is slow enough to track continuously with the eye, never a discrete jump. REAL BIAS FREQUENCY: professional decks bias at roughly 150kHz — documented here in this instruction and in the component's own code comments only, never rendered 1:1 (that would alias against a ~60Hz paint rate into a strobe, the exact round-9 legibility failure mode this registry has already shipped once) — the rendered drive rate is a decoupled, deliberately slow 8.3s sweep instead. GEOMETRY: the loop is plotted in a square region of side min(containerWidth, containerHeight) * 0.7, centered in the canvas, so it holds its shape at card scale regardless of the panel's own aspect ratio; H maps across an H_DOMAIN of +-1.5 (tight padding beyond the 1.4 max envelope amplitude, deliberately kept close so the trough-amplitude loop at amp=0.6 still reads as a lens with real horizontal width instead of a tall sliver dominated by the fixed ~0.75 vertical coercivity gap) and B across a B_DOMAIN of +-1.05 (padding beyond Bsat), each independently scaled to the square's half-side. RENDER ORDER per frame: clear, draw a thin border-token axis crosshair (vertical H=0, horizontal B=0) first so it always sits under the loop, then fill the closed loop path at 0.05 foreground-alpha (the enclosed hysteresis area, read as 'energy lost to saturation'), then stroke the same closed path at full foreground opacity, then paint the marker as a filled foreground circle (radius 3.5px) on top. TOKENS: fg/border are read once via getComputedStyle(document.documentElement) against --foreground/--border before the first paint (no paint before the first read), re-derived on a documentElement class MutationObserver so a theme flip repaints live; the loop stroke is --foreground at full opacity, the axis crosshair is --border (a separator only, never the loop's own line, never a fill), the enclosed-area fill is a very low-opacity (0.05) --foreground tint — never a hue anywhere, and light theme is the harder case for that low-opacity fill (verify it doesn't visually disappear against a near-white --background before shipping). --ns-accent never appears: there is no interactive affordance in this build (a hover-driven 'current drive level' readout could be added later, but it must never recolor the loop stroke or fill with accent — accent stays reserved for a focus ring on interactive chrome only, never the loop itself). RESTING LOOP: t0 — marker somewhere on the loop, loop at whatever phase of its 21s envelope-breath it started at; 2.5s — marker has traveled roughly 30% of one lobe, loop width has shifted measurably (~12% of a full 21s breath elapsed); 5s — marker has crossed into the opposite lobe, loop shape visibly wider or narrower than at t0. Unbounded, zero input, alive at rest. REDUCED MOTION: freezes at drive-cycle phase 0.62 (FREEZE_T = 0.62 * 8.3s = 5.146s) — chosen because with the 0.12-cycle DRIVE_PHASE_OFFSET applied, H is descending through zero at exactly that instant while the 21s envelope sits within a hair of its own peak amplitude (5.146s / 21s ~= 0.245 of the envelope's own sine, ~=0.9993 of its peak). At H=0 the marker sits at the widest vertical separation between the two branches (the coercivity gap, ~0.75 of the plotted B range) with the loop itself at peak envelope amplitude — the single frame that shows the full open loop at its widest, rather than a thin near-origin sliver at a small envelope amplitude. Checked via matchMedia('(prefers-reduced-motion: reduce)') at mount, no live-change listener needed since this component has no other motion state to reconcile. LIFECYCLE: canvas sized via getBoundingClientRect with devicePixelRatio (capped at 2) applied through ctx.setTransform, resize handled by a debounced ResizeObserver (150ms), the rAF loop paused by an IntersectionObserver when off-screen and by document.hidden via a visibilitychange listener (both resume with a fresh last-timestamp, no giant delta-time jump), and every rAF/observer/listener is torn down on unmount. A11Y: the canvas itself is aria-hidden (decorative raster); it is wrapped in a role=img element carrying an aria-label summarizing the reading in prose ('a closed hysteresis loop, widening and rounding as drive climbs toward saturation') since a continuously-reshaping loop has no single scalar aria-valuenow to expose the way an ordinary meter would. Below the label row, a static Geist Mono caption row reads the two REAL NUMBERS that never change — 'Hc 0.18' / 'Bsat 0.92' — so those constants are legible from a single still frame independent of motion. No canvas colour literal, no dependency: 2D canvas, DOM+canvas+CSS only. Props: label (accessible name and visible caption, default 'Bias saturation'), height (canvas panel height px, default 220), className."
      }
    },
    {
      "name": "bitting-cut",
      "type": "registry:ui",
      "title": "Bitting Cut",
      "description": "Every passkey in a sign-in-methods list gets a physical key blade whose six notch depths are cut deterministically from its credential id, so the MacBook passkey and the phone passkey stay visually distinct objects across sessions.",
      "files": [
        {
          "path": "registry/core/bitting-cut/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/bitting-cut.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "passkey",
          "webauthn",
          "settings",
          "security",
          "list",
          "svg",
          "aria-live",
          "accessibility"
        ],
        "instruction": "`<BittingCut initialPasskeys={Passkey[]} maxPasskeys? ariaLabel? onAdd? onRemove? className? />` renders a Settings > Sign-in methods list where every passkey draws its own key: a 112x40 inline SVG per row — a stroked ring bow (fill:none, 3px --foreground stroke, so the ring's centre reads as a literal hole) carrying a small device glyph (phone/laptop/tablet/desktop/hardware-key/generic, inferred from the row's name when `device` isn't set) drawn in --foreground strokes inside that hole, and a solid --foreground blade rendered as ONE closed SVG path whose bottom edge steps through six notch cells at one of five quantized depths. THE BITTING IS DETERMINISTIC: `bittingDepths(credentialId)` FNV-1a-hashes `credentialId#i` for i in 0..5 and takes `hash % 5` per notch — a pure function of the row's `id`, same output every call, every mount, forever. There is no `Math.random()` anywhere in that derivation; decorative randomized teeth are the explicitly forbidden alternative, because the whole point is that the SAME credential looks the SAME key on the next visit. ENROLLMENT: a passkey added at runtime (via the header's real 'Add passkey' button, which fabricates a fresh credential id from `crypto.randomUUID()` and stops offering itself once the list reaches `maxPasskeys`, default 6) mounts with cutPhase 'entering' and plays a 700ms one-shot pass: a cutter graphic (a short vertical line with a small circular head, hidden at rest) sweeps smoothly from the blade's shoulder to its tip on a CSS transform transition, while each of the six notches STEPS — not tweens — to its final hashed depth via a `setTimeout` scheduled at the moment the sweep's linear position crosses that notch's centre (a cutting wheel commits a depth as it passes, it does not interpolate one in), each step also flashing a 1px --foreground rect at that notch's x-position (direct ref style write: opacity 1 with transition:none, then next frame opacity 0 over 160ms — never React state, so the flash can't itself cause a re-render loop). Passkeys present in `initialPasskeys` at mount start at cutPhase 'settled' with their target depths already drawn — no entrance animation, because for a credential enrolled before this page loaded, that key was already cut; only a genuinely NEW enrollment plays the pass. REMOVAL runs the identical mechanism inverted: cutPhase 'filing' sweeps the same cutter across the same blade, but each notch it passes steps back to depth 0 (flat/blank) instead of toward a target — 'flattening the profile to blank' — and once every notch is blank the row transitions to 'exiting', collapsing via `grid-template-rows: 1fr -> 0fr` + opacity over 260ms (mirrors this repo's existing arm-then-confirm row-eviction pattern), after which it is spliced out of state, `onRemove(id)` fires, and a `role=status aria-live=polite` region announces 'Passkey {name} removed, {n remain}' (singular '1 remains', otherwise '{n} remain') — the only thing that ever announces; adding is silent. REMOVE ITSELF is a real per-row button that arms on first press (`data-armed`, accessible name flips from 'Remove {name}' to 'Confirm remove {name}', border/text tint to --ns-accent — the only place --ns-accent appears, since arming is an active interaction state, not decoration) and commits on a second press on the SAME button; losing focus (`onBlur`) or pressing Escape while armed reverts it unconfirmed. The button is `disabled` for the whole entering/filing/exiting span of its own row, so it cannot be re-armed mid-cut or double-filed. TEXT: every row shows the device name, 'Created {Month Day}' and a last-used phrase ('Last used today' / 'yesterday' / 'N days ago' / 'N weeks ago' / a date, or 'Never used' for a freshly enrolled key with no lastUsedAt) as plain sibling text next to the art — the key SVG is `aria-hidden`, decorative, and never the row's sole identifier; a screen reader gets the same facts as the graphic conveys visually. The list is a real `<ul role=\"list\">`/`<li>` structure with an accessible name. Last-used text follows this repo's SSR-safe pattern: `now` starts `null` and is set from `Date.now()` inside a mount effect, so the very first client render never reads the clock during render and can't mismatch a server pass; before that effect fires the row shows an inert '…' placeholder for last-used only (created-date, month/day only, needs no clock and is never a placeholder). REDUCED MOTION (`matchMedia` with a live change listener) makes both the cutting and filing passes land in a single synchronous step — full target depths or full blank depths applied immediately, cutter never becomes visible, row collapse has zero transition duration — so an assistive-tech user or anyone with the OS preference set never waits on an animation to see a stable, legible, already-finished key or an already-gone row. Colors are `--background --foreground --ns-muted --border --ns-accent` only, no hex/rgb/hsl literals anywhere including inside the SVG paths. DOM + SVG + CSS, no canvas, zero dependencies."
      }
    },
    {
      "name": "blast-hole-delay-sequence",
      "type": "registry:ui",
      "title": "Blast Hole Delay Sequence",
      "description": "A bench-blast shot pattern rendered as a grid of charged holes that fires row by row on a delay wave rather than all at once, each row flashing to peak brightness and settling to a spent, darkened crater before the wave reaches the next row.",
      "files": [
        {
          "path": "registry/core/blast-hole-delay-sequence/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/blast-hole-delay-sequence.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "status",
          "grid",
          "sequence",
          "delay",
          "mining",
          "generative",
          "ambient"
        ],
        "instruction": "A grid of holes (6x6 at card scale, growing up to 9x9 as the container's smaller dimension grows, cell size held near 24-40px by computeGridSize(minDim) = clamp(round(minDim/32), 6, 9), tracked live via a ResizeObserver on the root) that fires in a fixed row-by-row sequence, mirroring how a bench-blast round is wired: rows detonate in order, each tens of milliseconds after the last, so fragmented rock has somewhere to move into. Real electronic detonators run 25-50ms between rows, which is far too fast to read as a sequence, so the row cadence is deliberately decoupled from that real rate: rendered at 900ms/row (documented 36x real-time slowdown), with a full pattern (6 rows) taking 5.4s to fire. Within a row every hole fires together (matches real practice — delay is row-to-row, not hole-to-hole — and satisfies the 'transition shows departure and arrival' rule, since a whole row lighting up together reads as one legible event rather than a blink). Each row owns two CSS custom properties, --fire (0 unfired -> 1 peak) and --spent (0 fresh -> 1 spent-dark), computed once per row per animation frame from a single rAF loop and written directly via el.style.setProperty on a `display:contents` row wrapper (never React state per frame) — every hole cell in that row inherits both through ordinary CSS custom-property inheritance, so a 9x9 pattern costs 9 writes/frame, not 81. A row's flash rises to peak over 80ms (ease-out-cubic), holds 120ms, then decays to spent over 600ms (ease-in-cubic) — asymmetric fast-rise/slow-settle matching how a flash reads against a rock face — after which the full pattern holds fully spent (uniformly dark) for a 2s cleared pause, then a 1.5s recharge sweep resets rows to unfired one at a time (250ms/row, --spent easing back to 0, --fire never leaving 0 — a quiet reload, not part of the mechanic proper) before the cycle restarts; total loop ~8.9s. The loop's start phase is offset mid-hold on row 0 (not the pattern's literal t=0) so mount already shows a mid-flash, not a static grid. Colour: a hole's fill is color-mix(in oklch, var(--ns-muted), var(--foreground) calc(var(--fire) * 100%)) — unfired sits at --ns-muted, peak flash at --foreground, with a brief glow via a --foreground box-shadow scaled by --fire for overshoot without ever touching a literal white. The spent-dark state is filter:brightness(calc(1 - var(--spent) * 0.62)) applied to that same muted-based fill: a brightness scalar always darkens in the same direction regardless of which theme's --ns-muted it is scaling, so in light theme spent reliably reads darker than --ns-muted (the harder case, checked first) without any theme branch in JS, and in dark theme it settles even closer to --background. `--border` is never used as a fill. Reduced motion freezes on rows before index 3 spent-dark, row index 3 (the 4th row) at peak flash, and the remaining rows unfired-dim — the single frame that shows unfired/firing/spent simultaneously, which no frame in the live loop pins down as cleanly. The whole grid is one `role=img aria-label` element; individual cells are decorative and carry no ARIA. `autoplay: none` because the pattern runs on its own internal clock with zero pointer/scroll/press input — there is nothing for a synthetic-input driver to trigger. The rAF loop pauses via an IntersectionObserver when the root scrolls offscreen and resumes cleanly without a stall, and both the rAF and the two observers (Resize + Intersection) are torn down on unmount. Zero dependencies, DOM+CSS only, no canvas."
      }
    },
    {
      "name": "board-kanban-ascii-wip",
      "type": "registry:ui",
      "title": "Board Kanban ASCII WIP",
      "description": "A kanban board drawn as box-drawing frames, each column header showing its card count against its WIP limit; going over turns the frame accent and appends the overflow count, mid-drag, before you release.",
      "files": [
        {
          "path": "registry/core/board-kanban-ascii-wip/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/board-kanban-ascii-wip.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "kanban",
          "board",
          "ascii",
          "box-drawing",
          "drag",
          "wip-limit",
          "keyboard-navigation",
          "project-management"
        ],
        "instruction": "Build <WipBoard columns width? onChange? onOverLimit? className? aria-label?> as a kanban board whose column chrome is real box-drawing glyphs. FRAME: each column is exactly `width` monospace cells wide (default 34, set as `width: {n}ch` so the frame tiles to a whole character count) with a `┌─ In Progress ───────── 3/3 ─┐` header, per-row `│` side rules, blank `│    │` rails padding every column to a common row count, and a `└──────┘` footer. The header rule is uniform `─`, sized to `width − 8 − title.length − label.length` so the header is exactly `width` characters regardless of the label. LIMIT: the header label is a plain `count/limit` readout; when `count > limit` it reads `4/3 ▲1` and the whole column — frame, rails, footer and counter — switches from its resting muted ink to the `accent` token. There is no red, no amber, no fill colour and no glow: the overload state is one accent ink plus the number, so it survives both themes and reads at card scale. DRAG: `pointerdown` on a card's button takes `setPointerCapture` and then binds `pointermove`/`pointerup`/`pointercancel` on the DOCUMENT, not on the card — a real pointer would be retargeted to the card by its own capture, but a synthesised pointer (the catalog's preview driver) is dispatched to whatever sits under the cursor, so a card-scoped move handler goes silent the instant the pointer crosses into another column; the document sees both. Past a 4px threshold the card becomes `position: fixed` and tracks the pointer 1:1, and its row is collapsed to `h-0 overflow-hidden` so the source column does not stand a line taller than its neighbours while the card is airborne (no ancestor is transformed, so the fixed card escapes that clip). Column boxes and per-card mid-Y are cached ONCE at pickup — recomputing them mid-drag would make the hit test chase the placeholder it just opened. `pointermove` picks the target column by testing clientX against the cached column boxes (nearest edge when the pointer is between them) and the insertion index by counting cached card mid-Ys above clientY, excluding the dragged card. A `╌╌╌╌` dashed placeholder row opens at that index in the accent ink, and the target column's counter renders the PREDICTED count (`count + 1`) while the card is still in the air, the source column's `count − 1`, so you watch the limit tip over and flip the frame to accent BEFORE the pointer is released. `pointerup` commits; `Escape` mid-drag is a document-level listener (the drag is in flight regardless of where focus sits) that cancels and restores the origin and the true counts. Over-limit drops are deliberately ALLOWED — a WIP limit is a policy signal, not a lock — and `onOverLimit(columnId, over)` fires per commit so a consumer can warn, log or block. KEYBOARD / SEMANTICS: the header line is decorative (`aria-hidden`), so each column div carries `role=\"group\"` with `aria-label=\"Review, 5 of 3, over limit by 2\"` — otherwise an empty column would be entirely invisible to a screen reader. Every card is a real `<button>` with a full accessible name (title, issue ref, column, position, and the column's current count/limit) and a visible `focus-visible` accent outline inset by 2px so the ring sits inside the box rule. ArrowLeft/ArrowRight move the focused card between columns at the same clamped index, ArrowUp/ArrowDown reorder it within its column, both through the exact same `commit()` path as the pointer, focus follows the card across the move, and an `aria-live=\"polite\"` region announces `Moved Ship API pagination to In Progress. 4 of 3, over limit by 1.` MOTION: plain React state — no rAF loop, no canvas, nothing on a hot path. The only animation is a FLIP on commit: card rects are measured in `useLayoutEffect` before and after, the delta is applied as an instant inverse transform and released on the next frame over 120ms `cubic-bezier(0.2,0,0,1)`. It never runs mid-drag (the lifted card is fixed and its neighbours have already collapsed) and is skipped entirely when `prefers-reduced-motion: reduce` matches, where reflow is instant and the placeholder alone carries the anticipation. THEME: every colour is a token (`accent`, `muted`, `foreground`, `surface`) — the frame is `text-ns-muted/55` (deliberately NOT `text-border`: `--border` is tuned for 1px hairlines and is invisible as type on the light theme, #ebebeb on #ffffff, which would leave the box frame blank at rest) and switches to `text-ns-accent` on overload, a card is `text-ns-muted` at rest and rises to `text-foreground` on a `bg-surface` chip on hover, so both themes invert correctly with no hardcoded ink anywhere."
      }
    },
    {
      "name": "bobbin-lace-pricking",
      "type": "registry:ui",
      "title": "Bobbin Lace Pricking",
      "description": "A decorative divider/empty-state motif: a narrow working band of pins twists thread pairs into locked crosses down a pricked grid, while a pin two rows behind the band is pulled free the moment its crossing has secured. The load-bearing, uncontested mechanic no other lattice sibling has.",
      "files": [
        {
          "path": "registry/core/bobbin-lace-pricking/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/bobbin-lace-pricking.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "empty-state",
          "ambient",
          "canvas",
          "lace",
          "textile",
          "grid",
          "mono"
        ],
        "instruction": "Build <BobbinLacePricking className?> as a self-contained, aria-hidden 2D canvas (w-full h-full) that renders a pricked pin grid scrolling continuously under a fixed-position working band. Drive everything off ONE continuous clock: continuousRow = elapsedMs / ROW_INTERVAL_MS (ROW_INTERVAL_MS = 2600, a smooth fractional row depth), currentRow = Math.floor(continuousRow) is the row actively being worked, rowElapsedMs is the fractional remainder in ms. Pin pitch is derived from the container's SMALLER dimension: pitch = clamp(smallerDim / 10, 12, 20)px, cols = floor(width / pitch), and the whole field is a rectangular grid with a half-pitch x-offset on odd rows (a cheap diamond-stagger so it reads as a pricked lace pattern, not a plain checkerboard). The active row sits at a FIXED screen y = height * (1/3); every row's screen y = bandY + (rowIndex - continuousRow) * pitch, so the field visibly and continuously scrolls upward under the band (new pricking rows enter from the bottom forever — an unbounded loop, never a fixed-length pass). CLASSIFY EVERY VISIBLE ROW PURELY BY age = currentRow - rowIndex, no per-row/per-pin React or persisted state beyond the clock: age<0 draws a --border-token 1px dot only (untouched pricking, not yet reached — never a stroke on the lace itself). age===0 is the actively-crossing row: split columns into 3 batches via `col % 3`, batch start = batch * 700ms (BATCH_STAGGER_MS), each pin's local twist progress = (rowElapsedMs - batchStart) / 850ms (CROSS_MS) eased with easeOutCubic, drawn as two --foreground strokes that start parallel/vertical (theta=0) and rotate to a locked X (theta=45deg from vertical) as progress goes 0->1, plus a small filled pin dot on top; before its batch starts the pin shows dot-only (no cross yet). age===1 draws the settled X (theta=45deg, full strength) plus its dot — already crossed, not yet pulled. age===2 is the single 'pull row' at any instant: draw every column's settled X at full strength, and for exactly one column (pullCol = ((currentRow % cols) + cols) % cols, i.e. cycling one column per row-interval) additionally animate that column's dot only — slideY = -6px * easeOutCubic(pullProgress), alpha = 1 - pullProgress, pullProgress = clamp(rowElapsedMs / 180ms, 0, 1) (PULL_MS) — a sharp upward slide-out+fade of the pin glyph ALONE; the thread crossing it secured is drawn normally underneath and never animates or disappears. Every other column in an age===2 row still shows a plain dot + settled X (unpulled) exactly like age===1. age>=3 is finished lace ground: draw the settled X only, at a fixed low-moderate alpha (FINISHED_ALPHA = 0.38) in --foreground, no pin dot (long since pulled), and never recompute or re-animate it once drawn — rows there are cosmetically identical every frame apart from their continuous scroll position. This age-based classification guarantees exactly one row has age===2 (one pull event in flight) and exactly one row has age===0 (one crossing row in flight) at any instant, which is the ONE thing a viewer should track: the working band's position plus the pull of the pin just behind it, both well above the ~1s legibility floor (850ms crosses, one pull event every 2.6s with a 180ms visible slide, never a blink). TOKENS: colors read once via getComputedStyle(document.documentElement).getPropertyValue for --foreground and --border only (no --ns-accent anywhere — this is ambient, never interaction chrome, and the climactic cross/pull moments must never touch it), re-read on a MutationObserver watching documentElement's class so both themes render correctly without remount; --border is used ONLY for the not-yet-reached pricking dots (its correct, faint, separator/placeholder use), never as a stroke on any lace thread. CANVAS HOST: DPR-aware backing store capped at 2, ResizeObserver on the canvas element (not window.resize) recomputing pitch/cols/bandY on a 120ms debounce, IntersectionObserver (threshold 0) and visibilitychange both pausing/resuming the rAF loop, full cleanup (cancelAnimationFrame, disconnect all three observers) on unmount. No paint happens before the first token read — readTokens() and resize() both run synchronously before the first start(), and the same pair reruns on the ResizeObserver and MutationObserver paths. REDUCED MOTION: prefers-reduced-motion freezes on a single deliberately-chosen non-t0 frame — continuousRow pinned at STATIC_ROW (3) + STATIC_ROW_ELAPSED (375ms) / ROW_INTERVAL_MS, which lands mid-twist on the first crossing batch (col%3===0, progress ~0.44) with the band positioned roughly a third down the field, finished ground above and untouched pricking below (the PIN_SET freeze frame) — drawn once with no rAF loop, and re-drawn (still frozen) only on a token change or a debounced resize. Purely decorative: the canvas is aria-hidden, carries no interactive affordance, and any real accessible content in a consuming empty-state/divider lives as ordinary DOM alongside it. Props: className only."
      }
    },
    {
      "name": "bombe-drum-halt",
      "type": "registry:ui",
      "title": "Bombe Drum Halt",
      "description": "An ambient search/lookup loading indicator built as the electromechanical Bombe's drum-and-stop search cycle: four drum columns scan continuously until, every 4-6s, all of them lock still at once while a sensing bar brightens for a check, then release and resume.",
      "files": [
        {
          "path": "registry/core/bombe-drum-halt/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/bombe-drum-halt.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "search",
          "lookup",
          "ambient",
          "drum",
          "cryptography",
          "dom",
          "raf",
          "indeterminate"
        ],
        "instruction": "An ambient search/lookup loading indicator sourced from the electromechanical Bombe's drum-scanning search cycle: banks of rotating drums (each a candidate rotor position) spin continuously while a sensing relay bank tests each position as it passes, and when a candidate briefly satisfies the test the whole bank locks dead for a beat while a diagonal-board-style check runs, then releases and resumes — almost every stop a false positive. Renders four vertical drum columns side by side (DRUM_COUNT = 4), each an overflow:hidden window over an absolutely-positioned strip of explicit tick-mark `<div>`s (not a CSS repeating-linear-gradient) built as a 26-position revolution — one per rotor letter, historically the right count for a Bombe drum — with position 0 a distinct brighter/taller 'datum' mark, every 5th position a heavier mark, and the rest plain, repeated back-to-back enough times to cover the drum window across a full modulo sweep. This non-uniform 26-tick revolution is deliberate: a uniform 1-tick-period gradient (the first implementation) aliases back to a visually identical state every single tick — about every 78ms at the default spacing — so a t0/2.5s/5s screenshot triple is indistinguishable whenever no halt happens to be mid-flight; the 26-tick revolution (~stripLength/180px/s, on the order of 1-2s depending on card size) makes a drum's resting phase legible and non-repeating across the whole 2.5s/5s sampling window even with zero halts landing in it. Column separators are 1px var(--border) dividers between drums (never a fill). Geometry is derived from the container's smaller dimension via a ResizeObserver on the root: drum width = smaller/9, drum height = smaller*0.85, and tick spacing itself scales with drum height (tickPeriod = max(4px, drumHeight/45)) so the strip's density — not just the drum's outer box — reads correctly at card scale. All four drums are driven by ONE shared requestAnimationFrame clock rather than four independent CSS animation timelines or a CSS `animation: infinite`, specifically so a halt can pause that one clock precisely instead of fighting four separately-running loops back into alignment: a single `phase` state machine cycles scan -> decel -> hold -> reaccel -> scan, and every drum's per-frame position delta is `SCROLL_SPEED_PX_S (180) * speedFactor(phase) * dt / 1000`, applied identically to all four so they lock and release in unison. 180px/s is a rendered rate, not the historical one — the real Bombe tested on the order of hundreds of rotor-hypotheses/s, far above what could read as anything but noise on a 60Hz screen, so the visible scroll speed is decoupled from that real rate the same way round-9's rate-decoupling rule decoupled a strobing 240Hz row-scan into a slow legible sweep; at the default tick spacing 180px/s reads as roughly 13 tick-marks/s of smooth, blur-free motion. The 3-4 drum phase stagger (drum N reads N*0.6s 'ahead' of drum 0) is implemented as nothing more than a different seed offset added to each drum's accumulated position at mount — not an independent timer — so the columns never lock-step visually despite sharing one clock. Each drum's on-screen transform is `translateY(-(position mod stripLength))`, applied via a ref-held style mutation every frame (not React state) to stay off the render path. A halt is scheduled 4-6s out, re-rolled fresh after every resume (`HALT_MIN_S=4, HALT_MAX_S=6`, uniform random — never a fixed metronome). On entering decel, speed eases 1 -> 0 over 180ms via a cubic ease-in-out while a 1px-tall sensing bar spanning all four drums (positioned at 42% of drum height, the fixed 'sensing bar' height) brightens linearly from its idle var(--foreground) 20% opacity toward 90%; hold then freezes speed at exactly 0 for 900ms at the full 90% bar opacity (the 'check running' beat, the single longest and most legible sub-phase); reaccel eases speed 0 -> 1 back over 220ms as the bar dims back to 20%. The full halt event totals roughly 1.3s (180+900+220ms), comfortably long enough to register as three distinct beats — stop, hold, release — rather than a blink. On exactly 15% of resumes (rolled once, at the moment reaccel completes and a fresh halt is scheduled) a single randomly-chosen drum column is given a 40%-opacity outline ring at the same 42%-height sensing line — read as the mark currently under test, since that is where the check runs — that fades back to 0 opacity 600ms later via a plain CSS opacity transition — a rarer 'this one got a second look' variant that still resolves and never latches into a permanent state. Nothing here is a real search result or a readout; there is no click-to-force-a-halt and no percentage, by design, since either would imply an actual result being represented. Under prefers-reduced-motion the rAF loop never starts at all: the component renders directly into the single most-structured static frame — every drum's transform set once to its phase-staggered rest offset, and the sensing bar's opacity set once to 90% (the peak-brightness mid-hold instant, not a mid-scroll blur) — via the exact same style-mutation helpers the animated path uses, just called once instead of every frame. An IntersectionObserver pauses the driving clock's elapsed-time accounting (not the rAF calls themselves) while the root scrolls offscreen; on return it resyncs the time base and, if a halt was mid-flight when visibility was lost, treats that stale halt as invalidated (rather than resolving three sub-phases in one jumped frame) — it snaps back to scan, dims the sensing bar back to idle, clears any ring, and reschedules the next halt fresh, so scrolling away and back never produces a burst of catch-up motion or a glitchy multi-phase jump. Cleanup on unmount cancels the rAF handle and disconnects the ResizeObserver and IntersectionObserver. Every colour is a CSS custom property referenced directly (var(--foreground), var(--border)) — no colour literals, no accent (this is an ambient loader, not interaction chrome, so var(--ns-accent) never appears). Props: label (accessible status text, default 'Searching', applied as aria-label on the root's role=status wrapper — the drum grid itself carries no other ARIA since it is purely decorative motion), className. Pure DOM + CSS + requestAnimationFrame, zero dependencies."
      }
    },
    {
      "name": "bowditch-close",
      "type": "registry:ui",
      "title": "Bowditch Close",
      "description": "A geofence/traverse polygon editor built like a real closed survey traverse: the last click never auto-snaps to the first, the gap between them is drawn as the misclosure, and Balance runs the 1807 Bowditch compass rule to redistribute it across every leg in proportion to its length.",
      "files": [
        {
          "path": "registry/core/bowditch-close/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/bowditch-close.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "geofence",
          "polygon",
          "map",
          "svg",
          "cartography",
          "drag",
          "form",
          "data-viz"
        ],
        "instruction": "Build a geofence/traverse polygon editor that behaves like a real closed survey traverse, not a stock click-a-ring polygon tool. Vertices are placed by click in a drawing phase; once 3+ exist, clicking within CLOSE_RADIUS (22px) of vertex 0 places one more vertex — the closing vertex — and ends drawing. That closing click is NEVER snapped onto vertex 0: the ring is drawn open between the closing vertex and vertex 0, and the gap is drawn as a --ns-accent hairline, the misclosure, graded live as a Geist Mono ratio (perimeter / misclosure-length, e.g. '1:44') exactly as a Bowditch survey report would state it. THE ONE GOVERNING MECHANISM, falsifiable: pressing Balance traverse runs the 1807 Bowditch compass rule — for each vertex i, correction_i = misclosure * (chainage_i / totalPerimeter), where chainage_i is the cumulative leg distance walked to reach vertex i from vertex 0. Vertex 0 (chainage 0) never moves; the closing vertex (chainage = totalPerimeter) receives the FULL misclosure and lands exactly on vertex 0, merging into it. Every vertex between carries a fraction of the correction set by how much distance IT ran, not by its position in the vertex list. This forbids the two tempting wrong alternatives: the misclosure is NOT split equally per vertex (that treats a 5m leg and a 200m leg as equally suspect), and it is NOT dumped entirely onto the last point before closing (that treats every earlier vertex as perfectly surveyed, which is never true — real error accumulates continuously along the walk). A short first leg barely moves; the long final leg absorbs most of the correction, because Bowditch's rule assumes error is proportional to distance run. MOTION: all corrected targets are pre-computed once, then every vertex is driven by ONE shared spring scalar s (0 -> 1, k=92, zeta=0.9, one small overshoot) via position_i = origin_i + (target_i - origin_i) * s — the whole ring reads as a single drawstring pulling taut, never one point jumping to close a gap while the rest sit still. On settle, the closing vertex is dropped (it has merged into vertex 0) and the result (closure ratio, perimeter, area in hectares via the shoelace formula) fires through onBalance. RESET keeps a dashed --ns-muted ghost of the pre-balance ring visible under prefers-reduced-motion (where the spring is skipped and the correction snaps in a short transition instead of freezing — a state change is not the kind of motion reduced-motion exists to suppress). INTERACTION: placing a vertex is pointer-only (no natural keyboard equivalent to 'click here'); every placed vertex is a focusable button, and ArrowUp/Down/Left/Right nudge the focused vertex by 1 pxPerMeter (10x with Shift) while still in the unbalanced phase, so a misplaced corner can be corrected precisely by keyboard before balancing. ACCESSIBILITY: the drawing region carries an accessible label; each vertex button gets an aria-label naming its index and, on the closing vertex, that it IS the misclosure point; a role=status aria-live=polite region announces phase transitions and the live closure ratio; Balance and New traverse are disabled/enabled by phase rather than hidden. Colors are --background/--foreground/--ns-muted/--border/--ns-accent only, --ns-accent reserved for the misclosure hairline and focus rings — DOM + SVG + CSS only, no canvas."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-accent) and opens a small tooltip with its median, Q1, Q3 and current outlier count; var(--ns-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": "brass-check",
      "type": "registry:ui",
      "title": "Brass Check",
      "description": "The active-sessions list as a pit-head lamp-room check board: each session is a stamped brass tag hanging on a hook, and signing one out is a hold-to-release gesture on a damped spring that hands off to real gravity, bouncing once into a role=log tray that IS the revocation history.",
      "files": [
        {
          "path": "registry/core/brass-check/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/brass-check.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "sessions",
          "security",
          "list",
          "svg",
          "spring-physics",
          "hold-to-confirm",
          "aria-live",
          "revoke"
        ],
        "instruction": "Build an active-sessions/devices list (`sessions: {id, device, location, lastSeen, current?}[]`, `label?` for the board's accessible name, `onSignOut?(id)` firing the instant a tag leaves its hook) as a physical check board, not a settings table with delete buttons. STRUCTURE: a real `<ul>` of real `<li>` rows; each non-current row contains a real `<button aria-label=\"Sign out {device}\">` whose visible content is an SVG brass tag — a static hook path (fixed, never rotates), a rotating `<g>` holding a ring, a rounded tag body, and the session's stamped number (its 1-based position in the original `sessions` array, zero-padded, `font-family: var(--font-mono)`) — with device name, location, and last-seen as plain sibling DOM text beside the button, not inside it. GOVERNING SCALAR: exactly one number per row, hang angle `theta`, integrated every frame as a real damped spring — `accel = -K*(theta-target) - C*vel; vel += accel*dt; theta += vel*dt` (K=18, C=7) — written straight to the rotating `<g>`'s `style.transform = 'rotate(Ndeg)'`, never through React state (a genuine rAF hot path, self-stopping once at rest within an epsilon, matching this registry's other spring components). `target` is the only thing interaction ever touches: idle 0deg, hover 2deg (proves the tag is live, answers the pointer), and pressing+holding the button (pointerdown or a held Enter/Space, tracked with the identical state machine either way) drives `target` to 48deg — well past the 28deg release angle. COMMIT vs ABORT: every frame while holding, check `theta >= 28 AND elapsed-since-hold-start >= ~620ms`; only once BOTH are true does the hold commit. Ending the hold (pointerup, pointerleave, keyup) before that moment is an ABORT: `target` snaps back to 0 (or 2 if still hovered) and the SAME spring visibly swings the tag back and re-seats it at rest — never a static, ambiguous reset, and never height-collapsing or opacity-fading the row as a stand-in for 'nothing happened'. This is also what makes a stray/incidental click harmless: a fast pointerdown-then-up can't accumulate the minimum hold time, so it just nudges and re-seats. RELEASE HANDOFF: on commit, the row is removed from the board's array immediately — an instant list reflow, not an animated exit — while a separate absolutely-positioned 'ghost' tag (cloned at the exact screen position of the just-removed tag) takes over with real ballistic integration: `vy += GRAVITY*dt (2200px/s^2); y += vy*dt`, plus a small initial lateral slip and a decaying tumble (`vRot` seeded from the spring's angular velocity at release, damped over time) — all written directly to `transform: translate3d(x,y,0) rotate(deg)` every frame, explicitly NOT a CSS transition or keyframe easing curve, because an eased arrival would read as reversible. On first contact with the tray's floor Y, velocity reflects at 0.18 restitution (one bounce, matching the brief exactly); on the second contact it settles at a small deterministic resting tilt. The instant it settles, the ghost is retired and a real `<li>` is appended to the tray. TRAY: `<ul role=\"log\" aria-live=\"polite\" aria-relevant=\"additions\" aria-labelledby={a visible 'Sign-out history' heading's id}>` — new entries are prepended (most recent on top of the stack, matching a tray that fills as tags land in it) and each entry's accessible name is set via `aria-label=\"{device} signed out, {N} sessions remain\"` (its visible content — tag number, device, a Geist Mono HH:MM:SS clock — is `aria-hidden`, since the aria-label already carries the full sentence); that DOM insertion into the polite log IS the announcement, there is no separate sr-only status element. CURRENT DEVICE: the session flagged `current` renders the identical tag graphic but wrapped in a plain `aria-hidden` span, not a button — a thin dashed lanyard-cord line drawn through the ring in `var(--ns-muted)` instead of the fixed hook path, and a small visible 'This device' pill beside the device name. It renders NO release affordance at all — not a disabled button, nothing there to press. A11Y: focus moves to the sign-out button now occupying the removed row's former index (or the previous one if it was last) the instant a row is removed, via a `querySelectorAll('.ns-bc-signout')` lookup in an effect keyed off the removal. Tab reaches every sign-out button; `role=switch/checkbox/radio` is not used anywhere (these are plain buttons). REDUCED MOTION: hover and hold poses are set instantly with no spring interpolation (`theta` snaps straight to `target`, no per-frame animation), and a committed hold moves the tag from hook to tray in a single re-render — no ghost element, no flight, no bounce — with the identical `aria-label` announcement on the appended `<li>`. Colors: `var(--background)`, `var(--foreground)`, `var(--ns-muted)`, `var(--border)` only, plus `var(--ns-accent)` on the sign-out button's `:focus-visible` outline (interaction-only, never decorative). Zero dependencies, no canvas — DOM + SVG + CSS only. Distinct from empty-state-pegboard (reads inventory by absence against a printed outline) and from notification-bell-swing (arrivals accumulate impulse into a bell's clapper; here the row itself is the physical object that leaves)."
      }
    },
    {
      "name": "braze-capillary-fill",
      "type": "registry:ui",
      "title": "Braze Capillary Fill",
      "description": "A progress/validation-fill indicator rendered as torch brazing: molten filler is drawn sideways along a hairline joint gap by capillary action, brightest at the leading front, settling into a solidified track behind it, and pooling into a fillet the instant it reaches the far end before cooling, fading, and starting a fresh joint.",
      "files": [
        {
          "path": "registry/core/braze-capillary-fill/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/braze-capillary-fill.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "loading",
          "fill",
          "canvas",
          "joining",
          "brazing",
          "ambient",
          "monochrome"
        ],
        "instruction": "Renders a progress/fill state as torch brazing: a horizontal hairline joint gap (two --border sheet-edge lines either side of a small canvas-drawn channel) that a bright capillary front sweeps left to right on its own internal clock, unforced, looping continuously with zero input — this is the default and what the catalog card shows with no props at all. The loop has three eras totalling 12.7s: a 9.4s fill era (the front sweeps the gap over its first 6.3s on a linear 0-1 progress, then a 0.9s fillet-meniscus grows at the far end with a slight overshoot-and-settle via a custom ease, then a ~2.2s quiet tail before the next era), a 2.1s hold at full fillet during which the molten alpha eases down from its peak to the settled solidified alpha (a 'cooling' cue), and a 1.2s fade of the entire filled track and fillet's opacity back to zero — a fresh empty joint, never a reverse wipe of the front position, since brazing doesn't unmelt. Everything drawn is fg (--foreground) at a modulated alpha over the bg backdrop, never a literal colour: the solidified trailing track sits at 0.68 alpha, the molten leading edge (a linear gradient trailing behind the front, its length wobbling ±20% at a slow 0.08Hz sine standing in for local gap-width variance along the seam) rises to 1.0 alpha, and the unfilled gap ahead of the front is simply unpainted background between two --border reference lines (the sheet edges, drawn as pure separators, full alpha since --border's own low contrast already keeps them structural rather than a fill). Canvas height defaults to 96px; geometry (gap half-thickness, meniscus radius) derives from min(canvas width, canvas height) so it holds up at card scale. Driven by requestAnimationFrame with elapsed-time accumulation (delta capped at 250ms so a backgrounded tab can't produce a giant catch-up jump), paused via IntersectionObserver (threshold 0) when scrolled offscreen and visibilitychange when the tab is hidden, resumed cleanly on return. Tokens (--foreground, --background, --border) are read via getComputedStyle(document.documentElement) before the very first draw call and re-read on a MutationObserver watching documentElement's class attribute, with every resize path (ResizeObserver on the canvas, debounced 150ms) re-reading tokens too so a theme flip mid-resize never paints stale colour. Under prefers-reduced-motion the canvas freezes at FREEZE_PROGRESS = 0.62 (front 62% across the gap, 3.906s into the notional 6.3s front-sweep) — chosen because it is the one frame that shows both halves of the mechanic at once: solidified/molten track behind the front and untouched gap ahead of it, which neither an empty t0 frame nor the full-fillet hold frame shows on its own. An optional `progress` prop (0-1) pins the front position directly to real external state instead of the ambient demo clock, for wiring this up as an actual upload/import/save indicator rather than a decorative loop; when a fillet is warranted (progress >= 1) it renders at full molten alpha with no hold/fade cycle, since a real completed action doesn't loop. The whole canvas is aria-hidden with a sibling role=img element carrying a static accessible label describing the mechanic, since the continuously-changing fill position is not meaningful to announce moment-to-moment (same non-narration-of-continuous-value pattern as meter-latency-capillary). Zero dependencies, single canvas element plus a small DOM label header, no WebGL."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-accent, --ns-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": "brine-float",
      "type": "registry:ui",
      "title": "Brine Float",
      "description": "A pricing section built as one shallow tank: an SVG water line runs behind the whole tier row and each card is a hydrometer float riding it, heavier (cheaper) tiers sitting lower and the recommended tier riding highest, with the billing toggle changing the brine's density so a price change is felt as buoyancy.",
      "files": [
        {
          "path": "registry/core/brine-float/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/brine-float.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "pricing",
          "svg",
          "spring",
          "buoyancy",
          "toggle",
          "section",
          "interactive",
          "ambient"
        ],
        "instruction": "A three-tier pricing section rendered as one shallow tank: a single SVG water line runs behind the whole card row and each pricing card is a hydrometer float riding in it. GEOMETRY: the water line is one <path>, a two-segment cubic-bezier approximation of a sine wave (up-hump then down-hump across the full width, viewBox 0 0 100 24, preserveAspectRatio=none so it stretches full width), stroke currentColor resolving to --border, vector-effect=non-scaling-stroke for a crisp 1px line at any scale; an SVG <animate> on its `d` morphs it to its vertical mirror and back over a 10s loop (calcMode=spline for an eased, non-linear crest). BUOYANCY: each tier declares a fixed weight class 0..1 (1 = heaviest/sinks lowest, 0 = lightest/rides highest) independent of its literal price — cheaper tiers are conventionally heavier, and the recommended tier is deliberately given the lowest weight of the three so it always rides above the others regardless of price rank. Each card's vertical position is `equilibriumPx = SINK_MIN + weight * density * SINK_SPAN` where density is 1 at monthly billing and 0.55 at annual (annual's cheaper effective price reads as thinner brine — everything gets lighter at once). LAYERING: every card is two nested wrappers around a plain <article> — an outer wrapper carries `transform: translateY(equilibrium + hoverPush)` on a single CSS transition (600ms cubic-bezier(0.34,1.56,0.64,1), a back-out curve giving exactly one visible overshoot) that fires on both a billing-triggered equilibrium change and a hover/focus-within push of +4px (releasing springs it back through the same curve); an inner wrapper runs a separate infinite CSS keyframe bob (±3px) with a distinct duration (4.4/5.6/5.1s) and delay (0/0.9/1.7s) per card so the three phases never lock into sync. The two transforms live on different elements so the CSS animation and the CSS transition never fight over the same `transform` property. LAYOUT: the row wrapper reserves 24px top / 48px bottom padding so the full range of equilibrium + bob + hover motion never clips or reflows anything around it. CONTENT: real <article> cards with an <h3> heading, tagline, price (recomputed from billing state, not a separate write path), a static feature list, and a CTA button — the recommended card's CTA uses --ns-accent, the others are ghost-bordered. BILLING CONTROL: a native role=switch button (not a segmented pair) with aria-checked reflecting annual/monthly and aria-labelledby pointing at the visible Monthly/Annual text pair either side of it — the aria-checked announcement plus the visible price text is the whole story, so no per-frame live region is needed. A11Y: tab order follows document order regardless of float height (bobbing and equilibrium are transform-only, layout position is untouched); the switch is the first interactive control in document order, before any card CTA. REDUCED MOTION: `prefers-reduced-motion: reduce` removes the SVG <animate> entirely (static path), removes the equilibrium transition (billing changes snap instantly, no overshoot) and removes the bob keyframe animation outright — cards still reposition on billing change, just without motion."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 --ns-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 --ns-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(--ns-muted)` combined with `color-mix()`, plus `var(--background)` at rest and `var(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-accent text and border as its one legitimate accent use, signaling 'ready.' Every failure also drops a permanent 2px --ns-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": "caddisfly-case-assembly",
      "type": "registry:ui",
      "title": "Caddisfly Case Assembly",
      "description": "A card-scale ambient loader modelled on caddisfly larva case-building: mineral grains drift in one at a time, get measured against the open gap at the case's growing rim, and either cement into place (extending the tube in a slow spiral) or get rejected and drift away, working around the rim course by course.",
      "files": [
        {
          "path": "registry/core/caddisfly-case-assembly/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/caddisfly-case-assembly.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "loading",
          "canvas",
          "progress",
          "ambient",
          "physics",
          "biology",
          "particle",
          "status",
          "monochrome"
        ],
        "instruction": "A card-scale single-canvas ambient loader modelled on Trichoptera (caddisfly larva) case-building ethology (Hansell 1968): the larva selects mineral grains from the substrate one at a time, tests each roughly by size against the open gap at the case's growing rim, and either cements an accepted grain onto the rim's advancing edge with silk or lets a rejected one drift away, working around the rim in a rough spiral course so the tube extends and widens as courses complete. All geometry derives from the chart wrapper's getBoundingClientRect via computeGeo(W, H): the active course's rim radius is min(W,H) * 0.18 for course 0, growing by a 5px-at-260px-reference depth step (scaled by minDim/260) per completed course, so 'the case's visible depth' is literally a second, third, fourth... offset rim drawn behind the active one. Every rim (active and completed) is traced as a slightly irregular closed polyline via traceRim — two low sine harmonics perturbing the radius by angle so it reads as a real substrate boundary, never a perfect circle. One candidate grain is evaluated every 340ms (CANDIDATE_INTERVAL_MS), sampled from a bimodal substrate-grade size mix scaled to card size (60% 'fine' 3-5px, 40% 'coarse' 6-9px at the 260px reference). Acceptance is a pure size test against the currently open rim gap (itself resampled from the same bimodal mix after every acceptance, standing in for the substrate's natural irregular opening): accepted only if the gap is within +-22% of the candidate's size (ACCEPT_BAND), giving roughly the spec's 55-65% real-world acceptance rate since gap and grain are drawn from the same distribution. Each candidate's full lifecycle plays out over drift-in (100ms, ease-out from a wider radius down to a hover standoff at the gap), a brief 20ms pause at the gap, then its verdict: an accepted grain eases (cubic ease-out) from the hover standoff into its exact cemented radius over 220ms and the build cursor advances by its angular width (size / rim radius); a rejected grain drifts 18px (scaled) away from the hover point at a random heading while fading its opacity to 0 over 260ms — a visibly slower, visibly divergent motion from a cement, so the two verdicts never read as 'something appeared near the rim' at a glance. Only the ACTIVE course renders individual cemented grains, as small rounded quadrilaterals (canvas roundRect, corner radius 28% of size) at random rotation, --ns-muted fill with a --border outline per grain so adjacent grains stay legible against each other, not just against the background; once the active course's build cursor wraps a full 2*PI, its cemented grains are dropped and replaced by a permanent --border guide ring at that course's radius, and a fresh course begins at the next, larger radius. After 5 courses (TOTAL_COURSES) the case holds for 4000ms (HOLD_MS) at its finished state, then the last course's cemented-grain fill crossfades into its own guide ring over 1500ms (FADE_MS, grain alpha 1->0 while that course's ring alpha ramps 0->1 in lockstep) so the whole case genuinely reads as border-outline-only for a beat, then the entire sim resets (all rings cleared, cursor and course index zeroed, a fresh gap sampled) and a new case starts growing from a bare rim — an unbounded loop with no forced duration, never wired to any actual async operation's real progress. Colour: --ns-muted and --border are read via getComputedStyle(document.documentElement) before the first paint (retried on the next rAF if either is empty) and re-read on a MutationObserver watching documentElement's class; --border's per-grain outline alpha is not a flat constant but measured — a 1x1 offscreen canvas rasterises --border against --background and compares luminance (0.2126/0.7152/0.0722 weights), and low-contrast themes (typical of light mode, where --border sits close to --background at ~1.1:1) get a bumped 0.85 outline alpha instead of the normal 0.55, so grain-to-grain edges stay separated rather than blurring into one shape specifically in light theme. --ns-accent never appears anywhere in this component; it has no interaction, only an ambient clock. A ResizeObserver on the chart wrapper recomputes geometry and the canvas backing store (devicePixelRatio capped at 2); an IntersectionObserver (threshold 0) restarts the whole case from a fresh bare rim on re-entering view rather than resuming a stale mid-build state, and picks up any theme flip that happened while off-screen. Under prefers-reduced-motion the component runs zero rAF loops and zero timers: it deterministically builds (a seeded mulberry32 PRNG, not Math.random, so the frame is byte-stable) a frame named CASE_MIDBUILD — two complete courses as guide rings, a third course cemented exactly halfway (cursor at PI) with a real mixed fine/coarse grain rim — chosen because it shows both the depth-ring structure of finished courses and the raw grain-by-grain rim texture of an active one, and repaints only on theme/resize changes. Cleanup on unmount cancels both the animation and token-wait rAF handles and disconnects all three observers. Props: label (mono header text, default 'ASSEMBLING'), className. Zero dependencies, DOM+canvas only, no colour literal anywhere including fallbacks."
      }
    },
    {
      "name": "cambium-lay",
      "type": "registry:ui",
      "title": "Cambium Lay",
      "description": "A tree cross-section accreting live in SVG: a cambium ring lays wood outward year by year, each year closing with a thin dense latewood band, the section slightly lobed and drifting off-centre like a leaning trunk, and a scar event pinching a few rings before growth heals around it.",
      "files": [
        {
          "path": "registry/core/cambium-lay/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/cambium-lay.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "svg",
          "tree-rings",
          "generative",
          "growth",
          "accretion",
          "dendrochronology",
          "ambient",
          "hero"
        ],
        "instruction": "Build <CambiumLay yearMs? maxYears? storageKey? className?> as a full-bleed aria-hidden SVG (viewBox 0 0 240 240, pointer-events:none) over a 96-spoke radial perimeter (spoke count matters: 48 shows visible polygon corners once a ring's radius passes ~300px on screen, 192 would double every committed path's byte size for no visible gain, 96 is the floor that stays smooth). GROWTH LAW: within a virtual year (length yearMs, default 4000 — the only speed control), the season is split into an earlywood phase (the first 60% of the year's TIME) and a latewood phase (the last 40%), and each phase is given a fixed BUDGET share of that year's total radial-width target — earlywood 70%, latewood ~30% — never an equal split, because equal halves read as two soft bands instead of one ring closing on a dense line. The per-year radial-width target itself follows a decaying taper across `maxYears` (default 64): wide juvenile rings, narrowing toward a floor of 35% of the juvenile width, normalised so all `maxYears` targets sum exactly to the drawable radius (92 local units from a 6-unit pith). PER-SPOKE DEFORMATION: every spoke's share of a year's budget is scaled by a multiplier = clamp(1 + lobeBias(theta) + leanBias(theta, year), 0.2, 1.5). lobeBias is a fixed three-harmonic sum over theta (0.07*sin(3θ+0.6) + 0.04*sin(5θ+2.3) + 0.025*sin(7θ+1.1)), identical every year — the section's static lobed silhouette. leanBias(theta, year) = 0.16 * (1 - exp(-year/18)) * cos(theta - LEAN_ANGLE), a bias whose STRENGTH ramps in over the tree's first ~18 years and then holds at a fixed angle forever — because it never rotates and is sustained across every subsequent year, its cumulative effect is a pith that ends up visibly off-centre relative to the outer boundary, exactly what a leaning trunk's eccentric growth looks like, without ever moving the SVG's coordinate origin. SCAR: spokes 12-25 of the 96 (about a 50-degree arc) get their multiplier hard-overridden — 8% of normal for years 5-6 (the pinch), then 190% of normal for years 7-9 (the heal, catching that arc's radius back up toward its neighbours) — fixed years, not randomised, so the same demonstration replays every mount. COMMIT / LIVE SPLIT (why this cannot be canvas): a year's boundaries (start, earlywood-end, final radius per spoke) are a pure closed-form function of (year, start-boundary) — no simulation loop needed — so the moment a year completes, its two annuli (start→earlywoodEnd filled var(--ns-muted), earlywoodEnd→final filled var(--foreground), both fillRule=evenodd, boundaries smoothed through the 96 points with a CLOSED Catmull-Rom curve converted to cubic beziers) are pushed once into React state as two new <path> elements and never touched again — zero per-frame cost, real static DOM. Only the CURRENT, uncommitted year's outer boundary path mutates, on a plain setInterval at ~333ms (~3 attribute writes/sec): its `d` is recomputed by linearly interpolating between the year's start and its closed-form earlywood-end (or earlywood-end and final, once time crosses the 60% mark) at u = (elapsed-time-in-year / yearMs), and a CSS class toggle (not a per-tick style write) swaps its fill from var(--ns-muted) to var(--foreground) the moment it crosses into the latewood phase, so the forming ring visibly darkens right before it commits. PERSISTENCE: age is wall-clock, not foreground time — first mount writes Date.now() minus 14 virtual years to localStorage key `ns-cambium-lay:<storageKey>:first-seen` (so it never opens on a bare pith dot), every mount after that reads the same key and computes elapsed = min(Date.now() - firstSeen, maxYears * yearMs), so a returning visitor's tree has kept aging in the background and a sufficiently old visit is simply capped, fully grown, with no live front at all. FALSIFIABLE CONSTRUCTION: every year's start boundary is literally the previous year's final boundary (start = g.final), so lean, lobing and the scar are integrated FORWARD through the whole subsequent history and a ring committed before the scar is a frozen path string that year 20's math cannot reach — this is a live seasonal signal walked forward, not concentric circles with randomised radii drawn once. REDUCED MOTION: prefers-reduced-motion (checked live via a matchMedia listener) skips localStorage and the interval entirely and instead runs the exact same year-closed-form for a fixed 40 years up front, rendering a prewarmed static section — same taper, same lean, same scar — with no live front. INTERIOR UNDULATION (fifth pass — real geometry displacement, not a brightness effect; supersedes an earlier opacity-shimmer version of this component that moved no geometry at all): every committed boundary's radii (the immutable per-spoke Float64Array pushed once at commit — that record itself never changes, and is the actual falsifiable \"committed once\" claim now) get an additional per-frame sinusoidal offset, same harmonic as the geometry-baked \"wavy-rings\" wave (WAVE_HARMONIC=5) so both read as one texture, phase-shifted by BOUNDARY INDEX (not ring index — a boundary is shared between the ring inside it and the ring outside it, so indexing by boundary keeps adjacent rings seamless) and rotated by wall-clock time at INTERIOR_WAVE_OMEGA rad/sec, so a wave crest at a fixed angle visibly migrates from the outer boundaries toward the pith as time advances (same rim-to-pith direction the shimmer it replaces used, now as literal pixel displacement, measured ~4-5px at typical card scale over 3s on an interior ring). The per-boundary phase step is kept small (0.11rad) so adjacent boundaries move almost in lockstep — absolute amplitude and the differential between neighbours are different knobs, and a small differential is what keeps every annulus from self-intersecting regardless of the absolute swing. Amplitude ramps linearly to 0 over both the outermost ~6 boundaries (so the newest committed boundary, shared with the still-forming live front which this loop never touches, joins it with no seam) and the innermost ~6 (so the pith, drawn as a separate static dot right next to it, stays a crisp dot instead of a rosette — flat amplitude near a ~6-unit-radius boundary reads as scalloping, not undulation). A monotonic clamp (each displaced boundary held at least a fixed gap past its inward neighbour, per spoke) is a hard guarantee against inversion independent of amplitude tuning. Runs on a throttled requestAnimationFrame loop (24Hz), writing `d` directly via refs — the JSX for a committed ring carries no `d` prop at all while this loop is active, specifically so an unrelated re-render (a new ring committing) cannot snap a displaced path back to its undisplaced shape by re-asserting a stale JSX value. GATED OFF under prefers-reduced-motion: reduced motion keeps only the existing discrete ring-commit progression (a pop every REDUCED_RING_INTERVAL_MS, not a continuous sweep) as its motion, consistent with this component's own stated vestibular-guard reasoning for that interval — a continuous per-frame boundary sweep is exactly what that reasoning argues against, so under reduced motion the interior stays genuinely static between ring-commit pops rather than running a slowed-down version of this loop. ACCESSIBILITY: the SVG carries aria-hidden and pointer-events-none; one visually-hidden `role=status aria-live=polite` line ('decorative tree-ring illustration, N rings') reports the current committed count for anyone who lands on the region with a screen reader, updated only on the rare ring-commit, never per-tick. No colour literal anywhere — every fill is var(--ns-muted) or var(--foreground) directly on the SVG presentation attribute, so a theme flip needs no getComputedStyle and no re-render. Props: yearMs (ms per virtual year, default 4000), maxYears (hard growth cap, default 64), storageKey (localStorage namespace suffix so two instances age independently, default 'default'), className."
      }
    },
    {
      "name": "capstan-slip",
      "type": "registry:ui",
      "title": "Capstan Slip",
      "description": "A determinate progress bar redrawn as an analog tape transport: a capstan shaft turns at a strictly constant rate while a pinch roller's nip actually sets the tape's speed, and every ~1.1s the nip visibly compresses, slips, and springs back through one small overshoot.",
      "files": [
        {
          "path": "registry/core/capstan-slip/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/capstan-slip.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "transport",
          "tape",
          "svg",
          "flutter",
          "ambient",
          "mechanical",
          "loading"
        ],
        "instruction": "`<CapstanSlip label? showReadout? className? />` — a card-scale SVG side view of an analog tape transport, ambient and self-driving (autoplay: none — it animates on its own internal clock, nothing for a synthetic-input driver to trigger). Geometry is derived entirely from the container's SMALLER measured dimension S: shaftR = min(0.13*S, 0.22*containerWidth), rollerR = 0.72*shaftR, tapeHalfH clamped to [4,14]px at 0.05*S, restGap (nip overlap) clamped to >=2px at 0.012*S, compressMax (peak nip compression) clamped to [1.5,5]px at 0.014*S. The nip sits at nipX = 0.6*containerWidth, tapeY = 0.52*containerHeight; the capstan shaft circle sits tangent below the tape band, the pinch roller circle tangent above it, both drawn with fill var(--background) and stroke var(--foreground) plus one radial spoke line each so rotation reads frame to frame. MECHANISM: the capstan shaft rotates at a strictly constant SHAFT_REV_PER_S = 0.6 rev/s always — it is motor-driven and never slips, which is the whole point: real transports spec wow-and-flutter under 0.05% WRMS, and a slipping/glazed roller is what actually produces audible flutter, not the capstan. Tape linear speed instead comes from the pinch roller's nip: baseline BASE_SPEED = 28px/s. Every ~1.1s (EVENT_BASE_INTERVAL, jittered +/-200ms, using Math.random on the live path and a seeded mulberry32 RNG on the reduced-motion path so the freeze target is deterministic) a slip event fires, driven by one shared `slipDepth(elapsedMs)` curve reused for both the visual nip compression and the tape-speed multiplier: 0-40ms eases 0->1 (smoothstep) as the nip compresses toward compressMax; 40-130ms holds at 1 (tape speed pinned to 40% of baseline: `vel = BASE_SPEED*(1-0.6*slip)`); 130-250ms decays via an underdamped `exp(-3.2u)*cos(5.5u)` (u = normalised progress) back through zero and a small negative overshoot (nip springs slightly past neutral, tape briefly exceeds baseline speed) before settling — total event length ~250ms, comfortably long enough to watch the whole dip-and-recover. The roller's own spin rate is tied to that same instantaneous `vel` (surface speed = tape speed at the nip), so it visibly slows during the dip and visibly catches back up during recovery — the actual mechanic the piece is named for. Tape ticks are a fixed pool of 90 SVG `<line>` elements recycled by index (never remounted): a spawn accumulator adds one new tick every `(22px baseline spacing / 28px/s baseline speed)` of REAL elapsed time — a constant cadence independent of the current slip state — while every active tick's x position advances by `-vel*dt` each frame; because spawn timing is constant but travel speed dips during a slip event, the ticks that crossed the nip during that dip end up visibly bunched, and that bunched patch keeps trailing left, unbunching only as it ages out past the left edge — this is the entire visual proof of a slip, with no separate 'event marker' drawn. On mount (and 2.6s of synchronous pre-roll advance() calls before the first paint) the tape is never empty at t0; resting-loop check: t0 has some scroll offset with the nip at rest, 2.5s in roughly two slip events have fired with a bunched patch trailing the most recent one, 5s in the offset and slip-artifact count are both visibly different again — unforced, unbounded. REDUCED MOTION: `matchMedia('(prefers-reduced-motion: reduce)')` runs the identical `advance()` step function synchronously in 8ms increments with a seeded RNG (not Math.random, so the landing point is reproducible) until three slip events have fired, then continues exactly 40ms further into the following event and renders once with no rAF ever scheduled — freezing at the nip's peak compression with compressed tick spacing already trailing it, chosen because it is the single frame that shows both the roller deformation and the resulting mark-spacing differential at once. Colors: shaft/roller stroke and tape tick marks are `var(--foreground)`; shaft/roller fill and tape band background are `var(--background)`; the tape's top/bottom edge lines are `var(--foreground)` at 0.35 opacity (a light structural cue, not a separator); the bottom chassis rail is `var(--border)`, used only as a true separator beneath the whole transport, never as the roller or tape's own line weight. An optional hover-triggered numeric callout (`showReadout`, default true) shows the real professional-deck spec text \"0.05% WRMS ref\" in `var(--ns-muted)`/`var(--foreground)` plain text only — never accent-tinted, never rendered on the roller or nip itself. `--ns-accent` is not used anywhere in this component; there is no interaction chrome to color. ResizeObserver rebuilds geometry and restarts the simulation (debounced 120ms) on any size change; IntersectionObserver pauses the rAF loop when off-screen and resumes it (fresh `last=0` so dt doesn't spike) when visible again; unmount cancels the rAF, disconnects both observers, and clears pending resize timers. DOM + SVG + CSS only, no canvas, zero dependencies."
      }
    },
    {
      "name": "carbon-ply-fade",
      "type": "registry:ui",
      "title": "Carbon Ply Fade",
      "description": "Activity feed rendered as a carbon-paper multi-part form: one strike lands on the top ply at full density and echoes down through 4-5 contiguous, fully-visible ply rows, each registering 90ms after the one above at 0.68x the density, matching real pressure-copy force dissipation through a stack.",
      "files": [
        {
          "path": "registry/core/carbon-ply-fade/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/carbon-ply-fade.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "feed",
          "notification",
          "fanout",
          "physics",
          "carbon-paper",
          "aria-live",
          "activity"
        ],
        "instruction": "An activity feed reimagined as a carbon-paper multi-part form: one bordered panel containing 4-5 contiguous, fully-visible ply rows (border-b between them, no overlap) rather than overlapping offset cards — overlap would bury a lower ply's text under the opaque ply above it, which defeats the one thing that has to stay legible: the falloff must be readable on every ply at once. Each row is nudged ~2px right per ply below it via paddingLeft (the spec's '2px right per ply behind it'; the spec's ~6px DOWN offset is expressed as the row's own 40px height/pitch rather than a literal 6px overlap-producing translate, a deliberate deviation so four ply rows of real text can coexist on screen — noted here rather than silently kept). Row height and the horizontal nudge are scaled by a factor derived from the container's smaller dimension via ResizeObserver, clamped 0.8-1.15, so the stack reads at card scale. Ply count is available height divided by the 40px row height, clamped 4-5; on a standard card-scale container this settles at exactly 4. Every 1.1s a 'strike' event fires: the component advances to the next label in a built-in (or caller-supplied) rotating event list and, for each ply i from 0 (top) to plyCount-1, schedules a registration at i*90ms after the strike starts — the real mechanical delay of force traveling down a ply stack. When a ply's registration fires its paragraph's textContent is set to the new event label and its opacity is set instantly (no transition — carbon transfer does not fade in) to Math.max(0.12, 0.68**i), so ply 1 is always --foreground at 100%, ply 2 ~68%, ply 3 ~46%, ply 4 ~31% (light theme: --foreground 31% over --background is ~2.2:1, faint but present and clearly above --border's ~1.1:1, so the falloff reads as 'faint' rather than 'gone'). The 12% floor guards a hypothetical 5th ply (0.68**4 ~= 21%) rather than ever engaging at the default 4-ply depth. The ply row itself gets a small physical squash on impact — transform scaleY(0.985) over a 60ms ease-out transition, then a 140ms cubic-bezier(0.34,1.56,0.64,1) spring back to scaleY(1) — read as the row compressing under the strike. Between strikes every ply holds its last registered text and opacity unchanged for the remaining ~700ms of the cadence; nothing fades back out. Hovering ply 1 (onPointerEnter/onPointerLeave) sets a paused ref that skips advancing to the next event while still letting the current strike's cascade finish and letting the interval re-check itself every 1.1s, so a viewer can read the current stack without it being overwritten mid-look, and un-hovering resumes rotation cleanly. Ply 1 alone carries role=status aria-live=polite aria-label so assistive tech announces only the freshest copy; plies 2 and up are aria-hidden, decorative echoes of the same event. The strike flash is luminance-only (opacity of --foreground text, a border-border row divider, a bg-background fill) — --ns-accent never appears. prefers-reduced-motion clears every scheduled timeout and paints one fully-propagated, fully-settled strike directly (all plies at their falloff opacity, no scaleY transition, no further scheduling) rather than freezing mid-cascade. No canvas, no dependencies — pure DOM, refs-only imperative style writes (no per-frame React state) for the strike/registration/squash path, React state only for plyCount and scale from the resize observer."
      }
    },
    {
      "name": "card-dot-gain-screen",
      "type": "registry:ui",
      "title": "Card Dot Gain Screen",
      "description": "A card backed by a contact-screen halftone that reproduces real dot gain: a nonlinear per-dot growth curve where the deepest tones grow past the point of staying separate circles and physically bridge into plugged solid ink.",
      "files": [
        {
          "path": "registry/core/card-dot-gain-screen/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/card-dot-gain-screen.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "halftone",
          "dot-gain",
          "print",
          "canvas",
          "ambient",
          "monochrome"
        ],
        "instruction": "Build a card whose entire backing surface is a canvas-rendered contact-screen halftone, sourced from real offset-printing dot gain, not a decorative dot grid. Root is a `rounded-[14px] border border-border bg-background` card holding an absolutely positioned, `aria-hidden` canvas filling the card behind a normal content stack (heading, mono body copy, optional underlined link with a `focus-visible` ring in `--ns-accent`).\n\nPer dot, compute a plate tone `t` in [0,1] from a diagonal print-swatch gradient across the card (highlight top-left to shadow bottom-right, `(cx/w + cy/h)/2`) so a single card demonstrates the whole gain curve at once. Convert `t` to a PRINTED area via `printedArea = t + gain*midtoneBump(t) + gain*shadowPlug(t)`, where `midtoneBump = 4t(1-t)` is the classic Murray-Davies parabola (heaviest gain around 50% tone) and `shadowPlug = smoothstep(0.68,1,t)^2` is near-zero until deep shadow and then accelerates hard toward 1 — an explicit second term for the ink-spread that specifically hits shadow regions, on top of the ordinary midtone bump. Let `printedArea` exceed 1 (clamp at 1.6) and derive a dot's rendered radius as `r = pitch/2 * sqrt(printedArea)`. `pitch/2` is exactly the radius at which a dot on a square screen touches its orthogonal neighbours; letting the radius grow past that and simply filling the (now overlapping) circles is what makes adjacent dots visually PLUG — the merge is not special-cased, it falls out of overlapping opaque fills, same as ink spreading past a dot's plate edge in reality. This is the mechanic that distinguishes the component from chart-bar-halftone/chart-donut-halftone's clean, linearly SIZE-SCALED dots (no gain curve, no merge) and from background-halftone-rosette's two-screen moiré interference (registration between two screens, not per-dot growth).\n\n`gain` is not constant: walk it via two incommensurate sine terms (periods 7.3s and 11.7s, base ~0.30, amplitudes 0.05/0.035) so the plugged-shadow boundary visibly creeps over a period of several seconds with no input — standing in for plate humidity and ink-feed variance drifting a real press's dot gain over a run. Layer a small per-dot jitter on top from a deterministic hash noise field whose coordinates are offset by slow-moving time, so individual dots swell and shrink slightly out of lockstep rather than the whole screen breathing uniformly. Screen pitch is derived from the card's own smaller dimension (`clamp(minDim/32, 3.5, 9)` px) via `ResizeObserver`, never a fixed px value, so it stays a fine screen rather than coarsening into visible polka dots on a small card.\n\nLegibility: rather than punching an opacity hole over the text (which reads as the effect switching off), scale the plate tone `t` down to ~32% of its value inside the content block's padded bounding box, with a soft one-pitch-wide falloff back to 100% outside it — the same 'coverage gradient' idea background-halftone-rosette uses for its full-bleed scrim, applied locally, so the copy sits over a sparse but still-visibly-printed highlight patch while the card's corners run the full curve up into plugged shadow.\n\nInk color is `--foreground` read via `getComputedStyle(document.documentElement)` at mount and re-derived on a `MutationObserver` watching `documentElement`'s class — never a literal — so 'more ink' correctly means more `--foreground` coverage and the polarity is automatically right in both themes. `--ns-accent` never touches the screen itself, only the link's focus ring. DPR-capped (max 2) backing store sized off the card's own `getBoundingClientRect`. The animation loop is a plain `requestAnimationFrame`, paused via `visibilitychange` when the tab is hidden, and stopped entirely under `prefers-reduced-motion: reduce`, which freezes the clock at `PERIOD1/4` (≈1.825s) — the point nearest a simultaneous peak of both drift waves — so the reduced-motion frame lands on the richest, most legibly plugged shadow band rather than an arbitrary t=0. No dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa",
          "error": "#ea001d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717",
          "error": "#ff6369"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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/--ns-muted/--border/--surface/--ns-accent tokens."
      }
    },
    {
      "name": "catenary-contact-stagger",
      "type": "registry:ui",
      "title": "Catenary Contact Stagger",
      "description": "A live connection/sync-quality indicator built from overhead contact wire stagger: a fixed pantograph marker tracks the wire's deliberate zigzag span by span, and only arcs, briefly, on the rare span where contact genuinely breaks.",
      "files": [
        {
          "path": "registry/core/catenary-contact-stagger/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/catenary-contact-stagger.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "indicator",
          "connection",
          "sync",
          "svg",
          "ambient",
          "aria-live",
          "loading"
        ],
        "instruction": "Renders a live connection/sync-quality indicator as overhead contact wire stagger (OLE engineering): contact wire is deliberately zigzagged side to side, span by span, so a pantograph's carbon collector strip wears evenly across its width rather than grooving one spot. A fixed-width strip band (a thin var(--foreground) stroked rectangle at 0.28 opacity, sized to the container's smaller dimension x 0.68, centred vertically) is the static reference the zigzag must stay inside. The wire itself is an exact engineering zigzag, not a sampled curve: straight spans between alternating +/- extremes at every support, rendered as a <polyline> whose vertices are computed directly from a single continuous phase u(t) = elapsedMs / 2000 (one sweep leg, extreme to extreme, takes 2.0s) via triWave(n) = alternating -1/+1 at every integer n, vertex screenX = centerX + spanWidth * (n - u(t)) where spanWidth = containerWidth / 4 (four visible spans at rest). A small filled circle at the container's fixed horizontal centre — the pantograph's contact point — samples the exact same triWave(u(t)) function (not a re-derivation), so the marker's lateral position is always perfectly consistent with the wire riding under it; this marker is the one followable thing, per the legibility line, and its cadence is exactly the wire's: one full sweep every 2.0s. On the rare span where contact genuinely breaks, a dewirement arc fires: a self-rescheduling setTimeout loop (never a fixed interval, and never re-armed while it's already pending) draws a mean interval from a uniform 18-28s range at connectionQuality=1, scaled down as the optional connectionQuality prop (0-1, default 1) drops toward 0 — floored at 4 sweep legs (8s) so a degraded connection still reads as a distinct rare punctuation rather than joining the sweep's own 2s rhythm — severity reads via event rate, never colour or hue. Each arc lasts ~180ms and reads primarily through WEIGHT, not colour, so it survives a light card: the wire's stroke and the marker's radius both step up for the duration (1.4px to 3px, 3.5px to 5.5px), a Math.sin(pi * progress) vertical kick of up to 3px is added to both the wire polyline and the marker's y (decaying back to zero at both ends, so it reads as a bump, not a snap), and an SVG feDropShadow filter seeded from var(--foreground) rides along as secondary polish — a pure luminance glow, never accent, that renders as a light halo in dark theme and a dark halo in light theme automatically since it's built from the same token rather than a literal. The whole apparatus is direct-DOM: geometry is computed inside a single rAF loop that writes the polyline's `points` and the circle's cx/cy attributes on every frame via refs, no React state on the animation hot path (arcing is a low-frequency boolean flip, twice per arc, used only to toggle stroke weight/radius/filter). A ResizeObserver re-measures the container and recomputes spanWidth/amplitude/band geometry (both derived from the container's SMALLER dimension so it stays legible at card scale) and, if the container wasn't yet laid out at mount (0px on first measure — a hidden tab, an unlaid-out catalog card), retries activation on every subsequent resize until it succeeds. The phase clock's origin is set exactly once, on the first successful measure, and never reset: an IntersectionObserver pauses the rAF loop off-screen and cancels only the pending next-arc timer (an arc already 180ms into flight finishes naturally), but resuming on re-entry continues the same wall-clock phase rather than snapping back to a t0 extreme, since the wire is a continuous process whether or not a frame was painted for it. prefers-reduced-motion freezes on triWave(0.5) — dead centre over the strip, maximum contact margin, the 'everything is fine' frame — never at a stagger extreme and never mid-arc; the arc scheduler simply never starts in this mode. Accessibility: the root is role=group with an accessible label describing the connection this indicator reflects; a separate sr-only role=status aria-live=polite paragraph holds only 'Contact wire tracking nominal.' or, for the ~180ms of an arc, 'Momentary contact loss.', reverting immediately after — kept out of the labelled group so aria-atomic re-reads never duplicate the group's own label. No interaction: this is a pure ambient status read, nothing latches, nothing can be pressed. Props: connectionQuality (0-1, optional), minArcIntervalMs/maxArcIntervalMs (override the 18-28s range at quality=1), label, className. Zero dependencies, no canvas, pure SVG/DOM."
      }
    },
    {
      "name": "cathode-stack-glow",
      "type": "registry:ui",
      "title": "Cathode Stack Glow",
      "description": "A Nixie-tube stat readout: ten wire-mesh cathode digits stacked behind a shared anode mesh, the struck digit glowing over faint ghost outlines of the nine behind it, with a periodic silent conditioning sweep through all ten to keep unused cathodes from fouling.",
      "files": [
        {
          "path": "registry/core/cathode-stack-glow/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/cathode-stack-glow.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "typography",
          "stat",
          "counter",
          "price",
          "nixie",
          "glow",
          "flicker",
          "readout"
        ],
        "instruction": "A card-scale stat/price/counter readout drawn on a single DPR-aware Canvas 2D element that fills its root div (role=img, aria-label carries the real string value; canvas itself is aria-hidden). The `value` prop is split into characters; digit characters (0-9) become 'cells' each holding all ten Nixie-style glyphs stacked at a shared position, non-digit characters (currency signs, commas, decimal points) render once as plain static foreground text with none of the stacking machinery. Layout uses a monospace font so every cell shares one fixed advance width (measured once via ctx.measureText('0') at 1.35x for inter-cell gap); font size and halo radius derive from baseUnit = min(containerWidth / cellCount, containerHeight) — the smaller dimension per cell — so the mechanic still reads at small card sizes. Per digit cell, all ten glyphs (0-9) are drawn every frame: the nine that are not currently struck render via ctx.strokeText only (numeral outline, never filled) in the --ns-muted token at a low per-(cell,digit)-seeded opacity (3-7% dark theme, 6-9% light theme, clamped 2-9%) that slowly sine-drifts over a 1-4 minute per-seed period so the ghost stack is never perfectly static; each glyph additionally carries a tiny deterministic per-digit offset (a fixed fan direction per cell, magnitude scaling ~0-1.6px with the digit value) so the stack reads as physical depth rather than a blur. The struck digit renders via ctx.fillText in the --foreground token at globalAlpha driven by an aperiodic 3-6Hz brightness oscillator (92-100%, retargeted on a randomized 167-333ms cadence and smoothed toward that target each frame with a 10x/s lerp, so the flicker is resampled independently of the 60Hz paint loop rather than tied to it) plus a soft halo via ctx.shadowColor = foreground / ctx.shadowBlur = ~8% of baseUnit, pulsing in sync with the flicker. Independently per cell, a conditioning sweep timer (re-rolled to a random 15-30s interval after every sweep, so multi-cell rows desync) fires a ~800ms ripple: the struck position steps through digits 0-9 in canonical order at 80ms/digit (20ms rise, 40ms hold, 20ms fall envelope, brightness peaking at the same 100% as the idle struck digit so it reads as a real strike rather than a dim flash), after which the display switches straight back to the real value with a fast 20-30ms re-strike flash — never a crossfade, matching the fact that a real Nixie switches which cathode is struck rather than blending between numerals. Live `value` prop changes after mount diff the string index-by-index and apply the same instant-switch-plus-flash to any changed cell (a length change fully rebuilds the cell set). Colours are read once via getComputedStyle(document.documentElement) for --foreground/--background/--ns-muted before any paint, re-read on a MutationObserver watching class/data-theme, on prefers-color-scheme change, and again on every IntersectionObserver resume (canvas is otherwise paused via cancelAnimationFrame while off-screen); light vs dark is derived from --background's own parsed luminance rather than assumed from a class name, and drives the higher ghost-opacity range in light theme. `autoplay: none` because everything animates on independent per-cell timers regardless of pointer/press/scroll input. Under prefers-reduced-motion the canvas paints exactly once: the struck digit at full brightness, every ghost at its seed's median opacity, no flicker, no halo pulse, no sweep ever scheduled — the single frame that shows the correct number plus the stacked-mesh depth without an ambiguous in-between state. ResizeObserver re-measures and re-lays-out on container size change; unmount cancels the rAF, disconnects ResizeObserver/IntersectionObserver/MutationObserver and removes the color-scheme listener. Zero dependencies, canvas + CSS only, no color literals anywhere including the shadow/fill/stroke styles."
      }
    },
    {
      "name": "chain-scale",
      "type": "registry:ui",
      "title": "Chain Scale",
      "description": "A map zoom control that is the scale bar: drag its end handle in log space to zoom, and the bar's length always reads a sayable 1-2-5 round distance, snapping with a 180ms width spring at every ladder crossing instead of drawing an arbitrary raw number.",
      "files": [
        {
          "path": "registry/core/chain-scale/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chain-scale.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "slider",
          "map",
          "zoom",
          "scale-bar",
          "cartography",
          "drag",
          "form",
          "micro-interaction"
        ],
        "instruction": "Build a scale bar that doubles as the zoom control, governed by one scalar, metersPerPixel (m). At any m, the bar's rendered length is denom/m, where denom is the LARGEST value from the 1-2-5 preferred series (…0.5 1 2 5 10 20 50 100 200 500 1000…, spanning roughly 1cm to 10,000km) whose length still fits a maxWidth prop — pickIndex(m, maxWidth) walks the ascending ladder and keeps the last rung whose v/m <= maxWidth. This is the falsifiable contract: the printed distance is ALWAYS a 1-2-5 round number, never the raw drag value. Because the formula is continuous in m while the ladder is discrete, holding denom fixed and changing m makes the bar's pixel length grow or shrink continuously (following the pointer with zero added lag), right up until the next rung overtakes it — at that instant denom jumps and the bar visibly snaps to the new fraction of maxWidth. Animate exactly that snap and nothing else: on a ladder crossing, ease the bar's width from its pre-crossing pixel length to the new target over 180ms ease-out-expo (a cubic tween written directly to refs via rAF, restarting from whatever the visual width currently is if a new crossing arrives mid-tween), flash a 1px --foreground tick at the new edge as the detent mark, and slide the round-number label out/in. Within a single denomination, width/handle/label positions are set directly via refs with NO easing — only React re-renders for the checkerboard's block COUNT and fill pattern, which are ordinary state driven by the denomination's leading digit (mantissa 1 -> 2 blocks, 2 -> 2 blocks, 5 -> 5 blocks), alternating --foreground fill and transparent, 1px --border rules between them, no interior numerals — only '0' at the left and the current denomination's label at the right, in Geist Mono. Never let React's render-time style props touch the animated width/x values (that fight is what makes the spring flash and this is the exact anti-pattern to avoid); the wrapper div carrying role=slider is the ONLY place the drag/keyboard/wheel handlers live, and the checkerboard/tick/label/handle beneath it are all aria-hidden presentation. INTERACTION: pointerdown on the wrapper starts a drag that maps handle dx to metersPerPixel in LOG space (m = clamp(exp(startLogM - dx*k))) — never linear, or a city-to-street zoom would take a hundred screens of drag; wheel over the wrapper walks the same log mapping one WHEEL_LOG_STEP per notch; ArrowLeft/Right/Up/Down step exactly one ladder rung by setting m so the new rung sits at exactly maxWidth (an inherent detent, not a quantized continuous value); Home/End jump to the ladder's min/max rung; double-click resets m so the CURRENT rung's bar sits at exactly 3/4 of maxWidth. ACCESSIBILITY: the wrapper is role=slider tabIndex=0 with aria-valuemin/max as the ladder's index bounds, aria-valuenow the current rung's index, and aria-valuetext combining the denomination and the raw per-pixel reading (e.g. '500 m — 1px = 3.2 m'); aria-describedby points at a visually-hidden aria-live=polite sentence ('Scale: 500 metres. 1 pixel equals 3.2 metres.') rendered at a stable `${id}-desc` id so a host map region's own aria-describedby can reference the same live text; prefers-reduced-motion cancels the width spring and tick flash entirely, snapping straight to the new geometry. Zero dependencies, DOM + CSS only — no canvas, no SVG required for the geometry (plain divs), no hex or Tailwind palette colors anywhere in markup or inline style."
      }
    },
    {
      "name": "chargen-rom-slice",
      "type": "registry:ui",
      "title": "Chargen ROM Slice",
      "description": "A status glyph that reconstructs a real 1970s-80s CRT character-generator ROM read pattern: each glyph in a short string is built top-to-bottom from horizontal scanline slices arriving on a beam-synced sweep, so most on-screen glyphs are only ever a clean stack of fetched rows sitting above a hard-edged gap, whole for a single instant per cycle.",
      "files": [
        {
          "path": "registry/core/chargen-rom-slice/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/chargen-rom-slice.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "status",
          "glyph",
          "canvas",
          "bitmap-font",
          "crt",
          "chargen",
          "ascii",
          "monochrome"
        ],
        "instruction": "A card-scale status glyph, canvas-rendered, that simulates how a real VT100/Commodore/Apple II-class video controller reads a character-generator ROM: one scanline row at a time, in sync with the beam, rather than fetching a whole glyph at once. Each character in the (uppercase, 12-char max) `text` prop is drawn from a self-contained hand-drawn 5x8 bitmap font table (5 cols wide, 8 rows tall, the 8th row a blank descender row matching real chargen cell conventions) baked once into a `boolean[8][cols]` bitmap grid at mount. Geometry: `cellPx = clamp(round(min(wrap.clientWidth, wrap.clientHeight) / 20), 8, 16)`, then further clamped to `min(cellPx, floor(availableWidth / cols), floor(availableHeight / rows))` so a wrap smaller than the formula wants never overflows its own container; derived from the wrap's own measured box (the component's root fills `h-full w-full` of whatever the consumer sizes) via ResizeObserver (debounced 100ms) so it reads correctly at card scale. The canvas itself is sized in JS (`canvas.width/height` plus matching `style.width/height`, scaled by a capped devicePixelRatio) rather than relying on intrinsic size, with `ctx.imageSmoothingEnabled = false` and each revealed cell painted as a full, contiguous `cellPx x cellPx` block — no inter-row gap — so the sweep boundary between fetched and unfetched rows is the only horizontal discontinuity in a glyph. The sweep: each character's row-reveal starts `i * 60ms` after the shared cycle start (left-to-right stagger), reveals one additional row every 35ms (`Math.floor(elapsed / 35)`, clamped 0-8), so a single glyph fully assembles in 280ms and the whole string finishes roughly `(chars.length - 1) * 60 + 280` ms in; once every character has all 8 rows, the assembled string holds fully clean for 500ms, after which the cycle position wraps (`(now - cycleStart) % cycleMs`, cycleMs = build time + 500) and every row count resets to 0 — real elapsed time drives the math (via `performance.now()`), not frame count, so speed never depends on paint rate. `cycleStart` is seeded 170ms into the past at mount (not `now`) so t0 itself already reads mid-sweep instead of a blank canvas, and that 170ms offset is chosen specifically so the resting-loop's t0/2.5s/5s sample points — locked 340ms/680ms apart by the fixed 1080ms cycle — land in three visibly distinct states (an early mid-sweep, a later mid-sweep with most glyphs whole, and the clean hold) rather than two of the three both landing inside the hold. Colour is read once via `getComputedStyle(canvas).color` (the canvas carries `text-foreground`) and re-read on a `MutationObserver` watching `document.documentElement`'s `class` attribute so a live theme flip repaints without remount; there is no separate background fill — unrevealed rows are simply unpainted canvas showing the page's own `--background` through, so light and dark read identically (1-bit foreground-on-background, no tokens beyond `--foreground`/the page background, `--ns-accent` and `--border` are never touched). An `IntersectionObserver` on the wrap element halts and resumes the rAF loop as it leaves/enters viewport; a `ResizeObserver` re-measures and redraws on layout change; `prefers-reduced-motion` skips the rAF loop entirely and paints one fixed frame by running the same `computeRevealed` sweep math at a named `FREEZE_MS = 300` cycle position instead of live time — an early character fully assembled, the next mid-sweep with a visible hard slice seam, later ones thin or untouched — the single most legible static frame of the mechanic, deliberately not the clean hold and not the empty t0 state, and one that stays a genuine sweep frame (not a hardcoded per-index array) for any string length. `autoplay.mode` is `\"none\"`: the sweep runs on its own internal clock regardless of pointer/scroll/press input, so there is nothing for the site's synthetic-input driver to trigger. No interaction is implemented — the spec allows an optional hover/focus restart-from-row-0, but resetting `cycleStart` to `now` would blank the canvas for a beat before the 580ms rebuild (the `elapsed <= 0` branch reports zero rows for every character at the instant of reset), which is the wrong risk to carry for a feature the spec itself marks not required; the wrap carries no `tabIndex` since the canvas is `aria-hidden` and there is nothing keyboard-operable to expose. Cleanup on unmount cancels the rAF, disconnects the ResizeObserver, IntersectionObserver and MutationObserver, and removes the pointer/resize listeners. Zero dependencies, canvas + CSS only, no new colours (`--foreground` and the ambient page background only). The component's own root is `h-full w-full` and expects the consumer to size the parent container (see demo: a `h-40 w-80` bordered box), not to pass a sizing `className` onto the component itself."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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, --ns-muted for secondary row text and the counter, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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. 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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-muted --border --ns-accent`) with `--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-muted --border --ns-accent`), with `--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-muted --border --ns-accent`) so both themes render correctly, and `--ns-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": "clock-card",
      "type": "registry:ui",
      "title": "Clock Card Rack",
      "description": "A catch-up digest as a punch-clock card rack: one bordered card per collaborator, unread bursts holding the card proud of a shared top datum by up to 6px until a real 'Mark caught up' button settles it flush.",
      "files": [
        {
          "path": "registry/core/clock-card/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/clock-card.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "digest",
          "activity",
          "feed",
          "rack",
          "card",
          "time",
          "mono",
          "aria-live",
          "accessibility",
          "collaboration"
        ],
        "instruction": "A punch-clock card rack for a 'what happened while I was away' digest: one radius-12 bordered card per collaborator (rounded-xl border border-border bg-background), stacked in a rail under one shared hairline datum. Every entry is a time-punched row rendered on its author's own card ('edited section 2 — 14:10 to 14:32', real <time dateTime> elements, Geist Mono) — the interleaved anonymous feed pattern is structurally impossible here, not just avoided by convention, because rows only ever exist inside a collaborator's own <ul>. Time is the single governing scalar and nothing else is: it sorts rows within a card (latest punch first), sorts cards across the rack (the card whose most recent burst end-time is newest sits on top, recomputed fresh every render from `Math.max` over each card's bursts, never a stored index — so the rack keeps itself sorted for free as new punches land), and sets how far a card sits proud of the shared top datum when it holds unread work: `2px + 1px per 12 cumulative unread hours` (summed duration of that card's unread bursts), clamped at 6px so a two-week backlog cannot eject a card off the physical range the rack renders in — the clamp is what keeps 'proud' legible as a binary-ish 'there is unread work here' signal rather than a magnitude gauge. A card with zero unread bursts sits at exactly 0 offset (flush), never at the clamped floor. Settling is a single CSS custom property (--nsc-offset) driving `transform: translateY()`, transitioned 300ms cubic-bezier(.16,1,.3,1) (ease-out-expo) — a pure transform, no layout is touched, so marking a card caught up costs one composited animation. Reading is opt-in and explicit: each card has a real, per-card 'Mark caught up' button (disabled once nothing is unread, guarded with disabled:pointer-events-none so the disabled state never fires its own hover style) that reports via `onMarkCaughtUp(collaboratorId)` — the component holds no model of its own, `collaborators` is the only prop and every derived value (row order, rack order, proud offset, unread count) is recomputed from it on every render, so the caller flipping `unread` flags is the only way state changes. Completion is announced through a polite aria-live region reading 'Caught up with <name>'. A brand new punch row (a burst id not present in the previous commit, detected by diffing id sets against a ref updated inside a layout effect so the diff runs and the new row already renders clipped-shut on the very first paint, never flashing open and then replaying) plays a single 340ms clip-path reveal, like a card being dropped into its slot — a genuinely new punch, not merely 'this data changed'. A11y: each card is a `<section aria-labelledby>` pointing at a real `<h3>` holding the person's name, with a plain-text unread count immediately after it ('4 unread bursts' / 'Caught up') so the state is legible without inferring anything from a color or a pixel offset; rows are `<li>` inside a `<ul>`, each holding two real `<time dateTime>` elements, never a styled `<span>` standing in for one; rack re-sorting is a pure reorder of `<section>` elements keyed by collaborator id, so React moves the existing DOM nodes rather than remounting them and focus survives a re-sort for free. Props: `collaborators` (`{id, name, bursts: {id, section, start, end, unread?}[]}[]`), `onMarkCaughtUp`, `ariaLabel` (default 'Catch-up digest'), `className`. prefers-reduced-motion drops the 300ms settle transition and the row-reveal animation entirely — proud/flush and new/existing rows just render in their resting state directly, every control stays fully operable. Pure DOM + CSS + inline `<time>` — no canvas, no SVG. Demo: three collaborators at different proud depths (one two-burst backlog, one single fresh punch, one already caught up) plus a 'Simulate new punch' control that appends a new unread burst and shows the reveal-and-possible-resort together."
      }
    },
    {
      "name": "cmm-probe-touch",
      "type": "registry:ui",
      "title": "CMM Probe Touch",
      "description": "An empty-state ambient reproducing coordinate-measuring machine touch-trigger probing: a stylus indexes to 18 stations around a fixed part outline, approaches slowly along the local surface normal, dwells at contact for a luminance-only trigger flash, retracts, and travels to the next station, with older touched points rolling off a one-lap trailing fade so the inspection is always mid-pass, never finished.",
      "files": [
        {
          "path": "registry/core/cmm-probe-touch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/cmm-probe-touch.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "empty-state",
          "canvas",
          "metrology",
          "cmm",
          "process",
          "monochrome",
          "hover",
          "ambient"
        ],
        "instruction": "An empty-state ambient built on real CMM (coordinate-measuring machine) touch-trigger probing, standard contact metrology, rendered on a single 2D canvas sized off the container's own `getBoundingClientRect` (DPR-capped at 2). The part is a single fixed closed contour built once from an explicit vertex walk in local unit space (half-extents HX=0.82, HY=0.5): a rounded-rectangle-ish silhouette with a rectangular notch cut into the top edge (half-width 0.15, depth 0.16) and a semicircular boss (radius 0.15) bulging outward from the right edge, corners rounded at radius 0.12 with 14-segment arcs. That vertex list is wrapped in an `ArcPath` helper that walks it into cumulative arc length and exposes `at(sFraction)` (binary-searched linear interpolation along the closed loop) and `normalAt(sFraction)` (central-difference tangent at sFraction +/- 0.004, rotated 90 degrees, sign-checked against the contour's centroid so it always points outward) — this same lookup drives both the drawn contour stroke and every station position, so there is exactly one source of geometric truth. STATIONS = 18 stations sit at even arc-length fractions k/18.\n\nAnimation is driven by one monotonic virtual clock (`elapsed`, ms, advanced by real rAF delta so speed never depends on frame rate), pre-seeded at mount to one full lap (21600ms) plus 0.42 of a lap, so every station already has a valid touch history at t0 — a rolling trail with partial composition visible immediately, never a blank contour. Per-station cycle is a fixed 1200ms state machine read off `elapsed mod 1200`: 0-420ms approach (ease-out-cubic from a point offset `APPROACH_DIST=0.24` local units outward along the station's normal, down to the contact point — fast then decelerating into contact, matching a real probe's slow final approach), 420-600ms dwell-at-contact (the trigger fires — a luminance-only flash: the probe tip's fill radius and a soft outer ring pulse via `Math.sin` peaking at the dwell midpoint, drawn twice at increasing alpha/radius in `--foreground`, never a colour change or `--ns-accent`), 600-900ms retract (ease-in-cubic back out along the same normal to the approach point, mirroring the approach's deceleration with an acceleration away from the part), 900-1200ms travel (ease-in-out-cubic straight-line interpolation from the current station's approach point to the next station's approach point, so both departure and arrival are visibly eased, never a blink-swap). A full lap is 18 x 1200ms = 21600ms and the station index simply increments mod 18 forever — there is no terminal state, no lap-complete pause.\n\nTouched-point age is computed analytically, with no history array: station k's most recent contact time within a lap is `k*1200 + 420`ms, so `age = ((elapsed mod 21600) - thatTime + 21600) mod 21600` is always exactly 'time since k was last touched', correct whether that touch happened earlier in the current lap or the previous one. Every station is drawn every frame in two passes, exactly mirroring the alpha-ramp convention used elsewhere in this registry: a base pass at full alpha in `--ns-muted` under all 18 stations, then an overlay pass in `--foreground` at alpha = `clamp01(1 - age/21600)` — near-opaque for a just-touched station, fully transparent (letting the muted base show through) for one about to roll off the trail's one-lap window. The probe tip itself is a small filled circle in `--foreground` with a thin trailing stylus-shaft stroke (60% alpha) drawn outward along the active station's current normal, so it reads as a stylus tip rather than a bare dot. The contour itself is stroked once per frame in `--border` (a separator token, never a fill) at roughly 0.6% of the geometry scale.\n\nGeometry scale is derived from the container's smaller dimension (`scale = min(width,height) * 0.42`), centred in the canvas, recomputed on every debounced ResizeObserver callback (which also repositions the 18 accessible overlay buttons). `--foreground`, `--ns-muted` and `--border` are read via `getComputedStyle(document.documentElement)` on mount and re-read on a `MutationObserver` watching `documentElement`'s class, with a synchronous re-render on change — no paint happens before that first read. The rAF loop is paused via `IntersectionObserver` (canvas out of view) and `document.hidden`, and resumed cleanly on either becoming true again, with `last` reset to 0 so the next frame's delta doesn't include the paused interval.\n\nInteraction: 18 zero-visual, `aria-label`-carrying `<button>` elements are absolutely positioned over each station (repositioned imperatively on resize, not on every animation frame, since station positions themselves never move) with a `focus-visible:ring-2 ring-ns-accent` focus ring — the one permitted accent use, interaction chrome only. Hover or focus shows a small `role=\"tooltip\"` div in `--ns-muted` text reading a synthetic per-station deviation value (a deterministic sine-hash of the station index, formatted to three decimal mm, stable across renders and identical under reduced motion). This overlay is pure DOM state (`activeStation`/`tooltipPos` via `useState`) layered on top of the canvas loop; it never reads from or writes to the virtual clock, so dwelling a pointer over a station cannot stall, speed up, or otherwise perturb the global 1200ms/station indexing cadence, the same isolation rule `peen-coverage`'s hover dwell follows for its own global pass clock.\n\nUnder `prefers-reduced-motion: reduce`, the rAF loop never starts; the virtual clock is set once, synchronously, to `21600 + 9*1200 + 420 + 90` (station index 9 — roughly opposite the lap's start — at the midpoint of its 180ms contact-dwell window, the single most legible frame: tip visibly touching, trigger flash mid-pulse, full one-lap trail history already resolved) and rendered once. That constant is exposed as `FREEZE_PHASE = \"contact-dwell-station9\"` and surfaced on the root element via `data-reduced-motion-freeze`. Cleanup on unmount cancels the rAF, disconnects the ResizeObserver, IntersectionObserver and MutationObserver, and clears the debounce timer. Zero dependencies, canvas + DOM overlay + CSS only, every colour a token (`--background` `--foreground` `--ns-muted` `--border` `--ns-accent`)."
      }
    },
    {
      "name": "cockle-swell",
      "type": "registry:ui",
      "title": "Cockle Swell",
      "description": "A pull-quote block printed on a sheet of paper that is cockling — three drifting humidity sources buckle the sheet along a fixed grain direction (5:1 anisotropic hygroexpansivity), and the real DOM quote text rides the same height field, geometrically displaced rather than shaded.",
      "files": [
        {
          "path": "registry/core/cockle-swell/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/cockle-swell.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "testimonial",
          "quote",
          "paper",
          "canvas",
          "anisotropic",
          "type",
          "resting-loop"
        ],
        "instruction": "Build <CockleSwell quote? author? role? className?> as a card-scale pull-quote whose paper surface is cockling under non-uniform moisture. TWO LAYERS: a 2D canvas (w-full h-full, DPR-aware backing store capped at 2) renders the sheet's shading; the quote itself is real, selectable DOM text overlaid on top, never rasterised. GRID: GRID_MINOR = 96 cells across the container's SMALLER dimension, the other axis scaled by aspect ratio and capped at 216 cells, so ridge spacing stays constant from card to section scale (verified numbers below are at the shipping ~4:3 landscape card, gw=128 gh=96). MOISTURE FIELD, a pure function of wall-clock t (no accumulated simulation state, which is what makes the reduced-motion frame byte-stable): m(x,y,t) = 0.5 + sum of three gaussians (sigma = 0.20 * GRID_MINOR cells, amplitudes 0.16/0.11/0.09) whose centres drift on independent Lissajous paths with periods 11.3s / 17.9s / 23.1s — mutually incommensurate, so the field never repeats. A perimeter band (within ~18 cells of the edge, falloff exp(-dist/6)) additionally oscillates at the fastest period (11.3s) with amplitude 0.10, standing in for a sheet's edges equilibrating with ambient humidity fastest. WHERE VS WHAT: the moisture field's job is only to gate an ENVELOPE (env = clamp01((macroDev - 0.09) / 0.10), i.e. moisture deviation has to clear a floor before cockling engages at all, which is what keeps most of the sheet flat) — it is NOT the source of the ridge structure itself. A second, independent FINE FIELD (a ~9-cell-wavelength sum of three sines, varying mainly across the grain, with a slow along-grain drift) supplies that structure. This two-field split exists because a smooth 19.2-cell-sigma moisture gaussian run through ANY affordable blur kernel measures out ~isotropic (verified: a plain 3.2:1 kernel on the moisture gaussian alone gave 1.14:1, i.e. no anisotropy) — the kernel needs sub-20-cell content to have room to elongate. STRAIN AND THE 5:1 ANISOTROPY: eps_CD_SCALE = 0.020 (real paper's cross-direction hygroexpansivity is ~5x machine-direction; MD's role is played entirely by the kernel's own anisotropy below, not a second strain field — two anisotropic fields would fight for no visible gain). Per-cell strain = eps_CD_SCALE * max(0, macroDev) * 0.4 + 0.03 * env * max(0, fineField), then relu(strain - 0.0016) is the buckling threshold. ANISOTROPIC BLUR (the mechanic that actually produces ridges): a separable box-blur-x3-per-axis approximation of a gaussian, sigma_y = 1.6 cells (short, preserves the fine field's across-grain variation) and sigma_x = sigma_y * 3.2 * 4.5 (long enough to erase the fine field's along-grain variation entirely — the 4.5x beyond the spec's 3.2:1 is measured margin at the shipping aspect ratio: a bare 3.2:1 kernel measured only 3.0-3.35:1 in mean(|dz/dy|)/mean(|dz/dx|) across four sampled frames (t=0/2.5/5/8.2s); this configuration measured 4.4-5.0:1 across the same four frames). Normalise the blurred field by Z_REF = 0.006 and clamp to 0..1 — 0 is flat, and most of the grid stays exactly or near 0 (mean z 0.07-0.15 across sampled frames; not a uniformly undulating sheet). SHADING: one fixed light (elevation 22deg, azimuth 200deg) lights the height field's central-difference normal (SLOPE_GAIN = 16) via Lambert diffuse + broad sky + narrow specular (pow 42, grazing crest highlight) + slope-magnitude darkening (contact-shadow proxy, coefficient 0.12) + a faint sin(gy) grain-texture streak (0.02 amplitude) + an EXPLICIT elevation term (0.45 * zVal) added on top of NOMINAL = 0.62. The elevation term is load-bearing: slope-only shading is symmetric across a bump's two flanks (one faces the light, one doesn't) and the occlusion term darkens both, so without it nothing on the sheet ever reads brighter than flat paper regardless of ridge height — measured crest-over-flat gain with the elevation term: +0.185 raw shading units at a representative frame (spec floor: 0.10). Map the resulting raw value through shadeToT(raw) = clamp01(0.5 + (raw-0.5)*contrast + bias), where bias/contrast are +0.02/1.20 in light theme and -0.09/1.15 in dark (spec sec.6's per-theme ramp), THEN lerp between whichever of --background/--foreground has the LOWER measured relative luminance and whichever has the HIGHER (resolved at runtime via a 1x1 canvas fillStyle probe, never assumed) — crest-brighter-than-trough holds in both themes without inverting, and every pixel is a pure token mix. --ns-accent never touches the sheet. Render at grid resolution to an offscreen canvas, drawImage-scale to the display canvas (imageSmoothingEnabled) rather than a per-pixel loop at full backing-store resolution; reuse resize-scoped scratch Float32Arrays across frames rather than allocating per frame. TYPE: the quote is split into 12 (4x3) contiguous word-chunks, each wrapped in a real inline <span> (real spaces between chunks, no per-glyph splitting, so selection and screen-reader flow are unaffected). Span CENTRES are measured once per resize (and once after document.fonts.ready), with each span's own transform temporarily cleared before measuring, and CACHED as container-relative fractions — sampling live getBoundingClientRect() every frame instead would read back last frame's own transform (feedback) and would break the reduced-motion byte-stability requirement (a theme-flip repaint would re-measure an already-transformed span and drift). Every frame, read all 12 cached samples first, then in one batched write phase set each span's transform: translateY = -zVal * 3.5px (one-sided — crests only ever lift the line, never dip it), translateX and skewX from the local x-gradient (skew capped at 1.6deg), scale = 1 + zVal * 0.02 — all capped regardless of ridge amplitude elsewhere on the sheet. CONTRAST, the acute risk: the quote's ink is always flat --foreground with no per-pixel shading, and a translucent bg-background/55 backdrop-blur-sm wrapper sits behind the whole quote+attribution block as a token scrim. Inside a 14px-dilated bounding box of the quote wrapper, compress the theme-mapped t toward the flat-sheet reference (shadeToT(NOMINAL)) by 60%, THEN measure the actual composited pixel — lerp(groundColour, backgroundRGB, 0.55), matching the real scrim opacity, not the pre-scrim canvas colour — against the real --foreground token's relative luminance (WCAG-style ratio) and, if under 7:1, step t AWAY from whichever ramp end --foreground itself sits on (up to 40 steps of 0.025) until it clears or the ramp end is hit. This composited, direction-aware check is required, not optional: the scrim alone measured ~3:1 in dark theme at the composited brightest-crest-under-text frame (t~0.81 pre-clamp, contrast ~5.65:1), and a naive compress-toward-0.5 clamp (an earlier draft of this component) can walk the WRONG direction and silently never clear the floor. Verified: the clamp converges to t~0.67-0.68 in that dark-theme case, restoring ~7:1. RESTING LOOP: continuous rAF, always running, no settle condition — the three incommensurate sources keep drifting forever, so t=0/2.5s/5s/8.2s are all measurably different z fields and DOM-span transforms with zero input, and the loop never returns to a prior state within any practical observation window. HOST RULES: no paint before the first token read on three paths (rAF start, ResizeObserver fire, IntersectionObserver resume) — each explicitly re-reads tokens; ResizeObserver rebuilds the grid and re-measures both the text bbox and the span centres; IntersectionObserver (threshold 0) plus visibilitychange pause the rAF; a MutationObserver on documentElement's class re-reads tokens and, if reduced motion is active, redraws the static frame. prefers-reduced-motion draws exactly one composed frame at STATIC_TIME = 8.2s (the three incommensurate sources are near maximum mutual separation there, giving the most ridges crossing the quote block, versus t=0's comparatively flat sheet) with the DOM transforms frozen at the same instant; because every field is a pure function of t with no accumulated state and span centres are re-measured (not re-derived from a live, already-transformed rect), this frame is byte-stable forever."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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.",
        "rank": 2
      }
    },
    {
      "name": "compare-table-reach-rule",
      "type": "registry:ui",
      "title": "Compare Table Reach Rule",
      "description": "A tier comparison matrix where every feature row draws a real box-drawing rule that runs left-to-right and terminates with ┤ at the last tier that actually includes it, so the table's staircase of rule lengths is the comparison itself rather than a field of check glyphs.",
      "files": [
        {
          "path": "registry/core/compare-table-reach-rule/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/compare-table-reach-rule.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "comparison",
          "table",
          "pricing",
          "matrix",
          "ascii",
          "box-drawing",
          "marketing"
        ],
        "instruction": "Build <ReachRuleTable tiers? rows? title? className?> as ONE CSS grid with gridTemplateColumns `minmax(12rem,1.4fr) repeat(N, minmax(6rem,1fr))`, over tiers: CompareTier[] ({id,name,price}) in ascending price order and rows: CompareFeatureRow[] ({id, label, note?, support: (boolean|string)[]}) with one support entry per tier (false = excluded, true = plainly included, a string = the tier-specific value such as \"30 days\", rendered verbatim under the rule). Each row's label cell is a real <button data-compare-row={id} aria-pressed> that PINS the row on click. THE MECHANIC: for each row compute reach = the highest index i where support[i] is truthy (−1 if none). An aria-hidden absolutely-positioned <span data-reach-rule> is laid over the row, left = the x-centre of tier column 0 and width = (x-centre of column `reach` − left), both taken from a single cached measurement pass that reads getBoundingClientRect on the N header cells plus one offscreen 40-glyph mono probe for the exact 1ch advance; the pass is re-run by a ResizeObserver on the grid root only when its width moves by more than 2px (WIDTH_EPSILON), never per frame. The rule's text is generated per cell at that 1ch pitch: each glyph slot is attributed to the nearest tier centre and drawn `─` when that tier includes the feature or `┄` when it does NOT — so a non-monotone matrix (a feature a mid tier lacks but a higher tier has) reads as a dotted gap inside an otherwise solid run instead of being smoothed away — and the final character is `┤`, held in its own child span so it alone can take accent ink. The rule sits on the row's first-line centre with `white-space:pre; letter-spacing:0; overflow:hidden`. RESTING INK: every row's rule is rendered at full reach at REST_OPACITY 0.42 in --foreground, so the idle frame is a descending staircase of faint rules each capped by ┤ under a different tier — the comparison is legible with no interaction. ACTIVATION: on pointerenter or focus of the row's button that rule steps to opacity 1 and RE-DRAWS from zero glyphs to full reach on one shared rAF (a single loop pumping a Map of in-flight rows, self-cancelling when the map empties) at 14ms per cell with the total capped at 320ms — a per-cell reveal rather than a CSS width transition, so the ┤ terminator lands on its exact cell instead of sliding through the intermediate ones — and every tier column that lacks the feature drops to opacity 0.35. On pointerleave/blur the rule retracts right-to-left at STEP_OUT_MS 9ms per cell and relaxes back to 0.42 unless the row is pinned. The measurement pass re-runs once on document.fonts.ready, because the 1ch probe measured before the mono face swaps in gives the wrong pitch and every ┤ would land off its column centre. Only one row is active at a time; a pinned row stays at full ink, keeps aria-pressed=true and is what the rail relaxes back to. HEADER TALLY: under each tier's name and price sits a mono completeness readout — a 12-cell `▪`/`·` track filled to round(12 * included[i] / rows.length) followed by the literal fraction (`9/12`), where included[i] is the number of rows that tier actually includes. RESPONSIVE: the same ResizeObserver flips the component below 720px to one stacked card per tier — a feature list with ● / ○ per row, the tier-specific value on the right, and the same 12-cell track at the card head; the glyph rules are decoration and are simply not rendered there. Colour is token-only — --border for rules and separators, --foreground for ink, --ns-muted for notes, prices and values, --ns-accent used ONLY on the focus-visible outline and on the ┤ terminator of the pinned row. Screen readers get the matrix as role=table/row/rowheader/cell with a per-cell sr-only \"included\" / \"not included\" / value string, so no meaning lives only in the glyphs. prefers-reduced-motion (watched live via matchMedia) drops the per-cell stagger entirely: the rule snaps to full reach and full ink in a single paint and relaxes the same way. Zero dependencies. Props: tiers (CompareTier[]), rows (CompareFeatureRow[]), title (accessible name for the matrix), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-muted --border --ns-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 `--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-muted --border --ns-accent`), --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa",
          "error": "#ea001d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717",
          "error": "#ff6369"
        }
      },
      "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/--ns-muted/--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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.",
        "rank": 284
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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.",
        "rank": 282
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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": "contra-strike",
      "type": "registry:ui",
      "title": "Contra Strike",
      "description": "Order line-item ledger where a partial refund drags a struck span across the amount bar and, on commit, appends a negative contra row beneath the original, which is never edited, only ever struck through and kept legible.",
      "files": [
        {
          "path": "registry/core/contra-strike/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/contra-strike.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "table",
          "ledger",
          "refund",
          "billing",
          "accounting",
          "svg",
          "slider",
          "audit",
          "commerce",
          "support-tooling"
        ],
        "instruction": "Renders an order's line-item ledger as a real semantic <table> (thead/tbody/tfoot, th scope=row on each item name, th scope=col on each header) where a partial refund is recorded the way double-entry bookkeeping actually records one: the original row's DOM node and figures are never edited or removed, ever — the correction always materialises as a brand-new signed contra row appended directly beneath it. Each line item ({id, description, qty, unitPrice}) renders its amount as a fixed-width bar on ONE shared money-per-px scale derived from the largest line in the table, so every row's bar is visually comparable. Dragging anywhere on that bar (a real role=slider region, aria-label 'Refund <description>', aria-valuetext formatted exactly like '$25.00 of $75.00, 2 of 6 units') arms a single governing scalar, the struck fraction s (0-1): on a quantity line (qty>1) the drag snaps to whole-unit boundaries — s can only land on k/qty, so a partial refund can never claim a fractional unit — while a flat line with qty=1 snaps to the cent instead. That same s simultaneously drives the live refund figure, the length of an SVG line drawn across the amount bar (dash-array '4 3' — genuinely dashed — for as long as the row is only armed, never committed, so a mid-drag selection can never be mistaken for an already-settled correction), and, the instant a real 'Refund $x' button is pressed (its accessible name literally names the amount, and it stays disabled at s=0 — there is nothing to name yet), the width of the new contra row's own bar on the identical shared scale. Committing flips that same SVG rule from dashed to a solid stroke, height-expands the contra row from 0 (two rAF frames apart so the browser actually paints the zero-height frame first, then the real one, or the transition never plays), and re-derives the ledger's Subtotal/Refunded/Balance footer with a brief settle highlight — nothing here is ever mutable after commit; there is no undo, no revert, no toast, because the entire trust story of a ledger is that nothing already booked can un-happen. Once committed, the struck row's slider becomes non-interactive (aria-disabled, removed from tab order) — its qty/description/amount text is completely untouched, decorated with a permanent line-through on the amount (decoration, never deletion, still fully legible) and carries aria-description 'partially refunded, see contra entry below'. The contra row itself reads 'Contra — <description>' with a leading minus on both its unit count and its Geist Mono figure, and a live region (role=status, rendered unconditionally as a sibling of the table, never inside it) announces 'Refund recorded: -$X for <description>. New balance $Y.' the moment it lands. A consumer can pre-seed a line as already armed via defaultStruck (the draft strike renders at rest, same reasoning as approval-inline-diff's initialValues) or as already refunded via committedStruck, mounting straight into the settled state — contra row open, rule solid — with no animation, exactly like replaying a history feed. Keyboard: each slider takes Tab, ArrowLeft/Right (and Up/Down) step by one unit or one cent, PageUp/PageDown take a bigger stride, Home/End jump to 0%/100%; sliders use outline-none paired only with focus-visible:ring-2 focus-visible:ring-ns-accent (never focus-visible:outline-* on the same element), buttons use focus-visible:outline-2 focus-visible:outline-ns-accent with no base outline-none. --ns-accent appears only as those focus rings and a sub-second settle flash tied directly to the commit click — never as a resting fill; the commit button is outlined, not accent-filled. Money is formatted via Intl.NumberFormat with an explicit locale/currency (both overridable props, defaulting en-US/USD) so server and client render identical digits. Under prefers-reduced-motion the dashed-rule transition, the contra row's height-expand, and the settle flash are all skipped outright — the rule, the row, and the new balance simply appear in their final state, fully legible and operable either way. Pure DOM + inline SVG + CSS, no canvas, tokens only (--background --foreground --ns-muted --border --ns-accent), correct in both themes."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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.",
        "rank": 283
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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-ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": "crack-arrest-hole",
      "type": "registry:ui",
      "title": "Crack Arrest Hole",
      "description": "A press-and-hold STOP/ABORT confirm modeled on the crack-arrest (stop) hole: holding grows a fatigue crack from the edge toward a drilled hole, and reaching the hole halts it. Arrest is the confirmation, not a break-through.",
      "files": [
        {
          "path": "registry/core/crack-arrest-hole/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/crack-arrest-hole.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "button",
          "confirm",
          "stop",
          "abort",
          "cancel",
          "micro-interaction",
          "canvas"
        ],
        "instruction": "Build <CrackArrestHole children? arrestedLabel? holdMs? width? height? onArrest? className?> as a press-and-hold STOP/ABORT/CANCEL control, sourced from the real aircraft/structural-repair practice of drilling a crack-arrest (stop) hole at a fatigue crack's tip: a sharp crack tip concentrates stress and keeps propagating, and a round hole drilled there blunts that concentration and halts it — this is standard documented structural-repair practice, not an invented metaphor. THE MAPPING IS ARREST, NOT BREAK-THROUGH: unlike a destructive commit where reaching full progress unleashes something (a shatter, a fill), here holding grows a jagged straight-segment crack from the control's left edge toward a fixed drilled hole rendered on the canvas, and the crack REACHING the hole is what stops it — the confirmation is a halt, which is why this control suits STOP/ABORT/CANCEL and not a destructive delete. Releasing before the hold threshold heals the crack back to nothing on a damped spring and re-seeds a fresh random-walk path (a new mulberry32 seed) so the next attempt draws a visibly different crack — this heal-and-reseed cycle repeats indefinitely. Completing the hold arrests the crack permanently: the hole rim and crack both go solid at full --foreground opacity and thicken, the control pops with one small spring overshoot, the label swaps to arrestedLabel, and a polite aria-live region announces 'Arrested.' (with an earlier 'Halfway to arrest.' announcement at 50% hold, so progress reaches assistive tech, not just the canvas). After a ~650ms arrested dwell plus a short fade the whole control silently resets to idle with a freshly re-seeded crack path, ready to be held again — the full hold/release/heal/reseed cycle is continuous and needs no external reset. ALIVE AT REST: the drilled hole and a few short fatigue-striation ticks near the edge are always drawn, and even with no input the striations' opacity and a faint stress-concentration ring around the hole breathe on independent sine phases (three distinct periods/phases) so the material visibly reads as already under load — genuinely different frames at rest, not a static button. Keyboard-operable: holding Space or Enter drives the same state machine as pointer hold (pointerdown/up/cancel and lostpointercapture), and the accessible name comes from the real button label. Colors are --foreground for the hole rim and the growing/arrested crack, --ns-muted for the ghost depth pass and the idle fatigue shimmer, --border/--surface for the button's own resting chrome, and --ns-accent only on the button's focus ring and hover border — never mixed into the crack, hole, or ambient shimmer, which move in luminance only. All tokens are read from getComputedStyle(document.documentElement) and re-read on a MutationObserver watching its class; canvas fields start empty and are assigned in a useLayoutEffect before first paint. prefers-reduced-motion freezes the idle state on one static, non-t0 frame with the crack drawn about halfway to the hole (STATIC_DEMO_P = 0.52) so the mechanic reads in a single still, and stops the idle rAF loop entirely; holding the control still works under reduced motion (the crack still grows and arrests in real time, just without the shimmer, jitter, or spring pop) so the control stays fully operable with motion off. DIFFERS FROM confirm-slide-shatter (drag distance detonates a pane into tumbling shards — the commit IS destruction) and confirm-hold-ink (a hold pours ink to a full destructive fill that stays filled) and confirm-dial-align (the gate is rotational precision, not time or distance): this is the one confirm in the cluster built around arresting something in motion rather than committing to breaking or filling something. No gate descriptor: the arrested state can only be reached by a sustained hold past holdMs, and scripts/verify.ts's gate performs an instantaneous click (mousedown immediately followed by mouseup), so it cannot exercise a real hold — the same reason confirm-hold-ink and confirm-slide-shatter also ship without one. Direct-DOM/canvas physics on the hot path (refs only), React state limited to the arrested/announcement transitions. Zero dependencies."
      }
    },
    {
      "name": "crack-polygon-order",
      "type": "registry:ui",
      "title": "Crack Polygon Order",
      "description": "A decorative panel fill that tiles itself the way drying mud actually cracks: widely-spaced primary cracks grow first, then two more generations nucleate inside the largest surviving cells after a 900ms stress-rebuild pause each, every new crack tip T-junctioning into an existing one at ~90 degrees instead of crossing it, until the mature tiling holds, rewets to blank, and a new unrelated cycle begins.",
      "files": [
        {
          "path": "registry/core/crack-polygon-order/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/crack-polygon-order.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "panel",
          "background",
          "svg",
          "crack",
          "fracture",
          "tessellation",
          "generative",
          "desiccation",
          "ambient"
        ],
        "instruction": "Build <CrackPolygonOrder seed? className?> as a full-bleed fill for a card-scale panel (give it explicit height via className/a wrapper; the component itself is h-full w-full). STRUCTURE: a plain <div data-crack-polygon-order aria-hidden> holding one inline <svg viewBox=\"0 0 {measuredWidth} {measuredHeight}\" preserveAspectRatio=\"none\"> whose viewBox is set to the container's own measured CSS pixel size via a debounced ResizeObserver (120ms) — unlike an abstract stretched viewBox, 1 viewBox unit here equals 1 on-screen px, because every real-numbers threshold in the mechanic (90px/s growth, a ~2px T-junction test, a ~900px^2 minimum polygon area) is stated in real panel pixels. A <rect> filled var(--background) sits under everything; every crack stroke is var(--foreground) at 1.5px with vector-effect=\"non-scaling-stroke\" — never var(--border), which falls below the light-theme legibility floor at hairline width, and never any per-polygon fill (the tessellation must read from crack lines alone, not shaded cell faces). SIMULATION, not a pre-computed reveal: a Uint8Array occupancy grid at 2px cells (Grid class) records where ink already exists. Each crack is a single nucleation point growing TWO tips outward in roughly opposite headings (heading and heading+PI, +-~17deg jitter) — this is what makes primary cracks read as a widely-spaced network and what makes secondary/tertiary cracks visibly bisect the cell they were born in rather than doodling from one wall. A tip advances by discrete 3-5px segments at a rate that nets 90px/s, turning +-12deg per segment (seeded per-tip via a module-level `sharedRng` stream so the whole pattern is reproducible from one `seed`), and samples 3 points along every new segment against the grid's 3x3-cell neighbourhood (skipping the tip's own last 8 marked cells so it can't immediately T-junction into itself) — the instant a sample lands within that ~2px neighbourhood of ANY other ink, growth stops exactly there: the T-junction, and the reason later cracks can never be seen crossing an earlier one. Every generation is driven by an explicit phase machine (primary -> secondary-wait -> secondary -> tertiary-wait -> tertiary -> hold -> rewet -> cooldown -> primary...): primary spawns 4-7 nucleation points scattered uniformly at t=0; once every primary tip is done (edge or T-junction), a 900ms wait elapses, then a flood fill (findRegions, 4-connected BFS over empty grid cells) finds every still-open polygon, regions under 130px^2 are dropped, the rest sorted by area descending, and cracks nucleate inside the top ceil(60%) of what's left — same 900ms-wait-then-nucleate step repeats once more for the third generation, then the tiling holds for 4000ms untouched. REWET: over the following 2500ms the whole crack <g>'s opacity eases 1 -> 0 on an ease-in-out-cubic computed per frame (not a CSS transition, so it stays exactly in step with the same rAF clock driving growth) — the panel then clears (grid reset, crack map emptied) as a genuine return to blank, not just an invisible-but-still-there state, holds blank for a 600ms cooldown, and the next cycle's primaries begin. Full period is non-uniform by design, roughly 14-17s (6-8s to reach maturity + 4s hold + 2.5s rewet + 0.6s cooldown). PERFORMANCE: geometry writes for already-mounted <path> elements happen via direct refs.current.setAttribute('d', ...) inside the rAF callback, bypassing React entirely for per-frame growth; React state (the `cracks` array, id+gen+initialD only) only updates on the rare event a new generation's crack set changes (three times a cycle) or the panel resizes, gated by a `cracksDirtyRef` flag so a mid-fast-forward burst of spawns collapses into a single re-render. RESUME SAFETY: an IntersectionObserver sets a `visibleRef` the rAF loop checks before ticking (off-screen panels stop simulating but keep scheduling frames so they resume instantly back in view), and every dt fed to the simulation is clamped to 50ms regardless of source, so a backgrounded tab or a slow resize can never hand the sim a multi-second jump that would skip whole generations invisibly. THE t0 REQUIREMENT: a brand-new mount never starts from an empty panel — after the first valid ResizeObserver measurement, the exact same tick() function used by the live loop is called synchronously in a tight while-loop on a fixed 16ms clock for a random 1500-10500ms of simulated time (capped at 4000 iterations) BEFORE the real rAF loop ever starts, landing every visit at an arbitrary, already-partially-tiled mid-cycle frame; a real resize after that initial mount rebuilds the grid at the new size and restarts cleanly at a fresh primary generation (no repeated lead-in — a resize is not a new visit). REDUCED MOTION: on mount, if prefers-reduced-motion matches, skip the rAF loop entirely and instead run that same tick() function synchronously and repeatedly until phaseRef reaches exactly 'hold' (capped at 4000 iterations), then stop for good — the panel renders once, fully tiled, all three generations' T-junctions visible simultaneously, and never rewets. Zero dependencies, no canvas — SVG+DOM+CSS only, and the crack RNG (mulberry32, seeded from the optional `seed` prop or Math.random() per mount) is the only source of variation. DIFFERS FROM craze-rule: that component draws ONE branching fracture line across a fixed 40px divider strip, once, via IntersectionObserver, and never subdivides a cell — no generation hierarchy, no polygon tessellation, no ongoing simulation once armed. DIFFERS FROM compare-crack-seam: that fracture is a single draggable Voronoi-cell seam that IS a before/after image divider, spawning micro-fissures on drag velocity; crack-polygon-order has no pointer interaction, no comparison images, and its cracks free-branch across the whole panel on their own clock rather than tracking a cursor."
      }
    },
    {
      "name": "craze-rule",
      "type": "registry:ui",
      "title": "Craze Rule",
      "description": "A section divider that arrives as a propagating, branching fracture instead of a static <hr>: a seeded random-walk SVG path with 3-5 near-perpendicular T-junction branches draws in once via IntersectionObserver on an ease-out-expo stroke-dashoffset, then keeps a faint 6s idle creep at rest.",
      "files": [
        {
          "path": "registry/core/craze-rule/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/craze-rule.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "section-divider",
          "hr",
          "svg",
          "crack",
          "fracture",
          "scroll-reveal"
        ],
        "instruction": "Build <CrazeRule seed? className?> as a drop-in replacement for <hr>/border-top between page sections. STRUCTURE: a <div role=\"separator\" aria-orientation=\"horizontal\"> with a fixed height (40px) holding one inline <svg viewBox=\"0 0 1000 40\" preserveAspectRatio=\"none\" aria-hidden> — no measurement, no ResizeObserver; the abstract 1000x40 coordinate space stretches to the container's actual width for free, and every stroke carries vector-effect=\"non-scaling-stroke\" so line weight stays literal regardless of that non-uniform scale. THE GEOMETRY, generated once client-side in a useEffect on mount (a seeded random walk can't run identically on the server without pinning the seed, so the first render draws a flat placeholder line and the real geometry lands one client-only re-render later — this is a state update after mount, not a hydration mismatch): a mulberry32 PRNG seeded from the optional `seed` prop (falls back to Math.floor(Math.random()*2**31) for a fresh crack every mount) drives (1) the MAIN PATH — 26 points across the width, y = midline + a 1D random walk clamped to +-4 viewBox units, joined as straight line segments (jagged, not smoothed — cracks don't have bezier curvature) — and (2) 3-5 BRANCH PATHS, each a short 2-segment polyline anchored at a point along the main path, launched at the LOCAL TANGENT's perpendicular +-90deg with only ~14deg of jitter and a slight bend on its second segment. That near-perpendicular launch angle is the T-junction rule from drying mud and is the one specific thing that makes this read as a fracture rather than as a decorative squiggle — do not soften it toward a shallower angle. THE ARRIVAL: every path (main + branches) carries pathLength={1}, so stroke-dasharray/stroke-dashoffset are both authored in the fixed 0..1 range regardless of a path's real length — no getTotalLength() call needed. Each starts at stroke-dashoffset:1 (fully hidden) with `transition: stroke-dashoffset <duration> cubic-bezier(0.16,1,0.3,1)` (the easings.net ease-out-expo curve — the tip visibly decelerates like a crack running out of strain energy) and reveals to 0 the instant the wrapper's data-armed=\"true\" attribute is set, which happens exactly once, from an IntersectionObserver at threshold 0.2 that disconnects itself on first fire — the propagation is a one-shot arrival, never replayed on subsequent scroll-ins. Main path duration is 600ms; each branch is a separate, shorter (260ms) path whose transition-delay is computed at generation time from its anchor's position along the main path (t * 600ms, plus a little per-branch jitter) — so a branch pops exactly as the propagating tip would have run past its T-junction, not all at once with the main line. Timings are deliberately kept under ~1s total so the full branching topology has landed in the registry's own gate screenshot, not just the main walk. A small aria-hidden <circle> sits at the main path's terminal point, opacity:0 by default, and plays a one-shot ns-craze-tip-flash keyframe (--foreground fill, animation-delay pinned to the main path's own duration so it fires exactly as the main path finishes) for the 'momentary --foreground tip flash' the brief calls for — arrival, not an ongoing glow. AT REST, once armed: the two LONGEST branches (computed at generation time by summed segment length) each get their own idle creep on independent periods and phases — the longest runs `animation: ns-craze-idle-branch 5s ease-in-out infinite` (stroke-dashoffset swinging to -0.42), the second-longest `animation: ns-craze-idle-branch-second 7s ease-in-out infinite` (to -0.3, started 700ms later) — so two points of the rule visibly twitch out of sync rather than one tip alone, still calm rather than busy. The whole ink <g> (main path + every branch, via CSS inheritance of stroke-opacity) gets `animation: ns-craze-idle-breathe 6s ease-in-out infinite` cycling stroke-opacity 0.55->1.0, wide enough to register in a screenshot diff, not just on-screen. Both idle animations are animation-delayed to start only once the main path's own reveal transition has finished — so the material keeps reading as under stress without ever looking like it's still arriving. Colors are --border for every stroke and --foreground for the one tip flash; --ns-accent never appears (nothing here is interactive, so there is no interaction state for it to mark). A11Y: role=separator carries the divider's semantics and needs no accessible name; the SVG is aria-hidden decoration; there is no keyboard surface because there is nothing to operate — the registry's tab-reachability check is correctly skipped for a display-only component like this one. prefers-reduced-motion is handled entirely in CSS (not by branching the generation logic): every transition and animation is killed and stroke-dashoffset/opacity are forced to their settled values, so the crack renders fully formed on the very first paint, no propagation, no idle creep, geometry unchanged. DIFFERS FROM: footer-ascii-rule, whose top rule is a static repeated dash glyph — house-style dressing, not a mechanic, with zero branching topology and zero arrival animation. DIFFERS FROM: compare-crack-seam, where the fracture is a draggable Voronoi-cell seam that IS the interactive divider of a before/after image comparison, spawning and healing micro-fissures on drag velocity; craze-rule has no drag, no comparison, no pointer interaction at all — its only life is the one-shot arrival plus the ambient idle creep afterward. Zero dependencies, no canvas — DOM+SVG+CSS only, and the only JS running after mount is the disconnect() call inside the IntersectionObserver's own callback."
      }
    },
    {
      "name": "creep-span",
      "type": "registry:ui",
      "title": "Creep Span",
      "description": "An SLO error-budget meter drawn as a taut wire spanning the card, which sags by irreversible plastic creep: sustained burn above 1x permanently sets the wire, so a bad day three weeks ago stays a visible kink it never recovers from, and the dashed forward extrapolation's floor intersection is the projected exhaustion date.",
      "files": [
        {
          "path": "registry/core/creep-span/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/creep-span.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "meter",
          "budget",
          "slo",
          "error-budget",
          "timeline",
          "svg",
          "date",
          "status",
          "accessibility"
        ],
        "instruction": "Renders one SLO error budget as a taut wire spanning the card instead of a bar or gauge. Input is `history: {date, burn}[]` (oldest -> today, `burn` = that day's consumption multiple, 1x = exactly sustainable) plus an optional `currentBurn` overriding today's live reading, a `k` plastic-strain scale (default 0.05) and a `maxProjectionDays` cap (default 90). One governing scalar per day drives the whole mechanism: a monotonic accumulator, `strain[i] = strain[i-1] + max(0, burn[i]-1) * k`, that only ever grows — there is no decrement path anywhere in the code, so a day's burn dropping back under 1x cannot undo strain already accumulated. `strain` maps linearly to the wire's vertical position between a taut top rail (strain 0, full budget) and a floor rail (strain 1, budget exhausted, `budget = 0`) drawn the full width of the stage. The wire itself is one straight segment per day (never a single smooth path) so each day's segment can independently thicken its `stroke-width` — never change color — on any day whose burn exceeded 1x; a spike from weeks ago keeps its thickened segment and its sagged y-position forever, exactly the visible, permanent kink the component exists to show. Only the very last point (today) carries any animation: a small cosmetic elastic offset, `clamp(max(0, currentBurn-1) * 6px, 0, 11px)`, eased in via a single overdamped (`cubic-bezier(0.16,1,0.3,1)`, no-overshoot) CSS `d` transition on that one segment — rendered as an SVG `<path>`, not a `<line>`, purely because a moving line endpoint isn't CSS-transitionable in any shipping browser while a `<path>`'s `d` is (the same technique other meters in this registry use) — explicitly NOT fed into the strain array, so it can wobble with the live instantaneous reading without ever changing the printed budget number. Past that point, a dashed extrapolation (same governing scalar, held constant at `currentBurn`) continues the wire at whatever slope `currentBurn` implies; if it's projected to reach the floor rail within `maxProjectionDays`, the exact pixel where the dashed line meets the floor is marked with a small filled circle and IS the projected exhaustion date printed in the header — a path integral answering a date, not a level read at one instant. If `currentBurn` is at or under 1x, or the projected crossing is further out than the cap, no dashed line is drawn and the header reads 'not projected'. A Geist Mono header always shows three live figures as visible text (not just SVG or ARIA): REMAINING (percent, from `1 - strain[today]`), BURN (`currentBurn`, one decimal, 'x' suffix), and EXHAUST (the projected date or 'not projected') — the state a dashboard owner reads first, independent of any scrubbing. FALSIFIABLE CONSTRAINT: sag is monotonic non-decreasing per segment, verified by construction (a running accumulator, never a spring that relaxes toward a lower target) — a meter that lets sag recover when burn drops would misrepresent spent budget as still available. Below the header, an SVG stage (aria-hidden, decorative) draws the floor rail, solid history segments, dashed projection segments and the exhaustion marker; overlaid on it is the one real control, a `role=slider` date scrubber spanning the full chart, keyboard-focusable, that reads pointer hover/drag position OR ArrowLeft/Right/Up/Down (step one day), PageUp/PageDown (step seven), and Home/End (jump to the window's rails: earliest logged day, and either the projected exhaustion day or today if nothing is projected). Its `aria-valuetext` and a parallel always-visible Geist Mono readout line below the chart are both produced by ONE `composeReadout(index)` function reading the exact same `full.strain` / `full.burn` array that drew the wire — e.g. 'Aug 9 — 61% budget remaining, burning at 2.3x, exhaustion projected Sep 2' — so the number on screen and the shape on screen cannot disagree, whether the scrubbed day is logged history or the dashed projection (tagged '(projected)'). The scrubber thumb (a plain vertical line) is never accent-colored; `--ns-accent` appears exactly once, as the `focus-visible` outline ring on the scrubber, using the outline utility alone with no base `outline-none` on the same element. A separate visually-hidden `role=status aria-live=polite` span announces only actual data-state threshold crossings — burn crossing above/back under 1x, or budget reaching exactly 0 — tracked via refs so re-renders at the same state never replay the announcement, and critically NOT fired by scrubbing through dates (that's ordinary slider value-text reading, which AT gets for free from the slider role). Under `prefers-reduced-motion: reduce` (via `matchMedia` with a live listener) the elastic offset is applied instantly with no transition — the wire renders at its true position with no settle — while the scrubber, readout and live-region logic are all unaffected, since none of them were animation-dependent to begin with. Every stroke is `var(--foreground)`, `var(--ns-muted)`, or `var(--border)` — no hex, no `getComputedStyle`, since nothing here is a raster surface; both themes restyle for free. DOM + inline SVG + CSS only, zero dependencies, no canvas."
      }
    },
    {
      "name": "crimp-barrel-set",
      "type": "registry:ui",
      "title": "Crimp Barrel Set",
      "description": "A 'connect/link/attach' confirm control modeled on open-barrel wire crimping: two dies flatten a barrel into a witness-marked, strand-flared profile on a repeating 3.6s demonstration cycle, alive at rest, with a press firing one crimp on demand.",
      "files": [
        {
          "path": "registry/core/crimp-barrel-set/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/crimp-barrel-set.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "confirm",
          "connect",
          "link",
          "attach",
          "button",
          "svg",
          "motion",
          "aria-live"
        ],
        "instruction": "A 'connect/link/attach' confirmation control — the pattern that usually just swaps a button's icon or flashes a checkmark on success — rebuilt around open-barrel wire crimping (MIL-STD-1130 / Molex application-spec geometry): two dies close in from opposite sides on a barrel holding two conductor ends, flattening it into a stepped, witness-marked profile with a small flare of displaced strand material ('brush') at each mouth where it wasn't fully captured. A good crimp is judged by that witness-mark shape, not just whether it closed, so the component draws the witness marks and the strand flare as first-class geometry, not an afterthought. Rendered as a <button> wrapping an inline SVG (viewBox 200x90, scales to its box) — no canvas. The barrel is a single <path> stadium shape (120x36 logical units at rest) built fresh every frame from one scalar, closeAmt (0 open .. 1 seated): height eases from 36 to 22 (62% of rest) via cubic ease-out over 220ms, while a 6px-wide witness-dimple pair at two fixed x-offsets (30%/70% of the barrel width) cuts an additional 4px indent into the SAME path's top and bottom edges, but only in the final ~17% of closeAmt's range (the die-bottoms-out window) — dimpleAmt, flareFrac and the barrel's own height are all derived from that one closeAmt value every frame so they can never drift out of sync with each other or with the die position. Two SVG <rect> dies translate via CSS transform (translateY) from an open position visibly separated from the barrel to a seated position touching its current top/bottom edge, driven by the same closeAmt. Three conductor strand lines per side (6 total), anchored at the barrel's fixed end-caps (its width never changes, only its height, so this anchor point stays exposed past the barrel's silhouette at every closeAmt instead of drawing underneath it), displace outward 2-5px at the barrel mouth, driven by an actual underdamped spring step response (zeta=0.7, ~120ms settle) so they overshoot slightly and settle rather than stopping dead — real crimped strands have spring-back. Draw order is barrel, then dimple creases, then strands, then dies, so nothing occludes the flare and the dies still visibly land on top of the barrel they're compressing. This runs as an unforced, repeating demonstration cycle so the control is alive at rest with zero input: 1.28s open/idle, then 220ms close, 1.4s hold-seated, 700ms retract, repeat forever on a fixed 3.6s period — idle sits FIRST in the ambient loop (a deliberate deviation from the spec's literal idle-last/2.28s/4.6s numbers, which are mutually unsatisfiable against the spec's own three named sample frames: hold always ends by 1620ms into a cycle and no cycle can be shorter than the 2320ms close+hold+retract minimum, so no idle duration under an idle-last ordering can ever put a hold window at t=2.5s; idle-first with a shortened 1.28s idle is the smallest change that keeps close/hold/retract at their real, sourced 220/1400/700ms values while actually landing t0 in idle, t2.5s in hold-seated and t5s in a fresh cycle's mid-close). A press skips the idle wait entirely and starts closing on the very next frame. Hover or focus pre-stages the dies to a slight gripping position (closeAmt floored at 0.18 — barrel just perceptibly ovalized, well below the dimple threshold, no witness marks yet) as an affordance layered on top of whatever the ambient loop is doing. Pressing fires ONE full crimp cycle on its own independent clock (not the ambient timer), calling onConnect and announcing doneLabel the instant that press's dies seat, and once that press cycle finishes the ambient loop resumes fresh from open. Every colour is read once via getComputedStyle(document.documentElement) before any paint and re-read on every class-attribute mutation of <html> (a MutationObserver) for live theme toggles, never a literal. The witness mark's real legibility comes from the barrel PATH's own silhouette — the two dimples are literal notches cut into the same <path> geometry that draws the barrel, so they read as a shape (indent) in both themes with no colour dependency at all. A thin crease stroke is layered on top, coloured by darkening the barrel's own live --foreground rather than swapping to --background (swapping would read as a LIGHT notch in light theme, since --background there is the lighter of the pair — the exact defect this spec calls out); that crease is a secondary refinement, not the primary read, and is intentionally low-contrast in light theme rather than invisible geometry. --ns-accent never touches the seated barrel, dies or witness marks (the crimped-shut state reads in luminance/geometry only); it's confined to the button's own focus-visible ring. prefers-reduced-motion paints exactly one static frame — STATIC_PHASE='seated' (dies fully closed, witness dimples visible, strand flare at rest) rather than t0's bare open barrel, because that's the frame that actually explains the mechanic; a press in that mode still fires onConnect/doneLabel immediately, just without motion. The animation loop pauses via IntersectionObserver off-screen and is cancelled along with the MutationObserver on unmount. Props: label (visible + accessible name, default 'Connect'), doneLabel (announced via aria-live on a fired press, default 'Connected'), onConnect (called once per press at the moment that press's crimp seats), className. Zero dependencies."
      }
    },
    {
      "name": "crossfoot-gap",
      "type": "registry:ui",
      "title": "Crossfoot Gap",
      "description": "An editable invoice grid that validates itself the way a bookkeeper crossfoots a ledger: row totals right of a vertical rule, column totals under a horizontal one, and a corner gap whose width (not a warning icon) carries the size of the mismatch.",
      "files": [
        {
          "path": "registry/core/crossfoot-gap/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/crossfoot-gap.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "table",
          "form",
          "invoice",
          "ledger",
          "reconciliation",
          "svg",
          "aria-live",
          "accessibility",
          "mono",
          "budget"
        ],
        "instruction": "An invoice/allocation grid where each ROW carries a stated ground-truth amount (a receipt total, an invoice line) as a prop, and each interior cell is a native <input inputmode=\"decimal\"> where the user types how that amount splits across the grid's COLUMNS (cost codes, categories, periods). Because the stated amounts and the typed splits are two independently-sourced numbers rather than one matrix summed two ways, they can genuinely disagree: leave part of a receipt uncoded and the ledger falls short by exactly that line's amount; mistype a digit and it's off by pennies. One function (computeLedger) derives { rowStated, colTotals, sumStated, sumAllocated, diffCents } in a single pass from props — that same object feeds both the printed row/column/grand totals AND the gap's pixel width, so there is no second calculation that could drift out of sync with what's on screen. GEOMETRY: rows sit in a real <table> (<th scope=\"row\"> per line item, <th scope=\"col\"> per category) with a read-only, tabIndex=0 row-total cell (the stated amount) at the right of each row and a read-only, tabIndex=0 column-total cell (the computed allocation) in a <tfoot> row; the grand-total <td> at their intersection is measured directly via getBoundingClientRect so an absolutely-positioned <svg> overlay can draw two --border hairlines that terminate exactly at its edges: a horizontal rule from the table's left edge to cornerX − gap/2, a vertical rule from the table's top edge to cornerY − gap/2. gapPx = 0 when diffCents === 0, otherwise clamp(3px floor, log1p(|dollars|)/log1p(10000) scaled to 24px, 24px ceiling) — the floor keeps a one-cent miss from rendering as visually closed, the log curve makes a $0.09 typo and a $1,240 dropped line read as distinguishably different widths, and the 24px cap means the gap can never push the table into reflow. The signed diff prints in Geist Mono, aria-hidden and decorative, centered on the corner and rotated 45° along the bisector of the opening (one rule receding left, the other up). Gap width eases toward its target with a small hand-rolled requestAnimationFrame tween (ease-out-cubic, ~240ms) rather than a CSS transition on raw SVG attributes. The instant diffCents reaches exactly zero, a second pair of hairlines — the accountant's double rule — inks in under the grand-total figure via a pathLength=1 stroke-dashoffset keyframe (350ms, ease-out-expo cubic-bezier(.16,1,.3,1)), re-keyed so it replays every time the ledger newly closes rather than only once. Corner cell text always states the reconciled status in words (\"Ledger closed\" / \"off by −$0.09\") independent of the SVG, and a debounced (500ms) aria-live=\"polite\" region mirrors the same phrasing without narrating every keystroke — the visible gap itself updates immediately as you type. There is no submit step: every total, and the gap itself, is simply what is true of the current controlled state. The corner gap is the ONLY error indicator in the component — no per-cell warning icons, no boolean invalid flag anywhere. Props: columns ({id,label}[]), rows ({id,label,amount}[] — amount is the stated ground truth in dollars), cells (Record<rowId, Record<colId, string>>, controlled), onCellChange(rowId, colId, value), currency (default \"$\"), ariaLabel, rowHeaderLabel (default \"Line item\"), footerLabel (default \"Allocated\"), className. Reduced motion removes the rAF tween (gap width jumps straight to its target) and the ink-in keyframe (the double rule is simply present, fully drawn) — nothing becomes unreachable or unreadable, only motion is removed. Pure DOM + SVG + CSS, no canvas; every color is --background/--foreground/--ns-muted/--border/--ns-accent, --ns-accent used only on input focus rings. Demo: a four-line expense report (hotel receipt split across lodging/meals/transport, three single-category receipts) that foots exactly at rest, plus 'Simulate typo', 'Simulate dropped line' and 'Reset' buttons that put the two named failure magnitudes on tap."
      }
    },
    {
      "name": "cruet-settle",
      "type": "registry:ui",
      "title": "Cruet Settle",
      "description": "A checkout or invoice total settles like shaken vinaigrette: one agitated column stratifies by density into net, tax, fees and discount, each layer's height exactly its share of the sum.",
      "files": [
        {
          "path": "registry/core/cruet-settle/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/cruet-settle.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "checkout",
          "invoice",
          "pricing",
          "breakdown",
          "dither",
          "svg",
          "data-viz",
          "ledger"
        ],
        "instruction": "CruetSettle renders a checkout or invoice total as liquid settling in a narrow vessel after being shaken: on mount, and again on every change to the `lines` prop, the full sum first appears as one agitated, undifferentiated column — a single SVG rect filled with the registry's shared 4x4 ordered-dither pattern (the same Bayer matrix background-ascii-dither, chart-bar-halftone and chart-donut-halftone already use) — then a single 900ms ease-out-expo timeline (a CSS transition on each band's `d`, the exact technique meter-quota-meniscus already uses for its curve) moves every internal boundary from a hash-shuffled start position to its sorted final y. The hash is integer-only (Math.imul, no trig, no Math.random), seeded by boundary index and a shake counter that starts at 0 on both server and first client render, so the agitated start frame never hydration-mismatches; the counter only increments on a genuine client-side reshake. There is no per-layer stagger — every boundary moves on the same governing timeline, because a staggered entrance would imply the layers existed separately before the total and then arrived, when the actual semantic is one conserved sum decomposing into parts it was always made of. That conservation is structural: the vessel's top and bottom never move, only the boundaries between layers do, and every layer's final height is exactly amount_i / grossSum of the column, grossSum being the sum of every line's magnitude — a `kind: \"subtract\"` line (a discount, a credit) still claims its proportional slice of the column, rendered as a hatched void instead of a solid band, so it reads as a deduction rather than a fourth ingredient, and the displayed total is net additions minus subtractions. The line between the tax band and its neighbor carries a fine tick ladder (four ticks spanning the tax band's own height) so the tax fraction reads as a level, not just a stripe — the component finds the tax line by matching \"tax\"/\"vat\" in its key or label. The vessel, its ordered-dither bands, its 1px meniscus rules between layers and its 1px leader lines are all aria-hidden — pure decorative geometry. Each leader line rules out from a band's own centre toward the real content: a `<dl>` beside the vessel in Geist Mono, one row per line (term and formatted amount, a real minus sign on subtract lines) plus a total row, so the leader lines read as pointing at the actual labels rather than duplicating them. Each dl row is a plain `tabIndex=0` wrapper around its own `<dt>`/`<dd>` pair — deliberately roleless, since `role=button` on that wrapper would flatten the term/value pair out of the accessibility tree (button is a presentational-children role) and contradict the dl being the primary content. Hovering or focusing a row sets the `activeKey` that highlights its matching band with --ns-accent, so keyboard users get the identical layer-coupling pointer users get; the rows are passive highlight regions, not ARIA controls, by design. A total-value change announces once through a visually-hidden `role=status aria-live=polite` span, comparing against the previous render's total so mount itself never announces. `prefers-reduced-motion: reduce` skips the agitation phase outright and renders pre-settled, and a CSS media query separately kills every transition regardless of the JS-detected preference, so a race between the two never leaves a visible shake. Zero dependencies, DOM + SVG + CSS only, no canvas — every ink value is a CSS custom property referenced directly as an SVG presentation attribute, so both themes render correctly with no getComputedStyle or theme observer needed."
      }
    },
    {
      "name": "curd-cut-whey",
      "type": "registry:ui",
      "title": "Curd Cut Whey",
      "description": "A card-scale batch-processing status indicator modelled on cheesemaking curd cutting and syneresis: a solid mass is knife-cut into a grid of cubes that each shrink at their own jittered rate, releasing whey into the widening gaps, jostled by a periodic stir, then recombined into a fresh batch.",
      "files": [
        {
          "path": "registry/core/curd-cut-whey/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/curd-cut-whey.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "canvas",
          "batch",
          "queue",
          "processing",
          "chunking",
          "syneresis",
          "physics",
          "ambient",
          "status"
        ],
        "instruction": "A card-scale single-canvas component modelling cheesemaking curd cutting and syneresis as a batch-processing status indicator. All geometry derives from the chart wrapper's getBoundingClientRect via computeGeo(W, H): a square grid area (squareSide = minDim * 0.86, centred) subdivided into gridN x gridN cells where gridN = clamp(round(minDim / 90), 4, 8) — 6x6 at a typical ~540px card. One 30000ms loop, unbounded: a 1000ms CUT phase draws a solid --foreground-wash mass, then two perpendicular 500ms knife-line wipes (interior grid lines stroked in --border, each wipe progressively revealing more of its length) cut it into cells at size0 = cellFull * 0.96 (a small kerf); a 25000ms SHRINK phase where every cube's edge follows size(t) = size0 * exp(-k*t), mean k = 0.12/s with a fixed per-cube +-15% jitter multiplier (seeded once per batch via mulberry32, cubeEdge floored at size0 * 0.55) so cubes visibly drift out of lockstep rather than shrinking uniformly; a 3000ms HOLD phase at the floor size; and a 1000ms RECOMBINE phase that eases cube edges from the floor back up toward a full cell (ease-out-quad) while the whey wash fades to zero, after which a fresh batch (freshly seeded cube jitter table) begins its own cut. During the shrink phase every cube's gap-facing whey wash is a single rect filled in --ns-muted under the grid, its alpha ramping 0 to a measured peak over 25s via ease-out-quad(t/25) — the peak itself (0.5 normally, 0.72 in low-contrast themes) is not guessed: a 1x1 offscreen probe canvas rasterises --ns-muted and --background and compares their actual luminance, so light themes where --ns-muted sits close to --background automatically get the higher peak instead of the whey visually disappearing. Every 6000ms of shrink-phase time (at 6s, 12s, 18s, 24s) a stir event fires: each cube's centre gets a deterministic per-cube-per-stir displacement (a hashed pseudo-random +-1 pair scaled by jostlePx = 4px * minDim/320) that decays as exp(-10*t) * cos(2*pi*3*t) over 300ms, a real damped-spring jostle distinct from the continuous shrink, giving the eye a discrete beat to re-anchor on. Cubes are filled --foreground at 0.16 alpha with a --border stroke at 0.5 alpha as their hairline separator (never used as a gap fill) — --border only ever appears as a stroke, both for the knife-cut lines and the cube edges. Colour is read once via getComputedStyle(document.documentElement) for --foreground, --border, --ns-muted and --background with no literal fallback of any kind; if any is empty the mount loop retries on the next rAF and paints nothing until all four resolve. A MutationObserver on document.documentElement's class attribute re-reads tokens (recomputing the whey peak alpha) on every theme flip and forces a resize + (if reduced motion) a redraw of the static frame; a ResizeObserver on the chart wrapper recomputes geometry, resizes the canvas backing store (capped devicePixelRatio 2), and rebuilds the per-cube jitter table if gridN changes; an IntersectionObserver stops driving new frames while off-screen and, on re-entering view, re-reads tokens and restarts the batch from a fresh cut (never resumes a stale mid-shrink state) — no paint happens on any of these three paths before a token read succeeds. Under prefers-reduced-motion the component runs zero rAF loops and zero timers: it renders exactly one deterministic frame at elapsed = 15000ms (mid-shrink — cube sizes clearly varied, not uniform and not at floor, moderate whey pooling, the most structured frame of the cycle) using the same render function called once at mount, and never re-renders. Cleanup on unmount cancels both the animation and token-wait rAF handles and disconnects all three observers. Zero dependencies, DOM+canvas only, no colour literal anywhere including fallbacks."
      }
    },
    {
      "name": "cylinder-hillndale",
      "type": "registry:ui",
      "title": "Cylinder Hill-and-Dale",
      "description": "A full-width section divider rendered as a side elevation of an Edison-style wax cylinder phonograph: a rotating cylinder carrying a static helical hill-and-dale groove, a lead-screw rail, and a stylus carriage the lead-screw advances axially in exact lockstep with rotation, one wrap of travel per one full turn, always, at any frame rate.",
      "files": [
        {
          "path": "registry/core/cylinder-hillndale/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/cylinder-hillndale.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "hr",
          "phonograph",
          "cylinder",
          "edison",
          "helix",
          "lead-screw",
          "canvas",
          "monochrome"
        ],
        "instruction": "Build <CylinderHillndale height? className?> as a full-width <canvas> band wrapped in a <div role=\"separator\" aria-orientation=\"horizontal\">, a drop-in replacement for <hr>/border-top between page sections. SOURCE, NOT INVENTED: early Edison cylinder phonographs recorded via a vertically-modulated (\"hill-and-dale\") helical groove cut into a rotating wax cylinder — the stylus rides groove DEPTH variation, not the side-to-side lateral modulation Berliner's later disc records used. A lead-screw advances the stylus carriage axially at a fixed pitch in exact lockstep with cylinder rotation, which is what physically produces one continuous helix rather than concentric rings. THE MECHANIC: a cylinder turns while a stylus carriage rides its helical groove, stepping sideways by exactly one wrap's width for every full turn — rotation and translation locked together, never independent. GEOMETRY: side elevation only. radius = height * 0.32; end-cap ellipses at each end (rx = radius * 0.22, ry = radius); the cylinder body's horizontal extent (its two long edge lines at cy ± radius) spans from just past the left end-cap to just before the right, and that span IS the full canvas width scaled by the container — 'cylinder length mapped proportionally to canvas width' per spec. wrapSpacing = bodyLength / NUM_WRAPS where NUM_WRAPS is fixed at 10 — this is what keeps the two documented real numbers (a 6px wrap pitch and a 26s full traversal) mutually consistent at any rendered width: wrapSpacing scales with the container so the ratio bodyLength/wrapSpacing (and therefore traversal duration) never drifts, while ROTATION_PERIOD_S stays a fixed real time constant (2.6s per revolution, decoupled from Edison's real 160rpm) that never scales with size. THE NON-NEGOTIABLE IDENTITY: carriageX derives from rotationAngle, never from an independently-integrated position — traversalFrac = (elapsedSeconds / (NUM_WRAPS * ROTATION_PERIOD_S)) % 1; carriageX = bodyLeft + traversalFrac * bodyLength. Because this is computed directly from accumulated elapsed time every frame (not incremented per-frame and summed), the relationship 'one full wrap of translation exactly matches one full revolution' holds exactly regardless of frame rate or dropped frames — there is no drift to accumulate. THE GROOVE is a STATIC spatial curve, never animated over time: grooveY(x) = cy + amplitude * sin(2π * (x - bodyLeft) / wrapSpacing), amplitude = radius * 0.62, drawn once per frame across the whole body length as a sequence of short strokes (STEP ≈ wrapSpacing / 14) so each segment's lineWidth can independently encode a baked hill-and-dale DEPTH envelope — grooveDepth(xFraction), a fixed sum of two non-commensurate sine components (frequencies 3.1 and 7.3 cycles across the span, phase-offset) normalized 0..1 and mapped to lineWidth = 0.8 + depth * 1.8px. Depth is a function of axial position only, never of time or rotation phase — the groove doesn't re-record itself, the carriage just rides over whatever was already cut there, which is why the 'groove-depth shading pattern under it' visibly changes as the carriage's x advances, not as the clock ticks in place. STYLUS: the carriage itself is a small filled rect that translates PURELY HORIZONTALLY along a lead-screw rail line drawn above the cylinder body (at cy - radius * 1.4, stroked in --border as a true separator, never a fill); a thin --foreground stylus arm drops from the carriage down to grooveY(carriageX) — that arm's LENGTH is the one visible proof the two motions are locked, since it is computed from the exact same carriageX the carriage block itself uses, not a second independent oscillator. END CAPS: each end-cap ellipse carries a small filled tick at radius * 0.72 from center, angle = (elapsedSeconds / ROTATION_PERIOD_S * 2π) mod 2π — the explicit 'the barrel itself is turning' cue, kept visually and computationally separate from the carriage's translation so a viewer can confirm rotation is happening even while staring only at the carriage. RESTING LOOP: t0 is some arbitrary carriage axial position and rotational tick angle; at 2.5s (2.5s / 2.6s-per-rev ≈ one wrap crossing has just completed) the carriage sits roughly one full wrapSpacing further along the rail, a visibly different axial position, with a different groove-depth pattern under the stylus tip; at 5s the carriage has advanced roughly two wraps further still, clearly past its t0 position. Full traversal (NUM_WRAPS * ROTATION_PERIOD_S = 26s) wraps via modulo, producing the carriage's instant snap back to bodyLeft with a fresh pass beginning — an unbounded loop, never a process that finishes and stops. TOKENS: fg reads --foreground and border reads --border, both via getComputedStyle(document.documentElement), re-derived on a documentElement class MutationObserver so a theme flip is live; --ns-accent never appears anywhere — there is no interactive surface on a pure ambient divider, so it is never reserved for anything. --border is used exactly once, for the lead-screw rail line, and strictly as a separator stroke, never as a fill or the groove's own ink (the groove and cylinder outline are --foreground only, since --border measures ~1.1:1 in light theme and would make the groove's depth modulation structurally invisible). A ResizeObserver on the canvas re-lays-out and restarts the loop on container size changes; an IntersectionObserver pauses the rAF loop while off-screen and resumes it with a fresh last-timestamp (no giant delta-time jump) on re-entry. The loop also pauses on document.hidden and resumes cleanly on visibilitychange. prefers-reduced-motion skips the rAF loop entirely and draws exactly one frame at elapsedSeconds = TRAVERSAL_S * 0.4 — the carriage clearly past the start (not an ambiguous 'just began' t0 frame) with the groove's depth-modulated stroke width visibly non-uniform under and near the stylus tip. Direct-DOM rAF, zero React state on the hot path, zero dependencies. A11Y: role=separator carries the divider's semantics with no accessible name required; the canvas is aria-hidden decoration; there is no keyboard surface because there is nothing to operate, so the registry's tab-reachability check is correctly skipped. Props: height (band height px, default 88; radius and every other proportion derive from it), className."
      }
    },
    {
      "name": "damask-float",
      "type": "registry:ui",
      "title": "Damask Float",
      "description": "A feature grid woven from one reversible damask cloth — figure and ground are the same thread in the same colour, differing only in float direction, so hovering a cell flips the anisotropic highlight instantly with no fade and no translate.",
      "files": [
        {
          "path": "registry/core/damask-float/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/damask-float.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "feature-grid",
          "canvas",
          "textile",
          "anisotropic",
          "hover",
          "grid",
          "monochrome"
        ],
        "instruction": "Build two exports. DamaskFloatGrid<{children}> is an optional wrapping provider that owns ONE rAF loop and a MutationObserver on documentElement's class, broadcasting {elapsedMs, tokens, qualityScale} to every subscribed card each frame (same single-loop idiom as this repo's GrazingLightGrid/MagneticDock) — a card mounted without one runs the identical loop standalone. DamaskFloatCard<{heading, body, iconPath?, href?, className?}> is a single card: an absolutely-positioned 2D canvas fills the card (w-full h-full, DPR-aware backing store capped at 2, rendered into an offscreen buffer at HALF that resolution then upscaled via ctx.drawImage with imageSmoothingEnabled, ResizeObserver on the host element re-deriving every length), with the heading/body as real DOM text in a bg-background/70 backdrop-blur-sm scrim anchored to the card's bottom (an optional href renders a stretched <a> overlay for the hit target, keeping the host div's own pointer events dedicated to the hover front below).\n\nTHE MATERIAL: damask is figured cloth from ONE thread in ONE colour — ground and figure are reciprocal satin structures (5-end satin, counter-step 2) that differ only in which direction the exposed thread runs, so the whole component is monochrome by physical necessity, not by constraint. Per texel: thread pitch P = 0.018 * min(cardW, cardH) (in buffer-pixel space, after DPR and the half-resolution and adaptive-quality factors are folded in). S (0=ground/warp-face, 1=figure/weft-face) comes from a rasterised icon mask (the `iconPath`, an SVG path `d` in a 0 0 24 24 viewBox, filled via Path2D onto an offscreen canvas once per resize — pinned in cell coordinates, never scrolling) OR-ed with a procedural diamond/lozenge border motif tiled at 8*P and evaluated against a cloth-space Y that DOES scroll. Binding points break the float: bindingKey = (ix + 2*iy) mod 5 === 0 where ix/iy are integer thread indices, darkening that texel's target luminance by the ratio 0.08/0.29 (light) or 0.05/0.20 (dark) of its region's base tone — this is what stops ground/figure reading as two flat greys.\n\nSHADING: a Kajiya-Kay anisotropic term against a FIXED light (elevation 34deg, azimuth 15deg, straight-on view) with kd=0.34, ks=0.52, n=26: sinLT = max(0, L.T) where T is the thread tangent from its azimuth (ground assigned 0deg, figure 90deg, each jittered +-4deg by an integer hash of (ix,iy) — never Math.random, so the reduced-motion frame stays byte-stable), cosLT = sqrt(1-sinLT^2), F = kd*sinLT + ks*cosLT^n. F only DRIVES the specular streak (smoothstepped onto two escalating target-luminance stops, 'ground under streak' and 'streak peak on a float crown'); the base tone of ground vs figure is pinned to fixed per-theme luminance targets (ground 0.58/0.46, figure 0.29/0.20, light/dark) so the measured ~1.6-2.2:1 warp:weft contrast survives even where F is weak. Every target luminance is converted to a pixel by solving alpha = (targetL - bgY)/(fgY - bgY) against the LIVE --background/--foreground token luminances (read via getComputedStyle, re-read on the class MutationObserver) and blending each RGB channel bg+alpha*(fg-bg) — zero colour literals, and a theme swap or any token change is honoured automatically since nothing is baked in.\n\nALIVE AT REST, unconditionally: (1) take-up — the border motif's cloth-space Y advances at 0.021*min(w,h) px/s and wraps, so new cloth is always entering the frame while the pinned icon never scrolls; (2) loom sway — the light stays fixed but the cloth's own grain azimuth oscillates on two incommensurate sinusoids, +-3.5deg at an 8.7s period plus +-1.2deg at 13.4s, so the n=26 specular streak visibly travels and the state never repeats on one beat. Neither mechanism converges.\n\nHOVER: on pointerenter, record the entry point in card-local CSS px and the entry time; every texel within a front expanding from that point at 0.9*min(w,h) px/s gets S flipped to 1-S — a real change in which satin structure is exposed, computed before the luminance/streak math runs, never a post-hoc opacity crossfade. Pointerleave reverts instantly (no reverse-front, matching that a real float has no in-between state). A fixed-size 'tabby' patch — matching the DOM scrim's measured rect — is written AFTER the front/reversal logic every frame, pinned to a flat target luminance (0.68 light / 0.24 dark) regardless of S or the front, so text contrast never depends on the weave state; verify this at the frame where the front is crossing the patch boundary.\n\nHOST: prefers-reduced-motion draws exactly one frame at STATIC_TIME = 5.4s (sway near its +2.9deg extremum, off the flat t=0 minimum) and never runs the rAF loop or any listener; that frame is re-rendered on resize/theme change but is otherwise byte-stable. IntersectionObserver (threshold 0) and visibilitychange both gate the tick. An adaptive quality governor lives in DamaskFloatGrid: each card reports its own draw duration; if it stays over an 8ms budget for a sustained 900ms the shared buffer-resolution scale steps down (1 -> 0.75 -> 0.55), never on frame count or device sniffing. --ns-accent appears nowhere in the shading — only as the focus ring on an optional stretched-link href."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": "decatron-step-ring",
      "type": "registry:ui",
      "title": "Decatron Step Ring",
      "description": "Circular step/cyclic-position indicator modelled on a decatron cold-cathode counting tube: a glow spot steps around ten radial stations, stretching onto an interleaved guide electrode before snapping fully onto the next cathode, free-running as an unbounded decade counter.",
      "files": [
        {
          "path": "registry/core/decatron-step-ring/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/decatron-step-ring.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "stepper",
          "ring",
          "svg",
          "counter",
          "decatron",
          "cathode",
          "ambient",
          "onboarding"
        ],
        "instruction": "A circular step indicator built as an SVG decatron: twenty nodes fixed in a 200x200 viewBox arranged in a ring at 38% of the viewBox radius — ten main-cathode stations at 36-degree intervals (mainAngle(k) = -90 + k*36, so station 0 sits at the top) and ten guide-electrode stations interleaved at the angular midpoint of each gap (mainAngle(k)+18). `preserveAspectRatio=\"xMidYMid meet\"` alone derives the ring from the container's SMALLER dimension, so no ResizeObserver is needed for layout. All twenty rest-state dots are drawn once, statically, as `color-mix(in oklch, var(--foreground) 38%, var(--background))` circles (main dot radius 4% of ring radius, guide dots 85% of that) — a token-anchored blend rather than a flat low-opacity foreground, chosen because a fixed-alpha dim dot reads fine in dark theme and nearly vanishes in light theme; mixing against both `--foreground` and `--background` keeps contrast consistent across themes. On top of the rest ring: one halo+lit-core circle pair per main cathode (halo radius 14% of ring radius, using a `radialGradient` whose stops are `var(--foreground)` at decreasing stop-opacity, so it needs no JS colour read at all and repaints for free on a theme class flip) and one stretched `<ellipse>` per gap, geometry precomputed once (centered on the guide position, rx = half the chord between its two flanking main cathodes plus a small overshoot, ry = half the halo radius, rotated to align with that chord) — this ellipse is the guide-phase 'stretch' visual, connecting the two main-cathode positions it sits between. Motion: a single rAF loop (no React state on the hot path, all writes are direct `setAttribute(\"opacity\"|\"r\", ...)` calls on refs) drives a 1400ms step cycle: the first 30% (420ms) is the guide phase, where the current main cathode holds full brightness while the guide ellipse between it and the next station ramps opacity 0 to a 0.7-brightness ceiling; the remaining 70% (980ms) is the main phase, where the guide fades 0.7 to 0 while the current cathode's brightness falls 1 to 0 and the next cathode's rises 0 to 1 in lockstep, so departure and arrival are simultaneous and visible, never a blink. Whichever main cathode has nonzero brightness gets its halo radius multiplied by `1 + 0.08*sin(elapsed*0.001*2*PI*2)` every frame (~2Hz, +-8%) to read as gas-discharge noise rather than a static LED, applied continuously regardless of step phase so the component is visibly alive at any timestamp. With no `activeStep` prop the component free-runs forever from elapsed real time (`stepIndex = floor(elapsedMs/1400) % 10`, `progress = elapsedMs/1400 mod 1`), completing one 10-station lap every 14s, unbounded — this is the default showpiece demo and has no external driver. When an `activeStep` prop IS supplied, the component switches to a controlled hop queue: on every prop change it computes the shortest direction around the 10-station ring from the currently settled station to the new target (diff <= 5 steps clockwise, else counter-clockwise), queues that many discrete 1400ms hops (each hop running the identical guide-then-main envelope described above, chained back-to-back with no gap), and holds steady with only the idle plasma noise once the target is reached — it never teleports. `role=\"progressbar\"` with `aria-valuemin=1 aria-valuemax=10 aria-valuenow` is added only in controlled mode; the free-run default is `aria-hidden` decorative since it carries no external state to announce. `autoplay: none` because the ring animates on its own internal clock with nothing for the site's synthetic-input driver to trigger. Under `prefers-reduced-motion`, the rAF loop never starts; a single frame is rendered once at `progress=0.15` on the station-0-to-station-1 gap — 15% into a step, mid guide-phase stretch — chosen because it is the one frame that shows both the full ring layout and the two-phase transfer mechanic simultaneously, rather than a settled single-dot frame with nothing else lit. Cleanup cancels the rAF, disconnects the IntersectionObserver (which pauses the loop off-screen and resumes it with a fresh time origin), and clears the controlled-mode prop-watch interval. Zero dependencies, SVG+CSS only, no canvas, every colour a token read natively via CSS custom properties (`--foreground`, `--background`) with zero literals and zero getComputedStyle calls, since SVG `style` attributes resolve `var()` through the normal CSS cascade with no pre-paint race to guard against."
      }
    },
    {
      "name": "delta-frame-macroblock",
      "type": "registry:ui",
      "title": "Delta Frame Macroblock",
      "description": "A quiet full-width band that visualizes a video codec's own P-frame change-detection decision: a grid of macroblocks sits mostly static, and every 220ms tick a small, capped subset is flagged changed with a brief outline flash and a slightly shifted tone while every other block is explicitly left untouched, exactly the sparse per-block change map a real codec decodes.",
      "files": [
        {
          "path": "registry/core/delta-frame-macroblock/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/delta-frame-macroblock.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "divider",
          "canvas",
          "codec",
          "grid",
          "generative",
          "ambient",
          "monochrome"
        ],
        "instruction": "Build <DeltaFrameMacroblock className?> as a full-bleed <canvas> (block h-full w-full) drawn with Canvas 2D on a direct-DOM rAF loop, no React state on the hot path. GEOMETRY: block = clamp(round(minDim / 16), 12, 24) px where minDim = min(container width, height); cols = round(width / block), rows = round(height / block), then blockW = width / cols and blockH = height / rows so the grid fills the container exactly with no partial edge blocks (a 320px band at 16px blocks holds a 20-block-wide row). PER-BLOCK STATE, held in three Float32Arrays sized cols*rows and mutated in place: baseTone (a fixed per-block seed value in [-1, 1] from a mulberry32 PRNG re-seeded on every layout, drawn once and never reseeded during the session), toneOffset (starts at 0, only ever moved the tick a block is flagged, bounded to +-0.05 with reflection at the bound rather than clamping — an unbounded random walk would eventually saturate the narrow luminance band into salt-and-pepper noise, which both breaks light theme and eventually stops changing, failing the always-different-at-rest rule), and flagTime (the sim-clock timestamp, in ms, a block was last flagged; -Infinity until first flagged). TICK, every 220ms (accumulator-driven off real elapsed time so speed never depends on frame rate, not a raw per-frame animation of a real codec tick rate per the round 9 decoupling rule): pick a small capped subset of distinct block indices — n = clamp(round(count * 0.08), 2, 4), i.e. the spec's 6-10% sparse-update ratio capped to an absolute 2-4 blocks regardless of grid size, because at typical card-scale grids (150-400 blocks) the raw percentage produces 10-30 simultaneous flags which, against a 340ms outline lifetime, overlaps into continuous shimmer rather than discrete countable events (this is a deliberate, spec-authorized departure toward more aggressive/legible timing, not a defect) — set flagTime[i] = current sim time and nudge toneOffset[i] by a random +-0.018 step (reflected) for each chosen block. RENDER, every animation frame, full redraw from the typed arrays (never a persistent buffer, so a theme flip or resize repaints every block identically instead of leaving stale-theme pixels behind): clear to --background, then for every block fill with --foreground at alpha = clamp(0.05 base + baseTone*0.045*lightMult + toneOffset, 0.015, 0.22) — a narrow, low-contrast luminance band, value-only, no hue, where lightMult is 1.6 in light theme and 1 in dark (derived from the resolved --background token's relative luminance, not from string-matching a theme class, so it stays token-driven with zero colour literals). Then, for every block whose (simNow - flagTime) is within 0-340ms, stroke its rect in --foreground at 1px: full alpha (1.0) for the first 140ms (the hold), then linearly fading to 0 over the following 200ms (the fade) — 340ms total, chosen so at most roughly one and a half tick-generations of outlines ever overlap on screen at once, keeping each flash an individually followable event rather than a continuous glow. TOKENS: --foreground and --background are read via getComputedStyle(document.documentElement).getPropertyValue and re-derived on a MutationObserver watching documentElement's class attribute; a tokensRead guard means draw() never paints before the first successful read, checked explicitly on the rAF start, the ResizeObserver-triggered relayout, and the IntersectionObserver resume path. Canvas is DPR-clamped to 2 and resized via ResizeObserver (120ms debounce) which fully relays out the grid, reseeds baseTone, and resets toneOffset/flagTime — a resize is treated as a fresh mount, not a stretch of stale state. WARM START (both paths run a synchronous tick loop before any paint, so mount never shows a bare freshly-reset I-frame): normal path runs 16 synchronous 220ms ticks before starting the rAF loop, so the very first painted frame already shows a natural scatter of blocks at different points in their fade lifecycle (t0 = a scatter of freshly-flagged blocks mid-fade from a recent tick, static base grid otherwise unchanged since mount) plus pre-drifted toneOffset variance from earlier warm-up ticks. prefers-reduced-motion: reduce runs 24 synchronous ticks, draws exactly one frame at that exact final sim time (0ms into the last tick's 140ms hold, full-strength outlines, before any fade softens which blocks are flagged versus static — the most legible single frame of the mechanic), and binds no rAF and no further ticks. Pauses on IntersectionObserver exit, resumes with a reset last-frame timestamp on re-entry. No interaction: the flag pattern is never linked to pointer position — the mechanic is the codec's own change-detection decision, not a hover-responsive grid, and a pointer-linked flag pattern would also risk mixing accent into what must stay a value-only highlight. Props: className only. Zero dependencies, canvas + CSS token reads only, every colour --foreground/--background, no --ns-accent, no --border used as a fill or stroke."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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(--ns-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": "dew-coalesce",
      "type": "registry:ui",
      "title": "Dew Coalesce",
      "description": "An empty state as condensation on cold glass: a PRNG-seeded droplet field nucleates, grows, jitters and merges, and a droplet crossing the runoff threshold streaks down and wipes a real clean track through a masked fog veil.",
      "files": [
        {
          "path": "registry/core/dew-coalesce/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/dew-coalesce.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "empty-state",
          "svg",
          "mask",
          "prng",
          "condensation",
          "ambient",
          "aria-hidden",
          "cta",
          "inbox",
          "search"
        ],
        "instruction": "A decorative empty-state panel, `<DewCoalesce title description actionLabel onAction icon seed className />`, styled as condensation on cold glass. On mount, once the panel's real pixel size is measured, a droplet field is nucleated from a seeded PRNG (mulberry32, seeded by the `seed` prop so the starting arrangement is reproducible) — 10 to 26 circles depending on panel area, each an SVG `<circle>` stroked in `--border`, `fill: none`. A single `setInterval` tick (380ms, no rAF, no canvas) runs pure physics over that array each cycle: every non-runoff droplet's radius grows by a small random amount and its position takes a small random jitter walk (both amounts roughly tripled for three ticks right after a merge, reading as a settle wobble); a single pairwise pass then merges any two droplets whose centers are closer than 0.85x their combined radii into one droplet positioned at their radius-weighted centroid with a radius equal to the square root of their summed squared radii (equal-area union); any droplet whose radius has crossed 7.6px stops growing and is promoted to a 'runoff' state with a randomized fall distance, meander offset and duration; and if the field has fallen under its target count, a replacement droplet is nucleated at a random position (avoiding the content block) with the same probability each tick. Every geometry change between ticks is smoothed by a 340ms CSS transition on cx/cy/r, so the field reads as continuous, faintly wobbling motion rather than 380ms snaps. A runoff droplet is handed to a small subcomponent that plays a single CSS keyframe animation: it drifts sideways and then straight down by CSS custom properties (`--dew-dx`, `--dew-dy`) over its own randomized duration, fading out as it goes, and calls back to remove itself from the field on `animationend` — a replacement then nucleates on a later tick. The instant a droplet is promoted, a matching rounded-pill `<rect>` (width from the droplet's own diameter, height equal to its fall distance, rotated a few degrees toward its meander direction) is added, capped at 6 concurrent tracks, as a hole inside the fog veil's SVG `<mask>` (`feGaussianBlur`-feathered, same technique as this registry's tour-spotlight): the pill grows from zero height to full over 900ms using the same mount-then-flip-state idiom as empty-state-pegboard's Slot (so a real CSS transition fires on first paint), holds for 2.6s once the fog there is genuinely gone, then fades its own hole back to nothing over 5.2s — the glass re-fogs, exactly like real condensation — and unmounts itself. A second hole in the same mask, sized from a live `ResizeObserver` measurement of the heading/body/CTA block plus 22px of padding, is always present and never fades: that patch is what keeps the copy and button legible, not merely their DOM stacking order above the veil. Colors are exclusively `--border` (droplets), `--ns-muted` (the fog fill) and the ordinary content tokens (`--foreground`, `--ns-muted`, `--ns-accent` on the CTA) — no hex, no canvas. Accessibility: the entire condensation SVG is `aria-hidden` and `pointer-events: none`; the heading, description and CTA are plain DOM stacked after it in source order at full token contrast, so legibility never depends on the animation; the CTA is an ordinary focusable `<button>` with a visible focus-visible ring. `prefers-reduced-motion: reduce` seeds a single sparse, static field (about 9 droplets) with no growth tick, no merging and no runoff — the permanent content-area hole in the fog mask is still present from the same `ResizeObserver` measurement, so the wiped patch around the copy is there from first paint, exactly as in full motion, just frozen."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 `--ns-accent`; hover alone (unselected) brightens border toward `--ns-accent` at 40% mix and label to `--foreground` — visibly distinct from both rest and selection. Tokens only (`--background --foreground --ns-muted --border --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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.",
        "rank": 6
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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/--ns-muted/--border/--ns-accent, plus --surface for header/hunk/widget bands); --ns-accent appears only on the toggle's focus ring, never as decoration. Zero dependencies, no canvas."
      }
    },
    {
      "name": "divider-mosaic-split",
      "type": "registry:ui",
      "title": "Divider Mosaic Split",
      "description": "A full-width section divider rendered in real NAPLPS 'separated mosaic' mode: the same 2-wide x 3-tall sextant sub-cell grid this registry's divider-teletext-mosaic already ships in contiguous mode, but every lit sub-cell block is drawn inset by a genuine luminance gap on all sides, so the band reads as a field of small floating tiles rather than a solid mosaic shape, painted by a left-to-right column-group write sweep.",
      "files": [
        {
          "path": "registry/core/divider-mosaic-split/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/divider-mosaic-split.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "hr",
          "naplps",
          "videotex",
          "prestel",
          "telidon",
          "alphamosaic",
          "sextant",
          "canvas",
          "monochrome"
        ],
        "instruction": "Build <DividerMosaicSplit height? className?> as a full-width <canvas> band wrapped in a <div role=\"separator\" aria-orientation=\"horizontal\">, a drop-in replacement for <hr>/border-top between page sections. SOURCE, NOT INVENTED: NAPLPS (North American Presentation Level Protocol Syntax, the videotex standard behind Prestel, Telidon, and early consumer online services' graphics mode) and the teletext alphamosaic character sets it descends from both address a 2-wide x 3-tall sub-cell grid per character cell — the same sextant addressing this registry's divider-teletext-mosaic ships in CONTIGUOUS mode (lit sub-cells flush against their neighbours). NAPLPS adds a second selectable attribute per mosaic run: SEPARATED — the identical 6-bit sub-cell code, but every lit block is drawn inset by a fixed gap on all four sides so the mosaic reads as small floating tiles instead of a solid silhouette. Real terminals let a page author toggle this per attribute byte; broadcasters used separated mode for graphs/diagrams needing legible sub-cell boundaries, contiguous for photo-like fills. THE DISCRIMINATOR from divider-teletext-mosaic: same 2x3 sextant sub-cell grid and same 6-bit addressing, but (a) every lit block is inset by a real gap instead of drawn edge-to-edge, and (b) the write sweep moves left-to-right across narrow column-groups on a 90ms cadence instead of top-to-bottom across full character rows on a 420ms cadence — a genuinely different real broadcast attribute mode and sweep axis, not a reskin. GEOMETRY: this band is a single row of character cells (unlike the teletext sibling's 6-row page) — sub-cell side = height / 3 px so exactly 3 sub-cell rows fill the band vertically; a character cell is 2 sub-cells wide x 3 tall, cellW = 2 * subCellSide; column count = floor(width / cellW). THE GAP IS THE ENTIRE IDENTITY: each lit sub-cell rect is inset by 18% of the sub-cell's side on all four edges (Math.round(subCellSide * 0.18), floored to a minimum of 1px so it never anti-aliases away at small card-scale cell sizes), drawn as literal unpainted --background between two --foreground/--ns-muted fills — never a --border stroke around the block (--border measures ~1.1:1 contrast in light theme and would make the gap structurally invisible exactly where it needs to prove separated mode differs from contiguous). CONTENT: each sub-cell's bit comes from a slow generative field — three non-commensurate traveling sine components summed over global sub-cell coordinates and the component's own continuously-advancing clock, normalized 0..1, thresholded at 0.5 — distinct frequency/phase constants from the teletext sibling's field so the two components' generative textures don't read as literal duplicates. ALIVE AT REST, THE MECHANIC: a write cursor sweeps left to right in fixed-width column-groups (GROUP_COLS=3 character cells per group), re-sampling the field into a fresh 6-bit pattern for every column in that group every COLUMN_INTERVAL_MS=90ms and holding it there until the group's next turn; the just-written group renders at full --foreground brightness for the duration of that 90ms window (tracked per-column via a write timestamp against the component's own continuously-advancing clock), then settles to the dimmer --ns-muted fill once its flash window elapses — a genuine two-tone identity, not a single flat color. After a full left-to-right pass (every column-group written once), an 800ms PAUSE_MS sync pause holds the completed pattern untouched before the sweep restarts from column 0 with a freshly re-sampled field state, so the visible mosaic content itself changes pass over pass, not just the cursor position. The component's clock never resets. On mount, and after every resize (a new column count leaves nothing painted otherwise), a full warm pass writes every column-group once at increasing clock time so the opening frame is never a blank band. RESTING LOOP: t0 is mid-sweep with roughly half the columns freshly written and the current column-group's flash visible at the leading edge, the rest of the band still blank background; 2.5s lands on a different sweep pass with content re-sampled from the field, either mid-sweep at a different column or inside the PAUSE_MS hold; 5s is a third distinct sweep/pause state, proving continuous unbounded cycling rather than a single reveal-and-stop. TOKENS: bg/fg/muted are read once from getComputedStyle(document.documentElement) against --background, --foreground, --ns-muted before the first paint, and re-derived on a documentElement class MutationObserver so a theme flip is live; --border is never read or used (unused per spec — it cannot carry the gap's job); --ns-accent never appears anywhere, this is a resting divider with zero interactive surface, no pointer stir, no hover state, and the write flash is a --foreground/--ns-muted luminance swap only, never accent-tinted. A ResizeObserver on the canvas re-measures and reflows on container size changes (not just window resize, since a divider's width is driven by its layout container); an IntersectionObserver pauses the rAF loop while the canvas is off-screen and resumes it (with a fresh last-timestamp so no giant delta-time jump plays on return) when it re-enters the viewport. prefers-reduced-motion freezes after running one additional full column-group write pass past the warm start (every column rewritten at least twice, field well clear of its cold-start state), then draws once with the step index parked in the sync-pause slot so no flash renders and every column is filled with the settled --ns-muted tone — the fully-written pass mid-PAUSE_MS hold, chosen because the separated-gap tile structure is legible edge to edge with nothing hidden, explicitly not t0's half-written sweep state. The render loop pauses on document.hidden and resumes cleanly on visibilitychange. Direct-DOM rAF, zero React state on the hot path, zero dependencies. A11Y: role=separator carries the divider's semantics with no accessible name required; the canvas is aria-hidden decoration; there is no keyboard surface because there is nothing to operate. Props: height (band height px, default 42; sub-cell side derives as height / 3), className."
      }
    },
    {
      "name": "divider-petscii-vu",
      "type": "registry:ui",
      "title": "Divider PETSCII VU",
      "description": "A full-width section divider printed as a PETSCII / C64 reverse-video VU meter band: every cell has exactly two states, paper or ink, never a density ramp, driven by a real 300ms-integrated amplitude envelope with a fast-attack/slow-decay peak-hold column and an AGC ceiling that keeps the meter legible at any input level.",
      "files": [
        {
          "path": "registry/core/divider-petscii-vu/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/divider-petscii-vu.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "hr",
          "petscii",
          "c64",
          "vu-meter",
          "reverse-video",
          "audio",
          "canvas",
          "monochrome"
        ],
        "instruction": "Build <DividerPetsciiVu source? cellSize? className?> as a full-width, ROWS=7-cell-tall <canvas> band wrapped in a <div role=\"separator\" aria-orientation=\"horizontal\">, a drop-in replacement for <hr>/border-top between page sections. THE DISCRIMINATOR — read this before anything else: this repo's spectrogram-ascii-bands selects a glyph off a continuous 10-step ' .:-=+*#%@' density ramp per cell, drawn in six alpha buckets. This component has exactly TWO states per cell and nothing between them — the literal C64 'reverse' screen-code bit: a cell is either an opaque --foreground rect covering the full cell box (paper and ink swapped) or nothing at all, letting the page's --background show through untouched. No alpha, no glyph substitution, no ramp — ever. ANALYSIS: real amplitude, not frequency. Each 30ms hop reads a block of samples (from an AnalyserNode via getFloatTimeDomainData, a (out: Float32Array) => void provider, or the built-in synthetic phrase) and computes its mean square; that feeds a one-pole exponential RMS integrator with a 300ms time constant (rmsEnergy += (blockMeanSquare - rmsEnergy) * (1 - exp(-hopSeconds/0.3))) — the ANSI C16.5 VU-meter integration time, not an arbitrary smoothing constant. AGC CEILING: a slow tracked ceiling normalizes the raw RMS into the 0..1 the bar reads — the ceiling rises fast (12% per hop) toward a louder block and falls slow (0.15% per hop) toward a quieter one, floored at a small minimum so silence never divides by ~0; normalized = min(1, raw/ceiling), gated to exactly 0 below a 0.035 threshold so idle noise doesn't flicker single columns. This mirrors spectrogram-ascii-bands' per-band adaptive FLOOR (which tracks a quiet baseline so structure stands out) with the inverse ballistics for the inverse job: a VU meter's whole purpose is knowing how close to clipping it is, so it tracks a loudness CEILING instead. PEAK HOLD: a second normalized value, peak, snaps up instantly whenever level reaches or exceeds it, dwells for 260ms, then decays back down at a fixed 0.62 units/second — the fast-attack/slow-decay ballistics a real analog VU needle actually has, and the one piece of state that keeps the band visibly different at t=0, 2.5s and 5s even during a level plateau. RENDER: cols = floor(canvas width / cellW), cellW measured via an offscreen canvas's measureText('MMMMMMMMMM').width / 10 in the canvas's own font family, re-measured after document.fonts.ready — the same house convention every other grid-metric ascii component in this repo uses. litCols = round(level * cols); every column j < litCols with j % 4 !== 3 (every 4th column is a fixed structural gap, independent of level, the classic hardware LED bargraph look that keeps a fully-lit meter from reading as one solid rect) gets all 7 rows filled with an opaque --foreground rect. The peak column (round(peak * cols)) is drawn separately: if it wasn't already lit by the level fill, only cells where (row + col) is even are filled — a checkerboard SUBSET of the same two-state system, not a third visual tier, so the marker reads as textured against the solid level fill without ever introducing gradation. Binary inversion structurally caps how much of the band can ever be lit at once, and the synthetic phrase's own quiet stretches (see SOURCE) keep most columns at rest most of the time, so the band never becomes a solid slab. SOURCE: omitted, the built-in deterministic synthetic phrase runs — a ~109bpm kick pulse (exponential decay envelope, syncopated accent pattern seeded per-hit via mulberry32) under a slow 8.5s phrase swell crossed with a longer 21s breakdown gate pulling the whole thing to near-silence periodically, so the quiet stretches are baked into the source signal itself rather than faked at the display layer. Nothing in this component calls getUserMedia; a caller who wants a real meter passes their own AnalyserNode or sample provider as source, and no sound plays or is requested on mount either way. Both source types warm up the RMS/AGC pair through 40 synthetic-scale hops before the first paint, so the opening frame is never a cold integrator climbing out of silence. TOKENS: fg is read once from getComputedStyle(canvas).color and re-derived on a documentElement class MutationObserver so a theme flip is live; no other token is used — this component has no interactive surface, so --ns-accent never appears, and --border is never used as a fill or stroke since it measures under 1.2:1 against --background in light theme and would be structurally invisible as the meter's own ink. prefers-reduced-motion runs the ballistics forward roughly 3.4s past warmup before the single paint — a deliberately non-t0 frame chosen because the phrase is mid-swell there (a legible run of lit columns) and the peak-hold marker has already separated from the live edge, so both of the component's two visual features are on screen at once; no rAF loop and no resize-triggered redraw beyond that one frame's token/geometry updates. The loop pauses on document.hidden and resumes on visibilitychange, discarding the frame that was already queued before hiding so the hop rate cannot double on resume. Direct-DOM rAF, zero React state on the hot path, zero dependencies, no text glyphs drawn at all — every 'cell' is a filled rect, which is the most literal possible reading of a reverse-video screen code. A11Y: role=separator carries the divider's semantics with no accessible name required; the canvas is aria-hidden decoration; there is no keyboard surface because there is nothing to operate, so the registry's tab-reachability check is correctly skipped. Props: source (AnalyserNode or sample provider, default the synthetic phrase), cellSize (row height px, default 12; the band is fixed at 7 rows), className."
      }
    },
    {
      "name": "divider-telephone-cord-delam",
      "type": "registry:ui",
      "title": "Divider Telephone Cord Delam",
      "description": "A full-width section divider modeled on thin-film delamination buckling, a compressive-stress film debonding from its substrate and popping upward into the real 'telephone cord' morphology: a roughly constant-width ridge that telescopes side to side as its failure front advances and periodically re-forks, never a straight blister.",
      "files": [
        {
          "path": "registry/core/divider-telephone-cord-delam/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/divider-telephone-cord-delam.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "hr",
          "buckling",
          "delamination",
          "thin-film",
          "canvas",
          "monochrome"
        ],
        "instruction": "Build <DividerTelephoneCordDelam height? className?> as a drop-in replacement for <hr>/border-top between page sections, rendered on a single full-width <canvas> inside a <div role=\"separator\" aria-orientation=\"horizontal\">. SOURCE: real thin-film compressive buckling — a film under stress, bonded to a substrate, debonds and pops upward; the debonding front does not run straight, it telescopes side to side as it advances, producing the well-documented 'telephone cord' delamination morphology (seen in DLC coatings, silicon nitride films, dried paint): a ridge of roughly constant width, a characteristic wander wavelength, and recurring branch points where the front forks. GEOMETRY: the ridge centerline is two domain-scrolling sine terms — wavelength set as a FRACTION of the measured container width (~5.2 wiggles across the full strip), not a fixed pixel constant, so the wander reads at the same relative scale whether the divider is narrow or wide; a second sine at an incommensurate ~2.35x frequency multiplier is summed in to keep the wander from reading as a clean single sinusoid. Ridge (perpendicular) width derives from the band height, clamped 6-22px, so thickness stays proportional to the strip's own short dimension. PROPAGATION, FOREVER: the centerline is a continuous function of elapsed time (phase always advancing via requestAnimationFrame) — there is no settled or finished state, the film keeps failing indefinitely, matching the source process rather than a one-shot reveal. RE-BRANCHING: on a fixed ~2.4s cadence, a short parallel branch ridge is computed deterministically from the clock (mulberry32 seeded by cycle index, not accumulated state) diverging from the main ridge for roughly a sixth of the strip's width, fading in and back out across its ~1.9s life via a sine envelope — the front catching and re-healing, recurring for as long as the component is mounted, never a single fork. SHADING (raised in both themes, without inverting): each ridge segment is drawn as three offset strokes along its centerline — a broad dim stroke offset toward the falling edge (shadow), a narrower brighter stroke offset toward the rising edge (highlight), and a thin crisp crest stroke at the true centerline — the same offset-stroke emboss technique used for CSS bevels. --foreground and --background are both parsed to RGB and compared by LUMINANCE at read time; the highlight stroke always uses whichever token is brighter and the shadow stroke whichever is darker, so the same geometric edge of the ridge stays lit and the same edge stays shadowed in both light and dark theme — assigning the strokes positionally to the raw tokens instead would flip which edge reads as highlighted between themes, a real bug this project shipped once already. The flat, un-buckled film between ridge features is a faint 1px --ns-muted baseline, always present — never --border, which measures near-invisible as a fill/stroke against --background in light theme. --ns-accent never appears; nothing here is interactive. TOKENS AND PAINT ORDER: color fields start unassigned and are read unconditionally from getComputedStyle(document.documentElement) before the first resize/draw, re-read on a MutationObserver watching documentElement's class — draw() is gated on both a 'sized' and a 'tokensReady' flag so nothing paints before that first read completes. HOST: DPR-capped backing store (min(devicePixelRatio,2)), ResizeObserver on the wrapper (not window.resize) to catch layout-driven width changes, one requestAnimationFrame loop paused via IntersectionObserver (threshold 0) when scrolled offscreen and via visibilitychange when the tab is hidden. REDUCED MOTION: prefers-reduced-motion freezes the clock at t=3.2s and never starts the rAF loop — a deliberately chosen non-t0 frame where the main wander already spans the strip and a branch fork (cycle index 1, spawns at 2.4s, life progress ~0.42 at 3.2s) is clearly mid-development, not a degenerate edge-of-cycle state. DIFFERS FROM growth-ring/cambium-lay/lamina-dome: those are layered-growth DEPOSITION, material added in rings outward over time; this is compressive-buckling PROPAGATION, a failure front advancing and re-forking through a film that is already fully formed — opposite physics in the same 'material process over time' register. DIFFERS FROM craze-rule: craze-rule's fracture is a one-shot IntersectionObserver-armed reveal that draws in once and settles into a brief idle creep; this divider's front never finishes propagating and re-branching for as long as it is mounted, with no arrival trigger and no rest state to reach. A11Y: role=separator carries the divider's semantics and needs no accessible name; the canvas is aria-hidden decoration; there is no keyboard surface because there is nothing to operate, and the registry's tab-reachability check is correctly skipped for a display-only component like this one. Props: height (band height in px, default 56) and className. Zero dependencies, canvas 2D only."
      }
    },
    {
      "name": "divider-teletext-mosaic",
      "type": "registry:ui",
      "title": "Divider Teletext Mosaic",
      "description": "A full-width section divider rendered as a real teletext / videotex ALPHAMOSAIC graphics band: each character cell is a 6-bit code across a 2-wide x 3-tall sub-cell 'sextant' grid, the actual broadcast alphamosaic addressing (distinct from this registry's 2x2 quadrant block dividers and 2x4 braille-dot components), painted row by row on a fixed cadence the way a real teletext page arrives off the broadcast signal.",
      "files": [
        {
          "path": "registry/core/divider-teletext-mosaic/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/divider-teletext-mosaic.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "hr",
          "teletext",
          "videotex",
          "alphamosaic",
          "sextant",
          "ceefax",
          "canvas",
          "monochrome"
        ],
        "instruction": "Build <DividerTeletextMosaic cellSize? className?> as a full-width <canvas> band, ROWS=6 cells tall, wrapped in a <div role=\"separator\" aria-orientation=\"horizontal\">, a drop-in replacement for <hr>/border-top between page sections. SOURCE, NOT INVENTED: real broadcast teletext (World System Teletext / CEEFAX-era decoders) never had a per-pixel framebuffer — each character cell holds a 6-bit code selecting one of 64 patterns across a fixed 2-wide x 3-tall sub-cell grid, addressed by position (top-left, top-right, mid-left, mid-right, bottom-left, bottom-right), the real 'alphamosaic' graphics mode of the standard. THE DISCRIMINATOR — read this before anything else: this is a genuinely different sub-cell grid from anything else in the registry. Quadrant-block dividers quantize a 2x2 sub-grid (four Unicode block-element combinations); braille components address a 2x4 dot grid. This is 2x3 — six sub-cells, a different real broadcast protocol's addressing, not a reskin of either. GLYPH ROUTE, STATED: Unicode does define a full sextant block (U+1FB00-U+1FB3B, Symbols for Legacy Computing), but this repo's mono stack (GeistMono / GeistMono Fallback / ui-monospace) does not reliably carry that range, so sub-cells are drawn as filled canvas rects directly rather than risking tofu from unverified glyph coverage — which is also what a real teletext decoder chip did: it lit sub-cell segments off the 6-bit code, it never asked a font for a glyph. CONTIGUOUS MOSAIC: sub-cells are drawn edge-to-edge with no gap between them, matching the real 'contiguous mosaic' mode broadcasters used for photo-like graphics (as opposed to 'separated mosaic', which insets a gap per block) — cellW is derived once via an offscreen canvas's measureText('MMMMMMMMMM').width / 10 in the canvas's own font family (the same house cell-metrics convention every grid-based ascii component in this repo uses), re-measured after document.fonts.ready; cellH is fixed to cellSize. CONTENT: each sub-cell's bit comes from a slow generative field — three non-commensurate traveling sine components summed over global sub-cell coordinates and the component's own continuously-advancing clock, normalized 0..1 with a 1.2 gamma — thresholded at 0.5 with a small per-sub-cell bias drawn from a scattered 2x3 ordered-dither table (spread +/-0.42, scaled by a 0.32 dither strength), the same discipline real videotex picture-mosaic dithering used to fake tone gradients out of a strictly binary 6-bit grid. ALIVE AT REST, THE MECHANIC: a real teletext page doesn't arrive all at once — it paints row by row as it's received off the broadcast VBI signal. A write cursor advances one row every 420ms, re-sampling the field into a fresh 6-bit pattern for every cell in that row and holding it there until the row's next turn; after all 6 rows have been rewritten, a 600ms sync pause elapses (page fully painted, untouched, never blank) before the sweep restarts from row 0 — the component's own clock never resets, so no two passes sample the same field state and the mosaic is never identical from one page-load to the next. The just-written row gets a brief top-edge hairline in --foreground fading from 60% alpha to 0 over 220ms — the one-frame flicker of a row actively being received — skipped entirely under prefers-reduced-motion. On mount, and after every resize (a new column count leaves nothing painted otherwise), a full warm pass writes every row once before the first paint so the opening frame is never a blank grid. TOKENS: fg is read once from getComputedStyle(canvas).color (driven by the .text-foreground class on the canvas) and re-derived on a documentElement class MutationObserver so a theme flip is live; the write-flash hairline modulates that same color's alpha only, never a second color. --border is never used as a fill or stroke (it measures under 1.2:1 against --background in light theme and would be structurally invisible as the mosaic's own ink); --ns-accent never appears — this is a resting divider with no interactive surface. prefers-reduced-motion freezes after running one additional full row-write pass past the warm start (every row rewritten at least twice, field well clear of its cold-start state), then draws once with no flash hairlines and no further updates — chosen because it's the mosaic at its most legible full-page state, never the sync pause (the least eventful frame, though not empty) and never t0's cold warm-fill. The render loop pauses on document.hidden and resumes cleanly on visibilitychange. Direct-DOM rAF, zero React state on the hot path, zero dependencies. A11Y: role=separator carries the divider's semantics with no accessible name required; the canvas is aria-hidden decoration; there is no keyboard surface because there is nothing to operate. Props: cellSize (row height px, default 14; the band is fixed at 6 rows, cell width derives from monospace metrics), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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, --ns-muted, --border, --surface and --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 `--ns-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 `--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": "due-slip",
      "type": "registry:ui",
      "title": "Due Slip",
      "description": "A ruled read-receipt card styled as a library date-due slip: the first time each collaborator opens a shared doc, their initials and an HH:MM timestamp stamp into the next free row, so row position alone is reading order. No hover, no collapsing '+N' pile.",
      "files": [
        {
          "path": "registry/core/due-slip/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/due-slip.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "read-receipt",
          "presence",
          "list",
          "ledger",
          "stamp",
          "timestamp",
          "aria-live",
          "accessibility",
          "collaboration"
        ],
        "instruction": "Build a read-receipt ledger styled as a physical library book's date-due slip, not an avatar stack. The component (`DueSlip`) is fully controlled: a single `readers` prop, an array of `{ id, name, initials?, readAt }` in arrival order — index 0 is whoever opened the document first. It holds no ledger state of its own, only transient UI state (which rows are mid press-animation, whether the full-name view is open, the live-region message). MECHANISM: one governing scalar, arrival index (a reader's position in the array), derives every visual on that row — row position is plain document order in a real `<ol>`, and ink alpha eases from 1.0 down to a hard 0.55 floor by the 14th row (`alphaFloorRow`, default 14) so a long ledger's tail stays legible instead of dissolving to nothing. Stamp rotation is seeded independently, from a hash of the reader's `id` and clamped to +/-2.5deg, specifically so re-sorting or filtering the same list never reshuffles a person's tilt — bigger than that clamp would read sloppy, exactly zero would read typeset rather than stamped. Each new arrival (a reader present now but absent from the previous render — the initial mount does NOT animate, those rows are history) plays a 320ms ease-out-expo press-in (`scale` 1.06 -> 1.0, via the CSS individual `scale`/`rotate` properties rather than a compound `transform` string, so the static tilt and the transient press never fight each other) on just its ink chip, nothing else on the row. RULED CARD: a 12px-radius bordered card, 1px `--border` rules between rows (row height a 4px-rhythm multiple), showing up to `visibleRows` (default 6) rows. Once readers exceed that, the primary card's first N rows stay exactly where they are — row position is permanent, chronological, and never reflows — and a second card appears 4px down-and-right behind it, bearing a real integer (`readers.length - visibleRows`), the library-card equivalent of starting a fresh card once the old one is full. That integer is the ONLY thing about the overflowed readers shown outside the full list; it is deliberately not an avatar pile, because a pile's '+N' hides identity behind a hover and collapses exactly when the record is most worth reading — this counter never claims to BE the record, the full ordered list already exists underneath and is one interaction away. EXPAND: the card's title bar is the only focusable control in the whole component (a real accessible-name-bearing button, aria-expanded reflecting state) — activating it (click or Enter/Space) reveals every reader's full name alongside their initials and removes the row cap (the list gets a max-height + scroll instead of growing unbounded). Critically, activating that control only ever OPENS it — it is not a toggle-on-click — and Escape (while it holds focus) is the only way to close it again, so a verifier or user who has already pressed it once before checking the expanded state still finds it open, not flipped back. That's legible, not silently dead: once expanded, the trigger's accessible name and its visible hint both switch to state the real close path ('Esc to collapse'), the chevron glyph (which read as a toggle affordance) is replaced by that hint, and the row's hover fill drops — a second click is deliberately a no-op, but nothing about the control still claims to be a live toggle. A11Y: the slip is a real `<ol>`, each reader a real `<li>` whose accessible name reads 'Read by {name}, {HH:MM}' (matching the visible initials + `<time datetime=...>` content), so identity and order are carried entirely by DOM text and structure, never recoverable only through hover. A new arrival's stamp announces once through a polite, visually-hidden live region, phrased the same way. Colors are strictly the five shared tokens (`--background`, `--foreground`, `--ns-muted`, `--border`, `--ns-accent`); `--ns-accent` appears nowhere but the expand control's `:focus-visible` ring — never on a stamp, never as a persistent hover fill. Under `prefers-reduced-motion`, the press spring is stripped outright (belt-and-suspenders: a JS `matchMedia` check plus a CSS media-query fallback) but the row's static rotation and alpha stay, since a fixed tilt is not itself motion — stamps simply render at rest, fully legible, on the very first paint after they arrive. Pure DOM+CSS, zero dependencies, no canvas."
      }
    },
    {
      "name": "edge-burnish-glaze",
      "type": "registry:ui",
      "title": "Edge Burnish Glaze",
      "description": "A full-width section divider modeled on leather-edge burnishing: a slicker sweeps back and forth along the edge on a constant-speed triangle wave, a per-cell gloss field saturating fast under its ~18px contact zone and decaying by how long it's gone unvisited, so mid-sweep cells hold the most sheen while the turnaround the stroke is heading back toward reads dullest right before it arrives. An uneven gradient, never a flat 'shiny bar.'",
      "files": [
        {
          "path": "registry/core/edge-burnish-glaze/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/edge-burnish-glaze.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "hr",
          "leather",
          "burnish",
          "canvas",
          "monochrome",
          "specular"
        ],
        "instruction": "Build <EdgeBurnishGlaze height? className?> as a drop-in replacement for <hr>/border-top between page sections, rendered on a single full-width <canvas> inside a <div role=\"separator\" aria-orientation=\"horizontal\">. SOURCE: real leather-edge burnishing — a wood or bone slicker swept repeatedly along a raw leather edge, friction compacting and heat-setting the surface fibers into a glassy, translucent slick sheen; the finish is not permanent from one pass, worked spots left unrevisited lose their sheen (wax film dries, dust settles) and need reburnishing, which is why a real slicker sweeps the whole edge back and forth rather than finishing once. MODEL: a 1D Float32Array of cells spans the measured width, CELL_PX = 6px, count = floor(width/6), each cell holding a scalar gloss in [0,1]. The stroke position is a true constant-speed triangle wave across the full measured width, SWEEP_SPEED = 140px/s (period T = 2*width/140, so a 320px-wide divider completes one full there-and-back sweep in ~4.6s), direction flipping only at the two ends. Every frame: any cell whose center falls inside the stroke's 18px-wide contact zone (+/-9px) gains at GLOSS_GAIN 0.18 per ~1/60s frame (GAIN_RATE = 10.8/s applied as gain_rate*dt, frame-rate independent), saturating a freshly-passed cell to ~1.0 within a fraction of one pass; every cell simultaneously decays at a PERIOD-RELATIVE rate (not a flat per-second constant, which reads as invisible at real widths since a full period only eats a few percent of a flat -0.004/s decay) sized so a cell left completely unvisited for one full sweep period T drops to roughly 28% of full gloss (DECAY_FRAC_PER_PERIOD = 0.72 of range lost), comfortably under DULL_THRESHOLD at any measured width. Below DULL_THRESHOLD = 0.35 a cell renders matte, --ns-muted base only; above it, a --foreground overlay's opacity and glow height scale linearly with (gloss - 0.35)/(1 - 0.35), with the peak value kept close to raw --foreground itself (not an intermediate blended tone) so the distance from dull cells stays large in both light and dark theme, where the headroom above --background differs. WHY THE GRADIENT IS UNEVEN, DERIVED NOT ASSERTED: because gain saturates a visited cell almost immediately, the gradient is shaped entirely by the GAP since each cell's last visit. On a true constant-speed triangle wave an interior cell x is crossed twice per period T, with gaps 2x/speed and T-2x/speed between the pair; at the two ends those crossings collapse into one quick back-to-back pass at the reversal followed by a full period T of decay before the next pass (the longest gap in the system), while at mid-span the two gaps are equal and half as long (T/2 each). Net: mid-sweep cells hold the most gloss on average, and the far turnaround — longest since its last visit, the one the stroke is currently heading back toward — reads dullest right before the stroke arrives to refresh it, giving the viewer a second, slower cue for where the stroke is about to go next. PRE-ROLL: starting every mount from a blank field would read as freshly unwrapped rather than a worked edge; three full sweep periods are simulated synchronously at mount (a few hundred fixed 1/60s substeps, done before first paint and before the live rAF loop takes over) so t0 already shows mid-range gloss with a light dulling gradient rather than zero, and because three periods is an exact multiple of the sweep period the stroke lands back at one end at t0. The live rAF loop then continues the same fixed-timestep simulation from that pre-rolled clock using real elapsed dt (clamped to 100ms) each frame, redrawing the full cell field plus a radial-gradient highlight centered on the current stroke position every frame — pure luminance (--foreground fading to transparent), never --ns-accent, the single most repeated defect on record for this kind of moving highlight. TOKENS AND PAINT ORDER: --ns-muted and --foreground are read unconditionally via getComputedStyle(document.documentElement) before the first resize/draw and re-read on a MutationObserver watching documentElement's class; draw() is gated on both a 'sized' and a 'tokensReady' flag so nothing paints before that first read completes. HOST: DPR-capped backing store (min(devicePixelRatio,2)), a debounced (120ms) ResizeObserver on the wrapper — debounced because reseeding on every tick of a drag-resize would both burn cycles and repeatedly teleport the stroke back to x=0 — which reseeds the cell array, recomputes the width-relative decay rate, and re-runs the pre-roll at the new width, rather than window.resize; one requestAnimationFrame loop paused via IntersectionObserver (threshold 0) when scrolled offscreen and via visibilitychange when the tab is hidden. REDUCED MOTION: prefers-reduced-motion never starts the rAF loop; instead it simulates three full pre-roll periods plus one quarter-period more (exploiting the triangle wave's periodicity to continue the same array rather than resimulating from scratch) and freezes on that frame, named SWEEP_MIDSPAN — the stroke sits at the exact center of a traversal, not at either turnaround, with a clear gradient already formed from recently-swept bright cells to cells due for a revisit; this is the most information-dense single frame available, deliberately not the t0 turnaround frame. A11Y: role=separator carries the divider's semantics and needs no accessible name; the canvas is aria-hidden decoration; there is no keyboard surface because there is nothing to operate. Props: height (band height in px, default 48) and className. Zero dependencies, canvas 2D only."
      }
    },
    {
      "name": "eink-waveform-ghost",
      "type": "registry:ui",
      "title": "E-ink Waveform Ghost",
      "description": "An empty-state panel rendered as an idle e-ink display: a coarse grid of cells continuously replays real electrophoretic drive waveforms on a scattered, ever-changing subset of itself, each replay briefly shooting through to full black or full white before settling, while the whole panel periodically runs a synchronized full-refresh flash to clear ghosting.",
      "files": [
        {
          "path": "registry/core/eink-waveform-ghost/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/eink-waveform-ghost.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "empty-state",
          "canvas",
          "e-ink",
          "waveform",
          "print",
          "display",
          "ambient",
          "monochrome"
        ],
        "instruction": "Build <EinkWaveformGhost title? description? ctaLabel? ctaHref? className? style?>, a self-contained empty-state block (bezel panel + heading + description + optional CTA link) whose panel is rendered as an idle e-ink display, sourced from real electrophoretic display driving rather than an invented pixel texture. On mount, measure the panel's own inner screen area via getBoundingClientRect and derive a coarse cell grid from its SMALLER dimension: cellPx = clamp(minDim/20, 6px floor), cols = round(w/cellPx) clamped to a 6-cell floor, rows = round(h/cellPx) clamped to a 4-cell floor (roughly 20x14 at the default 4:3 panel). Each cell is assigned a fixed target grey in [0,1] once, at grid-build time, from a small baked landscape glyph (a mid-grey sun disc, two overlapping mountain-ridge triangles at different tones, a dark ground band, open sky) evaluated at that cell's normalised position — this glyph is the settled image and it never changes; only the DISPLAY of it moves.\n\nEvery cell independently replays its own drive waveform on a randomized schedule: draw the next replay interval per cell as -1.9 * Math.log(Math.random()) seconds (Poisson-like, mean 1.9s), so a scattered handful of cells are always mid-replay at any sampled instant even though the underlying image is static — this stand-in for e-ink residual-charge drift and partial self-refresh is what carries 'alive at rest', independent of the full-refresh event below. Each replay is exactly four fixed 60ms steps (240ms total), switched discretely frame to frame with NO easing between steps (a real waveform steps, it does not tween): step 1 holds the cell's pre-replay value, steps 2 and 3 both jump to whichever pole — full 0 (--background) or full 1 (--foreground) — sits FARTHER from that pre-replay value (the shoot-through overshoot: `overshoot = value >= 0.5 ? 0 : 1`), and step 4 lands on the cell's true baked target grey. Cell colour at any instant is `rgb = lerp(backgroundRGB, foregroundRGB, value)`, backgroundRGB/foregroundRGB read from `--background`/`--foreground` via getComputedStyle(document.documentElement) at mount and re-derived on a MutationObserver watching documentElement's class (no colour literal anywhere except a pre-first-read JS fallback triple, matching the sibling card-dot-gain-screen's pattern).\n\nLayered ON TOP of that continuous per-cell layer, not instead of it: every T_refresh = 14s +/- 3s (jittered per mount via `Math.random()` so multiple instances on one page desync rather than pulsing together), the whole panel runs a synchronized full-refresh — 3 alternating full-panel flashes (value 1, 0, 1) at 90ms each, 270ms total, drawn as one solid fillRect ignoring per-cell state — then every cell's value snaps to its target grey simultaneously and every cell's per-cell schedule is reset from that instant, before the next refresh interval is redrawn. Grid geometry recomputes and per-cell schedules rebuild from scratch on every ResizeObserver-fired resize (so a resized panel gets a freshly re-gridded glyph, not a stretched old one), and colours re-derive on the class MutationObserver.\n\nprefers-reduced-motion freezes on the state where zero cells are mid-waveform — every cell simply painted at its own settled target grey, drawn once with no rAF loop ever started (the described `STATIC_TIME`-equivalent moment: the clearest possible read of the resting page, deliberately not t0-mid-transition). The rAF loop is also gated by an IntersectionObserver (paused off-screen, resumed on re-intersection with a fresh token read before the first frame) — no paint happens before the first getComputedStyle call on mount or on any resume path. Optional interaction: a pointerdown on the canvas maps to the cell under the pointer and, if that cell is currently idle, restarts its local waveform in place (like marking the panel with a stylus) — its overshoot pole and eventual settle target are computed exactly as the ambient schedule does, so the interaction never introduces `--ns-accent` or any hue, only luminance already inside the --background/--foreground range; ignored entirely during a full-refresh flash and unbound under reduced motion. `--ns-accent` appears nowhere in the canvas, only on the optional CTA link outside it. Explicitly theme-agnostic by construction: because cell values interpolate directly between --background and --foreground, the paper/ink relationship (pale paper, dark ink) is the literal read in light theme and automatically inverts in dark theme (pale cells against a dark base) — that inversion is intentional and must not be 'fixed' with a hardcoded white paper base; the waveform-stepping and shoot-through mechanic itself is identical in both themes. Canvas is `w-full h-full` with a DPR-capped (max 2) backing store recomputed on every resize. On unmount: cancel the rAF, disconnect the ResizeObserver/MutationObserver/IntersectionObserver, remove the pointerdown listener, remove the reduced-motion media query listener. No dependencies."
      }
    },
    {
      "name": "empty-state-braille-orbit",
      "type": "registry:ui",
      "title": "Empty State Braille Orbit",
      "description": "A small ambient icon for empty states: an unbroken stroke traced through braille's 2x4 sub-cell dot addressing along a closed 3:2 Lissajous orbit, sampled by arc length so the curve never gaps or clusters.",
      "files": [
        {
          "path": "registry/core/empty-state-braille-orbit/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/empty-state-braille-orbit.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "empty-state",
          "icon",
          "ascii",
          "braille",
          "mono",
          "ambient"
        ],
        "instruction": "A small ambient icon for empty states: a single unbroken stroke traced through Unicode braille's 2x4 sub-cell dot addressing (U+2800 base + bitmask) along a closed 3:2 Lissajous curve, x = A*sin(0.35t), y = B*sin(0.233t + PI/2), giving each glyph cell an addressable 2-wide/4-tall dot sub-grid. The curve is sampled by ARC LENGTH, never a fixed time-step: each new point is found by bisecting the step in curve-parameter t until it lands within 0.6 dot-units of the previous point, guaranteeing no two consecutive dots ever gap by more than one dot — fixed-t sampling would gap at high curvature and cluster at low curvature. A ~140-sample ring buffer of the most recent curve points is remapped to glyph cells and sub-dot bits every frame, giving a slow comet-orbit read (bright head, fading tail) distinct from loader-braille's bar/dot progress fill, which spends the same addressable grid on a 1D tone instead of a 2D trajectory. The head-to-tail fade is an alpha ramp, not color arithmetic: every lit cell is drawn once in --ns-muted at full alpha, then again in --foreground at an alpha equal to the freshest sample touching it, so the two-pass crossfade works unmodified even if a token is ever redefined as non-grayscale. Amplitude eases 0-100% over 1.2s ease-out-expo once on mount; after that the orbit is a steady, undamped, energy-conserving loop with a ~27s period that never settles or stops. Cell metrics come from an offscreen measureText call after document.fonts.ready, and both tokens are re-read via getComputedStyle on a MutationObserver watching documentElement's class, so both themes render correctly without a remount. prefers-reduced-motion freezes on one baked ~90-dot curved arc (starting past the axis crossing near t=0, so the frozen segment reads as a legible orbit fragment rather than a straight dead line) — no ring buffer, no rAF. Purely decorative: the canvas is aria-hidden; the empty state's real accessible content (a heading and description) lives as ordinary DOM alongside it, never encoded in the glyph art. Props: cellSize (default 14, braille glyph cell px), size (default 120, icon box px, square), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-muted, --ns-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-mezzotint",
      "type": "registry:ui",
      "title": "Empty State Mezzotint",
      "description": "A small ambient icon for empty states built on real mezzotint printmaking: the plate is rocked to a dense burr that would print solid black, then worked SUBTRACTIVELY as light is scraped and burnished in. The registry's only subtractive glyph mechanic, where every other component builds density up from empty.",
      "files": [
        {
          "path": "registry/core/empty-state-mezzotint/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/empty-state-mezzotint.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "empty-state",
          "icon",
          "ascii",
          "mono",
          "ambient",
          "subtractive"
        ],
        "instruction": "Build <Mezzotint cellSize? size? className?>, a small ambient icon for empty states sourced from real mezzotint intaglio printmaking, not an invented texture. The historical process: a toothed rocker first covers the whole copperplate with a dense burr that would print solid black if inked and pulled as-is; the image is then made SUBTRACTIVELY, scraping and burnishing that burr smooth wherever light is wanted, working from dark to light. This is the registry's only subtractive glyph mechanic — every other density-ramp glyph component in this registry builds density UP from an empty grid; this one starts near-solid and takes it away. Two fields drive every cell in grid space: a slow three-term summed-sine scrape field (same family as the registry's plasma/flow fields, run 3-5x slower) decides how much burr survives at that cell, mapped through a soft threshold-and-edge (not a hard cutoff, since a burnisher leaves a graded tone, not a vector line) so roughly a quarter to a third of the grid ever carries any ink at rest — the scraped, light region is the dominant read and the surviving burr is a clear minority, never a full-frame black slab. Within the surviving-burr region, a per-cell sin-dot hash reseeded a few times per second and crossfaded by smoothstep supplies a trembling grain texture standing in for the rocker still working the plate — this, not the slow-moving scrape boundary, is what keeps the icon visibly alive at rest. Rendering is a two-pass crossfade: --ns-muted at fixed alpha under every surviving-burr cell for the lower-contrast bulk of the burr, then --foreground layered on top at alpha equal to each cell's live grain density for the deepest, most-recently-rocked ink; scraped cells get no fill at all, so the untouched --background canvas literally is the scraped plate in both themes. --ns-accent never appears in the plate — it is interaction-only chrome, used at most on a 'create your first item' button in the surrounding empty-state UI. prefers-reduced-motion freezes on STATIC_TIME = 8 (simulated seconds), not t=0 — at t=0 the scrape boundary sits nearer its long-run mean and the surviving burr region is thinner and less legible; t=8 lands on a wider, still soft-edged burr region with clearly mixed muted/foreground tone, the field's most structured single frame. No ring buffer, no rAF once frozen. Cell metrics come from an offscreen measureText call after document.fonts.ready, both tokens are re-read via getComputedStyle on a MutationObserver watching documentElement's class, so both themes render correctly without a remount. Purely decorative: the canvas is aria-hidden; the empty state's real accessible content (a heading, description, and any call-to-action) lives as ordinary DOM alongside it, never encoded in the glyph art. Props: cellSize (default 14, glyph cell px), size (default 140, icon box px, square), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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, --ns-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/--ns-muted/--border/--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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, --ns-muted, --border and --ns-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": "envelope-window",
      "type": "registry:ui",
      "title": "Envelope Window",
      "description": "An OAuth-style consent screen built as a physical envelope: the user's real account data sits on a sheet underneath, and each granted scope die-cuts a window through an opaque envelope layer so what the app can see is literally what shows through.",
      "files": [
        {
          "path": "registry/core/envelope-window/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/envelope-window.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "consent",
          "oauth",
          "privacy",
          "switch",
          "toggle",
          "scope",
          "permissions",
          "accessibility"
        ],
        "instruction": "`<EnvelopeWindow appName scopes={EnvelopeScope[]} onAllow? onDeny? />` renders a two-layer consent card. Layer one is a data sheet in normal document flow: one row per scope, each row rendering that scope's real `fields` (`{ label, value }[]`) in `--ns-muted` text — the user's actual name, email, repo names, calendar entries, never lorem placeholders, because consent is exactly the moment to show the real thing being exposed. Layer two is an opaque envelope (`--background` fill, `1px --border`, a soft inset shadow for paper depth) stacked on top via absolutely-positioned rects measured off each row with `ResizeObserver` + `useLayoutEffect`, so windows track row geometry through reflow. Each scope owns one die-cut rect whose governing scalar is aperture 0..1: it renders as two flaps (top half, bottom half) meeting at a centre seam, each an independent `clip-path: inset()` that shrinks the flap toward its own outer edge as aperture rises — at aperture 0 the two flaps fully cover the rect (window sealed, row hidden beneath an opaque leaf); at aperture 1 both flaps clip to zero area (window cut through, the real row shows and is hit-testable through the gap). The transition runs on `clip-path` alone, 240ms `cubic-bezier(0.16,1,0.3,1)` (ease-out-expo) — never a fade, the reveal is the cut. A subtle inset rim shadow on the rect wrapper gives the die-cut edge depth regardless of aperture. Denying (the Deny button) closes every non-required window by reversing the same aperture scalar back to 0 for each; it never introduces a different animation. `scope.required` scopes render as glassine windows instead: a translucent `color-mix(--background 55%, transparent)` pane plus an SVG diagonal-hatch pattern sit permanently over the row at partial opacity — visible, textured, legibly 'you can always see this', never a plain clear cut and never closable, so the app's true access minimum reads honestly on the sheet itself. The Allow button's label is computed, not authored: `Allow access to ${openCount} of ${total} areas`, where `openCount` counts every currently-open scope (required scopes always count). ACCESSIBILITY IS THE MECHANISM: each scope is a real `role=switch` button, `aria-checked` mirrors its aperture exactly (open=true/closed=false, no separate visual-only state), `aria-labelledby` names the scope label and `aria-describedby` points at a string built from `scope.sample` ('Shows: 14 events this week') so a screen-reader user hears what's behind the window before deciding, and required scopes add 'Required by this app.' to that same description plus `aria-disabled` (a real, focusable, still-announced control — not a `disabled` attribute that would drop it from the tab sequence). The underlying data sheet is never removed or `aria-hidden` regardless of which windows are visually sealed — every row stays one coherent, always-present document for assistive tech, because the switches (not pixel occlusion) are the single source of truth for what's granted; the decorative window/glassine rects carry `aria-hidden` and `pointer-events:none` so they never shadow the real row in the accessibility tree or the hit-test order. An `aria-live=polite` region announces the current Allow label as scopes change. `prefers-reduced-motion: reduce` drops the clip-path/track/thumb/button transitions to instant swaps — every aperture is still reachable, nothing sticks mid-peel. Colors are tokens only (`--background --foreground --ns-muted --border --ns-accent`); `--ns-accent` appears solely as the on-state switch track, the Allow button's border/tint, and focus rings — never a decorative fill. DOM+SVG+CSS only, zero dependencies, no canvas. Distinct from consent-scope-redact: that component is one flat sentence where a switch strikes out the spans it governs in place; this component is two physically stacked layers where a switch cuts (or reseals) a window through an opaque sheet to a completely separate, always-intact data layer underneath — closing a window here protects a specific rectangle of real content, not a run of inline text."
      }
    },
    {
      "name": "equation-kidney-cam",
      "type": "registry:ui",
      "title": "Equation Kidney Cam",
      "description": "A section divider whose midpoint tick rides a lookup table built from the real equation-of-time curve (an equation-marchante watch's kidney cam compressed to an 11s loop), sliding, slowing, and reversing direction with four unevenly-spaced zero-crossing brighten-pulses per cycle, never a symmetric back-and-forth.",
      "files": [
        {
          "path": "registry/core/equation-kidney-cam/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/equation-kidney-cam.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "section-divider",
          "hr",
          "svg",
          "cam",
          "horology",
          "lookup-table"
        ],
        "instruction": "Build <EquationKidneyCam className?> as a drop-in replacement for <hr>/border-top between page sections. THE DATA: a module-scope constant MONTH_EOT holds 12 real, almanac-sourced equation-of-time values in minutes (Jan-Dec, 1st-of-month) — the sundial-minus-clock offset that swings between roughly -14 and +16 minutes across the real year. A periodic Catmull-Rom spline is run through those 12 anchors (each segment's four control points wrap via modulo so December connects back into January, making the 12 anchors read as a 13th, closing point) and resampled at 5 points per segment for a 60-entry EOT_LUT, computed once at module load with plain arithmetic — no trig at runtime. THE ANIMATION: a single rAF loop reads a wall-clock elapsed time, maps it to a phase via a fixed CAM_PERIOD_MS=11000 (documented in-code as a ~2.87-million-times compression of the real 1-year period, purely for card legibility, not a simulated calendar), multiplies phase by 60 to get a float LUT index, and linearly interpolates between the two adjacent LUT entries — that interpolated value IS the follower's reading for this frame, no re-evaluation of the spline. THE OFFSET: the follower's on-screen position is `50% + (value / LUT_MAX_ABS) * 22%` applied as the tick element's `left`, where LUT_MAX_ABS is the LARGER-magnitude extreme found in the actual resampled table (near +16.4) — so the positive swing reaches the full +-22% of the divider's half-length while the smaller-magnitude negative swing (near -13.6) falls proportionally short of it; this asymmetry must never be forced to a symmetric +-22%, it has to fall out of the real numbers. Because the anchors are unevenly spaced in when they cross zero, the resulting motion is NOT a sine — verify visually that the drop from most-negative to zero is faster than the climb from zero to most-positive. THE PULSE: every frame compares the current sample's sign against the previous frame's; on a sign flip, restart a `ns-eqcam-pulse` CSS animation on the line element (set style.animation='none', read offsetWidth to force reflow, then set the animation string again) that lifts opacity from 0.43 to its resting 0.35 over 400ms — this fires 4 times per 11s cycle, at whatever intervals the real curve's own zero-crossing dates produce (never evenly spaced; do not add a timer-based even-interval pulse as a shortcut). STRUCTURE: outer <div role=\"separator\" aria-orientation=\"horizontal\" data-eqcam> at a fixed 28px height (every other dimension, e.g. the 14px tick height, derives from that one constant, satisfying 'derive geometry from the container's smaller dimension' for a component that is mostly width) containing (1) a 1px-tall line div at `background:var(--foreground); opacity:0.35` — deliberately NOT `--border` (whose ~1.1:1 light-theme contrast would make the follower's motion imperceptible) and NOT full-strength `--foreground` (this is a separator, not a headline) — (2) an absolutely positioned 1px-wide tick div, taller than the line, at `var(--foreground)` opacity 0.9, whose `left` is rewritten every frame, and (3) a decorative aria-hidden SVG built once from the identical LUT as a closed polar path (angle = i/60 of a turn, radius = base + LUT value * scale) — opacity 0 at rest, fading to ~0.18 only on `:hover`/`:focus-within` via CSS, absolutely positioned and centered so it can never affect layout. LIFECYCLE: an IntersectionObserver starts/stops the rAF loop as the divider enters/leaves the viewport, but never resets `startTime` — re-entering resumes the correct real phase, it does not restart the cycle, satisfying 'must NOT respond to hover/scroll by changing rate or resetting phase'. REDUCED MOTION: skip the rAF loop entirely and call the same `applyOffset` once with `EOT_LUT[MIN_INDEX]` — MIN_INDEX is computed once at module load as the argmin of the whole LUT, i.e. the real-world most-negative point (the mid-February analogue, the cam's tightest inward point), not t0, which could land anywhere including a near-invisible near-zero offset. COLOUR: every stroke/fill is `var(--foreground)` at an authored opacity (0.35 line, 0.9 tick, 0.18 ghost-on-hover, 0.43 pulse peak) or `var(--background)`; `--ns-accent` never appears anywhere in this component — it is a passive divider with zero interaction states to mark, and the spec explicitly forbids the brighten-on-cross effect from touching accent. A11Y: role=separator carries the semantics, no accessible name needed; every visual element is aria-hidden since there is nothing to operate. DIFFERS FROM: craze-rule, which is a ONE-SHOT scroll-triggered reveal (a propagating fracture that arrives once via IntersectionObserver, then idles) — pick craze-rule instead when the divider should announce a section boundary with a single arrival moment; equation-kidney-cam is the opposite, an already-settled line with continuous, non-uniform ambient drift that is running identically whether or not the viewer ever scrolled past it, and it never has a one-time 'arrival' state. DIFFERS FROM: divider-telephone-cord-delam, whose motion is a physical coil-unwinding metaphor with a single dominant oscillation frequency — equation-kidney-cam's signature is specifically an ASYMMETRIC, sign-changing, non-periodic-looking sweep sourced from astronomical data, with unevenly-timed pulses being the tell. Zero dependencies, no canvas — DOM+SVG+CSS only; the only per-frame JS is one LUT lookup, one lerp, one style write, and an occasional animation restart."
      }
    },
    {
      "name": "eval-regression-shear",
      "type": "registry:ui",
      "title": "Eval Delta Table",
      "description": "Two eval runs compared case by case: a table of baseline, candidate and signed delta, sorted by delta, with a diverging bar per case drawn off a labelled zero axis.",
      "files": [
        {
          "path": "registry/core/eval-regression-shear/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/eval-regression-shear.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "eval",
          "regression",
          "benchmark",
          "data-viz",
          "table",
          "diverging-bar",
          "mono",
          "dashboard",
          "comparison",
          "accessibility"
        ],
        "instruction": "Build <EvalDeltaTable cases tieEps? scale? label? className?> as a per-case comparison of two eval runs. DATA: `cases` is {id, baseline, candidate}[] with scores in 0..1; delta = candidate − baseline; rows are sorted by delta descending so gains sit at the top and regressions at the bottom. MARKUP: a real <table> with a visually hidden <caption> (the `label` prop) and five columns — Case (mono id), Base, Cand, 'Δ score' (the bar track), Δ (the signed delta, three decimals, using a real minus sign). Widths come from a <colgroup> with table-fixed so the bar track and the axis under it line up exactly; the id cell truncates inside a block span. CHART: the track cell is position:relative with a 1px --border zero rule at its centre spanning the full row height, so consecutive rows form one continuous zero line. Each case draws a single 7px-tall bar in --foreground at 70% alpha: positive deltas start at the centre and run right, negative deltas end at the centre and run left, width = |delta| / scale * 50% with a 1px minimum. Sign is encoded by side of the zero line only, never by colour. `scale` is the axis half-width and defaults to the largest |delta| in the data rounded up to 0.05; |delta| below `tieEps` (default 0.005) draws no bar at all and just reads as the printed 0.000. AXIS: a <tfoot> row prints the caption 'Δ score · candidate − baseline' beside a 1px --border rule under the track with −scale / 0 / +scale mono tick labels. HEADER: a mono line above the table reading 'n cases · n improved · n regressed · n unchanged'. No interaction, no animation, no canvas, no dependencies — the whole component is static markup that themes from tokens alone, and every number a bar encodes is also printed in the row so the table is readable by a screen reader without any chart-specific description."
      }
    },
    {
      "name": "expansion-gap-breather",
      "type": "registry:ui",
      "title": "Expansion Gap Breather",
      "description": "A full-height divider between two independently-sized layout regions, sourced from continuous-welded-rail breather switches: the seam's tapered-blade teeth interlock and slide apart as a rendered rail temperature swings the gap between 4px and 22px on a 14s cycle, actually resizing the element that sits between its neighbours rather than drawing over a fixed rule.",
      "files": [
        {
          "path": "registry/core/expansion-gap-breather/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/expansion-gap-breather.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "svg",
          "rail",
          "thermal",
          "ambient",
          "interlock",
          "layout"
        ],
        "instruction": "A full-height divider, `role=\"separator\" aria-orientation=\"vertical\"`, whose own inline width is the animated value — not a fixed-width element with motion painted inside it, so it genuinely pushes its flex/grid siblings as it moves. Width oscillates on a pure function of elapsed real time since mount: `originMs = performance.now()` is captured once in the effect, then every frame `t = ((performance.now() - originMs) % 14000) / 14000`, `gap = 13 - 9 * cos(2*PI*t)`, giving a 14-second full breathe cycle between 4px (hot, rail expanded, gap nearly closed) and 22px (cold, rail contracted, gap open); the cosine phase means t0 is always the 4px trough and the divider opens monotonically for the first 7s of every cycle, so t0/2.5s/5s samples are always distinct and moving in one direction, matching the round-9 legibility requirement. Because the loop reads a modulo of elapsed real time rather than an accumulated per-frame delta, pausing when an IntersectionObserver reports the divider off-screen and resuming later never desyncs the phase — it simply stops and resumes painting the same continuous curve. The teeth are two interlocking STROKED combs, not a rectangular slot and not a filled mass: each comb is a straight baseline rail at its own x plus a tapered tongue reaching TOOTH_DEPTH (18px) past that baseline in every OTHER pitch slot down the height (`i % 2 === slotParity`, left comb parity 0, right comb parity 1) — so the two combs' tongues occupy disjoint y-ranges by construction and a tongue tip can cross past the opposite comb's baseline (that crossing IS the interlock) with zero collision, since nothing of the other comb exists at that y. Both paths are drawn `fill=\"none\" stroke=\"var(--foreground)\"` at 1.6px (a CSS custom property resolved natively by the SVG, no getComputedStyle read needed since nothing here is canvas) inside a small fixed-width (66px) SVG overlay, centred (`left: 50%`, `translateX(-33px)`) over the spacer div so the tongues can extend past the spacer's own animated width without the spacer claiming that space in layout — the spacer is what pushes siblings, the overlay is purely visual. Tooth pitch is `clamp(height / 8, 14, 24)` and tooth count is `clamp(round(height / pitch), 5, 18)`, both derived from the container's own height via a ResizeObserver that only re-triggers on an actual >0.5px height change (guarding against the animation loop's own per-frame width writes) so 5-18 distinct interlocking fingers always read at card scale regardless of how tall the divider's container is. `--foreground` is used deliberately instead of `--border` because `--border` sits at ~1.1:1 contrast in light theme and this is meant to read as a structural divider, not a hairline; strokes keep the total visual weight thin (separator weight) rather than the solid slab a filled, edge-anchored comb would produce. `autoplay: none` — the breathe is driven entirely by elapsed real time, there is nothing for a pointer/press/scroll input to do, and per the round-8b bug a press-latched irreversible state would need `none` anyway even if there were interaction. Under `prefers-reduced-motion` the rAF loop never starts; the divider paints once at `gap = 13` (MID_GAP), the average width with tongue tips crossing 5px past the opposite baseline — the most-structured single frame, showing both the open channel and the interlocking geometry rather than either fully-closed or fully-open extreme. Cleanup on unmount cancels the rAF and disconnects both the ResizeObserver and IntersectionObserver. Zero dependencies, DOM+SVG+CSS only, no canvas, colours limited to `--foreground`."
      }
    },
    {
      "name": "extrusion-die-cut",
      "type": "registry:ui",
      "title": "Extrusion Die Cut",
      "description": "A card-scale processing/loader widget modelled on pasta die-face cutting: a textured rope continuously extrudes from a die aperture and a guillotine blade sweeps across it every ~2.2s, severing off a segment that falls and settles into a loose floor stack. The loop is unbounded and shows something always extruding and something periodically finished, without a single digit or bar ever drawn.",
      "files": [
        {
          "path": "registry/core/extrusion-die-cut/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/extrusion-die-cut.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "canvas",
          "extrusion",
          "food-process",
          "mechanical",
          "ambient",
          "generative"
        ],
        "instruction": "`<ExtrusionDieCut label? progress? className? />` renders a card-scale canvas simulating pasta die-face cutting. TWO CLOCKS, deliberately decoupled: rope growth (`ropeLen`) advances continuously every frame by `advancePxS * dt` up to a fixed `cutSpan` (die-to-blade distance, `containerWidth * 0.62`, recomputed on every resize); once `ropeLen` reaches `cutSpan` a blade stroke begins (`strokeT` counts 0 to `CUT_STROKE_S = 0.22s`) — during the stroke rope growth is paused, and the blade is drawn as a vertical line traveling top-to-bottom across the rope's span over that 220ms, a genuine sweep with a visible start and end position each frame, never an instant swap (the round-9 rule that a discrete event's transition must show departure and arrival). When the stroke completes, a `FallingSegment` is spawned at the die with length `ropeLen - 4` (a 4px severed gap short of the blade, so the cut reads as a genuine separation rather than a flush join), `ropeLen` resets to 0, and `advancePxS` is re-rolled from `cutSpan / CUT_INTERVAL_S` (`CUT_INTERVAL_S = 2.2s`) times a fresh +/-8% jitter factor (`CUT_JITTER`) — re-derived from the span rather than hardcoded, so span and cadence never drift apart on resize, and re-rolled per segment so the cadence is never perfectly metronomic (real die-cutters hunt slightly around their nominal rate). ROPE TEXTURE (die-drag striation): the rope is drawn as a run of `STRIATION_PITCH_PX = 3`-wide fill rects, each cell's alpha perturbed +/-6% (`STRIATION_VARIANCE`) by a stable hash of its ROPE-LOCAL cell index (`hash1`, a deterministic sine-hash, not `Math.random`) — because the hash key is the rope's own local coordinate rather than wall-clock time, the ripple reads as a fixed material property that scrolls into view as the rope grows, never as per-frame shimmer. FALLING SEGMENTS accelerate under a constant `GRAVITY_PX_S2 = 40px/s^2` (deliberately gentle for card scale, not real-world gravity) until they reach a floor y at `containerHeight * 0.88`, then mark `settled` and are re-laid into a loose staggered stack (each settled segment's y stepped up by `0.9 * ropeThickness` per stack position, x staggered `(i % 3) * 6px`) by `settleStack()`, called every step. Once more than `MAX_STACK = 6` settled segments exist, the oldest are given a `fadeStart` timestamp and fade their alpha to 0 over `STACK_FADE_S = 0.4s`, then are dropped from the array entirely — the stack never overflows the card. PROGRESS: an optional `progress` (0..1) prop scales `advancePxS` by `clamp(0.3 + progress*1.4, 0.3, 1.7)` on every re-roll — a higher progress means more of the span extrudes before the FIXED-cadence cut fires (so segments read longer/fuller at higher progress), but `CUT_INTERVAL_S` itself is never touched by `progress`; the cut cadence is the component's mechanical identity and must not become a progress-driven variable. TOKENS: `readTokens()` reads `--foreground`/`--background` via `getComputedStyle(document.documentElement)` and returns `null` if either is empty (stylesheet not yet applied); `boot()` retries on the next rAF until tokens resolve, and NOTHING is drawn before that first successful read — the same guard every other token-driven canvas in this registry uses, closing the exact 'paint before first token read' failure the builder brief calls out on the rAF-start, ResizeObserver AND IntersectionObserver-resume paths specifically (all three call `draw`/`kick` only after `tokens` is non-null). `--ns-accent` is never referenced anywhere in this file; the die, rope, blade and segments are all `--foreground` at varying `globalAlpha`, and there is no pointer interaction to justify accent chrome. REDUCED MOTION: instead of running the live loop and freezing wherever it happens to land, `buildStaticFrame()` constructs the target state directly (deterministic, seeded `mulberry32` PRNG for the stack's minor length variance) — rope at `cutSpan - 4` (severed stub, matching the live segment's 4px gap convention), blade stroke frozen at 60% (`CUT_STROKE_S * 0.6`), one freshly-cut segment sitting at the die not yet fallen, and three pre-settled stack segments behind it — the single frame that shows die texture, blade mid-travel, and a completed segment simultaneously, deliberately not the empty t=0 frame. `ResizeObserver` on the root recomputes `cutSpan` and re-derives the static frame in reduced-motion mode so it never shows a state from a stale width; `IntersectionObserver` (default threshold) pauses the rAF loop while offscreen and resumes it (picking up any theme flip that happened while hidden) on re-entry; `MutationObserver` on `documentElement`'s class watches for a theme flip and redraws immediately with the same sim state. DPR capped at 2. Zero dependencies, DOM+Canvas 2D only, no WebGL. The canvas is `aria-hidden` and `pointer-events-none`; `autoplay: mode: none` because it runs entirely on its own internal two-clock timer with nothing for a synthetic pointer driver to do."
      }
    },
    {
      "name": "facer-stamp-flip",
      "type": "registry:ui",
      "title": "Facer Stamp Flip",
      "description": "An ambient batch-processing loader (no total count, only continuous throughput) built as a letter-facing and cancelling machine's orientation stage: mixed-orientation envelopes travel a single-file lane, a fixed gate reads each one's stamp corner, and any envelope not already stamp-corner-up-right rotates into that orientation as it crosses, picking up a brief tap of ink as it continues.",
      "files": [
        {
          "path": "registry/core/facer-stamp-flip/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/facer-stamp-flip.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "batch",
          "upload",
          "ambient",
          "dom",
          "raf",
          "mail",
          "orientation",
          "indeterminate"
        ],
        "instruction": "An ambient batch-processing loader sourced from a letter-facing and cancelling machine's orientation stage: mixed-orientation envelopes feed in a single-file horizontal lane, a fixed sensor gate at 55% of the lane's width reads each envelope's stamp-corner rotation, and a mechanical rotator turns only the ones that aren't already stamp-corner-up-right into the common 0deg orientation before they continue on to a brief cancel tap. Real facer-canceller throughput runs on the order of 30,000 letters/hour (~8.3/s) — far above the paint-rate floor — so the rendered feed is deliberately decoupled from that real rate: one envelope spawns every 1.1s (SPAWN_INTERVAL_MS) and takes 3.3s (TRAVERSE_MS) to cross the full lane, off the left edge to off the right. Each envelope spawns with a rotation drawn from a FIXED period-4 sequence (0, 180, 90, 270deg, cycling by spawn index, never Math.random()), so exactly 1-in-4 envelopes is already correctly oriented and passes the gate untouched — a visible no-op case, not just a flip every time, which is what makes the flip read as a decision rather than a tic. Rendering is DOM + CSS only: each envelope is a pair of nested divs appended imperatively to a lane container inside one effect (not React state, to keep the per-frame position write off the render path) — an outer posEl whose `transform: translateX()` is mutated directly every animation frame by one shared requestAnimationFrame loop tracking real elapsed time (never Math.random-jittered, capped at 100ms/frame), and an inner rotEl holding the envelope's rotation as a plain CSS transform, so a browser-owned CSS transition can animate the flip independently of the continuous per-frame position mutation. Geometry derives from the container's smaller dimension via a ResizeObserver on the root: envelope height = 0.22 * min(containerWidth, containerHeight), width = height * 1.7 (a personal-envelope proportion); the gate line's x-position is 55% of the container's own full width (not the smaller dimension), since lane width and envelope size are deliberately different axes. Each envelope renders three distinct luminance tiers, all mixes of `--foreground` into `--background` (never `--border`, which is reserved for the gate line at rest): a 25%-mix fill so the envelope body itself is legible as the primary subject (not just structure), a 45%-mix 1px edge that does the shape-defining work a `--border` stroke is not allowed to do, and an 82%-mix `clip-path` triangle in the top-right for the stamp corner, clearly separated from both — verified in node against both theme token sets before shipping (light: body ~1.7:1, edge ~1.7:1 over body, stamp ~5.9:1 over body; dark: body ~2.0:1, edge ~2.0:1 over body, stamp ~5.7:1 over body). The stamp rotates together with the body as one rigid unit so it visibly occupies a different screen corner at each of the four spawn rotations and lands top-right only once resolved to 0deg. As an envelope's leading (rightmost) edge crosses the gate x-position: if its rotation isn't already 0, rotEl gets `transition: transform 340ms ease-in-out` and its transform is set to `rotate(0deg)`, and the gate line itself (a 2px var(--border) bar spanning the lane's full height) is flashed — `transition: none`, background snapped instantly to `var(--foreground)`, a forced reflow, then `transition: background-color 120ms ease-out` decaying back to `var(--border)` — marking the read event as a luminance-only brighten, never tinted with --ns-accent. If the envelope is already at 0deg the same gate flash fires but at a fainter peak (`color-mix(in srgb, var(--foreground) 45%, var(--border))`) rather than being skipped entirely — the no-op case gets its own small distinguishing beat so it reads as a resolved pass-through rather than becoming invisible next to the flips. 90ms after an envelope reaches 0deg (immediately for a no-op, FLIP_MS + 90ms for a flip), a small circular ink mark (15% of envelope height, `var(--foreground)`, no shake or splatter filter — this cancel tap is a brief secondary consequence, not the climax) snaps to full opacity over the stamp corner, holds 400ms, then fades over 160ms via a plain opacity transition and is left at 0. On mount, four envelopes are pre-seeded at staggered lane progress (75%, 42%, 25%, 5% of the way across, oldest/most-progressed first, consuming the first four slots of the rotation sequence) so t0 already shows several envelopes at different phases and rotations, at least one of them pre-gate at a non-zero rotation, rather than starting from an empty lane. Any pre-seeded envelope whose leading edge is already past the gate x-position at construction time is built already-resolved — its rotation snapped straight to 0deg with `gated` pre-set true, no flash fired and no cancel scheduled — rather than firing its gate event on the first processed animation frame: the machine has been running before the demo mounted, so that envelope's flip already happened off-frame, and without this the 75%-progress and 42%-progress pre-seeds would otherwise both fire a gate flash within the same ~73ms window right at t0, a cadence a viewer can't track. Envelopes are removed (DOM node detached, any pending gate/cancel timers cleared) once their progress passes 1.05 — fully off the right edge. An IntersectionObserver on the lane pauses the driving clock's elapsed-time accounting while scrolled offscreen and resyncs its time base on return, exactly as an ambient always-running loader should, with no catch-up burst of motion. Under prefers-reduced-motion the rAF loop and all timers never start; instead the component renders the GATE_FLIP freeze frame directly and once: the gate line held at its flashed `var(--foreground)` peak (no decay transition), one envelope at 24% lane progress with a 270deg rotation and no ink (not yet arrived, visibly different rotation from the mid-flip envelope), one envelope positioned with its leading edge exactly on the gate line at rotEl `rotate(90deg)` — the literal halfway point of a 180deg -> 0deg flip — and one envelope at 82% lane progress already at 0deg with its ink mark held at full opacity, `transition: none` (already cancelled, ahead of the gate) — pre-gate, mid-flip and post-cancel states all legible together in one frame. Cleanup on unmount cancels the rAF handle, disconnects the ResizeObserver and IntersectionObserver, clears every envelope's pending setTimeout handles, and detaches every appended envelope node. Zero colour literals: `var(--background)`, `var(--foreground)`, `var(--border)` and `color-mix()` blends of those tokens only — no `var(--ns-accent)` anywhere, since this is an ambient loader, not interaction chrome. Props: label (accessible status text, default 'Normalizing files', applied as `aria-label` on the root's `role=\"status\"` wrapper), className. Pure DOM + CSS + requestAnimationFrame, zero dependencies."
      }
    },
    {
      "name": "faq-answer-depth-gutter",
      "type": "registry:ui",
      "title": "FAQ Accordion",
      "description": "A plain FAQ accordion: real button semantics, one answer open at a time, a smooth height transition and a thin accent rail marking the open question.",
      "files": [
        {
          "path": "registry/core/faq-answer-depth-gutter/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/faq-answer-depth-gutter.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "faq",
          "accordion",
          "disclosure",
          "section",
          "marketing"
        ],
        "instruction": "Build <FaqDepthGutter items allowMultiple? defaultOpen? className?> where items is {id, question, answer: ReactNode}[]. STRUCTURE: each item is a row containing a real <button data-faq-trigger aria-expanded aria-controls={panelId}> inside an <h3>, plus a <div data-faq-panel data-open role=\"region\" aria-labelledby={triggerId}> panel holding the answer. Rows are separated by a single 1px --border rule on `.row + .row`, and every rule shares one left edge — nothing is inset per state. OPEN/CLOSE: opening sets the panel hidden=false, height:0, forces a reflow (void el.offsetHeight) and eases height to el.scrollHeight over OPEN_MS 260ms cubic-bezier(0.22,1,0.36,1); on transitionend the height is set to auto so later reflow or a text-wrap change stays correct. Closing pins auto back to the measured px, forces a reflow, eases to 0 over CLOSE_MS 200ms, and applies `hidden` only after that transition ends so a screen reader never sees a half-collapsed region. allowMultiple defaults to false: opening one closes the previous. INDICATOR: the open row carries a 2px x 18px --ns-accent rail in the root's left padding, absolutely positioned at a fixed offset so adjacent open rows always render as two discrete marks, never one continuous bar. No measurement, no offscreen mirror, no ResizeObserver, no per-item bar chart. ACCESSIBILITY: real button semantics, so Tab/Enter/Space are native and there is no keydown handler; aria-expanded on the trigger, aria-controls to the panel id, aria-labelledby back to the trigger; data-open exposed on both row and panel for probing; a visible :focus-visible outline in --ns-accent at 3px offset. REDUCED MOTION: prefers-reduced-motion (checked via matchMedia at toggle time) toggles the panel between 0 and auto with no transition, so nothing can be left stuck mid-animation. TOKENS ONLY: --border for the row rules, --foreground for questions, --ns-muted for answers and the plus/minus sign, --ns-accent for the open-row rail, the :focus-visible ring and the hovered question; both themes come out of the same variables. REST STATE: the demo mounts with one answer open, so the idle frame reads unmistakably as an FAQ. Zero dependencies."
      }
    },
    {
      "name": "fax-line-slip",
      "type": "registry:ui",
      "title": "Fax Line Slip",
      "description": "A document-preview / attachment-thumbnail loading state reproducing a Group 3 fax handshake and page transmission: two waveform-rendered handshake tones, then the page builds in as horizontal scan lines top to bottom, with one deliberate paper-slip shear partway down the frame before the lines resume true.",
      "files": [
        {
          "path": "registry/core/fax-line-slip/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/fax-line-slip.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loading",
          "canvas",
          "fax",
          "print",
          "document",
          "scan",
          "ambient",
          "monochrome"
        ],
        "instruction": "Build <FaxLineSlip label? className? style?>, a self-contained canvas-rendered loading/connecting state for a document preview or attachment thumbnail, sourced from the ITU-T T.30 Group 3 fax handshake and page-transmission sequence plus the documented thermal-fax paper-slip mechanical fault, not an invented spinner texture. Root is `relative h-full w-full overflow-hidden bg-background` with `role=\"status\" aria-busy=\"true\"`, an sr-only status label (default 'Connecting'), and an absolutely positioned `aria-hidden` canvas filling the host (`w-full h-full`, DPR-capped at 2, backing store sized off `getBoundingClientRect`).\n\nOne cycle runs four phases on a SINGLE canvas that is never cleared mid-build (content accumulates, like a page actually being received), driven by elapsed time via `performance.now() - cycleBase`, never frame count:\n\n1. Handshake, fixed 2200ms (`BURST1_MS=500, GAP_MS=500, BURST2_MS=1200`): a waveform trace occupies the top 15% of the frame height. First 500ms draws a tight-period sine trace (15 cycles across the container width) standing in for the 1100Hz CNG tone, revealed left-to-right as `revealFrac = t/500` (redraw the band's background then stroke the partial trace every frame — the trace visibly builds, it does not appear instantly). Next 500ms the band is silent (cleared to background, nothing drawn). Final 1200ms draws a tighter-period trace (28 cycles) standing in for the 2100Hz CED tone, same left-to-right reveal logic. This handshake plays at its real, human-followable pace — it is the ONE audible-register detail rendered visually and is deliberately not sped up.\n\n2. Scan build: once the handshake ends, clear the whole canvas to `--background` once, then commit exactly one scan line every 45ms (`LINE_INTERVAL_MS=45`, ~22 lines/sec — real G3 at 9600bps is ~5-6 lines/sec, but rendering 220 lines at that real rate would take ~40s, too slow for a resting loop, so this is compressed for pace, not decoupled for alias-avoidance). Line count is derived from the container's own smaller dimension: `lineCount = clamp(round(height/2), 30, 220)`, each line's raster row height `rowH = height/lineCount`. Track a `lastLineDrawn` cursor and, each frame, advance it up to `floor(scanElapsed/45)` clamped to `lineCount-1`, drawing each newly-committed row exactly once and leaving already-drawn rows untouched (this is what makes the build read as content arriving, not a repaint).\n\nEach row's content is procedurally baked 'scanned text', deterministic per `(lineIndex, seed)` so every raster row belonging to the same text line agrees: `lineSpacing = clamp(minDim/28, 5, 9)` px groups raster rows into text lines; a per-line hash (`hash01(lineIndex*1.7+seed, 3.1) < 0.12`) marks ~12% of lines as blank paragraph gaps; surviving lines get a right-margin `widthFrac` in [0.45, 0.9] from a second per-line hash; within a line's top half band (`bandFrac <= 0.5`, giving each text line visible gaps between it and the next), fill `letterCell`-wide buckets (`letterCell = clamp(minDim/70, 2, 5)`) left-to-right from a 5% left margin to the right edge, each bucket inked via `hash01(lineIndex*5.13+seed, bucket*3.71+seed) > 0.34` — word-like dashes, not a solid bar. `seed` is reseeded every cycle (`seed = cycleIndex*811.7`) so no two received pages match.\n\nPaper slip: once per cycle, at reseed time, pick `slipStartLine = clamp(floor(lineCount*(0.55+rand()*0.20)), 0, lineCount-3)` and `slipOffsetPx = ±(14 + rand()*8)` from a per-cycle seeded RNG. The 3 rows in `[slipStartLine, slipStartLine+3)` are drawn with their x positions shifted by `slipOffsetPx`; every other row draws at zero offset. Because each row is committed once, in order, with no easing between rows, the shift appears as a hard, instantaneous discontinuity exactly when the scan front reaches that band, and the very next committed row snaps back to true — never repeating within the cycle.\n\n3. Hold, fixed 1800ms: the fully composed page sits static, no redraw needed (canvas already holds the final pixels).\n\n4. Reset, fixed 200ms: the frame does not fade — each frame fill a `--background` rect from y=0 to `height * min(1, wipeElapsed/200)`, wiping the page away top to bottom like a fresh page feeding in. At the end of this phase the cycle wraps: reseed (`seed`, new slip position/offset), clear the canvas, and the handshake replays.\n\nColour: `--foreground` ink over a `--background` field, both read via `getComputedStyle(document.documentElement)` at mount and re-derived on a `MutationObserver` watching `documentElement`'s class (no colour literal anywhere except the pre-first-read JS fallback pair) — dark marks on light stock in light theme, and dark theme inverts which token paints which without swapping the token names. `--ns-accent` never appears anywhere in the canvas. Verify at card scale in light theme specifically that the paper-slip band's edge stays legible — it needs the same value contrast as every other row, only a position shift, so check it isn't reading as imperceptible against the already-lower light-theme base contrast.\n\nThe rAF loop is paused via `document.visibilitychange` and an `IntersectionObserver` (both gate a `wake()` that only restarts the loop if not reduced-motion and currently visible), and a `ResizeObserver` triggers a full `resize()` that recomputes geometry, restarts `cycleBase`, reseeds, and clears the canvas (a resized panel gets a freshly regenerated page, not a stretched old one). `prefers-reduced-motion: reduce` never starts the rAF loop at all: it reseeds once, clears, and synchronously commits every line via the same `commitLine` path used by the live build, freezing on the fully-scanned page WITH the paper-slip artifact baked into its band — the most information-carrying frame, showing both the completed content and the fault that gives the component its identity. No interaction — this is a passive connecting state. On unmount: cancel the rAF, disconnect the ResizeObserver/MutationObserver/IntersectionObserver, remove the visibilitychange listener and the reduced-motion media query listener. No dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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": "fiche-step-repeat",
      "type": "registry:ui",
      "title": "Fiche Step Repeat",
      "description": "A thumbnail grid that populates like a microfiche step-and-repeat camera: one cell exposed per raster step with a brief flash, then an index strip types in once the sheet is complete.",
      "files": [
        {
          "path": "registry/core/fiche-step-repeat/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/fiche-step-repeat.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "gallery",
          "grid",
          "thumbnail",
          "loading",
          "raster",
          "micro-interaction"
        ],
        "instruction": "Build <FicheStepRepeat images? reductionRatio? className?> as a card-scale DOM grid (CSS grid, no canvas) modeling a COM (computer output microfilm) step-and-repeat camera exposing a fiche sheet. Grid geometry: cols = clamp(round(containerWidth / 64px), 4, 7), rows = clamp(round(containerHeight / 64px), 3, 5), measured via a ResizeObserver on the root and re-derived on every resize, giving the closest card-scale abstraction of a real 7x14 fiche raster. A single requestAnimationFrame loop, driven off one elapsed-time clock from cycle start, walks four phases in order and writes DOM directly per cell (no React state per frame — 12 to 35 tiny cells re-rendering through React every frame is wasted work): (1) GROW — cells populate strictly in raster order (row-major, left to right then down), one cell every 850ms; the instant a cell's turn arrives it snaps from zero-opacity/no-geometry to full geometry in the same frame (a real exposure, never a fade-in), and only its luminance animates on top via an inline CSS filter:brightness() ramp — 120ms up to a peak of settled-value+12% (dark theme) or +8% (light theme, less headroom against near-white thumbnails; the amplitude is read live off a MutationObserver on documentElement's class so a theme flip mid-loop takes effect without a remount), then 280ms relaxing back to brightness(1); (2) TYPE — once every cell has populated, a header string like \"24× · 35 FRAMES\" (reductionRatio × cellCount) types in left to right over a fixed 600ms window, monospace, character-by-character; (3) HOLD — the complete sheet (all cells populated at rest, full header text) holds for 1.4s; (4) RESET — the header clears instantly and cells blank out in the SAME raster order they filled in (not reversed, not faded), spread evenly across a fixed 400ms regardless of cell count, each individual cell's removal an instant snap — this mirrors nothing in the real process (which never runs backward) and exists purely so the loop's restart reads as \"the same sequence, undone\" instead of a jump cut. The whole cycle then repeats unbounded. Each cell is two nested elements: an outer box with a `border border-border` outline and no fill, always visible even when empty (a bare separator-token box, never treated as a fill or stroke color), and an inner content layer holding either a caller-supplied `<img>` (from the `images` prop, an array of {src, alt} placed in raster order; cells beyond the supplied count get the placeholder) or a built-in placeholder silhouette (a faint outlined SVG frame/mountain glyph in text-ns-muted at low opacity) — that inner layer is what actually snaps from opacity 0 to opacity 100 the frame its cell is populated, and what the JS-driven brightness filter targets during its flash window. Hover/focus on a real image cell brightens it slightly via a plain CSS transition on the image itself (luminance only, never --ns-accent, never mixed into the highlight) purely to confirm it's a real target — it must never restart, pause, or otherwise perturb the raster cadence, which runs autonomously regardless of pointer input. Root carries role=\"list\" with a visually-hidden aria-labelledby summary naming the reduction ratio and frame count (read once, not re-announced per cell); each cell is role=\"listitem\"; supplied images keep their own alt text, the built-in placeholder is aria-hidden since it's decorative filler, not content. On mount, resume from an IntersectionObserver: when the root re-enters the viewport the rAF clock is re-armed cleanly (cycleStart reset to the resuming frame) rather than fast-forwarding through a stale elapsed offscreen time. prefers-reduced-motion skips the whole loop and renders once, directly, on the full-sheet HOLD frame: every cell populated at rest brightness, header fully typed — the most information-dense, most structured frame, deliberately not t0's near-empty sheet. Cleans up the rAF handle, the ResizeObserver and the IntersectionObserver, and the MutationObserver on unmount. Zero color literals — every color comes from Tailwind utility classes bound to --border, --surface, --ns-muted and --foreground tokens; the only literal numeric values are geometry, timing and the brightness filter multiplier. Zero dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa",
          "error": "#ea001d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717",
          "error": "#ff6369"
        }
      },
      "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 --ns-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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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-ns-accent, border-ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": "flamegraph-ascii-frames",
      "type": "registry:ui",
      "title": "Flamegraph ASCII Frames",
      "description": "A CPU-profile flame graph laid out in monospace cells (one row per stack depth, frame width proportional to sample count, self-vs-total time encoded as ink weight), where clicking a frame rezooms the whole graph to that subtree.",
      "files": [
        {
          "path": "registry/core/flamegraph-ascii-frames/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/flamegraph-ascii-frames.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "flamegraph",
          "profiler",
          "performance",
          "ascii",
          "mono",
          "data-visualization",
          "keyboard-navigation",
          "developer-tools"
        ],
        "instruction": "Build <FlamegraphAsciiFrames tree? rowHeight? className?> as a sampling-profiler flame graph drawn in monospace character cells — pure DOM text and CSS, no canvas and no rAF loop. INPUT is a sample tree of `{name, self, children?}`. ROLLUP: `total(n) = n.self + sum(total(children))`, computed once per tree in a `useMemo` that also assigns every node a positional key ('0', '0.1', '0.1.0') and indexes it by that key — the key encodes its own ancestry, so the breadcrumb chain is just its prefixes and no separate parent bookkeeping exists. LAYOUT is explicitly NOT squarified or slice-and-dice (that is treemap-ascii-partition's mechanism): depth d occupies row d, the zoom root spans all `cols` columns, and a child of a frame spanning [x, x+w) gets `childW = max(0, round(w * total(child)/total(parent)))`, laid left to right in the parent's own declared child order — stable, never sorted by size, so a call site keeps its position across renders. The children run only occupies `round(w * childrenTotal/total(parent))` columns; the remainder to its right is the parent's own self time and is left blank, which is what produces the stepped silhouette rather than a filled block. Children whose `childW < 3` are dropped and accumulated into ONE trailing marker frame carrying their summed sample count (`⋯ 54`), so narrow frames become a single honest marker instead of a row of unreadable one-column slivers; the rounding residual is absorbed by the LAST laid frame so the run ends exactly on the computed span rather than drifting a column per child, and the marker may borrow up to its own label length from the blank self-time gutter to stay readable at the 3-column floor. GLYPHS: a frame renders '│' at its left column followed by its name truncated with '…' to `w-1` columns; the rest of its row is blank, so the graph is mostly negative space. INK is bucketed self-fraction `s = self/total(frame)` at thresholds 0.02 / 0.10 / 0.30 into `text-border` / `text-ns-muted` / `text-foreground` / `text-ns-accent` — those four buckets are the ONLY encoding: no fills, no hue ramp, no gradients, no glow. Because a leaf is 100% self, accent ink lands exactly on the tips of the silhouette, which is where the CPU time actually is; the fold marker has no self-fraction of its own and therefore sits outside the four buckets at `--ns-muted`, and is static text rather than a control since there is nothing to zoom into. READOUT: one mono line beneath the graph, above a `border-border` rule, printing `name  self 12.4%  total 38.1%  1,204 samples` for the hovered/focused frame — percentages are taken against the CURRENT zoom root's total — or the zoom root's own totals at rest. INTERACTION: hovering or focusing a frame keeps it and its whole ancestor chain at their bucket ink while every other frame drops to `text-border` over a 120ms color transition (`motion-reduce:transition-none`), and pointer-leaving the graph restores rest ink and eases the readout back to the zoom root. ZOOM: `zoomPath: string[]` of node keys; clicking (or Enter on) a frame sets that path, the layout `useMemo` re-runs with that frame as the 100%-width root, and a breadcrumb of real `<button>` crumbs (`root › http.Server.emit › router.handle`) renders above it — each crumb truncates the path back to its own depth, and Escape resets to the full graph. The breadcrumb row is present only while zoomed but its height is always reserved, so nothing jumps. KEYBOARD: roving tabindex across frames — ArrowLeft/ArrowRight move across the frames at the current depth in column order, ArrowUp moves to the parent, ArrowDown to the first child, Enter zooms; the entire mechanic is reachable with no pointer, and every frame button carries a `:focus-visible` 2px `--ns-accent` outline. SIZING: `cols` comes from a ResizeObserver over the graph box divided by one monospace advance measured from a hidden 40-character probe; frames are positioned with percentage `left`/`width` derived from `(x/cols)` rather than px × cell width, so a browser's real glyph advance can never accumulate column drift. Colors are Tailwind utilities bound to `--background --foreground --ns-muted --border --ns-accent`, resolved by the cascade at paint time, so both themes repaint with no JS token read. Props: `tree` (FlameNode), `rowHeight` (px per stack row, default 22), `className`. No dependencies."
      }
    },
    {
      "name": "flash-entrain",
      "type": "registry:ui",
      "title": "Flash Entrain",
      "description": "A few hundred DOM fireflies pulse-couple over a static nearest-neighbour graph (Mirollo-Strogatz), so the field self-organises from scattered twinkle through travelling bands into unison and a handful of deaf individuals pull it apart again, forever, with no scripted reset.",
      "files": [
        {
          "path": "registry/core/flash-entrain/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/flash-entrain.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "ambient",
          "generative",
          "synchronization",
          "oscillator",
          "dom",
          "waitlist",
          "status",
          "footer",
          "empty-state"
        ],
        "instruction": "Build <FlashEntrain count? coupling? neighbors? deafFraction? periodMs? className?> as a full-bleed, DOM-only (no canvas, no WebGL) field of pulse-coupled firefly oscillators — a genuine Mirollo-Strogatz simulation, not a shared CSS timeline with staggered animation-delays. STATE: one Float32Array `phase` of length count (default 350), each firefly climbing from 0 toward a firing threshold of 1 at its own natural rate (rate = TICK_MS / period, period = periodMs (default 1250) times a +-15% per-firefly jitter, deaf fireflies additionally ~14% faster; every period is floored at REFRACTORY_MS + 60ms so ordinary uncoupled ticking never trips the visual rate cap on its own — only a coupling cascade can, and at the default periodMs the floor barely touches the deaf cohort's slowest sliver rather than clamping most of it, which is what keeps the deaf-faster property true at defaults instead of inverted). PHYSICS TICK runs on a fixed 30Hz accumulator inside one requestAnimationFrame loop (never per-rendered-frame): every tick, every phase increments once; any firefly whose phase is now >= 1 fires — its phase resets to 0 and it nudges each of its k=12 STATIC nearest neighbours (by planar distance, computed once at mount via brute-force k-NN into a flat Int32Array, never rebuilt). THE COUPLING ITSELF IS THE PART A NAIVE IMPLEMENTATION GETS WRONG: it must land on the neighbour's CHARGE, not its phase directly. Charge is the Mirollo-Strogatz concave transform x = f(phase) = ln(1 + (e^b - 1) * phase) / b with a fixed concavity constant b = 3; a firing nudges the neighbour's charge by `coupling` (epsilon, default 0.03) and the neighbour's phase is recovered as f^-1(x). This distinction is load-bearing, not cosmetic: a headless sweep of the naive 'phase += epsilon on every nudge' version (no concave transform) never produced meaningful synchrony — its order parameter stayed under ~0.1 after 90 simulated seconds at every epsilon tried from 0.02 to 0.25. With the concave charge coupling at b=3, k=12, coupling=0.03 (matching a 30Hz-tick, N=350, k=12 headless sweep against the Kuramoto order parameter), the field reliably rises from scattered chaos (order ~0.1-0.3) through partial synchrony into a noisy high-order regime (order commonly 0.7-0.95, continuously wobbling, never perfectly flat) over roughly 30-60 seconds of dwell time, and stays there — perturbed but never fully un-done — for as long as the deaf minority keeps reopening cracks in it; raising coupling much above ~0.05 in this concave formulation measurably REDUCES steady-state order rather than accelerating lock-in, so 0.03 is deliberately conservative, not a floor to push past. A nudged neighbour whose new charge reaches 1 fires in the same tick, processed via a queue so the cascade completes before the tick ends — this cascade, run against random initial phases and a fixed local neighbour graph, is what makes unison, travelling bands and competing partial-sync clusters all emerge from one rule instead of being separately authored. DEAF FIREFLIES: a fixed random 2% (deafFraction, floored at 1 individual so the story survives even a tiny population) never RECEIVES a neighbour's nudge (only their own natural rate advances their phase) while still SENDING a pulse to their own neighbours whenever they fire — they are the one and only source of relapse: no scripted desync exists anywhere, a deaf firefly simply drifts out of whatever local consensus has formed around it on its own faster clock and, on firing, perturbs its neighbourhood again, which is what keeps a synced patch from ever fully settling and restarts local entrainment there, differently every time the page is loaded. RENDER SPLIT (the whole performance story): count fixed-position, fixed-size (3-5px, seeded per firefly) DOM dots are pre-mounted once at `background: var(--ns-muted)` inside an aria-hidden, pointer-events-none absolute layer and never move; JS never writes opacity, transform or background-color directly on a fire event — it only toggles which of two identical-effect but distinctly-named `@keyframes` classes (ns-fe-flash-a / ns-fe-flash-b, alternated per firefly — re-adding the SAME animation-name is a no-op even after the previous run finished, so the alternation, not a reflow trick, is what forces a restart) is present on that dot, and the 200ms opacity 0.55->1->0.55 / scale 1->1.9->1 / color var(--ns-muted)->var(--foreground)->var(--ns-muted) envelope runs as a compositor-only CSS animation from there. ACCESSIBILITY / PHOTOSENSITIVITY, enforced in the render path independent of prefers-reduced-motion: (1) a 910ms per-firefly refractory period after each visual flash caps any single dot just under 1.1Hz even mid-cascade — physics keeps resetting that firefly's phase to 0 on schedule underneath, only the visual flash is what's rate-limited; (2) a global burst gate — when a single tick's cascade produces more eligible flashes than ~6% of the population (a unison event), the whole burst's VISUALS are suppressed unless at least 900ms have passed since the last accepted burst. Measured against a headless run at high order, accepted unison bursts land roughly every 0.9-1.5s (never below the 900ms floor) — a mildly irregular heartbeat, not a broken half-speed flicker — well under the three-per-second photosensitivity threshold. prefers-reduced-motion: reduce skips the rAF loop and every listener entirely: the field renders once at its seeded resting phases (dots stay at their default 0.55 opacity), with one spatial cluster of ~15% of the population — found by a BFS walk over the exact same static neighbour graph used for coupling, from one seeded anchor — held at +10% opacity as the frozen, static stand-in for a synchrony band; no information about the arc exists only in motion. Colors are var(--ns-muted) (resting) and var(--foreground) (flash peak) only, both resolved live by the browser from the injected <style> block's CSS custom properties, so a theme flip needs no JS. Props: count (population, default 350), coupling (epsilon, default 0.03), neighbors (k, default 12), deafFraction (default 0.02), periodMs (mean natural period, default 1250), className."
      }
    },
    {
      "name": "float-ribbon-draw",
      "type": "registry:ui",
      "title": "Float Ribbon Draw",
      "description": "A multi-stage pipeline/job-status bar modeled on the float-glass process: a fixed molten-to-set thermal gradient marks the pipeline's stages while a faint ripple scrolls continuously through the still-molten zone at a constant draw speed, so the bar keeps reading as 'actively running' whether or not a specific stage is highlighted.",
      "files": [
        {
          "path": "registry/core/float-ribbon-draw/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/float-ribbon-draw.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "progress",
          "pipeline",
          "stepper",
          "job",
          "canvas",
          "monochrome"
        ],
        "instruction": "Build <FloatRibbonDraw height? stages? activeStage? label? className?> as a full-width horizontal status bar. SOURCE, NOT INVENTED: the float-glass (Pilkington) process, the method that produces essentially all flat glass made today — molten glass flows continuously onto a bath of molten tin, floats and spreads to its natural equilibrium thickness, and is drawn forward by top rollers at a constant line speed while it cools along the bath's length, entering molten (~1,000C) and exiting set (~600C). The real process never starts or stops mid-draw, which is the exact property most 'job status' bars get wrong by pausing their idle animation while waiting on something. TWO DELIBERATELY SEPARATE THINGS: (1) a THERMAL GRADIENT — bright/dense 'molten' on the left fading to dim 'set' on the right — that is a FIXED function of x-position along the bar and never itself animates, because it represents the pipeline's stage territory, not elapsed time; (2) a RIPPLE — a faint sine wobble riding the ribbon's top edge, confined by a raised-cosine envelope to roughly the still-molten first third (full amplitude near x=0, damped to ~0 by x-fraction MOLTEN_ZONE=0.34) — that scrolls continuously at SCROLL_FRAC_PER_S=0.08 (8% of the container's width per second, so the speed is resolution-independent rather than a fixed px/s), which is the actual 'alive at rest' signal: material is moving through the pipeline right now, independent of which stage is highlighted. GEOMETRY/RESOLUTION: ribbon band height = height * RIBBON_FRAC (0.28), vertically centered in the canvas; both the fixed luminance curve and the ripple's y-offset are pre-sampled into two Float32Array(SAMPLES) lookups (SAMPLES=64) across the ribbon's length each frame, bilinearly interpolated when read — luminance at x-fraction xf is T_FLOOR + (1-T_FLOOR) * exp(-xf/LAMBDA) with LAMBDA=0.35 and T_FLOOR=0.08 (so it decays fast then trails, matching the bath's front-loaded real cooling curve, and never fully bottoms out to flat --ns-muted); the canvas fill is a linear gradient built from 12 stops sampled off that curve (a 2-stop gradient would misrepresent the exponential shape as linear). COLOUR IS LUMINANCE ONLY: every fill is parseColor()+lerpColor() between --ns-muted (t=0, 'set') and --foreground (t=1, 'molten') — read via getComputedStyle(document.documentElement), re-read on a documentElement class MutationObserver, no paint before the first read. This mapping holds in both themes without inversion because --foreground is already each theme's highest-density ink (near-white in dark, near-black in light), so 'hot' reads as 'densest ink' in both rather than as a hue swap. A thin 1px --border rule (its own alpha parsed from the token, not assumed) is drawn under the full ribbon length unconditionally — it is what keeps the dim 'set' end of the gradient from disappearing into --surface in light theme; check light theme first. RESTING LOOP: scrollPx accumulates every frame (width * SCROLL_FRAC_PER_S * dt) and never resets — t0, t+2.5s and t+5s each land the ripple's crest positions at a different phase along the same fixed gradient, so all three frames are visibly distinct while the gradient itself stays put. OPTIONAL STAGES: `stages` (string[]) and `activeStage` (index) overlay fixed 1px tick marks at even x-fractions (i/(stages.length-1)) over the ribbon, plus matching mono labels in a DOM row beneath it — the active tick/label render at --foreground, others at --ns-muted, never --ns-accent; the ribbon's gradient/ripple render loop keeps running completely unconditionally regardless of activeStage, since pausing it to 'wait' on a stage misrepresents the real process. A visually-hidden role=status aria-live=polite region announces 'Stage: <label>' exactly once per activeStage change (never a running commentary on the ripple) when stages/activeStage are supplied; with neither supplied the bar is a bare ambient processing indicator and no announcement fires. prefers-reduced-motion freezes at scrollPx=0 — the ripple's most evenly-spread crest layout across the molten zone — and stops the rAF loop entirely; the fixed gradient, border rule, and any stage ticks/labels render exactly as in motion, so the mechanic and any real stage state stay fully legible without motion. Standard canvas host lifecycle: DPR-aware backing store capped at 2, ResizeObserver on the canvas (not window.resize), IntersectionObserver (threshold 0) pausing the loop off-screen, visibilitychange pausing on a hidden tab, and full cleanup (cancelAnimationFrame, disconnect both observers, remove the visibilitychange listener) on unmount. No interactive elements, no gate descriptor: the root is role=group with an accessible `label` (default 'Processing pipeline'); the canvas itself is aria-hidden decoration. Zero dependencies, direct-canvas rendering, only the stage announcement and its own change-detection ref live in React state."
      }
    },
    {
      "name": "floret-pack",
      "type": "registry:ui",
      "title": "Floret Pack",
      "description": "A full-bleed phyllotactic hero background where primordia are birthed one at a time at a central meristem, golden-angle sequenced and locally relaxed as they advect outward, so the 34/55 parastichy spirals emerge from genuine growth rather than a closed-form layout.",
      "files": [
        {
          "path": "registry/core/floret-pack/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/floret-pack.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "phyllotaxis",
          "organic",
          "generative",
          "hero",
          "growth",
          "spiral",
          "empty-state",
          "botanical"
        ],
        "instruction": "Build <FloretPack plastochron? maxPrimordia? className? children?> as a full-bleed DOM-only background: a fixed pool of up to maxPrimordia (default 700) absolutely-positioned divs, each representing one phyllotactic primordium, pre-mounted once and thereafter updated only via style.transform/opacity/backgroundColor from a direct-DOM physics loop — no React state on the hot path, no canvas, no WebGL. GROWTH CLOCK: a meristem at the container's centre emits one new primordium every plastochron P (default 1400ms; faster reads as popcorn, slower as frozen) at theta = orderIndex * 137.50776deg exactly (the golden angle, mod 360, sequential — never batch-evaluated), with a small random birth jitter (up to ~2.2px) standing in for imprecise nucleation. RADIAL ADVECTION is solved analytically per floret from its own age (elapsed ms since birth) as r(age) = sqrt(R0^2 + 2*K*age), the exact solution of dr/dt = K/r — genuine area-conserving outward flow, not sampled from the closed-form Vogel layout r = c*sqrt(n): K is derived once at mount from the measured container's half-diagonal (the rim radius) so a floret's radial lifetime equals exactly maxPrimordia plastochrons, which is also what pins the live population at maxPrimordia by construction (Little's law) — a slot's index is literally birth order modulo the pool size, so the pool recycles itself exactly when a primordium reaches the rim, with no separate free-list. RIGID ROTATION rides on top of that same analytic radius, not instead of it: at the theta -> x/y resolution step, a fixed 6deg/s is added uniformly to every live floret's angle, so the whole resolved 34/55 parastichy pattern turns as one rigid frame while every floret keeps streaming outward from meristem to rim at the unchanged advection speed — a floret's radial lifetime completes in well under one full head rotation, so the rotation reads as the growing spiral pattern slowly precessing, never as a spinning graphic. LOCAL RELAXATION runs every physics tick on top of the analytic radius: each floret finds candidates in a +-10 birth-order window, takes the 4 nearest by actual distance, computes soft repulsion proportional to how far the pair sits inside a 4px desired-spacing threshold, sums it, and applies it as a displacement capped at 0.3px per 30Hz tick — this offset accumulates permanently and is never pulled back toward a target lattice, so what you see is divergence-angle arrival history, not a converged centroidal equilibrium. It is this relaxation, not the golden angle alone, that turns a field of jittered newborns into the visible 34/55 parastichy spiral families a few rows out from the meristem; switch it off and every floret just advects along its own fixed noisy ray with nothing to reorganise it, so the spirals visibly smear into noise — the falsifiable tell that this is grown, not evaluated. QUANTISATION: before conversion to Cartesian, each floret's analytic radius is rounded to the nearest 4px (the render lattice's spacing base), so an entire ring born close together in time snaps to a shared radius instead of drifting apart by sub-pixel floating-point noise frame to frame — this is what kills row shimmer. MATURITY AND COLOR: maturity = clamp(age / lifetime, 0, 1) selects one of three precomputed color stops interpolated from --ns-muted (young) toward --foreground (mature) at t = 0, 0.5, 1 — read via getComputedStyle at mount and re-derived on a documentElement class MutationObserver so a theme flip is live, no color literal anywhere in the component. Opacity ramps 0 -> 1 over the first 6% of a floret's lifetime (born, not popped in) and 1 -> 0 over the last 15% (senescence at the rim, the permanent sink); dot scale grows 0.35 -> 1 over that same birth window. RESIZE: a ResizeObserver recomputes cx/cy/the rim radius, but K is NOT recomputed — it stays fixed at its mount-time value, so a resize continues advecting the same live population (same birth order, same theta, same accumulated relaxation offsets) toward the new bounds rather than tearing the field down and re-sampling it from a formula; growing the container regrows the head into the new space over time, shrinking it accelerates florets into the fade band. PERFORMANCE: every floret's state lives in typed arrays sized maxPrimordia (angle, birth order, birth time, offset x/y, position x/y, maturity) mutated in place with zero per-tick allocation; a fixed-timestep accumulator steps physics at exactly 30Hz (33.3ms) regardless of display refresh rate, and the rAF loop pauses on document.hidden and resumes on visibilitychange. At mount, ~500 plastochrons are synchronously prewarmed through the identical emission+advection+relaxation function (using a coarser step and a proportionally scaled repulsion cap purely to keep the one-time warm-up cheap) so first paint already reads as a grown, still-filling head instead of an empty centre; full motion then continues that exact same loop live, so the meristem keeps visibly birthing florets and the rim keeps visibly losing them for as long as the component stays mounted. prefers-reduced-motion runs the identical prewarm, paints that one frame, and binds no rAF loop — only a resize/theme listener that triggers a discrete re-paint, never an animation — so the static frame alone already encodes the maturity gradient and packing; no information exists only in motion. Accessibility: the floret pool sits in an aria-hidden, pointer-events-none layer with zero focusable children; optional `children` render on top inside a bg-background/70 scrim so copy stays legible over the field all week. Props: plastochron (ms per emission, default 1400), maxPrimordia (live population cap, default 700), children, className."
      }
    },
    {
      "name": "flyball-throttle",
      "type": "registry:ui",
      "title": "Flyball Throttle",
      "description": "A spend-cap widget drawn as a Watt centrifugal governor: two hinged arms with ball weights fly wider as burn rate outpaces the sustainable rate, the sleeve collar they drive slides down the spindle, and the collar's own linkage swings a throttle lever closed over the purchase actions. It reads the derivative of spend, not the level, so 60% spent stays calm on day 28 and flares wide on day 6.",
      "files": [
        {
          "path": "registry/core/flyball-throttle/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/flyball-throttle.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "meter",
          "budget",
          "spend-cap",
          "governor",
          "physics",
          "svg",
          "spring",
          "status",
          "accessibility"
        ],
        "instruction": "Renders a spend-cap guard as a Watt centrifugal governor instead of a progress bar. Four props carry the read: `spendRate` (currency/day, the current burn rate), `cap` and `periodDays` (together defining `safeRate = cap / periodDays`, the sustainable pace), and `spent` (currency already spent this period — a genuinely separate level that gates the real controls and never touches the governor's geometry). The whole mechanism hangs off one governing scalar, `omega = spendRate / safeRate`: arm elevation angle is `clamp(13 + 22 * omega^2, 13, 68)` degrees (small-physics approximation, squared because centrifugal force scales with the square of angular rate; 68deg is a hard ceiling because the drawn linkage geometrically self-intersects past it; the 13deg floor is a real hinge limit, not decoration — without it the two balls' centers close to under their own diameter at low omega and merge into one blob, which is exactly the idle default frame this registry's owner judges first), each ball's position is placed directly from that angle at the end of a 46-unit arm hinged at a fixed pivot (`ballX = pivotX ± armLen*sin(angle)`, `ballY = pivotY + armLen*cos(angle)` — balls ride HIGHER, i.e. smaller y, as angle rises, which is the classic conical-pendulum flyball read), the sleeve collar's drop down the spindle is computed FROM that same angle through the linkage geometry (`armLen * (1 - cos(angle))`, never independently), and the throttle lever's rotation maps LINEARLY from that collar drop (from a 30deg resting angle above a short gate track down to 0deg, lying flat across it) so the lever visibly closes over two small glyphs standing in for the purchase actions as the collar drops. A dashed pushrod line (`stroke-dasharray`) from the collar up to the lever's pivot is the only thing connecting the two mechanisms — it exists purely to read as 'the collar drives the lever', not as a second data channel. Two arm `<path>`s and two ball `<circle>`s (the entire flyball linkage — four elements, no more) sit inside one wrapping `<g>` that fakes axial spin without ever actually rotating a side-profile diagram: a single shared `@keyframes` oscillates that group's `scaleX` between 1 and 0.74, with `animation-duration` bound to one CSS custom property (`--ns-flyball-spin-ms`) computed as `clamp(900 / max(omega, 0.08), 260, 4000)` — lazy, slow spin at low omega reads as healthy at a glance, a fast blur reads as hot, and there is deliberately no per-ball choreography: both arms and both balls share the exact same group transform. Every geometry-bearing SVG attribute that changes with omega (`d` on the arm and lever paths, `cx`/`cy` on the balls, `y` on the collar rect — all CSS-animatable properties in evergreen browsers) carries one shared 250ms, non-overshooting `cubic-bezier(0.16, 1, 0.3, 1)` CSS transition, so a stream of per-request spend updates settles smoothly frame to frame instead of twitching on every tick; there is no JS spring/rAF loop anywhere in this component. `spent` vs `cap` never feeds that geometry — `capReached = cap > 0 && spent >= cap` is computed and used for exactly two things: tinting the collar's fill solid (a data-state color change, same convention this registry already uses for a latched state, never `--ns-accent`) and gating the two real `<button>`s ('New purchase', 'Raise limit'), which are marked `aria-disabled` (never the native `disabled` attribute, so they stay in the tab sequence and screen-reader-discoverable even at cap) with their `onClick` short-circuited and `aria-describedby` extended to include a visible explanation paragraph (`data-flyball-closed-note`) stating the cap was reached and when purchasing resumes — the disabling is a real, independent control state, not something inferred from the lever's visual position. The whole SVG diagram is `aria-hidden`; the read starts as text. A visible paragraph (`aria-describedby`'d onto a `role=group` wrapper that also carries `aria-labelledby` to the label above it) states the figures in one sentence first: 'Spending $41/day against a $30/day sustainable rate — cap reached in 9 days at this pace' (or 'on pace to stay under cap this period' once the forecast runs past `periodDays`, or 'the cap has been reached for this period' once `capReached`). A bold Geist Mono state chip ('CALM' / 'HOT' / 'CLOSED') sits beside the label at all times — `band` is `closed` whenever `capReached`, else `hot` once `omega >= 1.3`, else `calm` — and a visually-hidden `role=status`/`aria-live=polite` span announces only the edge-triggered crossing between those three bands (not every omega tick, matching this registry's established edge-triggered-announcement pattern), skipping the announcement on first mount so a demo that starts mid-scenario doesn't narrate its own boot. Under `prefers-reduced-motion: reduce` (a CSS media query, no JS branch needed since nothing here runs a rAF loop) the spin keyframe animation — the one open-ended, ambient loop in the component — stops entirely, but the 250ms geometry transitions shorten to 120ms linear rather than vanishing: a press still reads as the arms actually moving to their new resting angle, just without the perpetual spin blur, since a state change is not the kind of motion prefers-reduced-motion is meant to suppress. Colors are `var(--foreground)` / `var(--border)` only inside the SVG (never a hex literal, never `getComputedStyle` since nothing here is a raster surface) and Tailwind token classes (`text-foreground`, `text-ns-muted`, `border-border`, `bg-background`, `focus-visible:outline-ns-accent`) everywhere else — `--ns-accent` appears only on focus-visible outlines, never as a fill or decorative color. DOM+SVG+CSS only, zero dependencies, no canvas."
      }
    },
    {
      "name": "flying-splice",
      "type": "registry:ui",
      "title": "Flying Splice",
      "description": "A logo ribbon whose real subject is the roll stand feeding it: one paper roll unwinds at constant web speed, so its radius falls to 0.426 of full while its RPM more than doubles (2.35x — 0.61 to 1.43 rev/s at the demo's 672x220 band), then rebuilds along the exact time-reverse of the same law and slows back down. An unbroken 22s oscillation with no reset, no pop and no second roll.",
      "files": [
        {
          "path": "registry/core/flying-splice/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/flying-splice.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "logo-wall",
          "marquee",
          "canvas",
          "mechanism",
          "print",
          "ribbon"
        ],
        "instruction": "Build <FlyingSplice paused? className? style?> as a 2D canvas band (never full-bleed) whose dominant visual is a SINGLE paper-roll stand, not the ribbon. GEOMETRY: M = min(bandW, bandH), W = bandW; R_max = max(0.19*M, 0.11*W) (the 0.11*W floor engages on wide bands and card crops, where 0.19*M would leave a spent roll too small to read as wound paper; with one stand the cluster is a single 2*R_max disc, so 0.11*W puts it at 22% of the band, clear of the hard 30%-of-band-width kill criterion); R_min = R_max * (27.5/64.6). The stand sits near the right edge (centre at W - 1.2*R_max) with the ribbon and its nip point to its left, so marks are 'printed' at the nip and scroll right-to-left, away from the roll that produced them. RADIUS/RPM, the actual mechanic: under constant web speed v = 0.42*W px/s the roll's angular velocity is omega = v/r throughout, so RPM climbs as the roll empties and falls again as it rebuilds. Because R_min/R_max is fixed, the RPM swing is exactly 64.6/27.5 = 2.35x at every band size — it more than doubles; the absolute rates depend on geometry (at the demo's 672x220 band R_max = 73.9px, R_min = 31.5px, v = 282px/s, giving 0.61 -> 1.43 rev/s). CYCLE MODEL — an oscillation, not a reset: a 22.0s cycle (unbounded, never terminating) split into a 15.0s run-down and a 7.0s rebuild, asymmetric on purpose so the run-down is the long read and the rebuild does not become a second competing event. Run-down: r = sqrt(R_max^2 - B_dn*p) with B_dn = (R_max^2 - R_min^2)/15.0, swept angle theta = 2*v*(R_max - r)/B_dn. Rebuild: r = sqrt(R_min^2 + B_up*q) with B_up = (R_max^2 - R_min^2)/7.0 and q = p - 15.0, swept angle theta = theta_downTotal + 2*v*(r - R_min)/B_up. Both legs are exact closed forms, never a per-frame accumulator, because prefers-reduced-motion must render exactly STATIC_TIME byte-stably without simulating up to it; do NOT ease the radius with an arbitrary curve, which would break the exact integral of v/r. Because omega = v/r on both legs it is continuous at BOTH turning points (v/R_min at the bottom, v/R_max at the wrap) and rotation never stalls or reverses — only dr/dt changes sign. The swept angle must accumulate across cycles as cycle*THETA_CYCLE + theta(phase) (reduced mod 2*PI only at draw time), NOT restart each cycle: with one persistent roll there is no role swap to hide a per-cycle angle reset behind, and a reset snaps the index lines at every wrap. The roll carries 4 radial index lines (3 below M=200px, alongside 6 marks instead of 9) rotating at theta so the speed change is legible rather than blurring, and the roll's own shrinking outer boundary IS the visible 'wrap edge' receding toward the core — no separate element needed. There is exactly ONE roll disc on screen at every instant of the cycle; no second stand, no changeover choreography, no paster arm, no knife, no splice tape. WEB: the ribbon is a visible strip of paper, not free-floating marks — a paper-coloured band of height 1.95*markSize centred on the band, running from the left edge to just inside the roll's CURRENT radius (anchored to the stand centre it juts out past a run-down roll as a bare rectangle), with darker hairline edges; the marks ride on it, and the strip visibly lengthening as the roll empties is itself part of the run-down read. The stand carries a post and foot below its roll, and a dashed capacity ring at R_max, so the gap between that ring and the roll's wrap edge makes the run-down legible in a single still instead of only across 22s of motion; the ring's alpha scales with (R_max - r)/(R_max - R_min) so it fades out entirely on a full roll, where it would otherwise sit on top of the roll's own wrap-edge stroke and read as a jagged doubled outline for the two seconds centred on the wrap. RIBBON: marks are drawn from a fixed seeded PRNG (mulberry32, not Math.random) so the pattern is identical on every mount — required for reduced-motion byte-stability. Each mark is one of three abstract placeholder families (concentric arcs, a bar cluster, a lattice), 0.14*M wide, spaced 0.155*W apart, generated purely from x = nipX - ((v*t) mod spacing) stepping backward — never accumulated per-frame state — so a mark only ever exists downstream of the nip. TOKENS: getComputedStyle(documentElement) for --background/--foreground/--border read as the FIRST statement inside the mount effect, before the ResizeObserver is constructed or anything paints, re-read on a MutationObserver watching documentElement's class; a `colorsReady` guard makes draw() a no-op until that first read completes on all three entry paths (rAF start, ResizeObserver's initial fire, IntersectionObserver resume). Ribbon/roll paper is mix(background, foreground, 0.13 light / 0.34 dark) — at 0.16 the roll is a near-black disc on a near-black band and the mechanism is invisible however correct its motion; marks are foreground at 0.78 alpha; the roll body is shaded by a shallow offset radial gradient standing in for a single-lamp Lambert term (azimuth 118deg, offset 0.22r, +0.10/-0.12 lightness — a deeper offset reads as a billiard ball rather than the flat end of a wound coil) over a --background hub disc at 0.17r plus sparse concentric ring banding for a wound-paper read — value only, no hue. --ns-accent never appears anywhere in this component (there is no interactive chrome); --border appears only as the band's top/bottom hairlines. CANVAS: DPR capped at 2, ResizeObserver on the host element (not window), rAF paused on IntersectionObserver offscreen and visibilitychange, prefers-reduced-motion (or the paused prop) freezes on the single composed frame at STATIC_TIME = 0.65*15.0 = 9.75s — 65% through the run-down, where the roll is visibly part-spent with a clear gap to the capacity ring — rather than on a full roll, which would show no run-down at all. No pointer interaction of any kind — hover/pointer highlights on a rotating disc are exactly the accent defect this component's spec calls out by name."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-muted to text-foreground, focus-visible:outline-2 outline-offset-2 outline-ns-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, --ns-muted, --ns-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": "frank-register",
      "type": "registry:ui",
      "title": "Frank Register",
      "description": "Paying from a prepaid wallet rendered as a postal franking meter: committing stamps an indicium onto the invoice while a descending BALANCE odometer rolls down and an ascending SPENT odometer rolls up by the same amount, the two always summing to credits purchased.",
      "files": [
        {
          "path": "registry/core/frank-register/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/frank-register.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "wallet",
          "payment",
          "odometer",
          "invoice",
          "svg",
          "aria-live",
          "accessibility",
          "credits"
        ],
        "instruction": "A prepaid-wallet payment control rendered as a postal franking meter, for API-credit products, ad platforms and expense wallets where the user has to trust that a charge really decremented the balance by exactly what it claims. `creditsPurchased` fixes the invariant denominator; `balance` (controlled) or `defaultBalance` (uncontrolled) tracks the wallet, and the ASCENDING spent register is never stored as its own state — it is derived every render as `creditsPurchased - balance`, so the reconciliation invariant (balance + spent === creditsPurchased) holds by construction rather than by hoping two independently-animated counters stay in agreement; a dev-mode console.assert double-checks it and a visible caption prints it plainly ('registers reconcile: $2,000.00'). Money is tracked in integer cents throughout so repeated commits never drift from float rounding. STRUCTURE: an invoice card (amount due, description) with a die-shaped commit button below it and a stamp zone above the button where the frank prints; below the card, two bordered odometer panels (Balance, Spent to date), each pairing a decorative digit-wheel readout with a plain-text mirror of the same number for screen readers. MECHANISM: one `commit()` call is the single governing scalar (the payment amount, in cents) that feeds three things at once — the balance delta, the frank's printed amount, and the die-drop trigger — never three independently-timed writes that could disagree. ODOMETER: each digit is a vertically clipped column over a 30-row strip (0-9 repeated three times); a commit computes a rest row (old digit's home position), a play row (new digit's home position, overshot by one strip-third if the digit carried forward past 9, undershot by one strip-third if it borrowed backward past 0), and a final row (new digit's home position, always back in the middle band) — the cell renders at rest while unarmed and at play once armed via a double-rAF-gated transition, then snaps instantly from play to final once the 400ms roll settles, a jump of exactly one strip period so the same digit is on screen before and after and the cut is invisible. Because the ascending register's row only ever climbs and the descending register's only ever falls, the two wheel families spin in opposite senses off the same 400ms drive — mechanically mirrored, not independent tweens. Digit columns diff by place value (never string index) against the previous rendered value; changed columns stagger 40ms each, right-to-left (rightmost changed place first, matching how a carry actually propagates in addition), unchanged columns never move. COMMIT SEQUENCE: the button IS the die — clicking drops it 8px over 140ms ease-out-expo, dwells there 120ms (so the stamp reads as pressure landing, not a blink), then returns over 180ms; the frank SVG (a rosette of 12 rays around a ring/hub, a divider, and amount/date/serial text in Geist Mono) is remounted per commit (keyed on a batch counter) so its one-frame press-darken animation (opacity 0.35 -> a 1.25x brightness flash -> settled) always replays, delayed to land at the same 140ms mark the die makes contact. Serial numbers increment per commit (zero-padded to 6 digits) and the date is read fresh from the clock at commit time, never during render, so there's no hydration mismatch. A commit is refused (button disabled, accessible name still present) if the amount exceeds the remaining balance — the registers can never go negative. ACCESSIBILITY: the commit button's accessible name always names the amount ('Charge $142.50'); after a commit an aria-live=polite, aria-atomic region announces 'Stamped — $142.50. Balance $857.50 remaining, $1,142.50 spent to date, registers reconcile.'; the frank SVG is role=img with an alt carrying its serial, amount and date; both odometers render aria-hidden decoratively alongside a plain visible text mirror ('$857.50') that is the actual accessible content, so a screen reader gets the number twice over (testifies, doesn't just assert) rather than depending on the wheel glyphs. REDUCED MOTION: wheels snap straight to their final digits with no roll, the die never travels (no drop, no dwell, no return), and the frank appears at full opacity with no press-darken — fully legible, all four state changes (balance, spent, frank, and the disabled/enabled button) still land correctly, only the choreography is skipped. Distinct from counter-carry-ripple (a single live numeric readout with no payment action, no conserved pair, no stamped artifact) and from the otp-reel digit-wheel family (keyboard entry into an OTP box) by animating a CONSERVED PAIR plus a stamped indicium, never a single number. Zero dependencies, pure DOM + SVG + CSS, no canvas; all ink is token-relative (--background, --foreground, --ns-muted, --border; --ns-accent only for the keyboard focus ring)."
      }
    },
    {
      "name": "frazil-dam",
      "type": "registry:ui",
      "title": "Frazil Dam",
      "description": "A card-scale processing/progress indicator modelled on frazil ice: fine crystals nucleate in turbulent water, drift across a channel via a 2-octave curl-noise flow field, and pile against a rack, the crest rising over 15-20s to a threshold, then calving a coherent chunk downstream and resuming from a smaller residual base, unbounded.",
      "files": [
        {
          "path": "registry/core/frazil-dam/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/frazil-dam.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "canvas",
          "progress",
          "processing",
          "loader",
          "ice",
          "curl-noise",
          "physics",
          "ambient",
          "status"
        ],
        "instruction": "A card-scale single-canvas component modelling frazil-ice accumulation: fine crystals nucleate in turbulent water, are advected left-to-right across a channel, and pile against a fixed rack near the right edge until the pile's crest crosses a threshold, at which point a coherent chunk calves off and is carried downstream, and accumulation resumes from a smaller residual base. All geometry derives from the chart wrapper's getBoundingClientRect via computeGeo(W, H): a channel band spanning 14%-86% of the container height, a rack at x = 0.78*W drawn full-height, and a dam wedge that tapers upstream (left) from the rack. The whole simulation — crystal spawn, the curl-noise velocity field, dam-crest growth, and the release/chunk animation — runs inside one fixed 20Hz tick accumulator (never per rAF frame), so the field that advects crystals is genuinely re-sampled at a fixed rate decoupled from display refresh: crystals spawn at the left edge at 8/s, each carrying a constant rightward base velocity (channel length / 5s, keeping single-crystal transit inside the spec's 2-8s real-time range) plus a divergence-free curl of a 2-octave value-noise potential field (curlVel = perpendicular gradient of potential(x,y,t), the same construction used by background-ascii-flow), which is what makes the turbulence look genuinely mixed rather than a laminar drift. A crystal lodges — is removed from the moving set — the instant its x reaches the rack itself, or reaches the dam's current leading (upstream) face and its y falls inside the dam's height profile at that x (damHeightAt: an eased power-1.4 ramp from 0 at the pile's upstream edge to the current crest height at the rack, so the pile reads as a wedge nosing upstream, not a vertical wall). The crest height itself runs on its own clock, independent of exactly which crystal lodges when: an accumulation phase eases the crest from a residual base up to THRESHOLD = 0.7 (70% of the channel's cross-section) over a per-cycle duration randomised 15-20s (compressed from the real minutes-to-hours river-ice timescale, matching the spec's illustrative-compression note); crossing threshold immediately starts a release phase in which the crest eases back down over 420ms to a fresh residual (55-65% of the prior crest, randomised per release, i.e. 35-45% of the mass is what calved) while a chunk sprite — three overlapping circles for an irregular but coherent silhouette — spawns at the crest's peak position and eases (ease-out, quick break then swept) across and off the right edge over the full 1200ms release window, after which the accumulation phase restarts from the new residual; the dam never returns to zero. Colour: the channel is filled once per frame with --ns-muted at low alpha (over whatever the card's own background is, this alone reads as near-black turbulent water in dark theme and pale-grey water in light theme with zero theme branching), the dam is a linear gradient from --foreground at its crest to --ns-muted at its base, crystals and the release chunk are --foreground circles, and the rack — drawn as a vertical line plus six perpendicular grate ticks — is stroked at full --foreground alpha (genuine structural contrast, never --border's ~1.1:1 separator contrast). Colour tokens are read once via getComputedStyle(document.documentElement) for --foreground and --ns-muted with no literal fallback of any kind; if either is empty (stylesheet not yet applied) the mount loop retries on the next rAF and paints nothing until both resolve. A MutationObserver on document.documentElement's class attribute re-reads tokens on every theme flip and forces a resize/redraw; a ResizeObserver on the chart wrapper recomputes geometry and resizes the canvas backing store (capped devicePixelRatio 2); an IntersectionObserver stops driving new ticks while off-screen and, on re-entering view, re-reads tokens and restarts the fixed-step accumulator from the current wall clock (the sim state itself — crest, residual, crystals — is not reset, only the accumulator's real-time reference, so a long time off-screen does not fast-forward through dozens of missed release cycles: MAX_CATCHUP_TICKS caps how many 20Hz ticks a single frame can catch up). Under prefers-reduced-motion the component runs zero rAF loops and zero timers: it deterministically builds one frozen frame with the accumulation clock parked at 55% of the way toward THRESHOLD from empty (crest ≈ 0.385 of the channel, mid-pile, no release in progress) and eight crystals scattered across the channel's transit region so the still frame shows both the building dam and crystals visibly in flight, not an empty channel. Cleanup on unmount cancels both the animation and token-wait rAF handles and disconnects all three observers. Zero dependencies, DOM+canvas only, no colour literal anywhere including fallbacks."
      }
    },
    {
      "name": "fugitive-ink",
      "type": "registry:ui",
      "title": "Fugitive Ink",
      "description": "A one-time secret panel that prints a new API key or recovery code fully legible, then lets it fade like security ink, armed by the user's own copy or by clicking away, never by a modal you close and lose.",
      "files": [
        {
          "path": "registry/core/fugitive-ink/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/fugitive-ink.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "secret",
          "api-key",
          "clipboard",
          "recovery-code",
          "reveal",
          "decay",
          "aria-live",
          "accessibility"
        ],
        "instruction": "A one-time secret panel for key issuance and recovery-code flows: the value renders fully legible in a real role=textbox, aria-readonly, keyboard-focusable, text-selectable element, with a Copy button labelled \"Copy secret key, shown only once\" and a permanent record line underneath (initially \"<prefix>…<suffix> — not yet copied\"). Nothing decays until the user arms the panel by clicking Copy or by letting focus leave the panel entirely (blur past its boundary) — a one-way latch, whichever happens first, never both and never re-triggered by a second copy. Once armed, a single governing scalar t runs from 0 to 1 across `decayMs` (default 60000). It is written once per ~100ms tick as the CSS custom property --fi-t on the panel root and inherited by every character span — there is no per-character JS timer. Each middle character (the prefix and last four characters are permanently exempt and never decay) carries only a static --fi-o offset derived from a hash of its index (0 to 0.4 of the window), and a shared CSS formula turns (t, o) into that character's own local phase, scaled so the latest-offset character still finishes exactly when t reaches 1. That local phase crosses three thresholds — the same formula for every character, just at staggered absolute moments — font-weight 600 to 300, then color --foreground to --ns-muted via color-mix, then a clip-path eating the glyph from its waterline (bottom) upward, capped at 88% so a top hairline always survives; the effect reads as granular, paper-like decay rather than a uniform wipe. A thin rule under the secret drains on the same t (scaleX(1-t)) and is labelled with the real integer seconds remaining, recomputed from wall-clock elapsed time so the label can never say something the pixels haven't caught up to. Clipboard and pixels expire together: the Copy button copies the full value for as long as t < 1; once t reaches 1 the button relabels itself and copies only the identifying stub (prefix…suffix) from then on — full value copy never returns, but the button never dies either. The record line survives decay unchanged and switches to \"<prefix>…<suffix>, copied <HH:MM>, will not be shown again\" (or \"shown\" if arming came from blur, not an explicit copy) — this is the permanent artifact a user comes back hunting for later, deliberately not a modal that closes and takes the record with it. Arming announces assertively via a visually-hidden role=alert (\"Copied. Key becomes unreadable in 60 seconds.\"); crossing the halfway point and full expiry each announce once, politely, via a separate aria-live=polite region. prefers-reduced-motion does not disable the decay — it quantizes the same --fi-t scalar to exactly three values (0, 0.5, 1) at the same three moments the announcements already fire, so the continuous CSS thresholds collapse into three discrete text states instead of a smooth ramp, and per-character transitions are removed. Every color is --background, --foreground, --ns-muted, --border and --ns-accent (focus ring only) — no hex, no gradients, no canvas."
      }
    },
    {
      "name": "fusee-cone",
      "type": "registry:ui",
      "title": "Fusee Cone",
      "description": "A burn-rate alert policy editor built as a clock fusee: error budget is the mainspring barrel, a chain rides down a tapering cone as budget depletes, and alert sensitivity at any budget level is the cone's local radius, authored by dragging exactly 4 monotone control points, with a real multi-window rule table derived from the shape.",
      "files": [
        {
          "path": "registry/core/fusee-cone/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/fusee-cone.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "slo",
          "alerting",
          "policy",
          "editor",
          "svg",
          "slider",
          "spline",
          "monotone",
          "table",
          "form",
          "accessibility"
        ],
        "instruction": "Renders a burn-rate SLO alert policy editor as a working clock fusee. MECHANISM: the error budget is a mainspring barrel (an SVG circle, left) and a tapering cone (right) whose local half-width at axial position x in [0,1] is a burn-rate TRIP MULTIPLIER — x=0 is the full-budget end, x=1 is the exhausted end. Exactly 4 draggable control points sit at fixed axial positions [0, 1/3, 2/3, 1]; only their y (the multiplier, 1x-20x) is draggable. The curve between them is a monotone cubic Hermite spline (Fritsch-Carlson tangent limiting), which guarantees the interpolated radius never overshoots between knots — not merely at the knots themselves — so 3 control points could not express a fast-burn/slow-burn knee and 6 would invite a wiggle the derived table cannot represent; 4 is the minimum that expresses a knee and the maximum that stays a single readable knee. FALSIFIABLE CONSTRAINT: radius(x) is enforced monotone non-increasing toward the empty end — dragging or arrow-keying a point is clamped live to the band [next point's committed value, previous point's committed value], so the sequence can never invert. Attempting to cross that band clamps the handle at the boundary, plays a 220ms spring-back scale bounce (skipped outright under reduced motion, though the clamp itself still applies), and a polite aria-live region announces 'Cannot raise this point above the previous one' (or the symmetric 'cannot lower... below the next one'), throttled to once per drag gesture or keypress rather than once per pixel. This is what forbids authoring a policy that gets LESS sensitive as budget runs out — the free-form curve editor that would allow that is structurally unavailable, not merely discouraged. GOVERNING SCALAR: remaining budget (0-100%, a labeled range input, or driven by Replay) sets axial position x = 1 - remaining/100, which places the chain's contact point on the cone (a quadratic-bezier catenary from the barrel's rim to that contact point, its sag proportional to how far the current burn rate is from tripping — taut near a trip, slack when far from one). ALERT TORQUE: torque = current burn multiplier (a second labeled range input, 0-20x, or replay-driven) x the cone's local radius at the chain's current position; the escapement fires when that torque meets or exceeds the torque the mechanism would carry if burn exactly equalled the local threshold there (algebraically: burn >= radius(x)) — fired/quiet state, the sag of the drawn catenary, and every row of the derived rule table all read off that same radius function, never a separately-tracked boolean. DERIVED RULE TABLE: a real HTML <table> (with <caption>, <thead>, real <th scope=col>) sampling the COMMITTED policy (never the live mid-drag preview) at 5 fixed budget levels (100/75/50/25/0%), each paired with a conventional lookback window (1h/3h/12h/1d/3d, lengthening as the derived multiplier shrinks, e.g. the default profile's 14.4x knot at 100% budget pairs with 1h and its 6x knot at ~67% pairs with 6h-scale windows) and the live-derived multiplier for that budget level formatted 'Nx'; this table is the screen-reader-primary surface and is the ONLY thing that updates on drag release or a committing keystroke — during an in-progress drag the cone, chain and torque readout all react continuously (so the shape being argued about is always what both people in a review see), but the table itself holds its prior committed values until release, exactly once. REPLAY: a button streams a built-in (or prop-supplied) recorded burn-rate history through the mechanism on a fixed cadence, moving the remaining-budget and burn-rate inputs together, visibly spinning the barrel's radial tick mark (up to 3 full turns across the whole budget) and sliding the chain down the cone; every sample that would have tripped against the CURRENT authored policy is appended to a short visible trip log and announced via the same polite live region ('Would have tripped at 47% budget remaining — burn 8.5x >= 3.5x threshold'). Both manual range inputs are disabled while replay owns them. A 'Preview spike' button forces the burn input to 25x — above the 1x-20x authorable range, so it always trips regardless of policy or budget position — an idempotent, non-toggling way to see the tripped state on demand. ACCESSIBILITY: each control point is role=slider, tabIndex=0, aria-orientation=vertical, aria-valuemin/max set to its LIVE permitted band (not a fixed global range), aria-valuenow the current multiplier, and aria-valuetext composed exactly as 'at {budget}% budget remaining, alert at {value}x burn' — legible on its own, no need to cross-reference a legend. Arrow/Page/Home/End step the value (Home/End jump to the current band's own bounds); every commit path (drag release or keystroke) runs through one clamp-and-announce function so keyboard and pointer can never diverge in what counts as a violation. The cone, barrel and chain SVG, plus the small filled/outlined escapement dot at the chain's contact point, are all aria-hidden — decoration once the sliders and table carry the real semantics; the fired/quiet badge is plain visible text ('TRIPPED'/'QUIET'), not color-only. Reduced motion (matchMedia, live listener): the chain's catenary path and the barrel's rotation transition are both forced to 0ms — every reposition is an instant jump, the barrel does not visibly spin, the spring-back bounce animation is skipped — while every value, clamp, and table update still applies exactly as before, so the component stays fully usable. TOKENS: --ns-accent appears only on a dragged or keyboard-focused control point handle, never as a fired/quiet or trip-log color — those states are spelled out in text and border/fill weight (border-foreground/70 idle -> border-ns-accent active), matching this registry's 'latching is data state, never accent' convention. Everything else is --foreground/--ns-muted/--border/--background token classes; no hex, no canvas — one inline <style> block supplies only the CSS transition-property declarations and the reduced-motion override, DOM + SVG + CSS."
      }
    },
    {
      "name": "gallery-ascii-gradient-orientation",
      "type": "registry:ui",
      "title": "Gallery ASCII Gradient Orientation",
      "description": "A gallery grid where each tile runs its own procedural 3-octave value-noise field and a 3x3 Sobel operator selects glyphs by the field's own gradient ANGLE, bucketed into '-', '/', '|', '\\\\', keeping only the adaptively top-12%-by-magnitude cells each frame so the field reads as a moving edge sketch.",
      "files": [
        {
          "path": "registry/core/gallery-ascii-gradient-orientation/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/gallery-ascii-gradient-orientation.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "gallery",
          "ascii",
          "canvas",
          "noise",
          "sobel",
          "grid"
        ],
        "instruction": "Build <GalleryAsciiGradientOrientation tileCount? columns? cellSize? className?> as a gallery grid of generated tiles, never external image assets. Each tile owns a small offscreen scalar buffer running its own 3-octave value-noise loop (same summed sine-octave family as background-ascii-plasma: a slow isotropic swell, a mid-frequency crossing band, a fast fine ripple), mulberry32-seeded per tile index so every tile settles into a distinct, phase-locked drift instead of nine copies of the same field. SOBEL, NOT PIXELS: a 3x3 Sobel operator (Gx, Gy) runs directly on that raw 0..1 Float32Array noise scalar array, supersampled ~3x the glyph grid's own resolution (vector-summed per glyph cell's supersample block) so the gradient estimate isn't degenerate at 1:1 with the glyph pitch — never on ctx.getImageData pixels, and never after any token or color is applied. magnitude = hypot(Gx, Gy); angle = atan2(Gy, Gx) mod PI (edges are undirected), bucketed into 45-degree wedges onto '-' (near 0deg), '/' (near 45deg), '|' (near 90deg), '\\\\' (near 135deg) — the same 4-glyph alphabet background-ascii-flow keys to a simulated velocity field's heading, but here keyed to a generated image's own gradient normal instead, recomputed every frame. ADAPTIVE SPARSITY: each frame recomputes the top ~12% of that tile's own cells by magnitude via a percentile cut over its own distribution (never a fixed threshold), so which cells survive tracks the scene's own shifting local contrast as the field drifts. Color is assigned only at the final glyph-draw step in --foreground, strictly after cell selection is complete, so edge selection is byte-identical between light and dark themes. Each tile is a real <button> with an accessible name describing it as generated content and its index (e.g. \"Gallery item 3: generative edge-sketch pattern\"), never \"image 3\" since the visual is procedural, not photographic; the canvas itself is aria-hidden. --ns-accent appears only on the tile's hover/focus ring (border and outline), never inside the generated content. prefers-reduced-motion freezes every tile's field at t=0 (the mulberry32 seed guarantees per-tile structure from frame one, so t=0 is never a degenerate all-flat frame), running Sobel and the threshold cut exactly once and skipping the rAF loop entirely. Cell metrics measured via an offscreen canvas after document.fonts.ready; a ResizeObserver on the grid re-measures and rebuilds every tile's buffer on reflow. Props: tileCount (default 9), columns (default 3), cellSize (default 9), className. Zero dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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/--ns-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": "galley-bracket",
      "type": "registry:ui",
      "title": "Galley Bracket",
      "description": "Remote collaborators' live selections drawn as proofreader's marks: thin corner brackets at each end of a range and a hairline underline, nesting outward by arrival order where ranges overlap, instead of overlapping tinted highlight fills.",
      "files": [
        {
          "path": "registry/core/galley-bracket/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/galley-bracket.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "collaboration",
          "presence",
          "selection",
          "editor",
          "svg",
          "accessibility",
          "realtime",
          "proofreading"
        ],
        "instruction": "A presence primitive for shared/collaborative text editing: given `text` (one paragraph's plain string) and `selections: { id, name, initials?, start, end, arrivedAt? }[]` (character offsets into `text`), it marks where OTHER people currently have something selected — never the viewer's own selection, which stays the platform's native blue. Each range is measured with `Range.getClientRects()` against the paragraph's own single text node (the same primitive a user dragging a selection and reading getClientRects() would hit), so a range that wraps across lines gets one hairline underline per wrapped line, while the corner brackets are drawn only at the range's true start and true end — never once per line. Every mark is a stroke, `var(--foreground)` at a base opacity of 0.55, never a filled highlight: two overlapping translucent fills produce a third color that identifies nobody, so instead, when ranges overlap, each selection's ONLY varying properties are derived from its arrival index within that overlap group (computed by union-find over interval overlap, then ranked by `arrivedAt` — defaulting to array position — within each connected group): outward offset (index × 3px, pushing the bracket's top edge up and its underline down, so later arrivals visibly embrace the earlier ones like nested proofreading brackets) and stroke opacity (index × -0.09, later arrivals slightly lighter, floored at 0.22). Nothing else — no hue, no fill, no z-order trick — varies per selection. The selector's initials (explicit `initials`, or derived from the first letters of `name`) are set small in a reserved left margin gutter, vertically aligned with the opening bracket's (offset) top. Marks draw in over 160ms via `stroke-dashoffset` the first time an id appears in the DOM — because React keys the mark group by `id`, a selection whose `start`/`end` merely change (a drag in progress) updates the existing element's coordinates without remounting it, so the reveal never replays and rapid re-selection never strobes. `prefers-reduced-motion` drops the reveal outright (marks appear at rest). The whole SVG layer is `aria-hidden`; it is decoration on top of an honestly-stated real surface: a `role=status aria-live=polite` region that announces the most recently changed selection 2 seconds after it stops changing (\"Ana Kader selected 12 words.\", plus \" in <contextLabel>\" when that prop is set) — the 2s debounce is what keeps a live drag-selection from flooding the region with one announcement per pointermove — and a keyboard-reachable list of jump buttons, one per active selection, each a real `<button>` whose accessible name is its own visible text (\"Ana Kader · 5 words\"); activating one moves the platform's own `Selection` object to that exact range via the same Range machinery used for measurement, so the collaborator's text is actually, natively highlighted and scrolled into view — not a custom paint standing in for it. Renders one paragraph per instance by design (multi-paragraph editors render one GalleyBracket per paragraph) so `start`/`end` stay simple indices into one text node rather than needing a cross-node offset scheme. Zero dependencies, DOM+SVG+CSS only, no canvas; every color is a token (`--background --foreground --ns-muted --border --ns-accent`) and `--ns-accent` appears only on the jump buttons' focus ring, never on a bracket or underline — the marks are strictly monochrome. Distinct from select-caustic: select-caustic is an effect on a `<select>` trigger/listbox, unrelated in domain. The component this is really meant to be read against is anything that would highlight a remote user's selection with a translucent fill — that approach gets muddier as more people select the same text; galley-bracket gets more articulate as ranges overlap, because overlap is exactly what arrival-order nesting is built to narrate."
      }
    },
    {
      "name": "gantt-ascii-critical-path",
      "type": "registry:ui",
      "title": "Gantt ASCII Critical Path",
      "description": "An ASCII Gantt chart where a real CPM forward/backward pass decides which bars are critical: zero-float tasks draw heavy `━` in accent, the rest light `─`, and dragging a task re-runs the pass so the critical chain moves.",
      "files": [
        {
          "path": "registry/core/gantt-ascii-critical-path/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/gantt-ascii-critical-path.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "gantt",
          "schedule",
          "timeline",
          "ascii",
          "box-drawing",
          "critical-path",
          "drag",
          "project-management"
        ],
        "instruction": "Build <GanttAsciiCriticalPath tasks? cols? today? startWeek? title? className?> as pure DOM box-drawing text — no canvas, no rAF, every recompute a direct response to pointer or keyboard input. MECHANISM: a genuine Critical Path Method pass, computed in ONE useMemo keyed on tasks + manualOffset + cols. Topological order comes from Kahn's algorithm over the dependency graph (indegree map, zero-indegree queue, self-edges and dangling dep ids filtered out first); any task Kahn never drains sits on a cycle, so it is excluded from the pass and renders its row as `×  cycle — excluded` rather than hanging. FORWARD pass in topological order: ES = max(0, max(EF of deps) + manualOffset[id]), EF = ES + duration; projectEnd = max(EF). BACKWARD pass in REVERSE topological order: LF = min(LS of successors), or projectEnd when a task has none; LS = LF − duration; totalFloat = LS − ES; critical ⇔ totalFloat <= 0 (the <= rather than === is what keeps a row well-formed when a drag pushes a task into negative float mid-gesture — it is still the tight chain, not a slack one). ROW: a 16-char label column truncated with `…`, a `│` rule, then `cols` timeline cells (default 32) at scale = max(1, ceil(projectEnd / cols)) days per cell. BAR: non-critical = `├` + `─`×(len−2) + `┤` in text-ns-muted; critical = `┝` + `━`×(len−2) + `┥` in text-ns-accent — the WEIGHT change is the primary encoding, so the critical chain still reads in a pure-monochrome screenshot and hue is only reinforcement. Nothing else is drawn on the row: no slack run, no float glyphs — bar weight is the only encoding, and float stays a number in the readout. PITCH (the constant that makes or breaks the drawing): the timeline track is `grid-template-columns: repeat(cols, 1ch)` and every glyph cell is `width: 1ch`, NOT a hardcoded pixel figure. In a monospace face 1ch is exactly one advance width, so consecutive `─`/`━` glyphs butt into a continuous rule at any font size and under any fallback font; a px constant that disagrees with the font metric by even a pixel draws every bar as spaced dashes, which is the single most common way this component is got wrong. The drag handler is the one place a pixel number is needed, and it MEASURES it — `track.getBoundingClientRect().width / cols` read off the laid-out row at pointerdown — instead of assuming one. HEADER: `W12`-style labels computed from DAYS (startWeek + day/7, so they stay truthful at any scale) over a rule of `─` with `┬` at every 7-day tick; today is a fixed day-index prop (default 9, never derived from new Date(), so the frame is deterministic for a screenshot gate) drawn as a `┊` column in text-foreground repeated down every row, and drawn only where no bar already occupies that cell so glyphs never collide. Empty timeline cells render literally nothing — the ink concentrates in the bars, the ticks and the one `┊` instead of washing the frame in low-contrast filler. NO DEPENDENCY ARROWS ARE DRAWN, deliberately: a connector lane anchored to the successor's row can only place a predecessor's corner glyph at the right COLUMN, never at the right ROW, so every edge reads as leaving whichever task happens to sit directly above the successor; drawing edges truthfully needs vertical routing across intervening rows, which is more apparatus than the edge earns here. Dependencies stay in the data, where they drive ES/EF/LS/LF — the bar WEIGHT is what tells the reader which chain the ship date rides on, and that reads at rest with no pointer. INTERACTION: pointerdown on a bar records startX, the current offset and the measured cell pitch, then attaches window pointermove/pointerup/keydown listeners (window listeners, not setPointerCapture, which a synthetic pointerId would throw on); pointermove converts pixel dx to whole cells via round(dx / cellPx) and writes manualOffset[id], re-running the CPM memo on the same frame — which is the whole point: a task you drag can hand criticality to a different chain and you watch the `━`/`─` weights swap live. Escape during a drag restores the previous offset; Escape on a focused bar clears its manual offset entirely, returning the task to its computed schedule; ArrowLeft/ArrowRight nudge the focused task ±1 cell through the identical code path, clamped to [−base, cols*scale − base] where base is the task's dependency-earliest start: the lower clamp stops a left nudge on a task already sitting at day 0 from banking invisible negative offset that the next right nudge would spend unwinding, the upper stops a held right nudge from running ES past aria-valuemax while the bar saturates at the last column — every keypress moves the bar, and aria-valuenow stays in step with the stored offset. Hovering or focusing a task brightens its label and prints `Integrate payments  ES 7  EF 14  LS 11  LF 18  float 4 days` into an aria-live=polite readout, which at rest prints the project summary (`25 days  critical chain 6 tasks  3 tasks carry float`) instead. THEME: every colour is a token class (text-ns-accent, text-ns-muted, text-ns-muted/40, text-foreground, text-border, border-border, bg-background) — no hex anywhere, so light and dark both render correctly. --border is reserved for the structural `│` and `┌` rules, which may safely recede. REDUCED MOTION: there is no animation loop at all; the only transition is the 140ms colour ease on the row label, carrying motion-reduce:transition-none so prefers-reduced-motion snaps instead of easing and nothing is lost. A11Y: each bar is a real <button> carrying role=slider with aria-orientation=horizontal, aria-valuemin=0, aria-valuemax=cols*scale, aria-valuenow=ES and an aria-valuetext naming its day range and float — the arrow keys are then the role's own expected operation rather than an undocumented extra on a button — plus an aria-label naming the task and the available action, a visible focus-visible:ring-2 ring-inset ring-ns-accent on every bar, and full arrow-key operation; the readout is aria-live=polite so a screen reader hears the recomputed float. Props: tasks ({id,label,duration,deps}[], default a 9-task checkout replatform plan whose zero-float chain is discovery → wireframes → design-system → checkout-ui → qa-regression → ship, with api-contract carrying 4 days of float, integrate-payments 4 and launch-comms 8), cols (default 32), today (day index, default 9), startWeek (default 12), title, className."
      }
    },
    {
      "name": "gather-marver",
      "type": "registry:ui",
      "title": "Gather Marver",
      "description": "A file-upload / processing indicator sourced from the real hot-glass gather-and-marver sequence: an off-centre blob rolls back and forth on a flat baseline, losing its lumps a little more with every roll pass while it spins continuously underneath, the correction visibly progressing rather than a generic spinner or indeterminate ring.",
      "files": [
        {
          "path": "registry/core/gather-marver/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/gather-marver.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "upload",
          "canvas",
          "glass",
          "processing",
          "feedback"
        ],
        "instruction": "A file-upload / processing indicator sourced from the real gather-and-marver sequence in hot-glass work: a gaffer gathers a molten gob on the end of a blowpipe (it comes off the furnace lopsided), then rolls it back and forth on the marver — a flat steel table — to centre its mass around the pipe's axis and skin its surface before blowing starts. The blob outline is a closed uniform Catmull-Rom spline (12 control points, drawn as cubic beziers with tension 1, not a polygon smoothed by canvas line joins) with its base radius derived from 42% of the container's SMALLER dimension. Each control point carries a fixed lopsidedness offset (up to ±35% of the base radius, generated once per mount with a seeded PRNG) and that offset is scaled every frame by DECAY_PER_PASS (0.82) raised to a continuous roll-pass count — one roll pass being one full left-travel-then-right-travel cycle at PASS_PERIOD_MS (1800ms) — so the correction shrinks smoothly rather than snapping once per discrete pass. The blob's centre swings horizontally ±28% of the container's width around the marver's centre on that same 1.8s period (a sine, not a linear ping-pong, so the reversal at each extent reads as a roll rather than a bounce), and independent of both the correction decay and the horizontal travel, the blob spins continuously at 24°/s — the pipe rotating under the gaffer's hand — which is what keeps the loop alive and unbounded even after the gob has visually settled into a round disc: a fully centred blob still visibly turns forever, it never stops and never resets. Two modes read off the same real-time clock. Indeterminate (no `progress` prop, the default): the correction decay runs off genuine elapsed visible time and never stops, asymptotically approaching a round disc and staying there, spinning, for as long as the component stays mounted and visible. Determinate (a `progress` 0-100 prop supplied): the correction decay is driven by `progress` instead of elapsed time via 0.82^((progress/100)*18) — a mapping calibrated so progress=100 lands at roughly 2.8% residual per-point deviation, i.e. a settled, fully-round, evenly-lit disc — while the horizontal travel and the spin keep running off the real clock regardless of whether `progress` itself is changing, so a HELD progress value still visibly rolls and turns rather than idling (matching the rest of the registry's convention for held-but-alive determinate meters). Completion is never marked with --ns-accent — the finished state is simply an evenly-lit --foreground disc, since accent is interaction chrome only and this is ambient process state. Fill is a radial luminance gradient (ctx.createRadialGradient centred on the blob) from --foreground at the centre (the molten 'hot skin') out to --ns-muted at the rim, identical in both themes; light theme is the harder case since the rim can wash out against --surface there, so if a future token revision drops the rim below roughly 3:1 contrast against the surrounding surface it should widen that gradient's endpoint rather than reintroducing a literal colour. The marver itself is a static 1px --border line spanning the container width at 66% of its height, used correctly as a separator the blob visibly rests on and rolls along — never as a fill or stroke of the blob shape. Colours are read once via getComputedStyle on the document root inside a useLayoutEffect (before the canvas can ever paint) and re-read on a MutationObserver watching documentElement's class attribute, so a theme toggle repaints correctly with no remount and nothing draws with a default/black fallback ink first. prefers-reduced-motion (checked on mount and on every media-query change) freezes on STATIC_PASS = 5 roll-passes-equivalent of decay (≈37% of the starting deviation retained) with travel centred and spin at zero — a deliberately partially-corrected, structured frame, not the fully lopsided start and not the fully round end, so the 'lumpy blob getting rounder' mechanic is legible from that single static frame without needing motion to prove it. The render loop pauses on IntersectionObserver (offscreen) and document.hidden (backgrounded tab) and resumes cleanly without a time jump, since elapsed time is accumulated manually frame-to-frame rather than read from a wall clock. Props: progress (optional, 0-100, controlled — omit for the indeterminate self-driving loop), aria-label (default 'Processing'), className (required to size the root — the canvas fills whatever box its container is given). role is 'progressbar' with aria-valuemin/valuemax/valuenow when `progress` is supplied, otherwise 'img' with just the aria-label, since an indeterminate run has no numeric value to announce."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa",
          "warning": "#7a5200"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717",
          "warning": "#f5a623"
        }
      },
      "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 --ns-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": "git-graph-ascii-lanes",
      "type": "registry:ui",
      "title": "Git Graph ASCII Lanes",
      "description": "A commit-history DAG rendered as `git log --graph` does, where a real lane-allocation pass assigns each branch a column and emits `│ ├─┐ ┘ ┼` connector rows, and hovering a commit lights its full ancestor set through the lanes.",
      "files": [
        {
          "path": "registry/core/git-graph-ascii-lanes/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/git-graph-ascii-lanes.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "git",
          "graph",
          "dag",
          "ascii",
          "box-drawing",
          "history",
          "keyboard-navigation",
          "developer-tools"
        ],
        "instruction": "Build <GitGraphAsciiLanes commits? head? className?> where `commits` is `{id, subject, author, date, parents: string[], branch?}[]` in newest-first topological order, and render it exactly the way `git log --graph` renders a history: a monospace lane grid of DOM rows, no canvas and no rAF loop anywhere. THE LANE ALLOCATOR (the actual mechanic, not a router): keep one `active: (string|null)[]` array, initially empty, and walk the commit list top to bottom. For each commit c: `lane = active.indexOf(c.id)`; if -1, `lane = active.indexOf(null)`; if still -1, `lane = active.push(null) - 1`; then `active[lane] = c.id` so the row's snapshot shows the slot occupied. Snapshot `active` for the commit row, then free every OTHER slot still holding c.id — a branch converging — setting it null and recording a CLOSE pair `(thatLane -> lane)`. Set `active[lane] = c.parents[0] ?? null`. For each extra parent p at index >= 1 (i.e. a merge): `pLane = active.indexOf(p)`, else the lowest null slot, else a pushed new slot; `active[pLane] = p`; record an OPEN pair `(lane -> pLane)`. ROW RENDERING: every lane is 2 characters wide (glyph plus a spacer column). The commit row draws `●` at the commit's own lane — `◍` instead when `parents.length > 1`, so a merge is distinguishable by GLYPH and never by hue — `│` at every other non-null lane, and a space at null lanes. The CONNECTOR row is emitted between two commit rows only when that commit produced open or close pairs: live lanes are laid down as `│` first, then for each pair spanning lanes a<b it writes `├` at a, `─` across every intermediate column, and `┐` at b for an open pair or `┘` at b for a close pair; any live lane whose `│` the horizontal run crosses is upgraded to `┼`, and untouched live lanes keep their `│`. Lanes past MAX_LANES = 6 collapse: the last lane column (index MAX_LANES - 1) renders `⋯` and every lane at or beyond it is drawn there. The entire grid is ONE `useMemo` over (commits, collapsedMerges) that rebuilds from an EMPTY `active` array — never patched incrementally — so layout is a pure function of the data. INK is monochrome plus one accent and carries no branch identity: at REST the first-parent trunk lane's glyphs and every commit subject are `--foreground`, every other lane glyph and connector is `--ns-muted`, shas and author/age are `--ns-muted`, and HEAD's dot and sha are `--ns-accent`. `--border` is reserved exclusively for the dimmed non-ancestor state below, so the resting braid is never drawn in the faintest token and is fully legible before any input. ANCESTOR LIGHTING: hovering or keyboard-focusing a row runs a BFS over `parents` from that commit, and every row NOT in the resulting ancestor set — plus every lane column whose current occupant is not in it — drops to `--border` while the ancestor chain and the lanes it occupies hold `--foreground` (`--ns-accent` on HEAD), so the branch you are asking about lights up through the braid. Pointer-out or blur restores full ink across a 140ms color transition with no state left behind. COLLAPSE: Enter or Space on a merge row toggles that merge into a `collapsedMerges` set; the grid pass then walks that merge as if it had only its first parent, hides every commit reachable from its second parent but NOT from its first (an exclusive-ancestor set difference), and emits a single `⋯ N commits from feat/router` row in their place — because the whole lane grid is re-derived, the side lane is never opened and the braid visibly straightens; Enter again expands it. Enter on a non-merge row instead pins its ancestor highlight so it survives pointer-out. KEYBOARD: roving tabindex across the commit rows with ArrowUp/ArrowDown and Home/End, Escape clearing both the hover and pinned highlight, a real `<button>` per commit carrying an `aria-label` naming the subject, sha, author, age, HEAD status and the available action, `aria-expanded` on merge rows, and a `:focus-visible` outline in `--ns-accent` at `-2px` offset on every row. Tokens only (`--background --foreground --ns-muted --border --ns-accent`) applied as Tailwind token classes, never hex, correct in both themes. There is no ambient animation to gate, and every transition is `motion-reduce:transition-none`, so `prefers-reduced-motion` leaves the component fully usable and instantly settled. Props: `commits` (defaults to a 13-commit sample history with two merges and two side branches), `head` (commit id HEAD points at, defaults to the newest commit), `className`. Zero dependencies."
      }
    },
    {
      "name": "glaze-crawl-heal",
      "type": "registry:ui",
      "title": "Glaze Crawl Heal",
      "description": "A live status badge drawn as a glaze surface mid-firing: gas bubbles burst through the molten glaze and the melt either flows back over the crater (heals) or, when surface tension has failed, pulls away into a ragged bare patch that never closes (crawling). Three real ceramics behaviours standing in for operational, degraded and down.",
      "files": [
        {
          "path": "registry/core/glaze-crawl-heal/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/glaze-crawl-heal.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "badge",
          "indicator",
          "canvas",
          "ambient",
          "aria-live",
          "role-status"
        ],
        "instruction": "Build <GlazeCrawlHeal status? label? showLabel? size? className?> as a live status badge sourced from real glaze-firing behaviour, not an invented colour code. GLAZE PHYSICS: in a kiln the glaze goes molten and gas bubbles rise through the melt and burst at the surface, leaving craters; whether the melt's viscosity and surface tension let it flow back over the crater (heal) or not (crawling — the glaze pulls away from the clay body into a bare patch that never closes) is a documented ceramics defect, and it is the component's entire state axis: healthy = bubbles burst and heal in well under a second (short healMs, thin/fluid melt), degraded = the melt is thicker so craters linger for one to three seconds before closing (long healMs, same mechanic, different viscosity), down = surface tension has failed outright — bubbles still burst (gas still escapes the melt) but the melt never flows back, so ragged, beaded-rim bare patches accumulate and persist until conditions recover. RENDERING: one DPR-capped <canvas> circle inset inside a border-border ring. Every frame redraws a solid disc filled with the canvas's own colour (getComputedStyle(canvas).color, the canvas carries text-foreground), then punches each live bubble/patch into that disc via globalCompositeOperation='destination-out' at its current radius — a perfect circle for a bubble, an 11-vertex ragged polygon (per-vertex radius 0.72-1.06x nominal, from a seeded mulberry32 walk) for a crawled patch. Punching with destination-out rather than painting a second token means a 'hole' always reveals whatever is genuinely behind the badge, so molten ink is dark-on-light in light theme and light-on-dark in dark theme for free, with no theme branch in the draw code. A healing crater gets a thin foreground rim stroke (its raised lip) that fades as it closes; a crawled patch gets small foreground beads at its ragged vertices instead (glaze balled up at the receding edge rather than flowing back) and never gets a rim, because it isn't healing. A slow-drifting, low-alpha destination-out ellipse (0.08 alpha, ~0.00015 rad/ms) sweeps across the disc as a specular sheen so the surface still reads as liquid in the seconds between bubble events. LIFECYCLE: a bubble opens 0->maxR over openMs (ease-out-cubic, the pop) then closes maxR->0 over a per-status randomised healMs (ease-in-out-cubic) and is removed; a momentary full-strength rim flash brackets the peak, selling the burst. A down-state patch bursts open the same way but then creeps 0->maxR on an exponential approach (r = maxR*(1-e^(-age/tau)), tau ~1.3s) with a small ambient wobble, asymptotically approaching but never reaching maxR — it simply never gets a healMs, so it never closes, which is what makes 'never heals' true rather than merely slow. Concurrency and nucleation cadence differ per status (healthy: 1 bubble, 650-1500ms between spawns; degraded: up to 2, 1100-2200ms; down: up to 3 patches, 900-1800ms) so the badge is genuinely alive at rest at every status, with no settled end state — screenshots taken a mount, +2.5s and +5s all differ. RECOVERY: when status transitions away from 'down', every live patch is handed a real healMs drawn from the new status's own healthy/degraded range and its current creeping radius becomes its heal-start radius, so it finally flows back closed on the same ease-in-out curve a bubble uses — the melt genuinely repairs itself once conditions are fixed, it does not just vanish. A11Y: the root is a <span role='status' aria-live='polite'> so every status change is announced by assistive tech; the canvas is aria-hidden and is decoration only — the accessible name and the announced text are one real string (STATUS_LABEL: 'Operational' / 'Degraded' / 'Down', or a caller-supplied label), rendered as a visible text node by default (showLabel) or as a visually-hidden span when showLabel is false, so a colour-blind or screen-reader user always gets the same information a sighted user gets from the canvas. No throttling logic is needed beyond that — status is caller-driven, discrete state, not a per-frame value. TOKENS: ink is read once via getComputedStyle(canvas).color in useLayoutEffect, before first paint, and re-read on a documentElement class MutationObserver; every stroke/fill on the canvas is that one ink value composited with alpha, so nothing is a second colour — --ns-accent never appears, because this is ambient state, not interaction chrome, and the badge's own outline ring is the one legitimate --border use. SCALE: default size is 16px, a realistic inline status-dot scale, and the mechanic (a bursting/healing/crawling disc) is proportioned to read down to about 14px; below roughly 10-12px the ragged-patch texture and rim detail stop resolving to the eye and the text label becomes the only reliable channel — stated plainly rather than hidden, which is exactly why the text is never optional to the accessibility tree. REDUCED MOTION: window.matchMedia('(prefers-reduced-motion: reduce)') freezes the canvas on one deterministic, non-t0 still per status built from fixed offsets/radii (and, for 'down', two fixed-seed ragged shapes) rather than any live simulation state — healthy shows one crater mid-heal and one nearly closed, degraded shows three craters at clearly different open/closing stages, down shows one large stalled patch and one smaller forming one — so the mechanic is legible in a single frame and the frame is byte-stable across repeated captures; status text and the aria-live region stay fully live regardless of the motion preference. Props: status ('healthy' | 'degraded' | 'down', default 'healthy'), label (accessible override), showLabel (default true), size (px, default 16), className. Zero dependencies, no canvas resize observer needed since size is a controlled prop rather than a fluid container."
      }
    },
    {
      "name": "glory-hole-cycle",
      "type": "registry:ui",
      "title": "Glory Hole Cycle",
      "description": "A 'still actively processing' status chip whose small canvas glow rises fast and cools slow on a fixed 4.0s beat, modeled on a glassblower's furnace reheat cycle instead of a generic pulsing dot.",
      "files": [
        {
          "path": "registry/core/glory-hole-cycle/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/glory-hole-cycle.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "indicator",
          "badge",
          "processing",
          "syncing",
          "canvas",
          "motion",
          "aria-live"
        ],
        "instruction": "A passive 'still actively processing' status chip — the pulsing-dot pattern's replacement — built on glassblowing's glory-hole reheat cycle: hot glass cools the instant it leaves the furnace mouth, so a gaffer plunges the piece back into the glory hole on a steady beat to bring it back to working heat, then withdraws it to work while hot, repeating for as long as the piece is still being formed. The chip is a rounded pill (rounded-full, border-border 1px ring, bg-surface fill) containing a fixed 36x36 CSS-px circular <canvas> (DPR-scaled on mount and window resize) plus a text label. The canvas draws one soft radial gradient disc, no grid, radius fixed at 60% of the canvas's smaller (36px) dimension, three gradient stops (opaque center, 55% alpha at 70%, 0% alpha at the edge). Colour is never a literal: on mount, and on every class-attribute mutation of document.documentElement (a MutationObserver, for live theme toggles), the component reads var(--ns-muted) and var(--foreground) via getComputedStyle and parses them to RGB; every frame's disc colour is a straight RGB lerp between those two parsed values driven by a scalar luminance L, never a hue shift and never var(--ns-accent) (interaction chrome only, never the climactic moment). The animation is a fixed, non-negotiable 4.0s cycle with two phases: a 0.7s reheat ramp (cubic ease-out) carrying L from 0.18 (near --ns-muted) up to 0.92 (near --foreground), then a 3.3s cool decay, an exponential L(t) = Lmin + (Lmax-Lmin) * exp(-t/tau) with tau = 1.1s, so it visibly dims fast then trails slowly like real radiative cooling rather than a linear fade. A subtle 1px inner ring (a plain DOM span, border colour set to the live --foreground RGB) pulses opacity across the identical 0.15-0.4 range on the identical rise/decay curve, so ring and disc read as one discrete 'reinsertion' pulse each beat rather than a continuous shimmer. The whole thing is one requestAnimationFrame loop computing phase from (now - startTime) % 4000ms; it is paused via an IntersectionObserver while the chip is scrolled off-screen and resumed on a fresh cycle boundary rather than a stale phase, and cancelled along with both observers on unmount. Nothing here restarts or accelerates on hover/focus — the cycle represents real elapsed process time, not UI feedback, so if a dismiss affordance is ever added beside it, it must leave the glow's phase untouched. prefers-reduced-motion skips the rAF loop entirely and paints exactly one frozen frame at t=0.7s into the cycle (L=Lmax, ring opacity 0.4) — the peak 'hot' frame, deliberately not an ambiguous mid-decay grey. Light theme is the harder case: because --ns-muted sits noticeably lighter than raw black there, the decay tail's low end is checked against --surface first so the last ~1.5s of every cycle still reads as 'dim, not gone' rather than disappearing into the chip background. Props: label (status text rendered beside the glow and announced via the chip's own role=status aria-live=polite region, default 'Processing'), className. Zero dependencies, no WebGL."
      }
    },
    {
      "name": "gluten-windowpane",
      "type": "registry:ui",
      "title": "Gluten Windowpane",
      "description": "A background-task progress indicator built on the baker's windowpane test: a dough membrane is stretched, held, and released on a repeating 4-cycle knead loop, going from opaque and jittery-stranded to translucent, straight-stranded and light-through as kneading 'develops', then rests fully developed before a fresh dough test begins.",
      "files": [
        {
          "path": "registry/core/gluten-windowpane/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/gluten-windowpane.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "canvas",
          "membrane",
          "elasticity",
          "dough",
          "ambient",
          "processing",
          "status"
        ],
        "instruction": "A card-scale 2D canvas loader modeling the baker's windowpane test for gluten development. Strand geometry derives from the container's smaller dimension: strand length = minDim * 0.8, patch half-height = minDim * 0.34. The loop runs 4 knead cycles of 3200ms each (1800ms stretch easing scaleX from 1 to 1.6 with an ease-in-out, 600ms hold at scaleX 1.6, 800ms ease-in-out snap-back from 1.6 to 1), then an extra 1500ms rest at scaleX 1 holding cycle 4's fully-developed values before the loop restarts fresh (total 14300ms). Per-cycle targets (strand count 8/14/22/34, angular jitter off the horizontal stretch axis +-35/+-22/+-11/+-4 degrees, membrane translucency/canvas alpha 0.05/0.18/0.35/0.62) are not stepped at cycle boundaries — they are the ARRIVAL values a linear interpolation reaches by the end of each cycle's 1800ms stretch, departing from the previous cycle's own arrival values (cycle 1 departs from a synthetic pre-test baseline: count 5, jitter +-45deg, alpha 0.02), so the spec's one legibility thing — opaque/jittery going to translucent/aligned — plays out visibly within a single stretch, not as a jump-cut between cycles. Hold and snap-back keep the arrived values (alpha gets one further +-15% ripple tied to stretch/relax for a felt 'thinning', but strand count and jitter stay put once arrived). Strand count is rounded to the nearest integer only at render time. Rendering: a dough-tinted fill (mix of whichever of --background/--foreground reads lighter by relative luminance, toward the darker one, at a fixed 22% tint) is painted at (1 - alpha) opacity onto an otherwise-transparent canvas clipped to the patch ellipse, so the real page --background underneath shows through more as translucency climbs — there is no separately painted background rectangle. Strands are thin lines in whichever token reads as ink (the darker-luminance one, i.e. --foreground in both themes as authored), evenly spread across the patch height. Each strand occupies a fixed slot in a 34-slot jitter/spread table (seeded mulberry32); slots activate in a midpoint-subdivision order whose every prefix is itself evenly spread, so raising the (continuously interpolated) strand count only ever adds a strand between two that already exist — a slot never moves or vacates once drawn, and the newest fractional strand fades in by alpha rather than popping in. A radial highlight, centered on the (optionally pointer-biased) stretch center, blends the dough fill toward the lighter of the two tokens and never toward var(--ns-accent) — brightness/alpha only. Colors are read once via getComputedStyle(document.documentElement) before any paint, with a MutationObserver on the documentElement class re-deriving on theme change and repainting immediately when reduced motion is active. Optional pointer interaction: pointermove within the card springs the stretch/highlight center toward the cursor (clamped to 18% of min(w,h), scaled 0.6x), decaying back to the container center on pointerleave — purely a highlight-position bias, never a state that persists (mode: none in autoplay, since nothing but the internal clock drives it). A ResizeObserver rebuilds canvas backing-store size and center on layout change; an IntersectionObserver pauses the rAF loop while off-screen and resumes it on re-intersection; all observers and the rAF are torn down on unmount. Under prefers-reduced-motion the whole rAF loop never starts — a single frame is drawn synchronously at cycle 4's own 600ms hold, mid-point (max translucency, tightest strand alignment, scaleX 1.6, full light-through highlight) rather than at t0, which would be the least structured, least informative frame. Root is `role=\"status\"` with a `sr-only` accessible label prop (default 'Processing'); canvas is `aria-hidden`. Props: `label` (default 'Processing'), `className`. Zero dependencies, canvas + DOM only, no color literals anywhere."
      }
    },
    {
      "name": "gravure-cell-wipe",
      "type": "registry:ui",
      "title": "Gravure Cell Wipe",
      "description": "A divider / background texture strip printed as a rotogravure cylinder: a static, seeded matrix of engraved cell depths sits under a doctor-blade band that sweeps and wraps every 20s, wiping the flooded land clean and leaving each cell's ink alpha at its depth-derived steady state, with a decaying brightness flash trailing every fresh pass.",
      "files": [
        {
          "path": "registry/core/gravure-cell-wipe/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/gravure-cell-wipe.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "hr",
          "gravure",
          "print",
          "ink",
          "canvas",
          "ambient",
          "monochrome"
        ],
        "instruction": "Build a full-width divider strip whose entire surface is a canvas-rendered rotogravure print cycle, sourced from real cylinder engraving and doctor-blade ink metering (the standard packaging/publication rotogravure process), not a decorative moving gradient. Root is a `role=\"separator\" aria-orientation=\"horizontal\"` div at a configurable `height` (default 192px) with `bg-background`, holding an absolutely-filled `aria-hidden` canvas at `w-full h-full`.\n\nGrid pitch derives from the container's SMALLER dimension: `pitch = clamp(min(w,h)/48, 6, 14)` px, `cols = floor(w/pitch)`, `rows = floor(h/pitch)`. Cell DEPTH (0-1, the engraved cylinder's fixed tonal matrix) comes from a single low-frequency value-noise field — a coarse lattice (one value per ~6 grid cells) bilinearly interpolated across the full grid, seeded from a FIXED constant (not reseeded on every rebuild) — built once per grid size on mount/resize and never touched again; the engraving does not reshape even when a theme flip forces a colour re-derive, only the wipe cycle moves.\n\nThe doctor blade is a vertical band `BLADE_WIDTH_CELLS = 3` cells wide sweeping left to right and WRAPPING at the grid edge (the cylinder is circumferential — column `cols-1` is adjacent to column 0), completing one full rotation every `ROTATION_S = 20` seconds: `bandLead = ((t/20) * cols) mod cols`. Because the ink pan floods a different angular position on the cylinder than the blade wipes, the grid splits into three regions by `behind = (bandLead - col) mod cols` (the wrap-safe cell-count since the blade's leading edge last reached this column): (1) `behind < 3` — currently under the blade: RAW ink, `alpha = depth * rawMul` (rawMul = 1.0 dark theme, 0.85 light — undamped ink read heavier than intended in light theme at full multiplier) plus a small deterministic per-cell sine flicker (~±0.025) standing in for ink still wet from the flood pass; (2) `3 <= behind <= 0.5*cols` — wiped this rotation: settled steady state `alpha = depth * cap + 0.12 * exp(-sinceWipeS / 0.9)` where `cap` is 0.85 dark / 0.7 light (dark ink on a light ground reads heavier at equal alpha) and `sinceWipeS = ((behind-3)/cols)*20`; (3) `behind > 0.5*cols` — re-flooded ahead of the blade's next pass, same RAW branch as (1). This third branch is what makes 'ahead of the band, denser, unsettled' actually visible on screen at any given instant, not just in the three columns directly under the blade — roughly half the strip reads raw and half reads settled at any moment, with the boundary translating smoothly as the blade rotates (never a hard once-per-cycle reset).\n\nEach cell draws as a filled circle at `(col*pitch + pitch/2, row*pitch + pitch/2)` with radius `0.375 * pitch` (a cell DIAMETER of 0.75x pitch — this is the reading of the spec's '0.75x pitch cell radius' that actually leaves a visible land/gutter between cells instead of over-tiling solid). Rendering is bucketed for performance: quantize each cell's alpha into ~24 discrete levels, accumulate each cell's arc into that bucket's `Path2D` (skip cells whose alpha rounds below ~0.01), then after the full grid pass do one `ctx.fill(path)` per occupied bucket with `globalAlpha` set to the bucket's level — one to twenty-four fills a frame instead of one per cell, filled with `--foreground`, composited over the strip's own `bg-background`.\n\nPointer interaction: on `pointermove` over the canvas, any cell within `3 * pitch` px of the pointer gets its DISPLAYED depth (not the underlying static field) nudged up to `+0.15` with a smooth radial falloff (`1 - smoothstep(0, 3*pitch, dist)`), applied before the region alpha math so a 'peeked' cell still respects whichever region it is in. On `pointerleave`, freeze the last pointer position and linearly decay the nudge's magnitude to zero over 600ms using wall-clock time (never the frozen reduced-motion clock, or the decay computes as permanently active), then drop it entirely — luminance only, it modulates the same `--foreground` alpha as everything else and must never touch `--ns-accent`, and it must leave no persistent tint once decayed.\n\nColor is read via `getComputedStyle(document.documentElement).getPropertyValue('--foreground')` at mount, re-derived (along with the dark/light `cap`/`rawMul` selection) on a `MutationObserver` watching `documentElement`'s class — that observer only re-derives colour and redraws, it must NOT rebuild the depth field, or every theme flip re-engraves the cylinder into a different texture. NO paint before the first successful colour read (draw is a no-op while the parsed colour is null) — covering the initial `requestAnimationFrame`, `ResizeObserver`, and `IntersectionObserver` resume paths. `--border` is never used as a fill or stroke anywhere. Backing store is DPR-capped (max 2) and sized off the root's own `getBoundingClientRect`, re-derived on `ResizeObserver` (which also rebuilds the static depth field for the new grid size only). The animation loop is a plain `requestAnimationFrame`, paused via `visibilitychange` and `IntersectionObserver` when hidden/off-screen, and stopped entirely under `prefers-reduced-motion: reduce`, which freezes the rotation clock at `FREEZE_PHASE = 0.5` — the blade at 50% across the grid, the one frame that shows both the unwiped ink field ahead and the wiped/settled field behind at once — while still letting a pointer peek decay play out once in real time if triggered, then settling back to the frozen frame with the peek fully gone. No dependencies."
      }
    },
    {
      "name": "grazing-light",
      "type": "registry:ui",
      "title": "Grazing Light",
      "description": "A feature-grid card whose icon and heading are blind-embossed into the surface: invisible relief that a low-angle light reveals as it rakes slowly across at idle, and that snaps to track the pointer's bearing across the whole grid at once.",
      "files": [
        {
          "path": "registry/core/grazing-light/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/grazing-light.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "feature-grid",
          "emboss",
          "link",
          "cursor",
          "grid",
          "svg-filter",
          "micro-interaction"
        ],
        "instruction": "Build two exports. GrazingLightCard is the feature card: a single <a href> (never a nested interactive control — its accessible name comes from the real heading + body text content, both plain, full-contrast var(--foreground)/var(--ns-muted) text that reads perfectly with every shadow stripped) containing a small SVG icon (default: a built-in sparkle path in a 0 0 24 24 viewBox, or caller-supplied <path>/<g> content via the `icon` prop), an <h3> heading, and a <p> body. GrazingLightGrid is an optional wrapping provider — <GrazingLightGrid><GrazingLightCard .../>...</GrazingLightGrid> — that a consumer wraps around N cards; a card rendered without one still works standalone.\n\nOne number drives everything: --rake-angle, a degrees value written onto each card's own root element every animation frame. From that one angle, cos/sin (computed once per tick) derive three things, all token-only: (a) the heading's dual text-shadow bevel (offset magnitude ~1.8px, BEVEL_PX) — one offset in var(--border), the opposite offset in var(--ns-muted) — written directly via ref.style.textShadow; (b) two feDropShadow filter primitives stacked inside the icon's own SVG <filter>, dx/dy set via setAttribute to the SAME cos/sin pair each tick (literal duplicate offsets, not a CSS approximation — flood-color var(--border) and var(--ns-muted) respectively, stdDeviation 0 for a crisp hard-edge bevel, not a blur); (c) a one-sided inset box-shadow on the card root (color-mix(in srgb, var(--foreground) 14%, transparent), 0 blur) standing in for the border catching the same light asymmetrically. No gradients, no filters beyond the two feDropShadow, no color outside --background/--foreground/--ns-muted/--border/--ns-accent.\n\nIdle: the angle eases through a 12s circuit, 4 quadrants of 90deg each, each quadrant run through an ease-in-out-cubic — so the light visibly lingers near 0/90/180/270deg and sweeps faster between them, rather than rotating at constant speed. When a GrazingLightGrid wraps the cards, ONE rAF loop (owned by the provider, not one per card — the same single-loop idiom as this repo's MagneticDock) computes that shared idle angle every frame and broadcasts it to every subscribed card, so cards mounted at different times stay phase-coherent instead of drifting apart. On pointermove anywhere inside the grid (one listener on the provider's own wrapper, not one per card), the SAME broadcast carries the raw pointer position instead of a null; every subscribed card independently computes its own bearing (atan2 of pointer-minus-own-center) and springs its angle toward that bearing (lerp with the shortest signed angular delta, so it never spins the long way around) — the effect reads as one lit surface with N cards tilting toward a single light source, not N independent hover states. Leaving the grid reverts every card smoothly back to chasing the shared idle circuit. A card with no GrazingLightGrid ancestor runs the identical math on its own rAF loop, scoped to pointer events on its own element only.\n\nHover/focus affordance is ordinary and separate from the light: the card's border tints toward color-mix(...,var(--ns-accent)...) via a plain Tailwind hover/focus-visible utility (interaction-only accent, standard focus-visible outline, no outline-none anywhere near it) — never the rake itself, so which card is interactive is never answered by the moving light, and no interaction of any kind lives inside the emboss. prefers-reduced-motion pins every card's angle at a fixed 315deg, skips the rAF loop and every pointer listener entirely — still legible, still bevelled, simply static. Zero dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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, --ns-muted, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-muted and reveal a small 'feature this' hint (opacity 0 to 1), and the featured tile alone carries a small --ns-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/--ns-muted/--border/--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": "grinding-chatter-lobes",
      "type": "registry:ui",
      "title": "Grinding Chatter Lobes",
      "description": "A card-scale loader substitute driven by regenerative chatter in cylindrical grinding: a rotating disc's rim grows a lobed profile as it re-cuts its own previous pass, the wave self-amplifying then self-limiting until a dress event clears it and a new lobe count begins.",
      "files": [
        {
          "path": "registry/core/grinding-chatter-lobes/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/grinding-chatter-lobes.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "loader",
          "activity-indicator",
          "grinding",
          "chatter",
          "regenerative-vibration",
          "self-excited",
          "generative",
          "physics"
        ],
        "instruction": "Build <GrindingChatterLobes label? className?> as a self-contained Canvas 2D loader, DPR-clamped to 2, direct-DOM rAF as the sole writer, zero dependencies. Disc radius = 0.38 * min(container width, height). The rim is sampled at 360 angular bins (1deg resolution) and rebuilt from a coefficient (lobe count N, amplitude, phase, rotation) every frame rather than accumulated as pixel history, so cost stays flat. The whole rim rotates continuously at 0.15 rev/s (slowed from real 1-5 rev/s wheelhead speeds to stay legible at card scale) via an incrementally accumulated rotation angle (never derived from absolute elapsed time, so long sessions don't lose precision). Each growth cycle picks a lobe count N from {5,6,7,8,9,10,11} and a random phase, both held fixed for the whole cycle (real chatter locks to one harmonic per stable cycle) via a mulberry32 PRNG reseeded per cycle index from the mount clock — real chatter theory (Tobias/Merritt) says the wave locks, not jitters. Amplitude follows a logistic self-limiting curve A(t) = Amax / (1 + exp(-r*(t - 3))), Amax = 0.06 * radius, r = ln(19)/3 so growth visibly ramps ~5%->~95% of Amax over the first 6s of a 40s growth window, then plateaus (matches real self-limiting saturation against contact stiffness). Every 40s a dress event fires: the rim's amplitude eases out (cubic) from its plateau value to zero over 1.2s while N holds, then the next 40s window picks a fresh N and phase and growth restarts from zero amplitude. The whole 41.2s cycle is phase-desynced by an offset derived from `Date.now()` at mount (not always zero), so any two page loads show different states and the resting loop is alive at rest: visibly different at t0/2.5s/5s with zero input, unbounded. Pointer interaction: moving the pointer over the card tracks its angle in SCREEN space (the simulated contact point stays fixed while the disc spins under it, converted to the disc's local rotating frame each frame) and locally boosts rim amplitude up to 1.5x within a 30deg window (raised-cosine falloff) around that angle, the boost envelope easing in while the pointer is present and decaying to zero over ~500ms after pointerleave; this never recolors the rim with --ns-accent (accent is not used anywhere in the component) and never touches the global 40s dress-event clock, which stays independent of pointer input. Rendering: rim stroke is var(--foreground) at 1.5px; rim fill is a radial gradient from var(--background) at the center to var(--ns-muted) (alpha-compressed to 0.55, derived at runtime via a canvas fillStyle probe so it stays a real RGB blend, never a colour literal) at the outer edge, so scalloping reads as depth rather than colour, checked in light theme first so the blended edge stays clearly short of --border-adjacent values. Colours are read via getComputedStyle(document.documentElement) before the first paint and re-derived on a MutationObserver watching the documentElement's class attribute, so a theme flip repaints correctly with no colour literal anywhere. A ResizeObserver rebuilds geometry on layout change and an IntersectionObserver pauses/resumes the rAF loop off-screen. Under prefers-reduced-motion the component renders one static frame named FREEZE_PHASE = 'lobe-70pct': amplitude fixed at 70% of Amax with a representative 8-lobe profile at a non-zero rotation — the most structured single frame, not t0 or a plateaued extreme — and the loop, pointer listeners, and IntersectionObserver are never attached, though the ResizeObserver and MutationObserver stay live so the frozen frame still resizes and retints correctly. Accessibility: the root carries role=\"status\" with a visually-hidden label (default 'Loading'); the canvas is aria-hidden. Props: label (status text, default 'Loading'), className (merged onto the root)."
      }
    },
    {
      "name": "groove-pitch",
      "type": "registry:ui",
      "title": "Groove Pitch",
      "description": "An ambient loader modeling a vinyl mastering lathe's variable groove pitch: a cutting-point marker spirals outward from a lead-in, its own turn spacing visibly widening over loud passages and tightening over quiet ones, completing a side and resetting to lead-in on an unbounded loop.",
      "files": [
        {
          "path": "registry/core/groove-pitch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/groove-pitch.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "indeterminate",
          "canvas",
          "vinyl",
          "lathe",
          "spiral",
          "ambient",
          "mechanism"
        ],
        "instruction": "Build <GroovePitch label? className?> as a self-contained ambient loader, zero dependencies, 2D canvas only (no SVG, no WebGL). MECHANISM: models a real vinyl mastering lathe's variable-pitch cutting — the lathe varies the radial spacing between adjacent spiral turns in real time based on program level, pulling pitch wider over loud/bassy passages (fewer grooves per inch, avoiding wall-to-wall breakthrough) and packing it tighter over quiet ones. Render a single continuous spiral polyline growing outward from a small lead-in radius at the canvas center, integrated in polar form: at each fixed 1/60s integration step, advance theta by ANGULAR_RATE * dt (ANGULAR_RATE = 2*PI/3 rad/s, i.e. one rendered revolution per 3s — deliberately decoupled from the real lathe's 33 1/3 RPM, which is documented in a code comment only and never animated 1:1, per the round-9 aliasing rule) and grow radius by (pitch(t) / (2*PI)) * dTheta, where pitch(t) = midPitch + amp*sin(2*PI*t/8) oscillates on an 8s period (amp/midPitch = (5.8/2.2 - 1)/(5.8/2.2 + 1) ~= 0.45, preserving the >2.5x pitch contrast ratio from the real reference numbers at any container size) so a pitch-band transition is visible roughly every 4s. midPitch is derived, not fixed: (maxRadius - leadInRadius) / 14 (14 = total revolutions over the 42s traversal), so the spiral's outer edge is reached at essentially TRAVERSAL_S = 42s regardless of container size. maxRadius = 0.425 * min(containerWidth, containerHeight) (spiral fits inside a circle 0.85x the container's smaller dimension); leadInRadius = 0.055 * maxRadius. Traversal time wraps modulo 42s — once the spiral reaches its outer edge it resets to the lead-in and a fresh 'side' begins, an unbounded loop, never a finished/stopped state. Draw the spiral as a single stroked path (var(--foreground) at 0.85 opacity, round joins/caps, line width ~0.6% of the container's smaller dimension) plus one filled circle marker (var(--foreground) at full opacity, radius ~1.8% of the smaller dimension) at the spiral's current leading edge/tip — this marker is the one thing a viewer should track by eye. RENDERING: canvas is w-full h-full inside a relative aspect-square wrapper (default max-w-[220px], overridable via className), DPR-capped backing store (min(2, devicePixelRatio)) resized via ResizeObserver on the wrapper. Colour is read once via getComputedStyle(document.documentElement).getPropertyValue('--foreground') before the first paint, and re-read on a MutationObserver watching documentElement's class attribute — no colour literal anywhere, no paint before that first read. The rAF loop is paused (cancelAnimationFrame, not just skipped) whenever an IntersectionObserver (threshold 0) reports the wrapper off-screen OR document.visibilityState !== 'visible', and resumes from wherever real elapsed time now places it (never resets phase) when both become true again. prefers-reduced-motion: reduce disables the rAF loop entirely and draws exactly one frame at traversal fraction 0.35 (14.7s into the 42s cycle) — chosen because it lands mid pitch-transition, the single frame that shows the turn spacing visibly different from its immediate neighbors rather than a frame where pitch looks locally constant; a live matchMedia 'change' listener re-derives this on an OS-level reduced-motion toggle. Root element is role='status' aria-label={label} (default 'Loading'); the canvas itself is aria-hidden. No pointer or keyboard interaction — this is a pure ambient/display loader, nothing to focus-ring, var(--ns-accent) never appears anywhere in it. Props: label (string, default 'Loading'), className (merged onto the root wrapper, e.g. to override max-w-[220px] or aspect-square)."
      }
    },
    {
      "name": "growth-ring",
      "type": "registry:ui",
      "title": "Growth Ring",
      "description": "Version history as a dendrochronological cross-section: one ring per save, ring width proportional to change size, a dark line wherever authorship changed hands, and restoring an older version previews the discard as outer rings hinging open before you confirm.",
      "files": [
        {
          "path": "registry/core/growth-ring/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/growth-ring.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "version-history",
          "restore",
          "listbox",
          "svg",
          "data-viz",
          "confirmation",
          "destructive",
          "mono",
          "accessibility"
        ],
        "instruction": "GrowthRing pairs an authoritative listbox of versions with a decorative SVG cross-section that mirrors the same data as concentric rings. Props: `versions` (`{id,label,author,delta,deltaLabel,time}[]`, newest first — the component reverses this internally so the oldest save sits at the core and radius always grows outward with time, matching the physical read of tree rings), `onRestore` (fires once a restore is actually confirmed, with the restored version), `ariaLabel`, `className`. The listbox (`role=listbox`) is the only interactive and only authoritative surface: each `role=option` renders its facts as real text in document order — label, author, delta, timestamp, then 'Current' on the newest row or 'Restore' on every older one — so an option's accessible name alone reads e.g. 'v9, Jae Lin, +76 words, Yesterday 17:12, Restore'. Roving tabindex drives keyboard nav (Up/Down/Home/End move the tabbable option and DOM focus together); Enter, Space, or a click on any option except the newest arms that version as a restore candidate. The SVG beside the list is `aria-hidden` end to end and derives everything from one governing scalar per version — its index in time. Radius is that index's cumulative position (strictly monotonic; grouping or sorting rings by author is never done, because position-equals-date is the entire point of the metaphor). Ring width is `clamp(|delta| / 25, 2, 14)`px, so an ordinary edit reads as a thin band and a giant paste still clamps to the same 14px ceiling instead of swallowing the rest of the chart. Adjacent rings alternate `--ns-muted`/`--background` fill with a 1px `--border` boundary circle between them; wherever the boundary's two neighboring versions have different authors, that boundary renders instead as a tight `--foreground` line (1.6px) — a latewood mark, readable independently of the alternating fill. The option currently holding real DOM focus gets a `--ns-accent` left rail on its row (the component's one non-button use of that token) and its matching ring gets an author-initials label in Geist Mono set along an invisible circular guide path; hovering *or* focusing a row pulses its ring by increasing `stroke-width` alone, on a 200ms transition, with no color, scale, or opacity change. Arming a version (everything but the newest) is the restore preview: an announcement text and Cancel/Confirm panel appear, the armed version's own outer edge is redrawn as a 2px `--foreground` cut line, and every ring chronologically after it — the versions a restore would discard — is regrouped into one `<g>` that folds open 8deg around a shared hinge line at the bottom of the disc — a 3D `perspective()/rotateX()` fold, not a flat 2D `rotate()`, because rotating a full closed ring in-plane just produces a second, off-center ring rather than a hinged lid — and drops to 40% opacity, so the discard is shown as a physical flap opening before anything is lost. Re-arming (clicking a different older row) just re-targets the same preview; Cancel or Escape drops it with no side effect. Confirm restore — the only other `--ns-accent` surface in the component, and a genuine second step — truncates the version list down to the armed version and everything older, calls `onRestore`, and returns to the resting, unarmed cross-section with the restored version now on top. Every arm/cancel/confirm transition writes a plain-language sentence ('Restoring to v10 removes 4 newer versions by 2 authors.', 'Restore cancelled.', 'Restored to v10.') into a single `role=status`/`aria-live=polite` region, so the destructive scope is announced in words, not left to the geometry. Under `prefers-reduced-motion: reduce` the hinge rotation is dropped entirely (the discarded rings' transform stays `none`) and only the opacity dim plays, and the hover/focus stroke-width pulse loses its transition and applies instantly — both still fully legible. No canvas, no dependency; every color used anywhere in the component is one of `--background`, `--foreground`, `--ns-muted`, `--border`, or `--ns-accent`."
      }
    },
    {
      "name": "guardrail-interlock-keys",
      "type": "registry:ui",
      "title": "Guardrail Interlock Keys",
      "description": "Output guardrails built as a trapped-key interlock: every policy gate holds one key, the release bolt only retracts when all of them are seated, and a tripped gate is a visibly empty, barred keyway rather than a red badge.",
      "files": [
        {
          "path": "registry/core/guardrail-interlock-keys/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/guardrail-interlock-keys.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "agent",
          "llm",
          "guardrail",
          "policy",
          "safety",
          "interlock",
          "approval",
          "mono",
          "status",
          "accessibility"
        ],
        "instruction": "`<KeyInterlock gates={InterlockGate[]} output label? title? operator? onOverride? className? />` renders a set of output guardrails as a mechanical trapped-key interlock instead of a row of coloured status badges. Each gate is `{ id, label, state: 'pending' | 'released' | 'tripped', detail? }` and draws as a 40x54 SVG slot: a 1px `stroke-border` outline path with a 7px-wide keyway notch physically cut into its top edge (`M0.5 53.5 V0.5 H16.5 V14 H23.5 V0.5 H39.5 V53.5 Z`), so the shape itself says 'this slot takes a key'. STATE IS ENTIRELY SHAPE-CODED, never hue-coded — there is no status colour anywhere in the component, so it reads identically in monochrome and in both themes. RELEASED draws the key SEATED: a 7x8 bit filling the keyway over a 3px stem running 18px down into the slot body, solid `currentColor` (`--foreground`). PENDING draws the keyway EMPTY with a 1.5px `stroke-dasharray='2 2'` outline breathing opacity 0.45 -> 1 on a 3.2s ease-in-out infinite alternate keyframe, each gate offset by a deterministic index-derived `animationDelay` of `((i * 260) % 1040) - 520` ms (a -520..+260ms spread in 260ms steps) so the row does not pulse in lockstep — this is the only ambient motion in the piece. TRIPPED draws the keyway empty AND physically obstructed: a 2px `--foreground` line from (8,18) to (32,4.1), i.e. a bar raked at 30 degrees across the notch, so the slot visibly cannot accept a key; the rule id sits beneath every gate in Geist Mono and goes to full `--foreground` medium weight on the tripped one. To the right, past a 1px `--border` rail, sits the RELEASE LOCK: an 88x54 body whose outline path deliberately leaves a 23px gap in its bottom edge (`M32.5 53.5 H0.5 V0.5 H87.5 V53.5 H55.5`) — that gap is the output port. The lock carries one 7x12 slot per gate across its top, evenly spaced at `SLOT_INSET + (LOCK_W - 2*SLOT_INSET) / n * (i + 0.5)` with `SLOT_INSET = 10` so the run keeps clear of the body's own side walls and the slots read as separate keyholes rather than one filled bar, outlined in `--border` when empty and filled solid `--foreground` when its key has arrived. KEY TRAVEL: a gate that reaches 'released' after mount launches a key glyph from its own keyway to its slot in the lock — measured live against the row's bounding box via two zero-size marker spans (one at the keyway, one at the lock slot), rendered in a `pointer-events-none` overlay, and translated over 420ms on `cubic-bezier(0.22,1,0.36,1)` with a 1px `--border` guide line drawn along the same path for the duration; on arrival the flight unmounts and the lock slot fills. Gates that are already 'released' at mount seat instantly with no flight, and a gate the owner re-arms out of 'released' takes its key back out of the lock. THE BOLT is a 22x6 bar sitting across the output port; it is rendered retracted (`translateX(20px)`, on a 420ms `cubic-bezier(0.34,1.8,0.64,1)` that genuinely overshoots ~3px before settling) ONLY when `seatedCount === gates.length`, and extended across the port otherwise. While the bolt is shut the output panel below renders at 0.35 opacity behind a 4px-pitch 45-degree diagonal hatch built as a `repeating-linear-gradient` of `color-mix(in srgb, var(--foreground) 8%, transparent)`; both the dim and the hatch lift over 320ms once the bolt retracts. A Geist Mono readout above the row states the count and the consequence in one line: '3/5 keys · output held'. OVERRIDE is a real `<button>` per tripped gate, labelled 'override <ruleId>' in mono with a visible `--ns-accent` focus ring; pressing it records the gate in an internal overridden set and seats a HATCHED key — an SVG `pattern` of 2px diagonal stripes at 45 degrees instead of a solid bit, in both the gate keyway and the lock slot — and pushes a permanent audit line ('override pii_scan · n.sapalidis · 14:02:31') that no prop, press or re-arm ever clears. The trail is newest-first inside a 59px `overflow-y-auto` list (exactly three 17px lines plus their two 4px gaps, so no line is ever half-clipped) — every entry is kept, but the component's own box stops growing after the third one, so a repeatedly-overridden interlock does not slowly reframe the layout around it. An overridden interlock therefore stays visually distinguishable from a genuinely passed one for the life of the component, which is the actual safety property being modelled. The component is controlled: it never mutates the `gates` array it is handed, it calls `onOverride(id)` and lets the owner decide what the gate becomes. INTERACTION: hovering (or keyboard-focusing) a gate lifts it 2px, holds it at full alpha, drops every other gate to 0.3 opacity and reveals that gate's `detail` in a fixed-height Geist Mono strip under the row, all on 180ms ease-out; `pointerleave` on the group relaxes all of it on the same timing, and Escape dismisses the strip. Each gate is itself a real `<button>` carrying an `aria-label` that spells out label, state and detail, so a keyboard user gets in one string exactly what a hover reveals in the strip; `focusout` on the row clears the strip and the dimming, so tabbing past the last gate leaves nothing stale behind. ACCESSIBILITY: the whole component is a `role=group` with an accessible name; a visually-hidden `role=status`/`aria-live=polite` region announces each trip exactly once ('pii_scan tripped, output held') and each override as it happens, rather than chattering on every render; every focusable control carries a 2px `--ns-accent` focus ring (offset 4px on the gates so it clears the lifted slot, 2px on the override buttons); the SVGs are `aria-hidden` decorations sitting behind text that already carries the semantics. REDUCED MOTION (`matchMedia` with a live change listener, plus a CSS `prefers-reduced-motion: reduce` block) makes key travel and bolt retraction instant, drops the pending breathe to a static 0.75 opacity dashed outline, and removes the hover and hatch transitions — every state stays fully legible, just static. Colours are `--background --foreground --ns-muted --border --surface --ns-accent` only, via Tailwind token classes and `currentColor`; `--ns-accent` appears solely as the focus ring. DOM + SVG + CSS, no canvas, zero dependencies."
      }
    },
    {
      "name": "hachure-fall",
      "type": "registry:ui",
      "title": "Hachure Fall",
      "description": "A route elevation profile drawn as 18th-century military engravers drew slope: strokes perpendicular to the profile line, hanging down the fall direction, whose length and density both derive from local grade. No colour ramp, no vertical-exaggeration slider.",
      "files": [
        {
          "path": "registry/core/hachure-fall/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hachure-fall.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "chart",
          "elevation",
          "route",
          "svg",
          "slider",
          "keyboard",
          "accessibility",
          "cycling",
          "terrain"
        ],
        "instruction": "Build <HachureFall data? label? className?> — a route elevation profile where slope is drawn as perpendicular hachure strokes, not colour or a decoded y-axis. DATA: `data` is ElevationPoint[] ({km, m}, strictly increasing km); buildSegments() projects each consecutive pair onto a fixed internal SVG coordinate system (VW=760 wide) where the elevation curve auto-fits its own silhouette into a fixed CURVE_BAND_H band (an ordinary one-time chart auto-scale, ordinary and NOT exposed as a prop or control) purely so the route has a recognisable shape. GOVERNING SCALAR: for every segment, gradePct = (elevation delta in metres / distance delta in metres) * 100 — real-world grade, computed independently of whatever vertical scale the curve happens to be drawn at. Every hachure property derives from gradePct alone: stroke length = clamp(K * |gradePct|, 0, MAX_LEN_PX) where K = STROKE_UNIT_PX(14) / UNIT_GRADE_PCT(5) is a MODULE CONSTANT, never a prop, printed verbatim in the component's bottom-right corner as '1 stroke-height = 5% grade' in font-mono; tick spacing = S0(9) / (1 + |gradePct|/100), so a steep segment's strokes crowd together and a near-flat segment's clamp to below MIN_LEN_PX and draw literally nothing (the 'flat kilometre is nearly blank' requirement). There is deliberately NO vertical-exaggeration control anywhere in the props or UI: the curve's auto-fit height and the hachure length's K constant are two independently fixed mappings, so exposing an exaggeration slider would let the same visual stroke length silently mean a different grade on two different routes — exactly the dishonesty hachure marks were invented to prevent. GEOMETRY: each segment's full set of ticks is emitted as ONE <path> element whose `d` attribute concatenates one 'M x,y L x2,y2' subpath per tick (built by the tickPathD/buildSegments functions from the data, never a literal per-stroke DOM node), stroke=var(--ns-muted) at rest. The perpendicular direction for each segment is rotated from its local tangent and normalised so its y-component is always >= 0 — 'down' on screen, the fall direction, independent of whether that segment is a climb or a descent. SCRUB CURSOR: pointermove over the root (no click/drag required — a hover-follow slider) or arrow-key presses set a target km; the rendered cursor position, the 'active' segment's promotion from --ns-muted to --foreground stroke, and that segment's tick lift all track a critically damped spring (k=4200 s^-2, c=2*sqrt(k), tuned to settle in ~90ms) toward the target rather than following it 1:1 — an undamped follow flickers across segment boundaries on real (noisy) elevation data, while a slower/underdamped spring would feel detached from the pointer. Ticks within CURSOR_WINDOW_PX(42 internal px) of the cursor's position along the active segment lift by up to CURSOR_LIFT_PX(2) with linear falloff, recomputed every animation frame via direct `setAttribute('d', ...)` writes on cached SVGPathElement refs (never React state on the animation hot path — React state is only touched, and only when the rounded value actually changes, to drive the accessible value text). The spring only runs while hovered or focused; there is no rest position to relax back to, the cursor group's opacity just fades to 0 on blur/pointerleave. prefers-reduced-motion collapses the spring to an instant snap and disables the tick-lift entirely, while keeping the colour promotion (state still legible, just not animated). A11Y: the whole interactive surface is a single role=\"slider\" div (tabIndex 0, aria-orientation=\"horizontal\", aria-valuemin=0, aria-valuemax=total km, aria-valuenow rounded to 0.1km, aria-valuetext formatted exactly 'km 14.2, 220 m elevation, 9% climb' via gradeWord()). Arrow keys step by max(0.1, totalKm/200) km. PageUp/PageDown do NOT step a fixed distance: classIndexFor() buckets each segment's |grade| into flat/2%/4%/7%/10%+ classes signed by climb vs descent, summarizeRoute() collects every km position where the class changes into `boundaries`, and PageUp/PageDown jump to the next/previous boundary — a real route-navigation unit, not an arbitrary step. Home/End jump to the route's start/end. aria-describedby points at a visually-hidden (sr-only, not aria-hidden) paragraph holding summarizeRoute()'s static plain-text shape summary: total ascent, every named climb (segments run of >3% grade grouped, distance-weighted average grade) as 'km A–B avg N%', and the single steepest point route-wide as 'steepest N% at km K' — independent of scrubbing, so the drawing's information exists without vision or interaction. No extra aria-live region: the slider's own valuetext already announces scrub changes through native AT slider semantics. TOKENS: profile line var(--border); hachures var(--ns-muted) resting / var(--foreground) active; scrub cursor and its dot var(--ns-accent) (the one interaction-only use of accent in the file); legend and readout text font-mono var(--ns-muted). No color ramp anywhere — steepness is read from stroke length and density only, never hue, which is the whole point of reviving hachures over a heatmap."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": "helicorder-line-wrap",
      "type": "registry:ui",
      "title": "Helicorder Line Wrap",
      "description": "An ambient status widget shaped like a seismograph helicorder drum: a pen traces one line at a time and steps down to a fresh line the instant it runs off the right margin, so the record grows as a stack of offset traces rather than one continuous scroll.",
      "files": [
        {
          "path": "registry/core/helicorder-line-wrap/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/helicorder-line-wrap.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "status",
          "canvas",
          "monitoring",
          "trace",
          "raf",
          "ambient"
        ],
        "instruction": "Build <HelicorderLineWrap lineSeconds? rows? quakeIntervalSeconds? paused? className?> as a card-scale ambient status widget reproducing a seismograph helicorder drum record. LAYOUT: `rows` (default 8) stacked flex-1 row divs inside one flex-col wrapper, each row a full-width/full-height <canvas> (its own backing store, DPR-capped at 2), separated by a `border-t border-border` hairline between rows (no border above the first). A completed row's canvas is a static bitmap that is NEVER redrawn once finished — only the current LIVE row is painted, incrementally, one short line segment per frame from the last plotted point to the new one. LIVE TRACE: the pen advances left to right across the live row's canvas at `rowWidth / (lineSeconds * 1000)` px/ms (lineSeconds default 12, so a real helicorder's 15-real-minute line is compressed to 12 app-seconds, a documented decouple — 15 real minutes would be far too slow to read as alive). Its vertical offset is `rowHeight/2 + microseism(x)`, where microseism is a smooth deterministic pseudo-noise (sum of two incommensurate sines) bounded to +-2px baseline amplitude, PLUS a quake envelope when one is live: quakes fire on an average `quakeIntervalSeconds` (default 28s, randomized 0.64x-2.07x per interval so it reads as irregular, not metronomic), amplitude 15-20px randomized, rising linearly over 200ms then decaying exponentially with a ~2.5s time constant, sign randomized per quake — clearly distinguishable from the idle baseline by height alone, never by color. WRAP: when the live row's x reaches the right margin, drawing PAUSES for an explicit 200ms two-phase transition — the pen-head overlay (a single absolutely-positioned 4px dot, its transform/opacity written directly every frame, never through React state) fades out over 80ms at its final right-margin position, then, invisible, the row step happens: while fewer than `rows` rows have ever been completed, the next row down simply becomes the new live row (nothing shifts); once all `rows` rows are full, every subsequent wrap instead SHIFTS the whole stack up by one row via canvas-to-canvas `drawImage` copies (row i takes row i+1's finished bitmap, the last slot is cleared and becomes live) so the oldest trace scrolls off the top and the tower keeps growing forever rather than the whole card fading and restarting. The pen head then fades back in over 120ms at the left margin of the new live row before drawing resumes — a visible departure and arrival, never a teleport, which is what makes the wrap itself (not a generic scrolling ticker) the followable event. TOKENS: canvas stroke color is `--foreground` read via `getComputedStyle(document.documentElement)` at mount and re-read on a documentElement class MutationObserver (re-painting only the static/reduced frame — the live trace's ink for already-drawn pixels does not retroactively recolor, matching how a real pen trace doesn't change color after the fact); row separators are the `--border` token via the Tailwind border classes, never a fill. HOST: one ResizeObserver on the wrapper (not window.resize) recomputes row width/height and the px/ms sweep speed from it; on any size change the whole tower resets and restarts from row 0 (a full resize invalidates all baked row bitmaps, so restarting cleanly beats trying to rescale finished traces). IntersectionObserver (threshold 0) and visibilitychange both pause the single rAF loop; prefers-reduced-motion and `paused` both freeze on a deterministically-drawn static frame (STATIC_ROW=4, STATIC_PROGRESS=0.6: rows 0-3 complete, row 4 traced 60% of the way across, one baked quake bump on row 1) rather than a blank or fully-filled stack, with the pen head hidden. No interaction is required (this is an ambient status widget, not a control) so nothing here uses `--ns-accent` — it is reserved for interactive chrome this component doesn't have. Pure DOM + 2D canvas, 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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-muted/--border mix, and --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-accent (60%) and --foreground (40%) tints; light flips to multiply with tints from --ns-muted and a --border/--ns-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"
      ],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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, --ns-muted labels, --background frame fill); no canvas, pure DOM/CSS with one ResizeObserver for the frame width, zero dependencies."
      }
    },
    {
      "name": "honeycomb-draw",
      "type": "registry:ui",
      "title": "Honeycomb Draw",
      "description": "An ambient background where wax cells start as loosely packed circles and locally relax, wall by wall, into a tight hexagonal comb: each neighbour pair's shared wall straightens on its own schedule once the circles overlap, so a cell can show straight facets and still-round ones at once, with new circles seeding in from the left edge and a periodic recap flash keeping the finished lattice alive at rest.",
      "files": [
        {
          "path": "registry/core/honeycomb-draw/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/honeycomb-draw.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "canvas",
          "hexagon",
          "relaxation",
          "generative",
          "ambient",
          "texture"
        ],
        "instruction": "Build <HoneycombDraw paused? className? style?> as a card-scale 2D-canvas background built on real comb formation (Pirk et al. 2004; Bauer & Bienefeld 2013): packed circular wax cells relax into a hexagonal lattice through purely LOCAL wall-by-wall straightening, never a hex grid fading in. LATTICE: cell centres sit on a jittered 'odd-r' offset-triangular lattice, spacing s = min(containerW, containerH) / 11, each slot jittered up to +-0.12*s in both axes (jitter derived once per slot index via a cheap sine hash, fixed for that slot's lifetime, not re-rolled per frame). GROWTH: each cell's radius eases from 0 to 0.62*s over GROWTH_SECONDS=2.2s via ease-out-cubic, timed from that cell's own spawn moment. WALLS: every cell tracks up to 6 walls, one per lattice neighbour direction (odd-r offset neighbour table), each with its own independent state: untriggered until the pair's summed radii overlap the shared distance by more than OVERLAP_TRIGGER_FRAC=0.06*s, at which point (permanently — walls never un-trigger) the wall's spring target becomes 1 and a critically-damped spring (k=SPRING_K=90, damping ratio 1.0, semi-implicit Euler: velocity += (-k*(progress-1) - 2*sqrt(k)*damping*velocity)*dt; progress += velocity*dt) carries its progress from 0 to 1 over roughly 1.4s. wallDist for a wall is the Voronoi/perpendicular-bisector foot: half the straight-line distance to that neighbour's actual (jittered) centre. RENDER: a cell's boundary is sampled at SAMPLES=36 angles; at each sample angle the boundary radius is linearly interpolated between the two angularly-nearest walls' OWN blended distance (each wall's blended distance = lerp(currentCircleRadius, wallDist, thatWall'sSpringProgress) — directions with no neighbour, or whose wall hasn't triggered, stay at the plain circle radius) — this per-wall independence, not a single global relax factor, is what makes a partially-relaxed cell show straight facets next to round ones simultaneously, the visible signature that distinguishes this from a hex grid whose opacity is ramping (the spec's explicit kill criterion). SEEDING: while filled-slot-fraction < FILL_CAP=0.85, new cells spawn at SEED_RATE=0.6/s, chosen as the leftmost column's topmost still-empty slot (reading order), so growth visibly advances as a front from the left edge rather than popping in randomly. RECAP: once filled-fraction reaches ~FILL_CAP, a uniformly-random already-placed cell is picked every RECAP_MIN..RECAP_MAX=4..7s (re-rolled each time) and its stroke flashes to --foreground for RECAP_FLASH=0.26s then eases (linear) back to --border over RECAP_FADE=0.9s — this recap cycle is what keeps the piece alive at rest indefinitely once growth is done, since growth itself is a one-shot ~14s process for an 11-cell-wide card. TOKENS: wall strokes are --border at rest (0.55 alpha, a genuine hairline separator use, not a fill) and --foreground during a recap flash (never --ns-accent, no interactive control exists on this component); the low-alpha fill (0.1 alpha --ns-muted) is constant whether a cell is still round or fully hexagonal, read live via getComputedStyle(document.documentElement) at mount and re-read on a MutationObserver watching documentElement's class. STATIC/REDUCED-MOTION: prefers-reduced-motion and paused both call settle(), which lays out every lattice slot, spawns every cell with its spawnAt backdated past GROWTH_SECONDS, and force-sets every wall to triggered/progress=1/velocity=0 in one synchronous pass (SETTLED_COMB) — the freeze frame is the fully-filled, fully-relaxed hexagon lattice with zero mid-relaxation cells and no recap flash firing, never a blank or half-built grid. `ResizeObserver` re-derives spacing and re-lays the grid (settle() if currently static, otherwise a fresh empty-then-growing layout()); `IntersectionObserver` (threshold 0) and `visibilitychange` both pause the single rAF loop. Canvas is `aria-hidden` decoration with `w-full h-full`; this is a pure background layer with no accessible content of its own — callers place real content in a sibling layer above it, as in demo.tsx."
      }
    },
    {
      "name": "honing-crosshatch",
      "type": "registry:ui",
      "title": "Honing Crosshatch",
      "description": "A card whose backing texture is a genuine cylinder-bore plateau-honing process: two families of fine scratches deposited at a fixed controlled 45 degree crosshatch angle, individual strokes turning over on a 4.5s birth-to-erosion lifetime so density holds steady instead of ever filling solid or emptying out.",
      "files": [
        {
          "path": "registry/core/honing-crosshatch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/honing-crosshatch.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "ambient",
          "canvas",
          "crosshatch",
          "honing",
          "process",
          "monochrome",
          "hover"
        ],
        "instruction": "Build a card whose entire backing surface is a canvas-rendered cylinder-bore plateau-honing simulation, sourced from real engine-cylinder honing process control, not a decorative diagonal pattern. Root is a `rounded-[14px] border border-border bg-background` card holding an absolutely positioned, pointer-active, `aria-hidden` canvas filling the card behind a `pointer-events-none` content stack (heading, mono body copy, optional underlined link that re-enables `pointer-events-auto` and carries a `focus-visible` ring in `--ns-accent`).\n\nSimulation state is a plain array of discrete, transient stroke objects `{x, y, family: 'A'|'B', birth}` — never a persisting grid — because the mechanic is individual scratches being born and eroding, not a coverage bitmap. Grid scale (for stroke length only) is derived from the card's own smaller dimension via ResizeObserver: `cell = min(width,height)/40`, `strokeLength = 1.4 * cell`. Two independent fractional accumulators, one per family, each birth strokes at a fixed 6/second (`acc += rate*dt; count = floor(acc); acc -= count`, frame-rate independent), for 12/second combined — this is what the spec calls 'alternating A/B', both families running concurrently rather than literally taking turns. Each new stroke's position is uniform-random across the canvas; its ANGLE is fixed by family, never randomised and never touched by anything at runtime: family A sits at `-22.5deg` from vertical, family B at `+22.5deg` from vertical, a fixed 45deg included crosshatch angle (mid of the real 30-60deg process control range) that must never vary, including under hover — a rejection criterion if the angle scatters or drifts.\n\nEvery stroke lives exactly 4500ms from birth to fully-eroded, then is dropped from the array (filtered each frame on `simTime - birth >= LIFETIME_MS`, where `simTime` is a locally-accumulated virtual clock advanced only while the rAF loop actually runs, so a paused/hidden card doesn't age strokes it isn't painting). Colour follows an envelope by age: for the first 280ms the stroke rises from `birthColor` to a brighter `peakColor` (the 'briefly brightening' beat), then linearly decays from `peakColor` back down to the surface base colour over the remaining ~4220ms — never a flat fade, and never additive: strokes are drawn with `ctx.globalCompositeOperation = 'lighten'` against a `source-over`-filled background-coloured base, so a stroke re-passing already-bright ground picks the max of the two per-channel values instead of stacking brighter or darker, the same clamp semantics as `peen-coverage`'s `Math.max`-clamped dimples, matching how a re-pass on an already-plateaued bore surface doesn't cut deeper.\n\nExpected resident population is `rate * lifetime = 6/s * 2 families * 4.5s = 54` strokes at any instant — this is NOT enforced by a target count, it falls out naturally from birth rate equalling death rate over a fixed lifetime, the same self-regulating process a real steady-state honing pass exhibits. On mount and on every ResizeObserver-triggered resize (which resets `simTime` to 0 and rebuilds from scratch at the new canvas scale), the array is backfilled to steady state immediately: for each family, spawn `round(6 * 4.5) = 27` strokes at uniform-random canvas positions with `birth = simTime - (rand() * 4500)`, i.e. ages spread uniformly across the full lifetime, so t0 already reads as full steady-state density rather than starting blank — the resting loop is then legible purely via stroke IDENTITY turning over: by 2.5s roughly half the visible strokes are different individuals from t0, by 5s the population has fully turned over again, while the aggregate angle and density stay visually constant the entire time.\n\nColour is read via `getComputedStyle(document.documentElement)` for `--background`, `--ns-muted` and `--foreground` at mount and re-derived on a `MutationObserver` watching `documentElement`'s class — never a literal. `surfaceBase` is always `--background` in both themes. Dark theme: `birthColor = --ns-muted` directly, `peakColor = mix(muted, foreground, 0.6)`. Light theme (checked first, the harder case): `birthColor = mix(muted, foreground, 0.35)` and `peakColor = mix(muted, foreground, 0.75)` — contrast compressed relative to dark but deliberately pulled toward `--foreground` so a birthing stroke clears `--border`-adjacent territory (~1.1:1) instead of reading as invisible against the base.\n\nInteraction: pointermove over the canvas tracks a local point and adds an EXTRA deposit stream to both families' accumulators — an additional 6/second per family (bringing the local rate to roughly 2x) inside a 6-cell radius disc around the pointer (`angle = rand()*2*PI, r = sqrt(rand())*radius`), decaying linearly to zero over 500ms after pointerleave rather than cutting instantly. This only adds extra strokes at the SAME fixed per-family angles — hover must never change the crosshatch angle itself (angle is a controlled process parameter, not an interactive one) and must never tint strokes with `--ns-accent`; the highlight is purely a denser local patch on the same base->stroke luminance ramp everything else uses.\n\nDPR-capped (max 2) backing store sized off the card's own `getBoundingClientRect`, redrawn on `ResizeObserver`. The render loop pauses via `IntersectionObserver` (not scrolled into view) and `visibilitychange` (tab hidden), and is a plain `requestAnimationFrame` otherwise. Under `prefers-reduced-motion: reduce`, the array is seeded once to the same steady-state backfill (both families evenly represented, ages spread across the full 4.5s lifetime) named `FREEZE_PHASE = \"steady-crosshatch-lock\"`, exposed as `data-reduced-motion-freeze` on the root, composed once, and the loop never starts. No 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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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, --ns-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 --ns-muted --border --ns-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": "idler-drop",
      "type": "registry:ui",
      "title": "Idler Drop",
      "description": "Delegated admin drawn as a gear train: your authority is the drive gear, each delegate is a driven gear whose tooth count sets a discrete role ratio, and revoking swings the idler out so their gear freewheels to a stop while yours keeps turning.",
      "files": [
        {
          "path": "registry/core/idler-drop/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/idler-drop.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "delegation",
          "roles",
          "admin",
          "permissions",
          "security",
          "svg",
          "list",
          "aria-live",
          "accessibility"
        ],
        "instruction": "`<IdlerDrop delegations={Delegation[]} title? className? />` where `Delegation` is `{ id, name, role: \"admin\"|\"editor\"|\"viewer\", grantedAt /* ms epoch */, sessionExpiresAt /* ms epoch — real data the post-revoke readout is computed from */ }`. Renders a `role=\"list\"` of rows; each row is one gear train. MECHANISM: a single shared train phase (module-level DEG_PER_SEC = 0.15 rev/s * 360) is advanced once per animation frame and applied identically to every row's drive gear via `rotate(phase, cx, cy)` on an SVG `<g>` — one clock, so no row's mesh can ever drift out of phase with another's. The drive gear is fixed at 24 teeth. Each delegate's driven gear is fixed at ROLE_TEETH[role] — 12 for admin, 8 for editor, 3 for viewer — because authority comes in exactly three legal roles, not a continuum; a continuously variable radius would visually imply a fractional permission the system has no way to grant. Drive and driven share one tooth module (MODULE = 2.6px/tooth) so every pair of gears in the piece is drawn at a center-distance that actually equals the sum of their pitch radii — the geometry is a real (if stylized) mesh, not two overlapping circles. Between them sits a 6-tooth idler mounted on a carrier arm pivoted at the drive gear's own center, at a fixed radius (rDrive+rIdler) from that pivot — this is what keeps the idler permanently meshed with the drive gear regardless of carrier angle, spinning with it always: the idler is drawn as an extension of YOUR authority, not the delegate's. WHILE ENGAGED (carrier at rest, angle 0) the idler also touches the driven gear, so the driven gear's rotation is a pure function of the shared phase: `drivenDeg = phase * (24 / roleTeeth)` — same direction as the drive gear (two external meshes reverse direction twice), always faster for a smaller-teeth role, i.e. viewer's tiny gear visibly whirs fast while admin's larger gear turns slower — small-and-fast reads as thin/easily-spun authority, big-and-slow reads as weighty. REVOKE swings the carrier by 40° over 250ms (`easeOutCubic`, imperative rAF tween off the click's own timestamp, not a CSS transition — avoids SVG transform-origin inconsistencies) which visibly opens a gap between the idler and the now-stationary-relative driven gear; AT THE SAME INSTANT the driven gear itself leaves the shared-phase formula and starts an independent exponential decay: its angular velocity at the moment of revoke (`v0 = 54deg/s * 24/roleTeeth`) decays as `v0 * exp(-t/1.2s)`, integrated to `angle0 + v0*1.2*(1-exp(-t/1.2))` — so it keeps spinning, slowing continuously, settling over several seconds rather than snapping to a stop. That coast is deliberate: it is the visual disclosure that the delegate's existing session persists past the revoke until its own token actually expires. The drive gear never slows down, never pauses, regardless of how many rows are revoked — it is drawn from the same shared phase as every engaged row, always. IDENTITY TEXT: each driven gear's name / role / grant-date live as real DOM text in a small `<div>` absolutely positioned (as a percentage of the row's SVG viewBox, so it tracks correctly at any render width) at that gear's own center — deliberately OUTSIDE the rotating `<g>`, so the label stays upright and legible while the ring spins or freewheels around it; a thin punched bore circle (`fill: var(--background)`, `stroke: var(--border)`) sits on the rotating layer beneath it for the visual read of a hub. Every gear also carries one radial timing-tick line so a perfectly regular tooth ring still reads as turning frame to frame rather than looking static between tooth-pitch repeats. CONTROLS: each row has a real `<select>` for role (disabled once revoked — the grant is historical, not editable) that changes `ROLE_TEETH[role]` live, immediately re-deriving that row's mesh ratio and geometry; and a real `<button>` labelled Revoke that, once pressed, disables itself, relabels to \"Revoked\", and reveals a `data-coasting` Geist Mono readout — `coasting: 14 min` — computed once from `Math.round((sessionExpiresAt - Date.now()) / 60000)` against the delegate's actual session-expiry timestamp, not a scripted number. A11Y: the whole train SVG is `aria-hidden`; the identity text, role select and revoke button are the real semantic surface. A visually-hidden `role=\"status\" aria-live=\"polite\"` region speaks the coasting fact in full on every revoke — \"Editor access revoked for Sam; existing session expires in 14 minutes.\" — never leaving the freewheel spin as the only signal. REDUCED MOTION: a `matchMedia(\"(prefers-reduced-motion: reduce)\")` check (with a live `change` listener) makes the rAF loop apply one static rest frame instead of scheduling further frames — phase pinned at 0, every engaged driven gear at 0°, and any revoked row's idler carrier still snapped instantly to its swung-out 40° with no tween — so engaged rows read as statically meshed, revoked rows read as statically disengaged, and the coasting label is present and correct either way; only continuous rotation is removed. Colors are `var(--foreground)` for the drive and driven rings/ticks, `var(--ns-muted)` for the idler (the connector, not an authority holder itself) and identity subtext, `var(--border)`/`var(--background)` for the punched bores, and `var(--ns-accent)` solely as the `focus-visible` ring on the select and button — never a resting color. DOM + SVG + CSS only, no canvas, zero dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": "index-contour",
      "type": "registry:ui",
      "title": "Index Contour",
      "description": "A travel-time reach editor: one slider handle sets minutes, and the reach renders as topographic-style contour bands re-thresholded from a real per-node cost field via marching squares, with every third line heavier and labelled inline in a break in its own stroke.",
      "files": [
        {
          "path": "registry/core/index-contour/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/index-contour.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "slider",
          "svg",
          "isochrone",
          "map",
          "contour",
          "input",
          "data-viz",
          "geo"
        ],
        "instruction": "Build a functional isochrone input, not a decorative contour field. MECHANISM: one governing scalar t (minutes, quantized to multiples of 5) drives which bands render; each band at level L (5, 10, 15, ... up to t) is a marching-squares re-threshold of a single caller-supplied per-node cost field (ContourCostField: cols, rows, values[] in minutes row-major, Infinity meaning unreachable, optional roads polylines, optional metersPerCell). Every band is derived from that one field — none is independently authored — so run marching squares fresh per level: 16-case edge table with linear interpolation between opposite-state corners (barrier/Infinity corners hug the interpolated point tight against themselves at t=0.1/0.9 rather than producing NaN), then stitch loose segments into polylines by shared endpoint (closed loops where the field is an island, open chains where a partial band clips at the grid edge or an unreachable pocket — that clipping is what 'the outermost partial band clips at t' means, and it falls out of the algorithm with no special case). If every node's cost is Infinity (no data yet), marching squares emits zero segments at every level by construction — that IS the loading state: the supplied `roads` skeleton keeps drawing (muted, permanent context) and no reach appears at all. A Euclidean radius/speed fallback must never be substituted, not even as a loading placeholder. INDEX-CONTOUR RULE: every 3rd line (15-minute multiples, 3:1 ratio on the 5-minute interval, matching USGS index-contour convention) draws 2px --foreground with its minute value set INSIDE a break in the stroke itself: measure the label's real rendered width via SVG getBBox() in a layout effect (not estimated), turn that into a stroke-dasharray gap sized to the label plus padding, and position the gap via stroke-dashoffset centered on the arc-length position of that band's own topmost vertex (computed from the polyline's own point list, not from getTotalLength on the DOM). Intermediate lines are 1px --ns-muted, no label. Never a filled gradient wash — every band is stroke-only, fill:none; a filled reach is both banned and illegible without a key. EMERGENCE: growing t does not fade a newly-revealed outer band in — it mounts at transform:scale(0.9) anchored on transform-origin set to the field's own cost-minimum node (the origin, found by argmin over the field, not a caller-supplied point), then a requestAnimationFrame flips it to scale(1) under a 220ms cubic-bezier(0.16,1,0.3,1) (ease-out-expo) transition, so it visibly inflates outward from the previous line rather than appearing by opacity. prefers-reduced-motion skips the tween (renders the settled shape directly, still fully legible — never disabled, just not animated). A11Y: the control is a single role=slider div, tabIndex=0, aria-valuemin/max/now plus aria-valuetext formatted 'N minutes, reach covers approx. X km²' (optionally ', approx. Y people' when populationPerKm2 is supplied) — the area is derived by counting reachable nodes (finite cost <= t) and multiplying by the field's own (metersPerCell/1000)^2, never guessed. ArrowLeft/Right/Up/Down step by the visible 5-minute unit (announced step equals visible unit), PageUp/PageDown step by the 15-minute index interval, Home/End go to the domain rails. The whole SVG (roads + every contour + every label) is aria-hidden, since it is derived geometry, not the source of truth — band count and the same derived area/population land instead in a separate sr-only role=status aria-live=polite region that updates on every committed (quantized) value change, and a visible Geist Mono readout (aria-hidden, since the slider already owns the value) mirrors the number beside the eyebrow label. INK: grid is rendered at a fixed real pixel scale (no viewBox rescaling) so 1px/2px stroke widths and the getBBox label measurement are both already true screen px with nothing to reconcile; every color is a token utility (text-foreground, text-ns-muted, text-border, bg-ns-muted, focus-visible:ring-ns-accent) — no canvas, no hex, no gradient."
      }
    },
    {
      "name": "indicator-rack",
      "type": "registry:ui",
      "title": "Indicator Rack",
      "description": "A plan selector built on the indicator-tablet rack of a pre-electronic cash register: four columns of ten printed tablets each, all ten always mounted, where changing the reading is a sort — the old tablet drops as the new one springs up, the two crossing mid-column 25ms apart — never a single glyph mutating in place.",
      "files": [
        {
          "path": "registry/core/indicator-rack/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/indicator-rack.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "pricing",
          "plan-selector",
          "radiogroup",
          "aria-live",
          "mechanical",
          "cash-register",
          "flag-rack",
          "waapi"
        ],
        "instruction": "Build a plan-selector/price-reveal control (`IndicatorRack`, props: `label` default \"Plan\", `plans: {value,label}[]` default three placeholder plans Plan A/B/C, `value`/`defaultValue`/`onValueChange` for the controlled/uncontrolled plan, `term`/`defaultTerm`(\"monthly\"|\"annual\", default \"monthly\")/`onTermChange`, `name`, `className`) modeled on the indicator-tablet rack of a pre-electronic NCR cash register. Render an `aria-hidden` decorative rack of FOUR columns (hundreds/tens/units/term, labelled HUND/TENS/UNITS/TERM underneath in 8px mono uppercase, with a static `$` plate to their left so the rack reads as a price at a glance) each holding TEN absolutely-positioned tablet divs (digits 0-9), all ten always mounted for every column — nothing is created or destroyed on a change, only repositioned via `translateY`. Column geometry derives from the container's SMALLER dimension via ResizeObserver: `lift = clamp(0.19 * min(w,h), 44, 96)`px (reference figures assume a 62px lift), `columnWidth = clamp(lift * 0.62, 28, 64)`px. A tablet's rest (down-stacked, unselected) position is `translateY(lift + digit*3)`px — a fixed 3px pitch between the ten edges, producing a foreshortened 30px band of plate-edge slivers at the foot of the column when the window (`height: lift + 30px`, `overflow:hidden`) clips each tablet's own `lift`-tall body; the selected tablet's raised position is `translateY(0)` — the top of the window, where its face is visible; a raised position of `-lift` puts the whole plate above the window and the readout renders empty, filling the window. On a plan or term change, diff old vs new digit per column (plan digits ARE the plan's figure, read out the way a register reads one (leading zeros included): `{a:[0,1,9], b:[0,4,2], c:[1,2,8]}` for hundreds/tens/units, i.e. the placeholder figures 19 / 42 / 128, chosen so every column changes on at least one transition; term digit is monthly=0/annual=1, but the term column's plates are printed M and A rather than 0 and 1 (a bare digit there reads as another price digit)) and, for every changed column, run two independent Web Animations API animations on that column's outgoing and incoming tablet elements: RAISE (incoming tablet, `delay:0`, `duration:340`) keyframes translateY [restY -> restY (hold to 40ms, the latch-release wait) -> -4px at 250ms with a `cubic-bezier(0.34,1.56,0.64,1)` overshoot easing -> 0 at 340ms with `ease-out` settle]; DROP (outgoing tablet, `delay:25`, `duration:248`) keyframes translateY [0 -> restY+2px at 178ms with `cubic-bezier(0.55,0,1,0.45)` gravity easing -> restY at 248ms `ease-out` bounce-damp]. The 25ms delay on the drop relative to the raise's `delay:0` is what makes the two plates visibly cross mid-column — the whole point of the mechanism; do not tween a single value between old and new instead. A re-selection that lands before the previous change has settled must cancel the in-flight `Animation` on the affected tablet(s) AFTER reading its LIVE `translateY` via `getComputedStyle`'s transform matrix (so the interrupted plate keeps moving from wherever it visually is, never snapping), and re-target a fresh animation from that value — the rack must never gate interaction, and the real DOM price text updates immediately regardless of animation state. Idle, unconditional, always-running CSS `animation:infinite` loops (the 'alive at rest' requirement): a 40px shop-light reflection band sweeps the window left to right, 26px/s, 6.00s linear period (`mix-blend-mode:overlay`, opacity ~0.35, `+` in dark theme / `-` in light theme via a `.dark` selector swap of the gradient's mixed color, capped so it never competes with the real price text outside the window); a 2px-wide flag tick above the window rotates `rotate(1.2deg)` to `rotate(-1.2deg)` and back, `ease-in-out`, 3.4s, never damping; the whole rack window nudges `translateX(0.5px)` and back on a 3.2s two-tick cycle standing in for a 1.6s drive-shaft pawl index. `prefers-reduced-motion: reduce` (via `matchMedia` + change listener) cancels all in-flight tablet Animations and freezes the DECORATIVE rack only on one composed still frame, independent of real interaction state but a pure function of the currently selected plan/term (no PRNG, byte-stable over time): the reflection fixed at `translateX(9px)`, the flag fixed at `rotate(1.2deg)`, the rack-tick animation removed, and per column the CURRENTLY selected tablet held at `translateY((21/62)*lift)` (still rising, short of seated at 0) while its cyclic predecessor digit `(selected + 9) % 10` is held at `translateY(restY(predecessor) - (22/62)*lift)` (still falling, short of its rest) — both inside the window, so the freeze frame shows the crossing rather than an empty rack — a permanently mid-crossing pair that still legibly differs per plan, while every other tablet sits at its normal rest translateY. This freeze is decorative only: the real radios, the real DOM price paragraph, and the aria-live region remain fully live and functional under reduced motion, they simply don't animate. Accessibility: two real `role=\"radiogroup\"` sets of native `<input type=\"radio\">` (visually clipped to 1x1px, not display:none, kept in the tab order) — one for plan, one for billing term — get arrow-key roving and Space re-affirmation for free from the browser; add a manual `onKeyDown` handler per radio for Home/End (not native on radio groups) that focuses and checks the first/last option in that group. Focus order in DOM: plan radios, then term radios, then the primary CTA button (`--ns-accent` fill, the ONLY use of `--ns-accent` in the component — never in the reflection band), then a secondary text-button. The real, always-visible price line is plain DOM text (`{planLabel} ${amount} per month, billed monthly|annually`, where amount is the same placeholder figure the rack is reading out — the literal word PRICE is not a price and fails the two-second test) sitting beside the aria-hidden rack. A single `role=\"status\" aria-live=\"polite\" aria-atomic=\"true\"` region announces on commit only, restarted on every plan/term change and firing after a 350ms debounce so arrow-key traversal through several plans produces one announcement: `\"{Plan label}, {amount} dollars per month, billed monthly|annually.\"`. Tokens: tablets are `--background` plates with `--foreground` digits and `--ns-muted`-mixed hairline top/bottom borders; column separators and the rack's own border are `--border`/`--ns-muted` mixes, never a bare `--border` fill (it is a ~1.1:1 separator, invisible as a fill); `--ns-accent` appears only on the CTA fill and on `:focus-visible` outlines. Pure DOM/CSS + Web Animations API, no canvas, no SVG, zero color literals."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa",
          "error": "#ea001d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717",
          "error": "#ff6369"
        }
      },
      "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 (--ns-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": "jacquard-card-chain",
      "type": "registry:ui",
      "title": "Jacquard Card Chain",
      "description": "An ambient status strip modelled on a Jacquard loom's control mechanism: a chain of punched cards feeds past a fixed needle bank, one card read every beat, the bank rippling to the new pattern and holding before the next card slides in, looping forever.",
      "files": [
        {
          "path": "registry/core/jacquard-card-chain/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/jacquard-card-chain.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "ambient",
          "canvas",
          "jacquard",
          "loom",
          "chain",
          "process",
          "monochrome"
        ],
        "instruction": "Build a card whose entire backing surface is a canvas-rendered Jacquard loom card-chain reader, sourced from the real Jacquard control mechanism, not a decorative punch-card grid. Root is a `rounded-[14px] border border-border bg-background` card holding an absolutely positioned, aria-hidden, pointer-events-none canvas filling the card behind a pointer-events-none content stack (heading + mono caption, both non-interactive).\n\nThe mechanism occupies the lower ~58% of the canvas (visualTop = h*0.42 downward) so it never collides with the heading/caption padded at the top — never derive the mechanism's vertical placement from the full card height, always reserve that top margin first. Needle count is derived from the card's own geometry: cardW = w*0.34 (a card is roughly a third of the strip's width, so ~3 cards are visible at once), needleCount = clamp(round(cardW/9), 12, 20), pitch = cardW/(needleCount+1), all needles centred on a FIXED read-gate x position at w*0.42 that never moves — only the chain moves under it.\n\nCard-chain motion: cardIndex = floor(elapsedMs/900) where elapsedMs is real wall-clock time since first paint (accumulate via performance.now() deltas in the rAF loop, never frame-count based). localT = elapsedMs - cardIndex*900. For localT < 680ms the card at the gate is held in place (slideT = 0); for localT in [680, 900) slideT eases 0->1 via easeOutCubic, and every visible card's on-screen x position is `readGateX + (k - cardIndex - slideT) * cardW` for card index k, so the whole chain glides left by exactly one card width over that 220ms window before the next 900ms cycle begins with the next card now at the gate — draw cards for k in [cardIndex-1, cardIndex+2] and clip the draw to the chain's horizontal lane so cards are simply cropped at the canvas edges rather than needing a fade mask.\n\nEach card's punched pattern is generated by a small deterministic integer hash keyed on (needle index i, card index k) — never Math.random() — so patterns are stable and never require storing history, and the SAME hash function is used both for a card's drawn holes and for what the needle bank reads when that card is at the gate, keeping the two visually consistent.\n\nNeedle bank: for each needle i, the displayed peg length lerps from the PREVIOUS card's bit value (patternBit(i, cardIndex-1)) to the CURRENT card's bit value (patternBit(i, cardIndex)) over a per-needle window that starts at `i * 4ms` into the current 900ms cycle and runs for 100ms, eased with a small overshoot (critically-damped-spring-style, e.g. an easeOutBack curve) so the bank resolves as a left-to-right ripple rather than a simultaneous snap — this is the load-bearing round-9 legibility fix: real jacquard read rates (100-1000 picks/min) sit at or above the page's paint rate, so the read is decoupled to one legible 900ms cycle (a ~160ms ripple, a hold, then a 220ms card slide) instead of animated 1:1. A bit value of 1 (hole present) draws a LONGER peg at --foreground; 0 (no hole) draws a SHORTER peg at --ns-muted — extended/retracted, never a colour swap.\n\nCard body: filled with --ns-muted at globalAlpha 0.35 standing in for card stock (never --border as a fill), outlined with a 1px --border stroke (correct separator use). Holes are literal cut-throughs: for every needle position where that card's pattern bit is 1, fill a small circle (radius ~0.26*pitch) with solid --background, punching through the muted card fill so the pattern reads as negative space rather than an inked colour, identically in both themes. A single 1px --border read-gate guide line is drawn top-to-bottom at the fixed gate x — also a legitimate separator use, giving the eye a fixed anchor to judge the chain's motion against.\n\nColour is read via getComputedStyle(document.documentElement) for --background, --foreground, --ns-muted, --border at mount and re-derived on a MutationObserver watching documentElement's class — never a literal, and the canvas is explicitly filled with --background every frame rather than relying on transparency, so the punched holes read correctly regardless of what sits behind the card in the page. No --ns-accent anywhere — this is ambient chrome, not interaction.\n\nDPR-capped (max 2) backing store sized off the card's own getBoundingClientRect via ResizeObserver. The render loop pauses via IntersectionObserver (not scrolled into view) and visibilitychange (tab hidden), and resumes with elapsed time still measured from the original mount timestamp so the chain never jumps or resets on resume. Under prefers-reduced-motion: reduce, the entire loop never starts; a single synchronous frame is drawn at a fixed elapsedMs corresponding to cardIndex=2, localT=400ms (named CARD_READ_HOLD, exposed as data-reduced-motion-freeze on the root) — a card fully resolved and held (not mid-ripple, not mid-slide) with a roughly mixed pattern, dense enough to read as structured without any motion. No interaction and no dependencies; the component is fully ambient/autoplay by design, so autoplay.mode is \"none\" — there is nothing for the site's synthetic-input driver to trigger."
      }
    },
    {
      "name": "jam-kickout-loop",
      "type": "registry:ui",
      "title": "Jam Kickout Loop",
      "description": "An ambient background-job status line built as bulk-mail sortation conveyor jam handling: most tiles ride the main line straight through, but a fixed 1-in-6 tile is diverted by a swinging arm onto a lower side loop and re-merged one cycle later for a second attempt, never stopping the line to clear it by hand.",
      "files": [
        {
          "path": "registry/core/jam-kickout-loop/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/jam-kickout-loop.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "status",
          "ambient",
          "dom",
          "raf",
          "conveyor",
          "retry",
          "queue",
          "indeterminate"
        ],
        "instruction": "A passive, always-on ambient status indicator modelled on bulk-mail sortation conveyor jam handling: a photo-eye pair flags a mis-timed or stalled piece, a solenoid-driven diverter arm kicks that single piece off the main line onto a lower recirculation loop before it reaches the sort point, and the loop re-merges it one belt-cycle later for another pass — the line itself never stops. Rendering is DOM + CSS: a horizontal main track plus one lower side loop rendered as a single SVG `<path>` (a rounded-rect diversion path built from two cubic-bezier corners and a flat bottom span), with small `<div>` tiles animated purely via `transform: translate3d` written each rAF tick from a shared driver — no CSS transitions, no per-tile React state, positions come out of a pure `poseAt(elapsedSinceSpawn, kicked, geometry)` function re-evaluated every frame. Geometry derives from the container's SMALLER dimension (loop depth = 0.3 * min(w,h)); the main line and the loop's diverter (72% of track width) and re-entry (18% of track width, i.e. behind the diverter) points scale off the container's full width, recomputed on a debounced ResizeObserver. Timing is fixed, never randomised: one tile spawns every 900ms at the left edge; the main-line rate is constant and calibrated so a tile reaches the diverter point at 1.6s after spawn, and that same rate governs the tile both before the loop and after re-entry; exactly every 6th tile (a plain counter, never Math.random()) is diverted — real bulk-mail recirculation runs roughly 2-5%, so 1-in-6 (~16.7%) is a documented legibility amplification for a card-scale viewing window, not a claim about real jam frequency. A diverted tile's kick is rendered as a 150ms arm-swing (a separate `<line>` pivoted with an SVG `rotate()` transform about the diverter point, driven off the same elapsed-time clock as the tile that triggered it, swinging out and re-seating over roughly 400ms total) during which the tile visibly leaves the main line onto the loop's first keyframe — never a fade or cut. Loop transit takes 1.9s along three keyframed spans (a short curve down to the loop's bottom-right corner, a flat traversal to the bottom-left corner, a short curve up to the re-entry point) matching the static path's own corner radius, after which the tile continues from the re-entry point to the right edge at the ordinary main-line rate like any other tile — a documented full second attempt, not a respawn. Every tile, first-pass or recirculated, fades over 250ms on reaching the right edge; spawning is continuous and unbounded, the loop never visibly drains to empty. On mount, a handful of back-dated spawn times seed an already-in-progress resting state (staggered mid-transit tiles, possibly one mid-loop) instead of an empty track on first paint. A fixed pool of tiles (`POOL_SIZE = 20`) is rendered once and reused, hidden at opacity 0 when unused, rather than mounting/unmounting DOM nodes per tile. No colour literals: the loop path and main line are `var(--border)` (structural, whisper contrast, never a fill), the diverter arm is `var(--ns-muted)` resting and brightens toward full opacity only while actively swinging (a luminance step, not a colour or `--ns-accent` — that token is interaction chrome only and this component has none), and every tile is `bg-foreground` regardless of whether it is ever diverted — the diverted tile's distinction comes entirely from its path leaving the main line, never from hue. Interaction: none, this is a passive ambient loop, not a button; per-click retry-with-cooldown is `button-retry-backoff`'s territory. Under `prefers-reduced-motion` the rAF loop and spawn cadence never start; instead a single named freeze frame, `MID_KICKOUT`, is rendered once from the same `poseAt` function evaluated at three fixed elapsed times — one tile mid diverter-swing (still partially over the main line, already angled onto the loop), one tile further along the loop's transit, and one ordinary tile mid main-line — showing normal transit, the kickout event, and loop transit together in one frame, with the arm frozen mid-swing to match. Cleans up on unmount: cancels the rAF, disconnects the ResizeObserver and IntersectionObserver (which pauses/resumes the rAF loop when the container scrolls off/on screen). Zero dependencies, DOM + SVG + CSS only, no canvas."
      }
    },
    {
      "name": "kelvin-wake",
      "type": "registry:ui",
      "title": "Kelvin Wake",
      "description": "A real site nav whose active-item indicator is a point source drifting along the link row, trailing a genuine Kelvin ship wake: a V-shaped envelope of transverse and diverging waves held at deep water's constant 19.47° half-angle regardless of how fast the source is currently moving.",
      "files": [
        {
          "path": "registry/core/kelvin-wake/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/kelvin-wake.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "nav",
          "wake",
          "fluid",
          "physics",
          "canvas",
          "monochrome",
          "accessibility"
        ],
        "instruction": "<KelvinWake wordmark? links? defaultActiveHref? className?> renders a real <nav aria-label=\"Primary\"> — a wordmark anchor and a <ul> of real <a> links, each with a normal href, visible hover and focus-visible states, and aria-current=\"page\" on whichever link is currently active (internal React state, updated on click, defaulting to defaultActiveHref or the first link). Nothing about navigation, keyboard order, or accessible naming depends on the canvas beneath it: a screen-reader or keyboard-only user gets a plain, fully working link list. Beneath the link row sits a thin aria-hidden canvas strip that renders Lord Kelvin's 1887 ship-wake analysis: a point source moving across a water surface sheds two wave systems — transverse crests roughly perpendicular to its heading and diverging crests fanning outward — confined within a V-shaped envelope held at a CONSTANT half-angle of arcsin(1/3) ≈ 19.4712° regardless of the source's speed, deep water's single most famous invariant, derived from the dispersion relation for gravity waves rather than anything to do with rotation or vortex shedding. Implementation: each transverse crest n (n = 1..N, N chosen from the strip's own measured height so spacing stays proportional at any rail scale) is a circular arc of radius n×wavelen centered ON the source, spanning ±arcsin(1/3) around the heading-reversed direction — concentric wavefronts clipped to Kelvin's cone, whose two endpoints are therefore cusp points sitting exactly on the envelope by construction. From each cusp a short diverging-wave segment continues along the local tangent (the arc-radius perpendicular that points forward and away from the centerline). The two envelope lines are drawn directly as well, so the constant angle reads at a glance rather than only being implied by where crests happen to end. This is a deliberate practical approximation of Kelvin's exact stationary-phase integral — stated as such in the source comment — built on the two constants that ARE exact and load-bearing: the envelope half-angle itself, and the fact that sin of that half-angle is exactly 1/3. The wake is redrawn fresh every frame purely from the source's current position and heading, carrying no history of past speeds; that is exact for the one claim the component exists to show, since Kelvin's angle is a property of deep-water dispersion alone, true instant-by-instant. Distinct from hero-vortex-street, also in this registry: that component sheds a von Kármán vortex street — alternating-sign rotational eddies advected by a summed Lamb-Oseen velocity field that decay on their own clock — a fundamentally different, unsteady, dissipative mechanism from this component's steady, irrotational dispersive-wave interference pattern. ALIVE AT REST: the source never stops. With no interaction it patrols the full rail width on an ~11s ping-pong sweep; clicking or otherwise activating a link eases the source, faster, to that link's measured center (held ~2.1s) before resuming the patrol from wherever it ended up — the same pointer-vs-idle-orbit driver pattern hero-vortex-street uses, applied here to click-vs-patrol, so both fast and slow travel leave the identical 19.47° V and the strip visibly differs at t=0s/2.5s/5s with zero input. All wake ink is --foreground/--ns-muted only, fading from foreground near the source to muted further back; --ns-accent is reserved for the source dot itself (legitimate active-indicator chrome) and for real link hover/focus-visible states — never for the ambient wave field. Colors are read via getComputedStyle(document.documentElement) inside useLayoutEffect (before first paint) and re-read on a MutationObserver watching documentElement's class. Backing store is DPR-capped(2) with a ResizeObserver-driven resize and a zero-size guard; the render loop pauses on both document visibilitychange and an IntersectionObserver (threshold 0) on the strip. prefers-reduced-motion renders one static frame with the source parked at 62% along the rail (not 0% or 100%, where the wake would be barely formed or already clipped at the trailing edge) so both wave systems and the full envelope are legible in a single frame; no rAF loop runs at all in that mode. The whole pattern — envelope rays, crest arcs and diverging tails — is bounded to 82% of the strip's half-height so ink stays clear of the frame edges rather than hard-clipping against the canvas bitmap. Below roughly 20px of strip height that budget can no longer fit three crests and the count collapses to the minimum of two, with the envelope reading as little more than two short ticks either side of the dot — keep the strip at or above the default h-9 (36px), which comfortably clears that floor with margin to spare. Zero dependencies, no external font."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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, --ns-accent only on the input's focus ring. No canvas, zero dependencies."
      }
    },
    {
      "name": "knife-edge-rack-focus",
      "type": "registry:ui",
      "title": "Knife Edge Rack Focus",
      "description": "A card-scale loader substitute built on the Foucault knife-edge test: a knife edge racks through a mirror's focus, sweeping the aperture's shadow pattern from one side to a flat null and back, with four baked zonal errors crossing that null at their own moment.",
      "files": [
        {
          "path": "registry/core/knife-edge-rack-focus/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/knife-edge-rack-focus.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "loader",
          "activity-indicator",
          "optics",
          "foucault-test",
          "shadow-pattern",
          "generative",
          "physics"
        ],
        "instruction": "Build <KnifeEdgeRackFocus label? className?> as a self-contained Canvas 2D loader, DPR-clamped to 2, direct-DOM rAF as the sole writer, zero dependencies. Disc radius = 0.42 * min(container width, height). The shadow pattern collapses to ONE function of radius, L(r, t) = clamp(0.5 + 1.8 * (t - offset(r)), 0, 1), because every point at a given radius shares the same optical path in a real Foucault test — no 2D raster needed. offset(r) is a piecewise-linear interpolation between 4 anchor offsets [-0.18, -0.06, 0.06, 0.18] (spaced 0.12 apart) at radii [0.25, 0.5, 0.7, 0.9] of the disc radius, flat-clamped beyond the anchors. Painted every frame as ONE radial CanvasGradient sampled at 48 stops from r=0 to 1.05*radius (the extra 5% for a soft edge) — the smoothness of a real shadow front comes from gradient interpolation, not per-pixel work, so cost stays flat regardless of how far the disc grows. t is driven by a triangle wave over a 16s period (8s rising -1->1, 8s falling 1->-1) accumulated from an internal driveTime that only advances while not paused/dwelling (never derived from absolute elapsed time). Exactly at a null (|t| < 0.05) the drive PAUSES for 0.6s before continuing — that dwell, not the sweep itself, is the clearest single followable event, mirroring how a real tester holds position there. The 4 zone offsets crossing t=0 at different points in the sweep is what makes concentric rings visibly rise or sink relative to their neighbours rather than the whole disc changing uniformly — the real diagnostic signal. Pointer interaction: pointerdown snaps the drive to the nearest null (by driveTime modulo the 16s period, comparing against the two null phases at ph=0.25 and ph=0.75) and holds it there via a separate userPaused flag (driveTime itself frozen, never advanced) until pointerup/pointercancel/pointerleave, which resumes the sweep exactly where it paused; a paused-state affordance is a 1.5px stroke around the disc rim at foreground/40% alpha (never --ns-accent, never --border as a stroke). Colours: the fill is a direct two-point mix between --foreground (L=0) and --background (L=1) via a canvas fillStyle probe reading getComputedStyle(document.documentElement) before first paint and re-derived on a documentElement class MutationObserver — this ramp never inverts between themes because it is a literal light/shadow phenomenon, not a decorative palette. A ResizeObserver rebuilds geometry on layout change; an IntersectionObserver pauses/resumes the rAF loop off-screen. Under prefers-reduced-motion the component renders one static frame at t = 0.3, named FREEZE_LABEL = 'half-sweep-zoned' (clear rise/fall banding across all 4 zones, deliberately not the null itself, which is nearly featureless and would under-inform a static viewer) — the loop, pointer listeners and IntersectionObserver are never attached, though the ResizeObserver and MutationObserver stay live. Accessibility: the root carries role=\"status\" with a visually-hidden label (default 'Testing'); the canvas is aria-hidden. Props: label (status text, default 'Testing'), className (merged onto the root)."
      }
    },
    {
      "name": "knit-ladder-run",
      "type": "registry:ui",
      "title": "Knit Ladder Run",
      "description": "An ambient status/health card background: a field of knit stitches builds downward course by course, and at random intervals a dropped stitch opens a ladder that runs a few rows before a latch-hook repair catches it and reknits the column. Structurally sound, with occasional self-corrected faults, in place of a static health badge.",
      "files": [
        {
          "path": "registry/core/knit-ladder-run/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/knit-ladder-run.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "health",
          "background",
          "ambient",
          "canvas",
          "knit",
          "textile",
          "mono"
        ],
        "instruction": "Build <KnitLadderRun className?> as a self-contained, aria-hidden 2D canvas (w-full h-full) that renders a knit-stitch field building continuously from the bottom edge upward. GRID: stitch pitch targets 10px; cols = clamp(round(width / 10), 14, 28) so the pattern reads at card scale on both small and large containers, pitch = width / cols (exact fit); every stitch is a small interlocked-loop glyph (two overlapping arcs, one opening down, one opening up, drawn as two canvas arc() strokes offset vertically by roughly a third of the stitch radius) in --foreground/--ns-muted, never text or an icon font. BUILD CLOCK: one continuous elapsed-ms counter drives continuousCourse = elapsedMs / 900 (COURSE_MS) and currentCourse = Math.floor(continuousCourse); a row's screen y = (height - pitch/2) - (continuousCourse - rowIndex) * pitch, so the newest course sits fixed near the bottom edge and every existing course drifts smoothly upward, eventually exiting past the top — an unbounded feed, never a fixed-length pass, matching a knitting machine's continuous take-down. Only rows with rowIndex <= currentCourse are ever drawn (future rows simply don't exist yet). AGE FADE (two-pass alpha overlay, same convention as other ascii/dot-grid siblings in this registry — never color-string arithmetic): every stitch is drawn once in --ns-muted at a fixed moderate base alpha (~0.55), then again in --foreground at alpha = clamp(1 - age/14, 0, 1) * ~0.75 where age = currentCourse - rowIndex — so stitches at the build edge read brightest (still only moderate opacity, per spec, never full-strength) and recede to the flat muted baseline by about 14 rows back, value only, no hue change. LADDER STATE MACHINE: one plain mutable object (never React state), gated to at most one active ladder at a time. Idle: nextTriggerAt is scheduled `elapsedMs + random(7000, 10000)`ms after the previous ladder's very last close animation finishes (or from mount for the first one). On trigger (only once currentCourse - length >= 0, i.e. enough history already exists — this naturally keeps t0 ladder-free per the spec's resting-loop description): pick length = randomInt(4,7), column = randomInt(cols), dropRow = currentCourse - length (the whole span already exists, so propagation never has to wait on new courses), openStart = elapsedMs, repairStart = openStart + length * 350ms (OPEN_STEP_MS). OPEN phase: for rowOffset k in [0, length), row (dropRow+k) collapses from a normal stitch to a bare gap starting at openAt = openStart + k*350ms, animated over a 120ms window (OPEN_ANIM_MS) — crossfade the stitch glyph's alpha down to 0 while a single --foreground vertical hairline (the run's rail, drawn as one moveTo/lineTo spanning the cell height) fades in to alpha 1; between openAt+120ms and its close time the cell is a pure rail, no glyph, so the run reads as absence, not a colored alert. REPAIR phase closes rungs bottom-up (the newest/last-opened rung closes FIRST, the original drop row reseals LAST) one rung every 300ms (CLOSE_STEP_MS): closeAt(k) = repairStart + (length-1-k)*300ms; over a 150ms window (CLOSE_ANIM_MS) starting at closeAt, crossfade the rail back to 0 while the stitch glyph fades back in, and draw a small latch-hook glyph (a short vertical stroke plus a partial arc, --foreground, same weight as normal stitches, never --ns-accent) at full-then-fading alpha marking the rung actively resealing — successive rungs closing bottom-up at a steady 300ms cadence reads as the hook glyph working its way upward. The ladder is marked inactive once the topmost (drop) row's close animation finishes; repair always completes well before a column could scroll off the top edge, so no explicit deadline logic is needed beyond the fixed cadence. TOKENS: only --foreground and --ns-muted are read, once via getComputedStyle(document.documentElement) and re-read on a MutationObserver watching documentElement's class (no paint before the first read); --ns-accent never appears anywhere, including on the hook glyph or the rail. CANVAS HOST: DPR-aware backing store capped at 2, ResizeObserver on the canvas element recomputing cols/pitch on a 120ms debounce, IntersectionObserver (threshold 0) and visibilitychange both pausing/resuming the rAF loop, full cleanup (cancelAnimationFrame, disconnect all observers) on unmount. REDUCED MOTION: prefers-reduced-motion renders one deterministic static frame (ROW_SETTLED) — NOT derived from the random ladder scheduler — with currentCourse fixed at 16 and a synthetic ladder fixed at column = floor(cols/2), dropRow = 11, length = 3, rendered as three bare open rails among otherwise-complete, normally-aged courses (no repair glyph, no rail/stitch crossfade animation, no rAF loop): a small ladder gap frozen mid-run, 2-3 rows open, not yet repaired, sitting among finished knit structure, redrawn only on a token change or a debounced resize. Purely decorative: canvas is aria-hidden, no pointer handling of any kind — a hover-driven 'picking' gesture would collide with text-stitch-unpick's established grammar. Props: className only."
      }
    },
    {
      "name": "knot-capsize-cycle",
      "type": "registry:ui",
      "title": "Knot Capsize Cycle",
      "description": "An ambient capacity/status gauge that expresses load as knot topology instead of a fill bar or needle: a granny-knot's two standing ends and two crossing loops breathe through a rigging load cycle (ramp, hold, release) and visibly slide-and-reseat into a capsized arrangement near peak load before re-dressing on release.",
      "files": [
        {
          "path": "registry/core/knot-capsize-cycle/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/knot-capsize-cycle.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "gauge",
          "status",
          "svg",
          "bezier",
          "knot",
          "rigging",
          "load-cycle",
          "ambient",
          "monochrome"
        ],
        "instruction": "An SVG-only ambient gauge rendering a granny knot as four cubic bezier strands (two standing ends, two crossing loops) breathing through a real rigging load cycle with no pointer/press/scroll input driving it, so autoplay.mode is 'none'. Two named control-point sets, DRESSED and CAPSIZED, describe the same four strands (each a {p0,p1,p2,p3} cubic bezier in normalised knot space) in two rope arrangements; every strand's anchor point (p0 for the two loops, p3 for the two standing ends — they are the same physical point, where a standing end threads into its loop) is identical in both sets, so only the free geometry ever moves. A single scalar `load` runs a fixed 8000ms cycle with no synthesis of an inverse function: ramp 0-4500ms (load 0 to 1 through easeInOutSine), hold 4500-6000ms (load pinned at 1), release 6000-8000ms (load 1 to 0 through easeInOutSine). A second scalar `capsizeT` (0 = dressed, 1 = capsized) drives `lerpCubic` between DRESSED and CAPSIZED for both loop strands every frame; capsizeT only ever changes via one of two timed, eased transitions, never a step: a 'capsizing' transition starts the instant load crosses 0.92 while still inside the ramp phase (checked once per cycle via a per-cycle-index flag) and eases capsizeT 0 to 1 over 900ms, and a 'redressing' transition starts the instant load crosses back below 0.3 while inside the release phase and eases capsizeT 1 to 0 over 700ms; between those two events capsizeT holds at whatever value it last reached, which is what keeps the capsized topology visible through the entire hold phase and the early part of release. Standing-end reach (the distance from anchor to free end) is separately scaled by 1 + 0.06*(load-0.5)*2 every frame, tightening the rope visibly as tension rises independent of any capsize event. 'Which strand is on top' at the crossing is drawn the way rope diagrams draw it: the strand passing underneath gets a literal gap cut into it, computed by splitting its cubic bezier at t=0.5 with De Casteljau's algorithm (subCubic) into two segments with a growable gap between them; loopA's gap amount is exactly 1-capsizeT and loopB's is exactly capsizeT, so the underneath cue slides in lockstep with the geometry, and whichever loop is currently 'over' (lower gap amount) is DOM-reordered to render last so it visually sits on top. All geometry is scaled off Math.min(container width, container height) * 0.39 and centred, recomputed on every debounced ResizeObserver callback (100ms) so it reads correctly at card scale. Both loop-group <g> elements and the two standing-end <path> elements read colour once from an inline `style={{ color: 'var(--foreground)' }}` on the wrapping <svg> and every stroke is `currentColor` — a knot has no legible 'dim' state, so it is drawn at full foreground value in both themes; the only value cue for tension is the standing-end tightening and loop curvature, never a colour ramp, and --ns-accent is never referenced anywhere in the component. Under `prefers-reduced-motion`, no rAF loop starts at all: the component renders once, synchronously, with load pinned at 0.92 (the exact capsize-trigger load) and capsizeT pinned at 0.5 — named CAPSIZE_MIDWAY in source — the frame where the loops are structurally mid-slide between dressed and capsized, chosen because it is the single most information-dense frame available (neither pure topology reads as ambiguous or accidental). An IntersectionObserver pauses the rAF loop when the container scrolls offscreen and resets the cycle's internal start time on resume so the phase doesn't jump on return; the ResizeObserver and IntersectionObserver are both disconnected and the rAF cancelled on unmount. Zero dependencies, one SVG element, no canvas, no colour literals anywhere."
      }
    },
    {
      "name": "lcd-response-smear",
      "type": "registry:ui",
      "title": "LCD Response Smear",
      "description": "An ambient panel-diagnostics status card whose canvas strip simulates LCD pixel response time compensation: a sweeping fill boundary crosses a row of simulated cells, each newly-lit cell overshooting its target luminance before settling and each newly-darkened cell undershooting (lagging) before catching up, the asymmetric rise/fall overdrive behavior real display reviews call the \"inverse ghost\".",
      "files": [
        {
          "path": "registry/core/lcd-response-smear/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/lcd-response-smear.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "status",
          "display",
          "canvas",
          "physics",
          "monochrome",
          "diagnostics"
        ],
        "instruction": "<LcdResponseSmear eyebrow? title? description? className?> renders a bordered, rounded status card (an eyebrow line, a title, a supporting description, and a small mono \"RTC\" badge in the header) with an aria-hidden canvas strip beneath it and a small mono readout row (RISE τ 6ms / FALL τ 11ms / OVERDRIVE COMPENSATED, stating the real panel numbers — see the dilation note below) below that. The canvas strip simulates LCD pixel response time compensation (overdrive/RTC): a single fill boundary sweeps back and forth across a row of cells spanning the strip's width. Each cell carries its own settled `level` (0 unlit / 1 lit), updated only at the moment the boundary crosses it — this is deliberately NOT re-derived from the boundary's current position on every frame, because comparing cell centers against a continuously-moving boundary disagreed with the edge-indexed crossing detection right at settle time and made every cell flicker dark-then-lit; `level` is the single source of truth once a transition finishes. Geometry: CELL_PX = max(6, containerMinDim / 56), where containerMinDim is the CARD's own bounding-rect smaller dimension (not the thin strip's), so cell size scales with how the card itself is laid out. Boundary velocity v(t) = 220 * (1 + 0.15*sin(2*PI*t / 9.1)) px/s, ping-ponging off the strip's own edges; the slow, deliberately non-round 9.1s modulation period keeps the bounce from reading as a metronomic loop. Every frame, the boundary's motion is integrated and compared against its previous position; every cell the boundary newly crossed while advancing is marked as a RISE transition (timestamped 'now', level set to lit), every cell it newly crossed while retreating is marked as a FALL transition (level set to unlit). A cell mid-RISE plays: fraction(t) eases 0 -> 1.18 (an 18% overshoot past its target luminance delta) by t+80ms, then eases 1.18 -> 1.0 by t+180ms — the bright halo an overdriven panel produces punching a cell past its brighter target before relaxing back. A cell mid-FALL plays: fraction(t) eases 0 -> 0.90 by t+120ms (i.e. it has NOT yet reached its target — an undershoot/lag, not an overshoot) then eases 0.90 -> 1.0 by t+300ms — falling transitions are driven less aggressively on real panels and consistently take longer, so this curve never crosses past 1.0, it just arrives later and more gradually than the rise curve. DELIBERATE, DOCUMENTED DEVIATION FROM THE SPEC'S REAL NUMBERS: a genuine 9ms/15ms GtG transition at 220px/s only spans 2-3px of travel, thinner than a single cell — invisible rather than a visible spike. Every keyframe TIME above is the spec's real millisecond value (4/9/6/15) multiplied by a constant TIME_DILATION = 20, so the halo spans several cells; every RATIO that carries the mechanic's identity (4:9, 6:15, the ~6:11 tau ratio, the 1.18x overshoot, the 0.90x undershoot) is preserved exactly, only the on-screen playback speed is stretched. The footer readout intentionally still states the true panel numbers (6ms / 11ms) since that's the real spec claim being illustrated, not the dilated playback speed. Both curves are evaluated as fromLum + (toLum - fromLum) * fraction where fromLum/toLum are LIT (0.86) and UNLIT (0.06) — interior levels, not pure 1/0, chosen so the 18% rise overshoot lands just under the clamp (0.86 + 0.18*0.80 ≈ 1.0) instead of extrapolating straight past --foreground and clamping away most of its visible amplitude; both LIT and UNLIT are themselves lerped between the current theme's --background/--foreground values (never absolute white/black), so overshoot direction is always 'away from the previous value' and the effect is correct automatically in both themes with zero special-casing. Once a cell's elapsed time passes its curve's settle time its transition is cleared, `level` reflects the finished state, and it renders as a flat, fully-settled lit or unlit cell for the rest of the sweep. Colors: --background and --foreground are read via getComputedStyle(document.documentElement), stored as null until that first read succeeds (every draw call is guarded on both being non-null, so nothing paints before the first token read, including on the ResizeObserver/IntersectionObserver resume paths) and re-read on a MutationObserver watching documentElement's class; cell fill is `rgb()` computed by parsing both tokens to [r,g,b] and lerping/extrapolating between them by the current luminance fraction, clamped per-channel to 0-255. This is monochrome by construction — both tokens are neutral greys (equal R=G=B) in this project's theme, and a per-channel linear lerp/extrapolation between two equal-channel colours can only ever produce another equal-channel colour, so every rendered cell is grey by construction, never a hue shift. ALIVE AT REST: the sweep runs unconditionally with zero input, unbounded, on a fixed-step rAF loop paused by an IntersectionObserver on the strip and document visibilitychange; the boundary is seeded at 38% across the strip on mount (never at 0, which would render a blank unlit strip at t0) so at t0 it is genuinely mid-sweep with a fresh rise-overshoot trailing it, by 2.5s the boundary has reversed direction at least once and is now showing the smaller fall-undershoot lag leading the opposite way, and by 5s the sinusoidal velocity modulation has shifted the boundary to a different position with a different overshoot/undershoot balance than either earlier checkpoint. Interaction is optional and read-only: nothing about the sweep depends on pointer presence, hover, or focus — the strip is purely decorative (aria-hidden) and the card's title/description/badge/readout row are the real, motion-independent accessible content. prefers-reduced-motion renders one deterministic static frame: the same boundary-integration and per-cell-crossing logic is replayed synchronously from the same 38%-seeded t0 up to STATIC_TIME=0.85s (so the boundary lands at a real simulated position rather than an arbitrary fixed spot), then whichever cell was crossed most recently has its render-time artificially pinned to its curve's named peak/dip instant (dilated t+80ms for rise, t+120ms for fall) instead of its true elapsed age — guaranteeing the frozen frame always shows the climactic overshoot/undershoot halo rather than risking a plain, already-settled edge for some container sizes; no rAF loop runs in this mode. DPR-capped(2) backing store with a ResizeObserver-driven resize (recomputes cell size/count and reseeds the sweep) and a zero-size guard. --border is used only as the card and strip's separator/frame stroke, never a fill. --ns-accent is not used anywhere in this component — there is no pointer-highlight or interaction chrome, and the overshoot halo is explicitly luminance-only per the project's standing rule that the climactic moment never carries interaction-chrome colour. Not motion blur: distinct from kelvin-wake (and this project's other trailing-wake components), which render a continuous gradient smear behind a moving point — this component's per-cell states change in discrete instant steps with two explicitly named curve keyframes (a peak or dip, then a settle), never a spatial blur convolved across the frame. Zero dependencies, no external font."
      }
    },
    {
      "name": "leaven-crest-fall",
      "type": "registry:ui",
      "title": "Leaven Crest Fall",
      "description": "A sourdough starter jar status indicator: the surface dome rises with a Poisson-nucleated bubble texture to a peak, then collapses as the bubble population thins in step with the falling height, fed and risen again on a repeating 12.6s cycle.",
      "files": [
        {
          "path": "registry/core/leaven-crest-fall/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/leaven-crest-fall.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "gauge",
          "svg",
          "fermentation",
          "cyclical",
          "ambient",
          "sourdough",
          "bubbles"
        ],
        "instruction": "A card-scale SVG-only sourdough-starter jar. A jar outline (a static <path>, rounded bottom corners, stroked in var(--border), never filled) sits fixed at JAR_LEFT=46/JAR_RIGHT=154/JAR_TOP=20/JAR_BOTTOM=196 inside a 200x220 viewBox scaled by preserveAspectRatio=\"xMidYMid meet\" so geometry is uniformly fit to whichever of the container's dimensions is smaller. The starter body is one filled <path> (var(--ns-muted)) whose top edge is the dome: every rAF frame, DOME_SAMPLES=22 x-positions across the jar's interior width each get a y-offset above BASE_Y=108 of `riseAmplitude(t) * bump(u)` where bump(u)=(1-cos(2*pi*u))/2 is a raised-cosine 0 at both jar walls and 1 at centre, plus a small two-harmonic ripple (amplitude 1.1, harmonics 3 and 5, phase rotating at 0.5 rad/s) that keeps the surface visibly non-static even during flat stretches of the rise/fall curve. Samples are joined into a smooth path with successive quadratic curves through each adjacent pair's midpoint, then closed down to the fixed JAR_BOTTOM. Height follows n(t) in [0,1], the fraction of one 12600ms cycle: an 8000ms logistic RISE (steepness k=9) from N_SETTLE=0.05/1.4~=0.0357 up toward 1, a 4000ms exponential-decay FALL back down to N_SETTLE (exp(-3*u)), then a 600ms FEED window with no height change but a brightness flash. The dome's height multiplier is 1+1.4*n and riseAmplitude(t)=48*n. Surface bubbles are a real Poisson arrival process: during the rise phase only, each frame has a LAMBDA*dt=0.8/60 chance of nucleating a new bubble at a random x, radius growing R0*age^0.3 up to MAX_R=3.4 viewBox units. The instant the fall phase begins, the current live (non-popping) bubble count is captured as peakCount; every frame during the fall, target=round(peakCount*clamp((n-N_SETTLE)/(1-N_SETTLE),0,1)) is recomputed and the smallest-radius live bubbles beyond that target are marked popping (radius eased 1->0 over 120ms) — so the bubble population visibly thins exactly as fast as the dome itself falls, a causal coupling rather than an independently-timed decoration. A fixed pool of 30 pre-rendered <circle> elements (unused ones r=0) is reused across the whole run so the DOM node count never changes. Every per-frame numeric write (dome path d, each bubble's cx/cy/r, the feed-flash overlay's opacity attribute) is a direct ref.setAttribute call inside one rAF loop; React state is only touched for prefers-reduced-motion detection and a ~1Hz sr-only status string (\"rising\"/\"collapsing\"/\"fed, beginning to rise\"), so the component never re-renders during the animation itself. autoplay: none because the whole cycle runs on its own internal clock with zero pointer/scroll/press dependency. Under prefers-reduced-motion the component renders one precomputed, seeded-PRNG-deterministic frame equal to t=8000ms of the cycle (n at its peak, ~14 bubbles at varied radii) instead of running the rAF loop at all, and the bubble circles in that frame are drawn stroked-outline only (no fill/no radius animation) to make clear nothing is mid-motion. Every colour is a CSS custom property read directly in SVG fill/stroke attributes (var(--background)/var(--foreground)/var(--ns-muted)/var(--border)) — zero literals anywhere, zero getComputedStyle, a theme flip repaints for free. The svg is aria-hidden with pointer-events-none; a single visually-hidden role=status/aria-live=polite span carries the current phase text for anyone landing on the region with a screen reader."
      }
    },
    {
      "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": "lenticule-swing",
      "type": "registry:ui",
      "title": "Lenticule Swing",
      "description": "A hero H1 lockup printed twice behind vertical lenticular slats (the problem statement and the product promise coexisting at once) that shimmers between both at rest and snaps cleanly to the promise as it scrolls out of view.",
      "files": [
        {
          "path": "registry/core/lenticule-swing/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/lenticule-swing.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "hero",
          "headline",
          "text",
          "scroll",
          "lenticular",
          "spring",
          "typography"
        ],
        "instruction": "A hero headline rendered twice — messageA (the problem) and messageB (the promise) — into a shared stage cut into N thin vertical strips (~pitch px each, default 10, actual count derived live from the stage's own width via ResizeObserver, clamped 22-64). Each strip is its own overflow:hidden peephole div holding two oversized copies of the full headline text (window for B underneath, window for A on top), each shifted left by exactly that strip's index times the stage width so the visible sliver in every strip lines up with its neighbors into one continuous line — the classic 'replicate the whole image, offset each copy, clip with a small window' lenticular-strip trick, no canvas. One custom property, --lens-angle (a signed degree number, used only inside calc(), no CSS unit), drives every strip: window-A's opacity is clamp(0, 0.5 - (angle/9)*0.5 + strip-jitter*0.12, 1) where strip-jitter is a deterministic hash of the strip's index baked in at mount (-1..1) — so as the whole headline sweeps from angle -9 (message A fully wins every strip) to +9 (message B fully wins every strip) the strips whose jitter sits furthest from the pack lag behind and keep interlacing a beat after the rest have committed, a residual seam a real lenticular print never fully resolves. Both windows in a strip also translateX by angle * that strip's centered index (i - (count-1)/2) * 0.05px, a few tenths of a pixel of per-strip parallax shimmer, the 'tilt the plastic sheet' cue. --lens-angle has exactly one writer: a single rAF loop that reads the component root's own getBoundingClientRect() every frame (viewport-relative, so it is correct whether the page itself scrolls or the root sits inside a nested overflow-y:auto ancestor — no scroll-event listener, no scroller coupling). At rest (scroll progress 0) a damped spring (k=34 s^-2, zeta=0.55, visible settle wobble) bounces the angle between -3 and +3 deg on a 4s half-period — an 8s full swing — so the two messages shimmer against each other and the resting frame is deliberately the ambiguous in-between, never a clean read of either. Once the root starts scrolling out of the viewport, progress = clamp(-rect.top/rect.height, 0, 1); an eased scroll term (easeOutCubic(min(progress/snapAt,1)) * 9deg) ramps the angle toward a full commit to messageB by snapAt (default 0.4) of the root's own height, while the idle spring's contribution fades to 0 weight over that same span so the two drivers hand off rather than fight — past snapAt the angle holds at its resolved value, a clean snap rather than a continuing creep. Consumers size 'the hero' by giving the root a min-height via className (the demo uses flex + min-h-[380px]); snapAt is relative to the root's rendered height, not just the single text line. The real <h1> is a visually-hidden (sr-only) element containing messageA followed by messageB as plain text, read exactly once, unconditionally, with no interaction required; the entire strip apparatus is aria-hidden. Zero interactive controls — this replaces a headline plus subhead, not a widget, so the a11y control audit's Tab-reachability rule does not apply. prefers-reduced-motion: the spring and the easing are both skipped; the angle is pinned to -9 (full messageA) at progress 0 and flips once, with no interpolation, to +9 (full messageB) the instant progress crosses snapAt — no oscillation, no creep, still legible and still resolves. All ink is var(--foreground); no color other than the token is used anywhere in the strip apparatus. Zero dependencies."
      }
    },
    {
      "name": "lining-wear",
      "type": "registry:ui",
      "title": "Lining Wear",
      "description": "A dunning card for past-due subscriptions where the clutch grip on 'active' is a friction lining that wears down: every failed charge slips the pad and permanently scores one lamination, and the plate disengages to 'paused' once the lining is spent.",
      "files": [
        {
          "path": "registry/core/lining-wear/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/lining-wear.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "billing",
          "subscription",
          "dunning",
          "status",
          "retry",
          "card",
          "aria-live",
          "accessibility"
        ],
        "instruction": "Build the past-due surface for a billing/subscription card as a clutch friction lining wearing down, not a yellow badge. One governing scalar, `lining = attemptsRemaining / maxAttempts` (attemptsRemaining = maxAttempts - attemptsUsed), drives the whole graphic. A 92x64px frame holds a 'grip': the remaining lining pad, height = 64 * lining, filled at var(--border)/25% with a var(--foreground) 2px top edge (the live wear boundary) and internal 1px var(--border) lamination rules every FRAME_H/maxAttempts px, so remaining depth is literally countable at rest by counting stripes. An 'Active'/'Paused' label sits pinned to the grip's top edge. `attemptsUsed` is a controlled prop, monotonic within a cycle (the caller increments it by 1 per failed charge). Each increase is one abrasion event: the whole grip (pad + laminations + label, one ref, one FLIP-style transform) slips sideways by (1 - lining) * 12px, capped at 12px so the label never leaves the card's reading line, then settles back to 0 with a spring whose duration eases from 300ms at lining=1 down to 700ms at lining=0 (a worn clutch grips worse and settles slower) — the SAME settle carries the grip's one-segment drop to its new resting position via the FLIP delta, so the final failure's heavier settle reads as the plate physically disengaging toward 'Paused' rather than a special-cased transition. Simultaneously (plain declarative render, not animated) one permanent scored line appears at var(--foreground) at the exact y where that lamination sheared off — a fixed position nothing later ever redraws, so all of a cycle's scars stay visible stacked above the current, shorter pad — and one debris tick drops into a row below the frame, deterministically jittered in height/rotation/offset (a seeded sine hash, never Math.random, so server and client render identically) so the row reads as drifting sediment, not a bar chart. Wear is strictly monotonic within a cycle: no code path decrements attemptsUsed or removes a scored line/debris tick on its own — the forbidden shape is a health bar that refills on success, because refilling erases the exact datum a past-due customer and support both need ('this card has already failed three times this month'). Recovery is a distinct re-line, triggered only by changing the `cycleId` prop (pass it alongside resetting attemptsUsed to 0): the current scored/debris state is snapshotted into a small 'last cycle' well (a tiny row of filled/outline ticks plus the label 'last cycle', fading in to 40% opacity) and an entirely fresh, unscored pad swaps in with no scored lines of its own. Every fact the graphic carries is ALSO written as real text outside the aria-hidden wrapper: a status paragraph reads exactly 'Payment failed, attempt 2 of 4. Next retry Thu 21 Aug.' (or the active/paused variants) built from `attemptsUsed`/`maxAttempts`/`attemptDates`; a second line spells out 'N of maxAttempts retry attempts remaining'; the retry schedule renders as a real `<ol>` of Attempt N / date / Failed-Scheduled-Pending built from the `attemptDates` array (oldest first); and 'Update payment method' is a real `<button onClick={onUpdatePayment}>` — nothing interactive lives inside the aria-hidden mechanism. Each NEW failure (attemptsUsed increasing) announces exactly once through a dedicated `role=alert aria-live=assertive aria-atomic=true` sr-only span holding the same status sentence — assertive because a failed charge is genuinely urgent, unlike the polite/throttled chatter appropriate to routine autosave or sync indicators. prefers-reduced-motion drops the grip's slip/settle transform entirely (it snaps straight to its resolved position) and disables the last-cycle well's entrance transition, but the lamination count, the scored lines and the debris ticks still decrement/accumulate on the very same render — the wear record is never gated behind motion. Direct-DOM refs carry only the grip's transform (a FLIP two-rAF handoff, matching the ratchet/retry components already in this registry); every other visual — scored lines, laminations, debris, schedule, status text — is plain declarative React keyed off props, no imperative DOM writes needed for those. Zero dependencies, DOM + CSS only, no canvas, no SVG required."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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/--ns-muted/--border/--ns-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-ascii-diffuse-fill",
      "type": "registry:ui",
      "title": "Loader ASCII Diffuse Fill",
      "description": "Indeterminate ASCII loader whose texture comes from real Floyd-Steinberg serpentine error diffusion, propagated cell-to-cell every frame, rather than a fixed ordered-dither threshold matrix.",
      "files": [
        {
          "path": "registry/core/loader-ascii-diffuse-fill/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/loader-ascii-diffuse-fill.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "ascii",
          "dither",
          "diffusion",
          "mono",
          "canvas"
        ],
        "instruction": "An indeterminate loader rendered as an ASCII grid, quantized every frame by real Floyd-Steinberg SERPENTINE error diffusion rather than the repeating 4x4 Bayer matrix the registry's existing ordered-dither family (chart-bar-dither, chart-line-dither, chart-radar-dither, masonry-ascii-settle, background-ascii-dither) all share. The distinction is real and citable in dithering literature, not a restyle: an ordered dither compares each cell's value against a fixed spatial threshold pattern independent of its neighbours, so the same value always quantizes the same way at the same grid position; error diffusion instead computes each cell's quantization error (its true continuous value minus the nearest of the 10 ' .:-=+*#%@' ramp levels) and propagates that error into UNPROCESSED neighbouring cells with the canonical Floyd-Steinberg kernel — 7/16 to the next cell in the scan direction, 3/16 to the down-behind cell, 5/16 straight down, 1/16 to the down-ahead cell — so a cell's outcome depends on what was already decided beside it. The scan direction alternates every row (serpentine: left-to-right, then right-to-left, then left-to-right...), with the kernel's horizontal offsets mirrored through the scan direction rather than hardcoded, so error doesn't compound into one persistent directional streak. That neighbour coupling is what gives real error diffusion its signature look: directional, frame-coherent artifact drift, categorically different from an ordered tile's uniform, position-locked texture. A low-amplitude flowing noise field is summed into every cell's pre-quantization value, present in both the filled and unfilled regions, so the diffusion PATTERN itself drifts frame to frame — not merely the fill boundary's position — keeping the component visibly different at t=0, 2.5s and 5s with zero input. The 'fill' is a soft boundary sweeping continuously and cyclically across the grid (never completing, never pointer-gated, wrapping and re-entering from the left forever, because this is an indeterminate state, not a completion bar); the full 2D value field is rebuilt and re-diffused from scratch every frame rather than carrying dither state between frames, matching how the algorithm actually operates on a value field. Cell size and grid geometry are derived from the container's own shorter dimension (at least 11 cells guaranteed across it, capped at 15px per cell) so the texture reads as grain rather than a handful of oversized glyphs at both small inline sizes and large ones; glyph advance width is measured on an offscreen canvas after document.fonts.ready and used to pick the exact font-size that makes '@' fill one cell, the same cell-metrics convention the rest of the ASCII family uses. Ink is drawn purely as glyph density (var(--foreground) only, fixed alpha) — the diffusion's ten quantization levels are what carry all the tone, so nothing is layered on top by opacity. Tokens are read via getComputedStyle on the document root inside a useLayoutEffect (before the canvas can ever paint) and re-read on a MutationObserver watching its class attribute, so a theme toggle repaints correctly with no remount and nothing draws with a default/black fallback ink first. prefers-reduced-motion freezes the clock at a fixed non-t0 time with the fill boundary parked ~46% across the grid — a mid-fill frame chosen specifically so filled ink, unfilled paper AND the diffusion turbulence at the seam are all on screen simultaneously, never a degenerate all-empty or all-full frame. The render loop pauses via IntersectionObserver (offscreen) and document visibilitychange (backgrounded tab) rather than continuing to burn frames unseen. role=progressbar with an accessible label; the canvas itself is aria-hidden since the loader has no numeric value to expose (fully indeterminate, no progress prop). Props: aria-label (default 'Loading'), className (required to size the root — the canvas fills whatever box its container is given, same convention as background-ascii-dither)."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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`/`--ns-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": [],
      "cssVars": {
        "theme": {
          "color-surface": "var(--surface)"
        },
        "light": {
          "surface": "#fafafa"
        },
        "dark": {
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "error": "#ea001d",
          "success": "#2d7a2d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "error": "#ff6369",
          "success": "#47a447"
        }
      },
      "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 --ns-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 (--ns-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": [],
      "cssVars": {
        "theme": {
          "color-surface": "var(--surface)"
        },
        "light": {
          "surface": "#fafafa"
        },
        "dark": {
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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-spirograph-trace",
      "type": "registry:ui",
      "title": "Loader Spirograph Trace",
      "description": "A determinate loader that traces a real hypotrochoid: the full closed rosette is always visible as a ghost, and progress is the fraction of that curve's arc length inked in, so 0% and 100% are both legible shapes rather than an empty ring.",
      "files": [
        {
          "path": "registry/core/loader-spirograph-trace/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/loader-spirograph-trace.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "progressbar",
          "svg",
          "spirograph",
          "hypotrochoid",
          "determinate",
          "indeterminate",
          "accessibility"
        ],
        "instruction": "Build <SpiroTrace value? size? label? R? r? d? className?> as a plain SVG progressbar with zero dependencies and no canvas. CURVE: a hypotrochoid with R = 5, r = 3, d = 2.1 — x(phi) = (R - r)*cos(phi) + d*cos(((R - r)/r)*phi), y(phi) = (R - r)*sin(phi) - d*sin(((R - r)/r)*phi). Because gcd(R, r) = 1 the pen closes after exactly r/gcd(R,r) = 3 revolutions, so phi runs 0 -> 6*PI and the result is a single closed five-petal rosette (petal count = R/gcd = 5). It is sampled ONCE at 720 points into one 'M ... L ... Z' path string (memoised on the R/r/d props), then measured and affinely fitted into a 0 0 100 100 viewBox with a 6-unit margin, uniform scale, centred. RENDERING PROGRESS: two <path> elements share that exact same `d`. The ghost path is stroke=\"var(--foreground)\" strokeOpacity 0.22, strokeWidth 1.1 — the entire route, always on screen, never a circular track. The traced path sits on top with pathLength=\"1\" (so dash units are normalized ARC LENGTH, not user units), strokeDasharray=\"1 1\", strokeDashoffset={1 - progress}, strokeOpacity 0.92, strokeWidth 1.6, strokeLinecap=\"round\", and a 260ms cubic-bezier(.33,1,.68,1) CSS transition on stroke-dashoffset so an incoming value glides along the curve instead of jumping. A pen dot (r = 2.2, fill=\"var(--ns-accent)\" — the single accent use in the whole component) rides the curve at the current position, located with ghostPath.getPointAtLength(progress * getTotalLength()) inside a rAF that runs ONLY while progress is in flight and then stops itself; a settled or unchanged value places the pen once and schedules no frames. INDETERMINATE: when `value` is undefined (or NaN) the dashoffset binding is dropped and the same curve instead carries a fixed 0.18-length dash window (strokeDasharray=\"0.18 0.82\") driven around the closed path by a single CSS animation from stroke-dashoffset 0 to -1 over 2400ms linear — a constant arc-length rate, one full traversal per 2.4s. It is never a swap to a different widget: same rosette, same ghost, same pen. A ref records where the window's head is each frame, so when a real value later arrives the component seeds the inline dashoffset at that head with the transition suppressed for one write (forced reflow), re-enables the transition, then writes the target — the trace converges to the true value from wherever the sweep happened to be rather than snapping back to empty. ARIA: the wrapper is role=\"progressbar\" with aria-valuemin=0, aria-valuemax=100 and aria-label from the `label` prop; aria-valuenow is set only in determinate mode and OMITTED ENTIRELY while indeterminate, which is the correct indeterminate signal rather than a fake 0. A Geist Mono tabular-nums readout sits beside the rosette showing the rounded percent, or an em-dash rule while indeterminate; it is aria-hidden because the progressbar role already carries the value. Display-only: no pointer or keyboard interaction, no focusable control, nothing to focus-ring. TOKENS: every stroke and fill is a presentation attribute reading a CSS custom property directly (var(--foreground) for both hairlines, var(--ns-accent) for the pen), so there is no getComputedStyle and no MutationObserver — the cascade handles a theme flip for free, and both themes are correct by construction. prefers-reduced-motion: reduce disables both the 260ms transition and the 2400ms sweep animation in a media query, and the script path checks matchMedia too — determinate progress snaps straight to its new dashoffset with the pen placed once, and the indeterminate state renders a static 0.18 dash window at a fixed offset with the pen parked at its head, so the resting frame is still a legible partially-inked rosette rather than a frozen empty one. Props: value (0-100, omit for indeterminate), size (px, default 160), label (default 'Loading'), R (default 5), r (default 3), d (default 2.1), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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|--ns-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": "log-viewer-ascii-tail",
      "type": "registry:ui",
      "title": "Log Viewer ASCII Tail",
      "description": "A live `tail -f` log pane with a real ring buffer, a per-line severity glyph column, and follow-mode that detaches the instant you scroll up and reports how many lines arrived while you were reading.",
      "files": [
        {
          "path": "registry/core/log-viewer-ascii-tail/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/log-viewer-ascii-tail.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "logs",
          "terminal",
          "streaming",
          "ascii",
          "mono",
          "virtualized",
          "observability",
          "developer-tools"
        ],
        "instruction": "Build <LogViewerAsciiTail capacity? seed? initialLines? services? className? aria-label?> as a Geist Mono `tail -f` pane whose honesty is structural rather than decorative. RING BUFFER: a ref-held `Array(capacity)` (default 2000) with a monotonic `total` counter; a line's ring slot is `seq % capacity`, so an append overwrites the oldest slot and memory is bounded no matter how long the stream runs — there is no growing array anywhere on the path. The ordered view is read back from `max(0, total-capacity)` to `total`. STREAM: one deterministic LCG, `s = (s * 1664525 + 1013904223) >>> 0`, drives everything, so a given `seed` replays byte-identically: draw one for the inter-line delay `90 + (s/2^32)*260` ms (cadence therefore jitters like real traffic instead of ticking on a fixed interval, and the same draw advances a virtual wall clock so timestamps agree with the delays), one for the severity from a weighted table (DEBUG 0.42, INFO 0.40, WARN 0.13, ERROR 0.05), one for the service, one for the message. The generator is a self-rescheduling setTimeout, never setInterval. FLUSH: appends land in the ref and are coalesced into ONE rAF-scheduled setState per frame — a burst of lines inside a single frame costs a single render, never one render per line. RENDERING: rows are a fixed 18px tall and absolutely positioned inside a spacer of `rows.length * 18`, and only the rows intersecting the viewport (plus 6 of overscan each side) are in the DOM, so a full 2000-line buffer is a few dozen nodes. LINE FORMAT: `12:04:31.882 │ · │ api-gateway  │ upstream 502 after 3 retries` — the service column is padEnd'd to 11 chars and the separators are drawn in `text-ns-muted/45`, so the pipes read as true faint vertical rules down the pane (NOT `text-border` — `--border` is tuned for 1px hairlines and is invisible as type on the light theme). SEVERITY COLUMN: glyph AND weight together, never hue alone — DEBUG `·` in `text-ns-muted/45`, INFO `─` in `text-ns-muted`, WARN `≡` in `text-foreground font-medium`, ERROR `█` in `text-ns-accent font-semibold`. The four-glyph column is the component's whole visual texture at rest: a mostly quiet field of `·` and `─` with the occasional accent `█` standing out, which is what makes an error burst findable without reading a word. FOLLOW: while following, every flush sets `scrollTop = scrollHeight`; the scroll handler drops follow the moment `scrollHeight - scrollTop - clientHeight > 8`, and a sticky bottom button renders `└─ 37 new lines ─ End to follow ─┘` with the count (inked in `text-ns-accent`) of appends since detach. Clicking that bar, pressing End, or scrolling back inside 8px re-attaches and zeroes the counter — the counter is the component's actual promise: nothing scrolled past you unannounced. Home/ArrowUp/ArrowDown/PageUp/PageDown also scroll the pane from the keyboard. There is no minimap, gutter or density index of any kind — the severity column and the line count are the whole read. FILTER: a `role=radiogroup` minimum-level segmented control with roving tabindex and Arrow/Home/End navigation (selecting with the keyboard also MOVES focus to the newly checked radio, so the ring never strands on a now-`tabIndex=-1` button) — each option shows its own severity glyph, so the control doubles as the legend for the column — plus a substring `input[type=search]` matched against message and service; matches are re-inked to `text-ns-accent` in place by splitting the message around each occurrence. Both re-derive the visible set straight from the ring buffer, and the header shows `visible/total lines · ring N`. ACCESSIBILITY: the pane is `role=log` with an `aria-label`, tabIndex=0, and an explicit `aria-live=\"off\"` — a live region emitting ten lines a second is unusable with a screen reader, so announcement is deliberately opt-out and the pane is navigable as a focusable scroll region instead. Every focusable control (pane, level radios, filter input, the detach bar) carries a visible `focus-visible` accent outline. LIFECYCLE: `document.hidden` stops the generator entirely (the timeout is not rescheduled) and `visibilitychange` restarts it. REDUCED MOTION: `prefers-reduced-motion: reduce` seeds the same `initialLines` (default 200) of real data and then does not start the generator at all, rendering a `▸ resume stream` toggle instead — the data on screen is honest, only the motion is opt-in. THEME: every colour is a token (`--background --foreground --ns-muted --border --surface --ns-accent`); there is no hex anywhere, no canvas, and no dependencies. Row hover raises a line from `text-ns-muted` to `text-foreground` over a `bg-surface` chip in 100ms and relaxes back on pointer-out."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": "lug-cage-tally",
      "type": "registry:ui",
      "title": "Lug Cage Tally",
      "description": "An ambient loading glyph shaped as five pin wheels spinning at mutually-prime rates past a fixed read mark, each engaged pin brightening in a brief flash and nudging a 24-notch tally bar forward one segment, the bar rolling its window open-ended rather than ever resetting to empty.",
      "files": [
        {
          "path": "registry/core/lug-cage-tally/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/lug-cage-tally.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "loading",
          "spinner",
          "indicator",
          "svg",
          "mechanical",
          "tally",
          "aria-live"
        ],
        "instruction": "A card-scale ambient loading glyph rendered as pure SVG: five small pin wheels in a horizontal row, each an 8-pin rim (fixed active/inactive pattern, exactly four of eight pins active, shuffled once per wheel at mount via a seeded mulberry32 PRNG and never touched again) spinning at its own constant deg/s rate (51.4286, 60, 72, 90, 102.8571 — full-rotation periods of 7s, 6s, 5s, 4s, 3.5s chosen so no two periods share a small common factor, keeping the five wheels' combined phase relationship visibly fresh for well over a minute). A fixed, non-rotating tick mark sits above each wheel at its 12-o'clock read position, drawn at full var(--foreground) opacity as the one static high-contrast reference point; every 45deg of a wheel's rotation carries its next pin past that mark. The animation loop (rAF, direct SVG attribute/style writes only, zero React state on the hot path) tracks each wheel's cumulative pin-step count from elapsed time; whenever that count increments, it looks up which pin index just crossed the read mark (wheels visit pins in descending index order as they turn) and, if that pin is active in the wheel's fixed pattern, triggers two effects: a 90ms luminance-only flash on that pin's SVG line (opacity jumps to 1 with transitions disabled, then eases back to its resting 0.85 over 90ms) and one advance of a shared 24-notch tally bar below the wheels. The tally fills its notches left to right (each a 140ms ease-out fill-opacity transition to var(--foreground) at 0.8) until full, after which every further tick rolls the window: the leftmost notch fades to 0 opacity over 200ms, then the whole strip is instantly re-indexed one slot left and the rightmost notch begins its own 140ms fill — the bar never hard-resets or flashes back to empty. Geometry is derived once per resize from the container's smaller dimension via a ResizeObserver (wheel diameter = smaller/6.5, tally bar height = wheel diameter x0.3, everything else — gaps, pin length, notch width — scaled off that same base unit) and applied through a memoized layout object; only the resize-driven layout touches React re-render, every per-frame write goes straight to refs. An IntersectionObserver pauses the rAF loop while off-screen and resumes it without resetting the elapsed clock, so wheel phase never jumps. `prefers-reduced-motion` skips the rAF loop entirely and instead freezes five wheels at distinct static rotation angles with two of the ten flash states caught mid-decay (opacity 0.93 instead of resting 0.85 or full 1) and the tally bar pinned at 60% fill — the frame chosen to read as 'mechanism mid-tally', never a clean idle state. Every colour is a token read directly in SVG attributes/inline styles (var(--foreground), var(--border)) with zero literals and zero getComputedStyle reads, since nothing here computes or interpolates a numeric colour value; --border only ever outlines an empty notch, never fills one, and there is no --ns-accent anywhere — this is ambient chrome, not interaction chrome. The whole glyph is a single `role=\"status\" aria-live=\"polite\"` element with the SVG marked `aria-hidden`; a visually hidden `label` prop (default 'Loading') is the only thing announced, and it renders no interactive controls. Props: label, className. Zero dependencies, no canvas, no WebGL, cleans up rAF/IntersectionObserver/setTimeout handles on unmount and on every resize-triggered effect re-run."
      }
    },
    {
      "name": "manifold-bleed",
      "type": "registry:ui",
      "title": "Manifold Bleed",
      "description": "A connected app's standing grants drawn as a valve manifold: each scope is a gate valve on a supply line from your account to the app, and revoking it drains the line backward toward you only after the server confirms, leaving a hairline ghost of what was once granted.",
      "files": [
        {
          "path": "registry/core/manifold-bleed/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/manifold-bleed.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "connected-apps",
          "oauth",
          "permissions",
          "switch",
          "svg",
          "valve",
          "async",
          "accessibility"
        ],
        "instruction": "Build `<ManifoldBleed appName scopes={ManifoldScope[]} onGrantChange? />` for a connected-app / standing-grants management page, where `scopes` is `{ id, label, description, defaultGranted? }[]` — each scope one row. Render each row as a horizontal supply line running from the account (implicit reservoir, left) to the app (right), with a real gate-valve switch sitting right at the header where the branch leaves the account, exactly like a real plumbing manifold clusters its valves at the supply header rather than partway down each line. The valve is a native `<button role=\"switch\" aria-checked>` (never a styled div) containing a small aria-hidden SVG: a circle body (stroke var(--border), fill var(--background)) with a straight handle line through it that rotates via CSS transform — 0deg (parallel to the pipe) when open/granted, 90deg (perpendicular, blocking) when closed/revoked, exactly how a real ball-valve handle reads. To the right of the valve, a second aria-hidden SVG draws the line itself as TWO overlapping strokes sharing one `pathLength=\"1\"` normalization: a permanent 1px var(--ns-muted) hairline track spanning the full line (rendered for every row, granted or not, forever — this is the evidence trail; the list never shortens or removes a row even after every scope in it has been revoked), and on top of it a var(--foreground) 'ink' stroke with `stroke-dasharray=\"1 1\"` whose `stroke-dashoffset` is set to `1 - fill` where `fill` is 1 when granted and 0 when revoked. Because the path runs left(valve)->right(app) and dasharray period equals the full path length, animating dashoffset from 0 to 1 retreats the painted portion from its FAR end (the app side) back toward its near end (the valve/account side) — the drain is structurally backward-toward-the-source, not a cosmetic reversal. `stroke-linecap=\"round\"` on the ink stroke means its own retreating edge doubles as the meniscus, so no separate marker element is needed. The governing scalar per row is that same `fill` (0 or 1): stroke-dashoffset (`1-fill`), stroke-width (`1 + 2*fill`, so a live line reads bolder than the hairline it eventually matches), and the valve-handle rotation (`(1-fill)*90deg`) are ALL pure linear functions of it and nothing else is choreographed — a single CSS transition (`stroke-dashoffset`, `stroke-width`, `transform`, ~600ms, ease-out-expo `cubic-bezier(0.16,1,0.3,1)`) on those three properties reproduces exactly what tweening the scalar itself and re-deriving every frame would, with zero rAF loop. FALSIFIABLE CONSTRAINT — fill may change ONLY after server acknowledgment: clicking a valve never touches `fill` directly. It sets a per-row `pending` flag (aria-busy on the button, the row's status word showing '…' instead of 'Active'/'Revoked'), calls `onGrantChange(scopeId, nextGranted)` (or, if that prop is omitted, an internal ~320ms `setTimeout`-based simulated round trip so the component is demoable with no backend), awaits it, and only on resolution flips the internal granted state that `fill` is derived from and fires the announcement. A second click on a row already pending is ignored, not queued or raced. Optimistic fill on grant — flipping the line before the promise resolves — is the explicitly forbidden shortcut: this is the one panel whose whole job is telling the truth about what access exists right now, and rendering authority that doesn't exist yet is a lie in exactly that panel. Every row also renders a small persistent status word to the right (`data-manifold-status={id}` `data-granted={granted}`, text 'Active' / 'Revoked' / '…' while pending) — plain text, never color-only, and present at rest so a caller (or a test) can read state without waiting for the SVG's transition to finish. Accessibility: each switch's accessible name comes from `aria-labelledby` pointing at the row's own visible label text (e.g. 'read:repos'), `aria-describedby` points at the visible plain-language description, `aria-checked` mirrors the CONFIRMED state only (never the optimistic click), and a single shared `role=\"status\" aria-live=\"polite\"` region announces exactly `\"{label} granted\"` / `\"{label} revoked\"` once the server actually confirms. All pipe/valve SVGs are `aria-hidden` — redundant encoding of the switch state and the visible status word, never the only carrier of it. `prefers-reduced-motion: reduce` removes every transition via a scoped `@media` rule so fill, stroke-width and the valve angle land on their target in one step — no drain animation plays, the row is still fully legible from the status word and the valve's resting angle alone. A hairline hover state (button background tint + valve body stroke brightening to var(--foreground)) is a plain CSS `:hover` rule, unaffected by the reduced-motion guard. Zero dependencies, 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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-surface": "var(--surface)"
        },
        "light": {
          "surface": "#fafafa"
        },
        "dark": {
          "surface": "#171717"
        }
      },
      "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": "melt-pond-drain",
      "type": "registry:ui",
      "title": "Melt Pond Drain",
      "description": "A card-scale ambient loader/empty-state panel modelling Arctic melt-pond drainage: a shallow top-view basin whose pool fills over ~9-11s, then drains rapidly through a crack that opens at a new rim location each cycle, leaving a shallow residual pool that refills and repeats.",
      "files": [
        {
          "path": "registry/core/melt-pond-drain/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/melt-pond-drain.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "empty-state",
          "canvas",
          "ambient",
          "water",
          "glaciology",
          "cycle"
        ],
        "instruction": "A card-scale (`w-full h-full`) canvas-2D ambient panel with zero props to bind and zero pointer interaction — the mechanic is melt/gravity driven, never touch driven. Internally it runs a small state machine with two phases, `fill` and `drain`, over a single scalar `level` (0-1, the fraction of the basin's near-full threshold FULL_LEVEL=0.93 currently reached). `fill` interpolates level from a post-drain residual up to FULL_LEVEL over a randomized 9-11s duration (eased in/out cubic) before flipping to `drain`. `drain` is a fixed 1200ms event with three stages driven off elapsed-fraction u: 0-30% grows a `dimple` scalar 0->1 via smoothstep while level holds (the funnel forming); 30-85% holds dimple at 1 while level eases from its pre-drain value down to a residual = preDrainLevel * (1 - dropFraction), dropFraction randomized 0.8-0.9 each event (never to zero — a shallow residual pool always remains); 85-100% eases dimple back to 0 while level holds at the residual (the floor settling). On completing a drain, the NEXT drain's rim location `thetaDrain` (an angle around the basin ellipse) is chosen as the previous angle plus a random 100-260deg offset, so it always lands at a materially different spot, never the same rim point twice running. The basin is an ellipse (rx = 0.46x the container's SMALLER dimension, ry = 0.6x that) drawn as a flat --ns-muted fill at ~16% alpha plus a ~38% alpha rim hairline, both real geometry so it reads as a depression regardless of theme. The water surface is 64 points sampled evenly around the ellipse, each point's radius = a shared `coverage` factor (0.16 + 0.76*sqrt(level), so the residual pool is visibly smaller than a near-full one and the basin rim is never fully consumed) minus a local indent near thetaDrain (smoothstep falloff over a ~35deg half-width, scaled by `dimple`, max ~24% of the water radius) — the two effects compose into one physical read: the whole pond visibly shrinks (level) while a funnel dimple bites in locally at the drain point (dimple), exactly the departure/arrival sequence the round-9 cadence rule requires rather than an instant level-snap. Those points are stitched into a smoothed closed quadratic-curve path and filled/stroked in the --foreground token; the fill alpha is not a hardcoded per-theme branch but computed once from --foreground's own read relative luminance (0.42 + luminance*0.42), so a bright (dark-theme) foreground lands near its pale, reflective end and a dark (light-theme) foreground still reads as a distinct, subtler value step rather than flat pale-on-pale, backed by a soft specular highlight ellipse and a slightly-higher-alpha edge stroke for definition in the harder light-theme case. While `dimple` is above ~0.02, a small three-segment zigzag crack is drawn in --ns-muted from the water's edge at thetaDrain out to the basin rim, alpha scaled by dimple, so the crack itself only exists for the ~800ms it is mechanically relevant. At mount the whole state machine is seeded already mid-drain (levelAtDrainStart 0.8-0.92, drainElapsed a random 0-60% into the 1200ms event) rather than at a random point in a 9-11s fill — this guarantees the level is visibly different by 2.5s and that at least one full drain-to-refill cycle boundary has been crossed well inside the 5s resting-loop check, deterministically, regardless of the RNG seed. Colours are read once via getComputedStyle(document.documentElement) for --foreground/--ns-muted and re-read on a MutationObserver watching its class attribute (no paint happens before that first read); --ns-accent and --border never touch the water surface or the crack. Canvas backing store is dpr-clamped to 2 and resized via ResizeObserver on the canvas itself (not window resize, since this is a card-scale element that can resize independently of the viewport); a paired IntersectionObserver pauses the rAF loop when the card scrolls off-screen and resumes it on return, alongside a visibilitychange listener for background tabs. `prefers-reduced-motion` freezes on a deliberately fixed mid-drain instant (dimple fully formed at 1, drain 55% elapsed so level is partway through its fall, floor partly exposed) rather than the live t0 seed or a flat full/empty pond — the single most structured frame, showing basin, water level, AND the drain mechanism at once. `autoplay: none` because the cycle runs on its own internal clock with no pointer/scroll/press input for a synthetic-input driver to act on. The root carries `role=\"img\" aria-label=\"Loading\"` since this occupies a loader/empty-state slot with no discrete states to announce; the canvas itself is `aria-hidden`. Zero dependencies, canvas-2D only, no WebGL, no SVG."
      }
    },
    {
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-muted (68% and 36% foreground at the middle two stops, pure --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-muted at ~20% via a decorative absolutely-positioned div, never --ns-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, --ns-muted, --border and --ns-accent (plus their standard opacity variants); zero dependencies, DOM and CSS only, no canvas."
      }
    },
    {
      "name": "mesh-lash",
      "type": "registry:ui",
      "title": "Mesh Lash",
      "description": "A currency amount field where two meshed gears are the FX quote itself: tooth ratio draws the exchange rate, and mechanical backlash (the dead angle a reversed gear turns through before the other flank engages) is the provider's spread, printed as a currency figure straight off the gear math.",
      "files": [
        {
          "path": "registry/core/mesh-lash/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/mesh-lash.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "input",
          "currency",
          "fx",
          "exchange-rate",
          "gears",
          "svg",
          "mechanical",
          "spread",
          "fee",
          "form",
          "accessibility"
        ],
        "instruction": "Build <GearLashField fromCurrency? toCurrency? rate spreadFrac defaultAmount? label? receiveLabel? onAmountChange? className?> as an uncontrolled currency-amount field whose gear illustration is the actual FX computation, not a decoration next to one. MECHANISM: one governing scalar driveAngle = amount * DEG_PER_UNIT (3.6 deg/unit — a direct function of the typed amount, mathematically identical to accumulating a constant deg-per-unit rate over every input delta). Backlash is the textbook control-theory dead-zone nonlinearity applied to driveAngle: a `contact` value that free-wheels with driveAngle while it moves in one direction (contact = driveAngle - halfWidth) but, on a direction reversal, stays frozen until driveAngle has crossed the FULL 2*halfWidth gap before it starts moving again (contact = driveAngle + halfWidth once driveAngle - contact < -halfWidth) — this literally reproduces 'the drive turns through a visible dead angle before the driven picks up' on every reversal. halfWidth is derived, not hand-tuned: lashWidthDeg = spreadFrac * DEG_PER_UNIT * LEGIBILITY_GAIN (a fixed, stated gain constant of 500) so a real 0.3-2% spread, which would otherwise be a fraction of a degree, reads as an actual visible wedge of drive rotation. drivenAngle = contact / ratio, where ratio = teethDrive/teethDriven is the DRAWN gear ratio (not the exact rate): decompose rate into mantissa*scale where scale is a power of ten pulled out until mantissa sits in [0.2, 5] (so a far-apart pair like JPY doesn't demand an unrenderable 1:150 tooth ratio), then search driveTeeth over [8,40] for the driveTeeth/drivenTeeth pair (drivenTeeth = round(driveTeeth*mantissa), also clamped to [8,40]) whose ratio is closest to mantissa. HARD CONSTRAINT: the printed converted amount is read straight off drivenAngle — (drivenAngle / DEG_PER_UNIT) * scale — there is no second, independently-computed 'real' total rendered beside the gears; the display IS the computation, so the whole claim (fee equals dead angle) stays checkable. The disclosed spread cost is |amount*rate (the exact, ungeared rate) - that gear-integrated converted amount| — i.e. the gap between what a frictionless conversion would give and what the drawn, backlashed gear pair actually outputs, formatted in `toCurrency`. QUANTIZATION HONESTY: because tooth counts are integers, the drawn ratio only approximates the true rate; print that residual verbatim as a small mono caption, e.g. 'drawn 23:8, true 2.8712' (append '×{scale}' when a power-of-ten was pulled out), so the imprecision is disclosed rather than hidden. TEETH RENDERING: SVG has no primitive for tiling a fill around a circle, so the tooth SILHOUETTE (the geometrically load-bearing part — right tooth count, trapezoid profile, correct mesh) is a computed closed path (a loop over teeth count building four points per tooth — root-left, tip-left, tip-right, root-right — via polar coordinates, tip width and root width each a fixed fraction of the angular pitch so the tooth reads as a trapezoid, not a rectangle or a triangle); the brief's literal request for teeth-as-an-SVG-pattern-of-trapezoids is honored as the tooth FILL — one small repeating <pattern> tile (a single trapezoid polygon on a background rect, both in tokens) painted inside that computed silhouette via fill=url(#pattern), so the pattern is real and load-bearing (it is what paints every tooth) even though the outer boundary itself comes from trig, a genuine SVG limitation rather than a shortcut. Both gears are drawn in local coordinates centered on (0,0) inside a <g>, positioned and rotated in one written style.transform = `translate(cx,cy) rotate(deg)` — rotation is written imperatively to that ref, never through React state on the hot path, exactly mirroring how meshed real gears counter-rotate (the driven gear's transform gets the NEGATIVE of drivenAngle). A single 1px circle in var(--ns-accent) sits at the geometric mesh point (pitch-circle tangent point between the two gear centers) and its opacity is toggled to 1 on every input, fading back to 0 ~420ms after the last change — the only place --ns-accent appears, and only while input is live. MOTION: typed input (onChange on the native input) transitions the gear rotation over 200ms ease-out; scrub input (a pointer drag on the aria-hidden gear scene, data-mesh-role=\"scrub\") writes rotation with transition:none, 1:1 with the pointer, using SCRUB_UNITS_PER_PX to map horizontal drag distance to a currency-unit delta from the amount at pointerdown; both paths funnel through the exact same applyAmount function so there is one source of truth for the mechanism regardless of input mode. A11Y: the source amount is a real <input inputMode=\"decimal\"> with a visible <label htmlFor> plus an aria-label repeating the currency code, and remains the ONLY control users need to operate the whole thing (Tab reaches it, arrow/number keys just work as normal text input — no custom keyboard handling is needed or added); converted amount and spread cost are plain text inside one role=\"status\" aria-live=\"polite\" region, debounced ~450ms after the last input so rapid typing or dragging doesn't spam a screen reader, plus a matching sr-only sentence ('They receive €1,132.09. Spread cost €4.20.') — the visible gear scene and its wrapper are aria-hidden entirely, and scrubbing it is a supplementary pointer affordance only, never the only way to change the amount. REDUCED MOTION: prefers-reduced-motion drops every rotation transition to 0ms (gears jump straight to their final resting angle on every change, mount included) while the honesty line and spread-cost figure still print exactly as computed — no motion is required to read the mechanism, only to watch it. TOKENS: only var(--background), var(--foreground), var(--ns-muted), var(--border), var(--ns-accent) appear anywhere, in markup and in the SVG's pattern/paths alike; no hex, no gradients, no orange. DEMO: a compact card ('Send to a MXN account') wrapping one GearLashField at a realistic USD->MXN remittance rate (17.05) and spread (1.2%) — a pair whose tooth counts land visibly apart (20 drive teeth vs 34 driven), so the ratio itself reads at a glance instead of two near-identical circles — fully interactive with zero synthetic input — the on-site landing card demonstrates itself separately via the autoplay descriptor, which is a `drag` cycle over the gear scrub surface so the round-trip motion (forward, then back) shows off the dead-angle catch-up on the return leg, the component's whole reason for existing."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 (--ns-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 --ns-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/--ns-muted/--border tokens."
      }
    },
    {
      "name": "meter-matrix-scan",
      "type": "registry:ui",
      "title": "Meter Matrix Scan",
      "description": "A level meter rendered as a genuinely row-multiplexed LED dot-matrix panel: five rows addressed round-robin off a real 240Hz-per-row clock, and each LED's brightness is a literal 8-step PWM duty-cycle quantization rather than a smooth analog fill. The row-address concept shows as a calm, low-amplitude brightness gradient a couple of rows wide, drifting slowly down the panel and back: legible on a second look, never a strobe.",
      "files": [
        {
          "path": "registry/core/meter-matrix-scan/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/meter-matrix-scan.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "meter",
          "level",
          "gauge",
          "led",
          "dot-matrix",
          "pwm",
          "multiplex",
          "canvas",
          "monochrome"
        ],
        "instruction": "Build <MeterMatrixScan value? max? label? height? className?> as a full-width <canvas> panel wrapped in a <div role=\"meter\" aria-label={label} aria-valuemin={0} aria-valuemax={max}>, a drop-in level/value meter. SOURCE, NOT INVENTED: cheap commercial LED dot-matrix signage (scrolling ticker boards, gym scoreboards, elevator floor indicators, budget character displays built on row/column multiplex driver chips) can't afford one continuous driver per LED — the panel scans one row at a time at a frequency well above human flicker fusion, relying on persistence of vision to read a complete static image, and within each row's brief active slice an individual LED's apparent brightness is set by PWM (pulse-width modulation) duty cycle: fully on or fully off at any instant, perceived brightness being the fraction of the row's active window it spends on, quantized to a small number of duty steps (a driver's PWM bit depth). That literal quantized time-division duty cycle, never a continuous alpha/density value, is this component's entire identity and must remain visibly stepped, not anti-aliased into a smooth gradient. REAL NUMBERS: ROWS=5 (fixed); ROW_SCAN_HZ=240, each row the active scan target for 1000/240 ~= 4.17ms, a full 5-row panel completing one full-frame refresh every ~20.8ms (~48Hz), comfortably above flicker fusion, matching real multiplex-board practice; PWM_LEVELS=8, a 3-bit duty-cycle depth typical of budget driver chips. GEOMETRY: cellSize = containerHeight / 5 (square cells, so exactly 5 rows fill the panel's own height); columns = floor(containerWidth / cellSize); each LED is drawn as a filled circle (canvas arc), radius = (cellSize - gutterPx) / 2, gutterPx in the 2-4px range, not a Unicode glyph. VALUE / FILL: the meter reads a single scalar 0..max mapped left-to-right across the column count — filled = (value/max) * cols; every column below the integer floor of filled is drawn at full PWM_LEVELS brightness; the single boundary column at that floor is drawn at a duty level quantized from its fractional remainder (Math.round(frac * PWM_LEVELS), 0..8 discrete steps) — this is the real technique budget LED bargraphs use to fake sub-LED resolution by PWM-dimming exactly one boundary LED rather than genuinely lighting a fractional pixel; columns past the boundary are undrawn (--background shows through, meaning fully off). SIMULATED SENSOR, WHEN value IS OMITTED: when the value prop isn't supplied, a self-contained generative field drives the reading continuously — three non-commensurate traveling sine components on the component's own never-resetting clock, amplitude-bounded to stay inside roughly 23-87 on a 0-100 domain — so the meter is a genuine 'alive at rest' ambient demo with zero external wiring; when value IS supplied, that value renders directly and is the source of truth, letting the same component serve as a real production meter. ROW-SCAN, RENDERED CALM ON PURPOSE: a real board's per-row scan is invisible at rest (that's the entire point of persistence of vision) and only becomes visible when something samples the panel at a rate that doesn't divide evenly into its own refresh rate — the well-documented reason a camera filming multiplexed LED signage sometimes shows a faint drifting band. A literal 1:1 real-time render of the raw ROW_SCAN_HZ clock against a ~60Hz browser paint rate aliases close enough to the paint rate itself to read as a hard strobe/flicker rather than that calm hardware phenomenon — a rendering-pipeline artifact, not the mechanic. So the row-address concept is deliberately mapped onto a slow, continuous sweep position instead of a discrete per-frame row index: sweepPosition(t) is a triangle wave over SWEEP_PERIOD_S=7.5s (one full down-and-back pass across the 5 rows, smooth, no jump-cut at either end). Each row's brightness contribution is a soft Gaussian weight against the sweep's current center (SWEEP_SIGMA_ROWS=1.4, so 2-3 rows share a gentle gradient, never a 1-row strip) scaled by a low-amplitude SCAN_HIGHLIGHT_ALPHA=0.055 — luminance-only (never accent), added onto an already-lit LED's alpha for lit columns, and painted as a fainter version of the same gradient across only the off/background columns of that row (never stacking on top of an already-boosted dot) so the sweep reads independent of how much of the meter is filled. LIGHT THEME: the lowest PWM band (1 of 8, alpha 1/8 = 0.125) is floored to a MIN_ON_ALPHA (0.16) so it never rounds to invisible against a near-white --background. TOKENS: bg/fg are read once from getComputedStyle(document.documentElement) against --background/--foreground before the first paint, re-derived on a documentElement class MutationObserver so a theme flip is live; --ns-accent never appears anywhere in the LED brightness/duty-cycle mapping (luminance only, per spec) — there is no pointer-scrub affordance in this build, so accent is never reached for at all. A ResizeObserver on the canvas reflows on container size changes; an IntersectionObserver pauses the rAF loop off-screen and resumes it (fresh last-timestamp, no giant delta-time jump) on re-entry. RESTING LOOP: t0/2.5s/5s differ on two independent axes compounding — the sweep's slow triangle-wave position (always cycling, real clock) and, in uncontrolled mode, the slowly drifting simulated value — so the lit pattern, the PWM boundary step, and the sweep's gradient position are all visibly different at every timestamp, unbounded, zero input, and calm rather than flickering. prefers-reduced-motion freezes with the sweep locked off entirely (no gradient overlay drawn at all, every row rendered identically at its true duty level) at a value taken past the field's cold-start instant (uncontrolled mode) or the supplied static value (controlled mode) — the fully legible static readout with the PWM quantization bands still visible, no sweep artifact. The render loop pauses on document.hidden and resumes cleanly on visibilitychange. Direct-DOM rAF, zero React state on the hot path (value is read through a ref so a controlled prop updates without re-subscribing the effect), zero dependencies. A11Y: role=meter with aria-valuemin/aria-valuemax fixed from props and aria-valuenow written imperatively to the wrapper on every draw (cheap DOM attribute write, no aria-live spam) so assistive tech querying the node gets the true current reading whether it's externally controlled or internally simulated; the canvas itself is aria-hidden decoration. There is no keyboard surface because there is nothing to operate — non-interactive by design, correctly exempt from the registry's tab-reachability check as a display-only meter. Props: value (0..max, omit for the internal simulated sensor read), max (domain ceiling, default 100), label (accessible name, default 'Level'), height (panel height px, default 60; ROWS is fixed at 5, cell size derives as height / 5), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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-ns-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-ns-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) the bead's keyframe animation is stripped entirely via a media-query CSS block (the stain still appears, just without the drop), but the `d` transition is shortened to a plain 140ms linear ease rather than removed — curvature IS the reading, so a value/zone change still visibly settles into its new shape instead of teleporting there, it just loses the back-out spring overshoot. Every stroke and fill is a `stroke-current`/`fill-current` Tailwind class tinted `text-border`, `text-ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "warning": "#7a5200"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "warning": "#f5a623"
        }
      },
      "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 --ns-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, --ns-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.",
        "rank": 279
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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-ns-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-ns-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 shortens to a plain 140ms linear ease instead of collapsing to instant — snap, re-arm, and ordinary climbs all still visibly settle into their resting shape rather than teleporting, since the strip's curvature is the only passive signal the threshold was crossed — 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-ns-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": "millstone-furrow-flow",
      "type": "registry:ui",
      "title": "Millstone Furrow Flow",
      "description": "An ambient processing-section background modelled on a dressed millstone pair: a static bedstone furrow layer under a slowly rotating runner layer, their crossing sweeping a drifting moiré band around the stone, while grain particles spiral outward from the eye, shrinking as they grind finer, and disperse into fading meal dust at the rim.",
      "files": [
        {
          "path": "registry/core/millstone-furrow-flow/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/millstone-furrow-flow.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "divider",
          "canvas",
          "moire",
          "particles",
          "radial",
          "ambient",
          "processing",
          "industrial"
        ],
        "instruction": "Build <MillstoneFurrowFlow className?> as a `w-full h-full` (or JS-set style dimensions) 2D canvas, DPR capped at 1.5. Geometry is derived from the container's SMALLER dimension: stone radius = min(w,h) * 0.48, eye (grain-feed origin) radius = stone radius * 0.04, both centred in the container. TWO CONCENTRIC FURROW LAYERS, each built once per resize as a single Path2D (never rebuilt per frame): the bedstone layer is 13 sickle-curved lines, the runner layer is 12 — deliberately DIFFERENT counts, because two identical-count radial patterns rotated against each other produce 12 drifting X-crossings, not a moiré; the |13-12|=1 frequency mismatch is what beats into a single band that sweeps once around the stone per runner revolution. Each furrow is stepped (20 samples) as a quadratic bezier in polar space — angle eases through a midpoint offset by half of a fixed 12-degree sickle sweep before reaching the endpoint, radius eases linearly from eye to rim, giving the line its curve rather than a straight radial spoke — and the bedstone's sweep sign is the mirror of the runner's (a real bedstone is dressed as the runner's counter-pattern so the two sets scissor rather than merely overlay). Stroke colour for both layers is a single CanvasGradient (createRadialGradient from eyeRadius to stoneRadius, rgba stops built from the --ns-muted token, alpha ~0.62 at the eye down to ~0.24 at the rim), rebuilt whenever geometry or theme changes, read via getComputedStyle(document.documentElement) plus a MutationObserver on its class, never a literal. Per frame: stroke the static bedstone Path2D once at globalAlpha 0.72, then stroke the runner Path2D once inside ctx.save()/translate(cx,cy)/rotate(runnerAngle)/translate(-cx,-cy)/restore() — rotation is a canvas transform around the shared centre, never a per-point recompute, and a single stroke() call per layer avoids the seam artefacts a segmented per-alpha-step stroke would leave. Runner rate: the real operating rate is ~120 RPM (~2 rev/s), documented but never animated 1:1 — rendered at a deliberately decoupled 0.08 rev/s so the beat band reads as a slow drifting sweep (~12.5s per lap), not a strobe. This crossing is the component's own resting motion and must stay visibly different at t0/2.5s/5s with zero particles on screen — verify by temporarily disabling the grain/dust draw calls. GRAIN PARTICLES: one spawns at the eye every 900ms, at a seeded-random angle (mulberry32, not Math.random, for a reproducible feed pattern), 5px diameter. Its radius-from-centre advances from the eye radius to the stone radius over a duration computed as BASE_TRAVEL_MS (4500ms, the documented ~4.5s eye-to-rim transit at a 240px reference stone radius) scaled by (stoneRadius / 240) and clamped to [1500, 9000]ms, so any container size still reads a full multi-second transit rather than a fixed-px/s crawl — eased by progress^1.15 (accelerating slightly, mimicking centrifugal build-up). Diameter shrinks linearly from 5px at the eye to 1px at the rim (coarse feed to fine meal). The particle's angle is its spawn angle plus 0.6x the runner's angular delta since spawn, so it traces a shallow spiral rather than a straight radius, visually coupling it to the rotating furrow layer beneath. On reaching the rim it does not vanish: it disperses into 3-6 jittered 0.5px dust motes that each fade from full to zero opacity over 800ms before being culled. Steady state is roughly 5 grains mid-travel (900ms spawn interval against a ~4.5s reference transit) plus 0-2 dust dispersals at any moment (drop the 900ms feed rate first if this reads as clutter at card scale before ever killing the component). Grain particles and dust are drawn in the --foreground token at full opacity (dust scaled by its own fade), never --ns-accent — furrow depth and grain colour are both fixed luminance, with zero interactive tint anywhere. NO PAINT BEFORE THE FIRST TOKEN READ: colours and the gradient are derived once before the first frame and re-derived on every documentElement class mutation. The animation clock is the raw rAF timestamp used directly as an absolute time throughout (never an elapsed-since-mount counter reset on resume), so pausing via IntersectionObserver on scroll-out and resuming on scroll-in never rewinds or offsets anything — a long hidden gap is handled by fast-forwarding the spawn schedule (skip stale catch-up spawns) rather than bursting or producing negative-progress grains. ResizeObserver recomputes stone/eye radius, travel duration, and both furrow Path2D objects without touching the grain feed's own clock. prefers-reduced-motion renders exactly one static frame, chosen deliberately non-t0: the runner layer offset 18 degrees from the bedstone (a visible cross-hatch, not alignment), one grain frozen at 60% radius (mid-grind, mid-size) and one dust mote frozen mid-fade at the rim — feed, grind, and dispersal all visible in the single still. Zero dependencies, canvas + CSS only, no interaction. Props: className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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-ns-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-ns-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-ns-accent on hover, and an accent focus-visible ring (ring-2 ring-ns-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": "mudflow-levee-build",
      "type": "registry:ui",
      "title": "Mudflow Levee Build",
      "description": "Ambient activity track whose channel is self-built by the flow's own coarse fraction shearing out to the margins: surges deposit growth at the edges as they pass, the two levees narrow the channel over many surges, and once one bank critically narrows the flow breaches it and avulses sideways, leaving the old levee pair behind as a static scar.",
      "files": [
        {
          "path": "registry/core/mudflow-levee-build/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/mudflow-levee-build.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "activity",
          "canvas",
          "levee",
          "debris-flow",
          "geomorphology",
          "generative",
          "ambient",
          "avulsion"
        ],
        "instruction": "A card-scale canvas visualisation of lateral levee formation in a debris flow. Two lanes stack vertically inside the container (top half, bottom half), each holding its own pair of Float32Array levee-height buckets (one bucket per horizontal position; bucket width derived from `Math.max(3, minDim/40)` against the container's smaller dimension so the ridge resolution scales with card size). Only one lane is 'active' at a time (carries live surges); the other sits inert until a breach hands it the channel. A surge spawns on the active lane roughly every 1.6-3.4s (mean 2.4s, sampled by averaging two uniform draws for a center bias), then sweeps left to right over a fixed 1800ms. Every ~90ms while a surge is in flight, it deposits randomized growth (0.4-0.9px, scaled by a per-surge 'coarse fraction' of 0.6-1.4) onto BOTH the top and bottom margin buckets at its current horizontal bucket — so levee height at any point along the channel is a literal record of how many surges have passed there and how coarse each one was, never a uniform ramp, and the confined channel width (lane height minus both margins) visibly narrows as levees build. The instant a margin bucket's height crosses 78% of the lane's half-height, a breach triggers: that bucket's height (plus a softened pull on its immediate neighbours) eases toward 12% of its pre-breach value over 300ms (a visible gap opening), the breached lane freezes permanently (marked inactive, never receives deposits again, keeps rendering its frozen ridge and gap indefinitely as a scar), the OTHER lane's arrays are zeroed and marked active, and a fresh surge spawns there immediately — the active flow visibly jumps to the other half of the track. Levee ridges are drawn as clusters of small (2-3px) foreground-opacity square markers rather than a filled bar: marker count per bucket is `Math.ceil(height / dotPitch)` and each marker's jittered offset is a deterministic hash of (lane, margin, bucket, index) via a sine-hash, so growing height only ever appends new markers at the outer end and every existing marker holds an exact, stable pixel position frame to frame (bounded memory — nothing beyond the height arrays is ever stored). The active lane's flow band is drawn each frame as a horizontally tapered translucent (up to 0.5 alpha) foreground fill following the current surge's position (falloff over 6 buckets either side), confined vertically between that bucket's live margin heights, with a slow per-bucket alpha flicker (hash of bucket + 90ms time-bucket) reading as turbulence; `--border` draws only the track's fixed 1px outer frame at inset 0.5, never the levee itself. Colours are read once via getComputedStyle(document.documentElement) before the first paint and re-derived on a MutationObserver watching the class attribute (foreground and border only — no accent, no background literal). At mount, the component warm-starts: lane 0 is pre-seeded with an asymmetric ridge (as if several surges already passed) and one surge already 45% across the track, so t0 never shows a flat channel. A ResizeObserver debounced 120ms fully re-measures and restarts the whole state (bucket count, lane geometry, a fresh warm start) on any size change. An IntersectionObserver pauses the rAF loop entirely while off-screen and resumes it on return. Under prefers-reduced-motion, no rAF ever starts: a single static frame is built directly depicting the moment immediately after a breach — lane 0 frozen with a well-built, visibly gapped ridge (the scar), lane 1 active with a fresh, lightly-built pair and a single non-scrolling translucent fill standing in for its live flow — the single most structured frame available. The root is `role=\"img\"` with an accessible label (default \"Channel activity\"); the canvas itself is `aria-hidden` and sized via canvas.width/height off devicePixelRatio, not CSS alone. `autoplay: none` because the surge cadence runs on the component's own internal clock and there is no pointer/press/scroll input for a synthetic-input driver to perform. Zero dependencies, canvas + CSS only, every colour a token (--foreground --border)."
      }
    },
    {
      "name": "mull-hinge",
      "type": "registry:ui",
      "title": "Mull Hinge",
      "description": "A margin comment thread glued to its anchored phrase by a translucent tissue-strip hinge whose shear, opacity and tearing are a live readout of anchor health under concurrent edits.",
      "files": [
        {
          "path": "registry/core/mull-hinge/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/mull-hinge.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "comments",
          "annotation",
          "margin",
          "collaboration",
          "thread",
          "svg",
          "spring",
          "aria-live",
          "accessibility"
        ],
        "instruction": "A margin comment thread whose connector to its anchored phrase is a stateful strain gauge, not decoration. The component is controlled: the consumer's collab/document layer supplies `driftPx` (currentAnchorTop minus anchorTopAtCreation, in px — positive means the anchored text moved down as other people's edits landed above it) and `orphaned` (true once the anchored text itself was deleted upstream); MullHinge owns only the physics and the render, never the measurement. STRUCTURE: a flex row — a small quoted-phrase marker on the left (the anchor), a short SVG tissue strip in the middle (the hinge), and the thread card on the right, laid out as a real <section aria-labelledby aria-describedby> containing an <h3> heading, an <ul> of existing comments each with its own 'Reply to {author}' button, a real <textarea> composer, and a 'Post reply' submit button. PHYSICS: driftPx changes are never applied directly — they drive a damped spring (stiffness 210 s^-2, zeta 0.9) whose displayed value is what everything else reads, so a large upstream paste reads as the hinge being yanked into its new position rather than teleporting there. From that displayed drift, shear angle = atan(drift / hingeLength) is computed and clamped to +/-38deg: past that angle the strip would visually slip off the card's glued edge and read as broken when the anchor is in fact still perfectly attached, so the cap exists specifically to prevent that false impression. Both the quoted-text marker's own vertical offset and the hinge polygon's anchor-side edge are driven off this SAME capped value — never the raw uncapped drift — which is what keeps the hard constraint true: the strip is glued at both endpoints at all times, it only refuses to look torn before it actually is. Tissue fill-opacity falls from 0.5 at rest to 0.25 as the capped shear ratio approaches its cap (stretched tissue reads thinner). Once raw |driftPx| exceeds 120px a 'Mend' button fades into the card header — clicking it calls onMend so the consumer can accept the current position as the new baseline. ORPHAN: the hinge is always rendered as two SVG pieces sharing one precomputed jagged clip-path polyline (fixed at build time, never recomputed per-frame) that sit flush and invisible as a seam at rest; when `orphaned` flips true they separate along that exact boundary via a 300ms ease-out CSS transform transition, then the card-side piece plays one damped ~4deg sway (a second spring, stiffness 70 s^-2, zeta 0.32) pivoting around the point still glued to the card, while the text-side remainder becomes an inert, drooped, frayed stub. A visible 'Orphaned' badge appears in the card header at the same moment, and the card keeps the last-known quoted snapshot inline in its comment list permanently — nothing about an orphaned thread is ever hidden or deleted. A11Y: the SVG hinge is aria-hidden and every fact it encodes is duplicated as plain text via aria-describedby: 'anchored to \"<quote>\", drifted N paragraphs down/up' while attached, or 'original text deleted' once orphaned. A 'Jump to anchor' button moves focus to the quoted-phrase marker and scrolls it into view (also firing an optional onJumpToAnchor callback for apps that own a real document to scroll). Reduced motion swaps the drift spring and the sway spring for instant snaps (set directly to target, no interpolation) and drops all CSS transform transitions to none, via a scoped @media(prefers-reduced-motion: reduce) block — the tear still happens, instantly, with no sway. Differs on purpose from a static margin-citation component: that kind of furniture never claims to track anything, so it can never be wrong. Here the connector is stateful and its entire point is to make broken attachment legible before you read a single reply — the alternative, which every shipped margin-comment competitor ships instead, is a leader line that always looks pristine and silently orphans. Pure DOM + inline SVG + CSS, no canvas, no dependencies. Colors are --background, --foreground, --ns-muted, --border and --ns-accent only; --ns-accent appears solely on focus rings and the Mend affordance, never as permanent decoration."
      }
    },
    {
      "name": "nav-blue-noise-scrim",
      "type": "registry:ui",
      "title": "Nav Blue Noise Scrim",
      "description": "Dimming backdrop for a command palette or search overlay, dithered with a temporal blue-noise mask that reshuffles every frame instead of a flat tint.",
      "files": [
        {
          "path": "registry/core/nav-blue-noise-scrim/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/nav-blue-noise-scrim.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "nav",
          "scrim",
          "backdrop",
          "overlay",
          "dither",
          "canvas",
          "blue-noise"
        ],
        "instruction": "Build a full-bleed canvas scrim for the area behind a command palette or search overlay, dithered with a real void-and-cluster blue-noise point distribution (Ulichney 1993) instead of a flat --background tint. Void-and-cluster iteratively balances a binary pattern against its own Gaussian-blurred energy so 'on' cells never cluster and no region empties out, producing a per-cell RANK (0..N-1) over a 32x32 tile: thresholding rank against any coverage level yields a blue-noise-distributed dot set. This is a different point-generation algorithm from the repo's ordered-dither family (background-ascii-dither's Bayer mode, the chart-*-dither components), which threshold against a small regular 4x4 matrix and read as a visible crosshatch lattice at any zoom — void-and-cluster's aperiodic, non-lattice point set reads as fine isotropic grain instead, the distinction the dithering literature draws between ordered and blue-noise dither families. TEMPORAL layer: every rAF tick advances a frame offset by the golden-ratio conjugate mod 1 and thresholds frac(rank/N + offset) < coverage, so every cell cycles through the full blue-noise rank order over N frames, decorrelated frame to frame — the animated-noise / golden-ratio-sequence technique real-time renderers use for TAA dithering, an honest approximation of full spatiotemporal blue noise (which needs a pre-baked 3D noise volume) built from one 2D mask rather than a from-scratch spatiotemporal solve. A scrim has a job: content sits on top of it and must stay readable. The canvas paints a controlled flat --background wash (alpha 0.55) as the base dim, with the blue-noise grain layered on top at a fine cellSize (default 4px) and low alpha (0.5) in --foreground — texture on top of legibility, not the source of it; the overlay panel itself stays fully opaque so grain never touches its text regardless of coverage. Coverage defaults to 0.07 (7% of cells lit at any instant) so the mean shimmer stays subtle. Token colors (--background, --foreground) are read via getComputedStyle(document.documentElement) inside useLayoutEffect, before first paint, and re-read on a MutationObserver watching documentElement's class — no color field is ever used before that read. prefers-reduced-motion freezes to a single static blue-noise mask instance (frame offset 0) rather than reshuffling every frame: still aperiodic and still distinct from Bayer, just not animated, since a per-frame reshuffling grain is exactly the motion this media query exists to suppress. ResizeObserver (debounced 100ms) drives cols/rows so the grain cell size stays fine and proportional across viewport sizes rather than becoming a coarse checkerboard in a small container; the render loop pauses on document visibilitychange. The canvas itself is aria-hidden and pointer-events:none — purely decorative scrim texture; the demo's surrounding dialog owns real overlay semantics (role=dialog, aria-modal, Escape closes, Tab trapped on its single input). Props: cellSize (default 4), coverage (default 0.07), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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-ns-accent) and a hover color change from --ns-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, --ns-muted, --border, --ns-accent, --ns-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-overstrike-typewriter",
      "type": "registry:ui",
      "title": "Nav Overstrike Typewriter",
      "description": "A real site nav with a typed ruled line beneath its links, shaded by striking two or three characters into the same cell (real typewriter overstrike compositing, not this registry's usual single-glyph density ramp), with the current page reading as the deepest stack.",
      "files": [
        {
          "path": "registry/core/nav-overstrike-typewriter/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/nav-overstrike-typewriter.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "nav",
          "typewriter",
          "overstrike",
          "mono",
          "canvas",
          "monochrome",
          "accessibility"
        ],
        "instruction": "<NavOverstrikeTypewriter wordmark? links? className?> renders a real <nav aria-label=\"Primary\"> — a wordmark anchor and a <ul> of real <a> links, each with a normal href, visible focus ring, hover state, and aria-current=\"page\" on whichever link's `current` prop is true. Nothing about navigation, keyboard order, or accessible naming depends on the canvas: a screen-reader or keyboard user gets a plain, working link list. Beneath that link row sits a thin aria-hidden canvas strip, decorative only, drawn as genuine typewriter OVERSTRIKE shading — the technique typewriter artists use to strike several characters into one cell without advancing the carriage so their ink compounds into a mark denser than any single glyph in the machine's character set (an 'M' over an '8' over a ':' to fake a mid-tone). This is explicitly NOT this registry's ~68-component glyph-density-ramp mechanic, where one glyph is selected off an ordered list by luminance: every cell here gets 1-3 *separate* canvas fillText passes, each with independent jitter and alpha, composited on top of each other so coverage genuinely accumulates where strokes overlap — collapsing that into a single denser-glyph lookup would silently become the ramp mechanic and erase the whole reason this component exists. The strip is column-mapped onto the link row above it (measured via getBoundingClientRect, so it tracks any wordmark/link/viewport change): the column band under the item carrying aria-current=\"page\" always strikes at the deepest stack (3 passes), which is literally how the current-page indicator reads — 'the active item is whichever cells carry the deepest overstrike stack' is not a metaphor. A slow ping-pong sweep (~7.2s per return cycle) — a carriage returning and passing back over the line — continuously crosses the strip, giving whichever cells it's over one extra, freshly re-picked overstrike pass that fades by distance from the carriage, so the strip visibly re-types itself at rest with no input; this is unconditional self-animation (not an autoplay-only effect), confirmed distinct at t=0s/2.5s/5s. prefers-reduced-motion freezes the sweep at phase 0.63 rather than t=0 — a point where the carriage sits clear of the active column's own permanent 3-stack, so a single static frame shows both the always-deep active band and a freshly re-struck ambient cell at once, rather than a blank strip or the two effects visually merging. Colors are --foreground and --ns-muted only, read via getComputedStyle(document.documentElement) inside useLayoutEffect (before first paint) and re-read on a MutationObserver watching documentElement's class; --ns-accent appears only as real interaction chrome on the links themselves (hover/focus-visible), never in the ambient overstrike field. Cell size is derived proportionally from the strip's own measured height via an offscreen canvas measureText call made after document.fonts.ready, not a fixed pixel constant, so the mark stays compact and legible rather than turning into a coarse zoomed-in grid on a narrow nav. ResizeObserver (debounced 100ms) recomputes the grid and the active column band on layout change; the render loop pauses on document visibilitychange. Zero dependencies, no external font."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-muted to text-foreground) and focus-visible:outline-2 outline-offset-4 outline-ns-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": "neon-tube-striation",
      "type": "registry:ui",
      "title": "Neon Tube Striation",
      "description": "A horizontal section divider rendered as the positive column of a lit glow-discharge tube: a traveling ionization-wave striation pattern drifts along its length while faint sputtering darkening deepens at both electrode ends over time.",
      "files": [
        {
          "path": "registry/core/neon-tube-striation/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/neon-tube-striation.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "hr",
          "neon",
          "plasma",
          "glow-discharge",
          "canvas",
          "monochrome",
          "ambient"
        ],
        "instruction": "Build <NeonTubeStriation height? className?> as a full-width divider: a <div role=\"separator\" aria-orientation=\"horizontal\"> wrapping a single <canvas w-full h-full/style-height>, a drop-in replacement for <hr>/border-top between page sections. The tube is a horizontal rounded capsule (a pill path, radius = half its diameter) spanning the canvas's full width, vertically centred; diameter (strokeW) is derived from the container's smaller dimension — the divider's own height prop (default 96) — at STROKE_FRAC = 4.5% of that height, clamped to [3, 10]px, so a taller/shorter divider still reads as a thin lit line inside its box rather than a fat bar. Every rendered frame draws two passes over the same pill path: pass 1 is a blurred halo underlay (ctx.filter = blur(strokeW*1.6px), globalAlpha 0.4, filled with the base-luminance mix colour) for the glow bloom; pass 2 clips to the crisp pill and fills it in 2px-wide vertical column strips, each column's colour independently computed. TOKENS: colour is never a literal — --background and --foreground are read once via getComputedStyle(document.documentElement) and resolved to actual RGB by painting each token string onto an offscreen 1x1 canvas and reading back getImageData (robust to hex/rgb()/named-colour token values), refreshed on a MutationObserver watching documentElement's class attribute for theme flips; every rendered colour is a straight RGB lerp between that background and foreground pair (mixRGB(bg, fg, t)), never accent — --ns-accent never appears anywhere in this component, and --border is never used as a fill or stroke. LUMINANCE MODEL: each column's mix fraction t (0 = --background, 1 = --foreground) is BASE_MIX(0.82) × striation × (1+noise) × (1-sputter), clamped to [MIN_MIX 0.42, MAX_MIX 0.99] — the 0.42 floor is what keeps the darkest striation trough meaningfully above --background in light theme, the specific failure mode (a neon tube fading invisible at its dimmest phase) this component is built to avoid. STRIATION: a travelling sinusoid, wavelength = 7% of the tube's rendered width (10-14 visible bands across a typical divider), amplitude tuned so unclamped bright:dark ratio is 1.6:1 around the base. Its phase is computed directly from elapsed real time every frame — phase = 2π × elapsedMs / 4000 (DRIFT_PERIOD_MS), one full wavelength of phase advance every 4 seconds — never accumulated as a fixed per-rendered-frame step; that is the deliberate decoupling of the rendered drift rate from a raw per-frame increment, which is what keeps the visible drift speed identical regardless of display refresh rate instead of aliasing faster on a high-Hz monitor. GAS NOISE: a whole-tube ±3% luminance multiplier resampled on its own aperiodic timer at a random 4-7Hz interval (not tied 1:1 to the 60Hz paint loop) and eased 15%/frame toward that resampled target, so the flicker is smooth rather than a stepped blink. SPUTTERING: two fixed end zones, each the first/last 6% of the tube's width, darken via 1-exp(-t/30s) toward a capped -35% luminance multiplier as elapsed seconds grow, strongest exactly at the edge and tapering linearly to zero at the 6% boundary — capped, not unbounded, so the tube stays lit and alive rather than slowly going dark forever. RESTING LOOP: t0 has striation phase 0 and near-zero sputtering (freshly struck); by 2.5s the band phase has advanced ~225° (visibly different band positions) with slightly deeper end darkening; by 5s the phase has advanced roughly another 112° past that and end-darkening is measurably deeper than at t0 — an unbounded, unforced loop that never completes or resets. REDUCED MOTION: a single frame is painted once at mount with striation phase locked to 130° (an asymmetric, clearly banded arrangement — the phase-0 frame reads near-symmetric and less legible) and the sputter ramp evaluated at a fixed t=20s (partway into its 30s time constant, so both mechanics are visible in the one frozen frame) with zero gas noise; no rAF loop runs at all in this mode. LIFECYCLE: sizing happens via a ResizeObserver-driven, 150ms-debounced resize() reading canvas.getBoundingClientRect (capped devicePixelRatio<=2), first paint is gated behind document.fonts.ready so nothing paints before layout and tokens are both settled, an IntersectionObserver pauses/resumes the rAF loop off-screen, and a visibilitychange listener cancels and restarts the loop on tab hide/show so a queued frame from before hiding can never double up with a freshly scheduled one. Cleanup on unmount cancels the rAF, clears both the resize-debounce and noise-resample timers, and disconnects the MutationObserver, ResizeObserver and IntersectionObserver. A11Y: role=separator carries the divider's semantics with no accessible name required; the canvas is aria-hidden decoration; there is no keyboard surface because there is nothing to operate. `autoplay: none` because the tube animates purely on its own internal clock with zero pointer/press/scroll input to drive. Props: height (divider height in px, default 96 — the geometry-deriving smaller dimension), className. Zero dependencies, canvas + CSS only, no WebGL."
      }
    },
    {
      "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": [],
      "cssVars": {
        "light": {
          "error": "#ea001d"
        },
        "dark": {
          "error": "#ff6369"
        }
      },
      "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": "newsletter-cadence-rail",
      "type": "registry:ui",
      "title": "Newsletter Cadence Rail",
      "description": "A newsletter capture whose headline element is a mono day-rail showing the publication's actual rhythm (six weeks of days, an issue mark every interval, a caret on today), so before typing anything you know exactly how often it lands and when the next one is.",
      "files": [
        {
          "path": "registry/core/newsletter-cadence-rail/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/newsletter-cadence-rail.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "error": "#ea001d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "error": "#ff6369"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "newsletter",
          "capture",
          "form",
          "cadence",
          "ascii",
          "mono",
          "marketing"
        ],
        "instruction": "Build <NewsletterCadenceRail anchorISO intervalDays? issues? onSubmit? className?> as a newsletter capture whose headline element is the publication's cadence drawn as a mono day-rail. anchorISO is any known past issue date and intervalDays (default 14) is the cadence; every date in the component derives from those two numbers alone. THE RAIL: an aria-hidden <div> of 42 single-character cells at a 1ch mono pitch, cell 0 = today−28d and cell 41 = today+13d. Per cell, d is that cell's date at local midnight and the glyph is '│' when floor((d − anchor)/86400000) mod intervalDays === 0 (an issue day), '▮' when d is today, else '·'. Ink comes from tokens only: past ordinary cells --ns-muted at opacity 0.35, future ordinary cells --ns-muted at 0.6, issue days --foreground at 0.9, today's caret --ns-accent at 1. (Do not draw the ordinary dots in --border: at those alphas it is effectively invisible in both themes, and a countable dot field is the whole point of the rail.) Directly beneath sits a second aligned mono row of week labels at cells 0, 7, 14, 21, 28, 35, each label a 'Jul 07' string padEnd'd to exactly 7 characters so it self-aligns to its cell at the same 1ch pitch, tabular-nums, --ns-muted. RESTING PULSE: today's caret opacity oscillates 0.55 → 1 → 0.55 on a pure CSS @keyframes; the animation-duration is computed once at mount as (3.2s * (0.92 + Math.random()*0.16)) and set inline, so two instances on one page never breathe in lockstep and no rAF loop exists anywhere in the component. THE READOUT: a mono line computed from the same arithmetic — 'issue #48 shipped 4 days ago · next issue Tue 12 Aug · every 14 days' — where the issue number is extrapolated from the highest-numbered entry in `issues` plus the number of intervals elapsed since its date, plus an optional three-row archive list (number, title, date) rendered as real <a> links with their own focus ring. Because the rail is aria-hidden decoration, this sentence is what a screen reader gets, and it always carries the same facts. THE FORM: a real <form noValidate> with <input type=email required autoComplete=email>, a submit button and an aria-live=polite status line. Validation fires ONLY on submit, never per keystroke: empty → 'Enter an email address', failing /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/ → 'That address looks incomplete', each written to the status line with aria-invalid set on the input. ON SUCCESS the form is replaced by a confirmation and a '▸' marker is planted at today's cell and TRAVELS along the rail toward the next issue cell at exactly one cell per 90ms (a setInterval stepping an index, cleared on arrival and on unmount); the destination cell flips from '│' to '█' in --ns-accent when the marker arrives — but ONLY when the next issue actually falls inside the rendered span (in the 28-cell layout it usually does not, and the marker then simply rests on the last cell with no destination glyph, because an accent block must never land on an ordinary day), and the status settles to \"You're on the list — first issue lands Tue 12 Aug\". RESPONSIVE: a ResizeObserver measures the rail container against a hidden 10-character mono probe to get a real ch width — at least 42ch renders 42 cells, at least 20ch re-renders at 28 cells (today−21d … today+6d, week labels at 0/7/14/21), below that the rail is dropped entirely and only the mono readout line survives. REDUCED MOTION: prefers-reduced-motion (checked via matchMedia and re-checked on change, and belt-and-braces in the stylesheet) drops the caret pulse keyframe entirely and places the ▸ marker at its destination in a single step instead of travelling. Nothing responds to the pointer — this is a calm ambient rail, not a hover toy. Tokens only (--ns-muted, --foreground, --border, --ns-accent, --error for the invalid state); the component writes no localStorage and has zero dependencies.",
        "rank": 281
      }
    },
    {
      "name": "night-store",
      "type": "registry:ui",
      "title": "Night Store",
      "description": "A cache diagnostics rack where every key-group is a storage-heater brick holding heat: a hit charges it toward full glyph-density stipple, idle time cools it on a decay matched to its own TTL, and a real eviction contracts the rack by exactly one slot. Keys hit over and over flicker cold-hot instead of settling: the visual signature of a thrashing cache.",
      "files": [
        {
          "path": "registry/core/night-store/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/night-store.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "cache",
          "dashboard",
          "monitoring",
          "stipple",
          "svg",
          "data-visualization",
          "disclosure",
          "accessibility"
        ],
        "instruction": "Build a cache diagnostics panel (`HeaterRack`) where every tracked key-group renders as a storage-heater brick holding heat. Props: `entries: CacheEntryInput[]` (each `{ id, keyGroup, ttlMs, hitCount, lastHitAt }`, owned and updated by the caller from real cache events), `hitRatio: number` (0..1, caller-computed rolling ratio), `evictionsPerMin: number`, an optional `hitRatioAlertThreshold?: number`, and `className`. MECHANISM (the one rule that drives every brick, and the falsifiable constraint of the whole component): a brick's temperature T is a PURE FUNCTION of elapsed time since its own last hit — `T(dt) = exp(-dt / tau)` where `tau = ttlMs / 3000` seconds (chosen so a never-hit entry already reads visibly cold at roughly a third of its lifetime, early enough to PREDICT an eviction rather than report one after the fact) and `dt = (now - lastHitAt) / 1000`; a hit simply resets `lastHitAt` to now, which snaps T back to 1. Every visual property of a brick derives from that brick's OWN T and nothing else — no global shimmer, wave, or stagger is allowed anywhere in the rack, because once bricks move together for ambience the thrash-flicker of a key being hit-then-cooled-then-hit-again over and over becomes invisible, and that flicker is the component's only diagnostic value. Re-sample T periodically (a plain `setTimeout` re-tick loop, not a physics simulation — there is nothing to accumulate since T is a pure function of elapsed time) at roughly 220ms under normal motion and exactly 1000ms (1Hz) under `prefers-reduced-motion: reduce`, per `matchMedia` with a live change listener. RENDERING — glyph-density stipple, never color: temperature is communicated purely by how many of a brick's dots are visible, quantized to a fixed 5-level ramp (`level = floor(T*5)`, clamped 0-4). Each brick has its own small fixed pool of 16 dot positions in a `viewBox=\"0 0 1 1\"` SVG, generated once by a deterministic per-id PRNG (an FNV-1a-style hash of the entry `id` feeding a mulberry32 generator, same technique as this registry's other stipple components) and then sorted by distance from the brick's own center — so `visibleCount(level) = round((level/4)*16)` dots reveal outward from that brick's core as it warms, giving even a single brick a 'hot core, cool fringe' read, and the same pattern makes the WHOLE RACK read as a hot-core/cool-fringe texture once several bricks are hot at once. Every dot is `fill: var(--foreground)`; a brick's own background is a subtle `color-mix(in srgb, var(--background), var(--ns-muted) calc(var(--t)*7%))` wash (still grayscale, not a color ramp) driven by a `--t` custom property written straight onto the `<li>`. A brick that JUST got hit (T snapping toward 1, more dots turning on) or is cooling (dots turning back off) gets the SAME plain CSS `transition: opacity 220ms ease-out` on each dot — this is the entire 'single fast charge ease' the mechanism calls for: no hand-written JS ramp, just a short opacity transition doing the easing for both directions, stripped to an instant step under `prefers-reduced-motion: reduce`. LAYOUT AND ORDER: bricks render in a plain flex-wrap `<ul>`, in EXACTLY the order of the `entries` prop — the rack is never re-sorted by temperature at any point, because re-sorting would make it impossible to watch one brick's heat over time, which is the entire point of the view. When an id disappears from a later `entries` array (a real eviction happened upstream), the component doesn't unmount it immediately: it keeps the row, marks it `leaving`, and animates `max-width`/`opacity`/`margin`/`padding`/`border-width` to zero over 260ms (a small, real contraction of the rack by exactly one slot) before finally removing it from state — implemented by keeping an internal `rows` array reconciled against `entries` in a `useEffect`, with per-id `setTimeout` cleanup on unmount. ACCESSIBILITY: each brick is `<li role=\"listitem\">` with an `aria-label` stating the full diagnostic sentence as text — `\"{keyGroup}, {hitCount} hits, {forecast}\"` — where `forecast` is derived purely from T and tau: `\"just hit, full heat\"` when T >= 0.94, `\"cold, at risk of eviction now\"` when T <= 0.15, otherwise `\"cooling, likely evicted in ~{round(tau * ln(T/0.15))}s\"` — a genuine prediction computed from the same decay constant driving the visual, not a canned string. The stipple SVG itself is `aria-hidden`. Beyond that label, every extra numeric fact (raw temperature %, TTL, time since last hit, the same forecast sentence) sits behind a real per-brick disclosure `<button data-ns-disclosure aria-expanded aria-controls>` (a '+' / '−' glyph with an sr-only accessible name, 'Show/Hide details for {keyGroup}') that reveals a `<dl data-ns-detail>` of those facts on click — never a hover-only tooltip, and this disclosure button is also what keeps the component genuinely keyboard-reachable, since a passive diagnostics grid otherwise exposes no natural control. The header renders three live stats as a `<dl>` — hit ratio, entry count, evictions/min — as plain visible text that simply re-renders on every prop change (not itself wrapped in `aria-live`, to avoid spamming assistive tech on every tick); a separate `sr-only` `role=\"status\" aria-live=\"polite\"` span announces a sentence ONLY when `hitRatio` crosses the caller-supplied `hitRatioAlertThreshold` in either direction ('Hit ratio dropped below 70% — cache under pressure.' / 'Hit ratio recovered above 70%.'), tracked via a ref holding the previous above/below state so re-renders at the same ratio never re-announce. TOKENS: --foreground for every dot and text value, --border for brick borders and the disclosure button's resting border, --ns-muted for secondary text and the background wash, --ns-accent ONLY on the disclosure button's `focus-visible` outline — no hex, no `rgb()`/`hsl()`, no Tailwind palette classes, no canvas. DEMO: a 12-key-group cache (`user:session`, `product:price`, `product:stock`, etc.) with short 6-45s TTLs so decay and eviction play out on screen, driven by a weighted random-hit interval (two key-groups deliberately over-weighted so they visibly thrash), a rare real eviction of whichever entry is coldest once nothing has been evicted in the last ~22s, and rolling hit-ratio/evictions-per-minute stats recomputed from a trailing event window — entirely the demo's own simulation, so the rack animates ambiently without needing any synthetic autoplay driver input."
      }
    },
    {
      "name": "nomogram-edge",
      "type": "registry:ui",
      "title": "Nomogram Edge",
      "description": "A two-input estimator built as a real paper nomogram: three vertical ASCII scales in Geist Mono where a straightedge stretched between draggable left and right handles crosses the middle scale at the exact computed answer, log-spaced so the crossing is mathematically exact rather than decorative. Scaled and paced as a pricing-page hero: a thick line springs into place on mount and the crossing idles through a few illustrative readings on its own before anyone touches it.",
      "files": [
        {
          "path": "registry/core/nomogram-edge/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/nomogram-edge.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "nomogram",
          "estimator",
          "calculator",
          "pricing",
          "capacity",
          "slider",
          "svg",
          "drag",
          "log-scale",
          "form"
        ],
        "instruction": "Build a two-input estimator as a working three-scale nomogram, not a pair of sliders with a computed label. GEOMETRY (the part that must be exact): three vertical tick-glyph scales (─ for minor ticks, ┬ for major, on a 1px │ spine) sit in three equal-width grid columns with zero gap, so the middle column's spine is the exact horizontal midpoint between the left and right spines. Left and right scales are anchored so their own domain MAXIMUM maps to fraction 0 (top of a fixed-height track) and fraction grows downward; with a single shared log modulus R = ln(leftMax/leftMin) (product mode) or linear span R = leftMax-leftMin (sum mode) taken from the LEFT domain only, frac(u) = ln(leftMax/u)/R (or (leftMax-u)/R for sum) and frac(v) = ln(rightMax/v)/R using that SAME R — this is what makes the halfway-average of the two handles' fractions a well-defined function of the product/sum alone, regardless of how it decomposes into u and v. The middle scale's own ticks are drawn at frac(w) = ln(wMax/w)/(2R) (or (wMax-w)/(2R) for sum), wMax = combine(leftMax, rightMax) — so a straight line whose y at the left spine is frac(u)*trackHeight and at the right spine is frac(v)*trackHeight crosses the middle spine (the true geometric midpoint) at exactly frac(u*v)*trackHeight by the intercept theorem. The crossing value itself is computed analytically (result = leftValue OP rightValue) and placed at (fracLeft+fracRight)/2 — the drawn SVG line and the printed number are never two separate approximations of each other. Because R comes from the left domain, a right (or middle) domain with a smaller log-ratio/span reaches less than the full track height and ends at a real drawn '══' scale cap partway down rather than filling the column — that is correct nomography (a shorter physical scale for a narrower range), not a layout bug, and it's the part of the geometry that makes the two inputs' sensitivities genuinely different: the same handle travel covers a different fraction of a shorter scale's own domain. RENDERING: inline SVG line only (a quadratic bezier that collapses to exactly a straight line when its control point sits at the true midpoint, i.e. no visual sag at rest), `vector-effect=\"non-scaling-stroke\"` on the path since the SVG uses `viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\"` (percentage coordinates so the line tracks any container width with zero JS measurement) — non-uniform x/y scaling would otherwise smear stroke width, which is also why nothing else in the SVG is round (no circles for handles or the crossing mark; those are real DOM elements). The crossing glyph swells to ╪ (larger, bold, --foreground — never accent) with the read-off value printed beside it in a small bordered chip; ordinary middle-scale ticks carry no numeric labels, only the live crossing does, so nothing static competes with the live answer. Outer scales label their major ticks with compact mono figures (100/1k/10k style) outside the spine, away from the center column. INTERACTION: left and right handles are small rotated-square DOM markers (not SVG), draggable via pointerdown/move/up on their full-height track (so a click anywhere on the track jumps the handle there, matching this registry's slider-vernier convention) with pointer capture; drag is continuous and unquantized so the crossing visibly moves in real time as you drag (this IS the point — you see at a glance that doubling one input barely moves the crossing while nudging the other swings it wildly, when the two domains' log-ratios differ); on release the handle snaps to the nearest generated tick (by fractional distance, not raw value) with a spring-flavored `cubic-bezier(0.34,1.56,0.64,1)` ease on its `top` position — value commits are instant, only the visual position eases, same idiom as slider-vernier. Keyboard: ArrowUp/Right and ArrowDown/Left step one tick, PageUp/PageDown jump roughly a decade (product) or five ticks (sum), Home/End go to the domain rails — all computed from the nearest-tick index so keyboard nav never gets stuck between detents. While a handle is down the straightedge carries a small quadratic sag (~3 viewBox units, control point offset from the true midpoint) that eases back to zero — a straight line — on release via a `d`-attribute CSS transition; this sag is purely decorative, the crossing computation never reads the drawn path. `prefers-reduced-motion` is detected in JS (matchMedia, not just a Tailwind variant) so every eased transition — handle snap, crossing glyph settle, straightedge sag — is set to `none` outright rather than merely shortened, while remaining fully usable (every value change still applies instantly). ACCESSIBILITY: both handles are `role=\"slider\"` `tabIndex=0` with `aria-orientation=\"vertical\"`, `aria-valuemin/max/now`, and `aria-valuetext` composed as '<this handle's own reading> — estimated <formatResult(result)>' (e.g. '100k requests — estimated $1,000/mo') so either handle alone tells you the full answer regardless of which one last moved; a `sr-only` `aria-live=\"polite\"` region holds the same composed sentence and is updated only on commit (drag release or a keystroke), never on every pointermove, so dragging doesn't flood a screen reader with per-pixel announcements. All three scales and the straightedge SVG are `aria-hidden` — the two role=slider handles are the entire accessible surface, so the component is fully keyboard-operable with no gesture-only path (Tab reaches both handles; there is no third focusable element). Focus rings and the grabbed-handle fill/border are the ONLY places `--ns-accent` appears anywhere in this component — the crossing glyph, the header readouts, and the straightedge are all `--foreground`/`--border`/`--ns-muted`. MOTION: on mount both handles start pinned at the visual midpoint of their track (a neutral placeholder position, not a real reading — every printed number is already correct from the first render, only the drawn line/handles/crossing are deferred) and spring out to their real reading on the same overshoot curve as an interactive snap, once one paint has happened with no transition so there is nothing to un-render. Once that entrance settles, and only if nobody has touched a handle yet, the crossing idles through a small fixed set of illustrative (left, right) pairs on a slow ease-in-out loop — spread across each domain so the resting frame visibly demonstrates the sensitivity story (one side swinging the crossing further than the other) before any input. The idle loop calls `onValuesChange` (so an embedding page can mirror it) but never touches the `aria-live` region or fires a screen-reader announcement — it is not a user commit. The very first real pointerdown or keystroke, synthetic or human, cancels the idle loop permanently for that mounted instance and hands control back to the ordinary drag/keyboard path. All of the above is skipped outright under `prefers-reduced-motion`: handles render at their true reading from the first frame, no entrance, no idle drift, fully usable exactly as ever. PROPS: `mode` (\"product\" default or \"sum\") swaps log for linear scales and multiplication for addition; `leftMin/Max`, `rightMin/Max`, `defaultLeftValue/RightValue`, per-column `label`s, and `formatLeft/Right/Result` overrides (used for both the header readouts and each handle's `aria-valuetext`, tick glyphs use their own fixed compact formatting since they're decorative); `trackHeight` (px, default 480 — sized as a standalone instrument, not a form-row control); `ambient` (default true) turns the idle drift described above off entirely, e.g. when several instances share a page and only one should draw the eye; `onValuesChange(left, right, result)` fires on every value change including mid-drag and idle drift."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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).",
        "rank": 280
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-muted) over a slower trailing 260ms — never red, never --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "error": "#ea001d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "error": "#ff6369"
        }
      },
      "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 --ns-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/--ns-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-ns-accent)."
      }
    },
    {
      "name": "overflow-chip-mux",
      "type": "registry:ui",
      "title": "Overflow Chip Mux",
      "description": "The list/tag overflow indicator, replaced with real NES PPU sprite multiplexing: instead of collapsing extra items into a static \"+N\" pill, only 8 chip slots (the hardware's real 8-sprites-per-scanline limit) are ever visible at once, round-robining which contending item occupies each slot on a throttled cadence, exactly how 8-bit console flicker actually worked.",
      "files": [
        {
          "path": "registry/core/overflow-chip-mux/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/overflow-chip-mux.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "overflow",
          "tags",
          "chips",
          "nes",
          "sprite-multiplexing",
          "retro",
          "ambient",
          "accessibility"
        ],
        "instruction": "Build <OverflowChipMux items? slotBudget? swapIntervalMs? ariaLabel? className? style?>, a chip row that replaces the generic \"+N more\" overflow pattern with real NES PPU sprite multiplexing. The 2C02 PPU can only evaluate 8 hardware sprites per scanline; a 9th contending object in a frame is silently dropped from that frame entirely (a hardware limit, not a software choice), and games with more on-screen objects than that round-robinned which subset of contenders got the 8 available slots each frame, cycling on a throttled schedule so every object gets its turn some fraction of the time — the well-documented NES 'flicker' artifact. `SLOT_BUDGET` defaults to 8 (the real per-scanline limit) and is the count of chip DOM nodes ALWAYS rendered, keyed by fixed slot index 0..budget-1, never by item identity — a swap changes only which item's label a slot currently shows (text content + a one-shot CSS flicker keyframe replayed via a per-slot `flashKey` remounting an inner span), never which DOM node exists, so there is no layout thrash or accessibility-tree churn on every tick.\n\nRound-robin model: each slot tracks `age` (ticks since it last changed). Every `swapIntervalMs` (default 130ms, ~7.7Hz — a deliberately decimated cadence in the range real multiplexing code used, roughly every 8 frames at 60fps, specifically to stay legible instead of reading as an unreadable strobe), find the single oldest non-pinned slot, evict its item to the back of a FIFO queue holding every other contending item, and pull the item at the front of that queue into the freed slot with age reset to 0 — exactly one item rotates in/out per tick, never a full reshuffle, so a complete cycle back to the starting arrangement takes exactly `items.length * swapIntervalMs`. If `items.length <= slotBudget`, no interval starts at all and every item renders in its own slot with zero rotation — this correctly matches the real hardware, which only starts dropping sprites once a 9th contender shows up on the scanline; the shipped default seed is 14 items specifically so multiplexing is visibly engaged out of the box (a caller testing with a short list would otherwise see nothing alive, which is not a bug in the component).\n\nInteraction: each chip is a focusable `<button type=\"button\">`; `onPointerEnter`/`onFocus` pins its slot (excluded from the eviction search, so it never rotates out while hovered/focused) and `onPointerLeave`/`onBlur` unpins it (age reset to 0, so it re-enters the rotation fresh rather than being evicted immediately for being 'old'). The pin affordance's `focus-visible` ring is the only place `--ns-accent` appears anywhere in this component — the swap/flicker mechanism itself, including the one-shot per-swap flicker keyframe, uses opacity only (0.25 to 1 over 90ms), zero hue, zero accent, exactly per the token rules. All chip fill/border comes from `--foreground`/`--ns-muted`/`--border` via ordinary Tailwind token classes (`border-border`, `bg-background`, `text-foreground`, `text-ns-muted`) — no canvas, no getComputedStyle, nothing to re-derive on theme change.\n\nAccessibility is required, not optional, and is what earns this component the right to replace '+N more' at all: a `sr-only` `<ul>` enumerating every single item (not just the currently-visible 8) is present in the DOM at all times regardless of overflow state, and a visible (non-sr-only) plain-text line below the row always reads `${visibleCount} of ${total} shown, ${total} total` so both sighted and non-sighted users have the true count without depending on the flicker ever being witnessed. `prefers-reduced-motion` never starts the interval at all — the component simply renders its initial build state (slots 0..budget-1 holding items 0..budget-1 in source order, round-robin index 0) and stays there, which is deliberately the SAME code path as the first frame of the animated version, not a separate branch, so the reduced-motion freeze is provably the 'first budget-full pass' frame the spec calls for. A `matchMedia` change listener starts/stops the interval live if the OS preference toggles mid-session. Interval is cleared on unmount and whenever `items`/`slotBudget`/`swapIntervalMs` change (which also rebuilds the round-robin queue from index 0). No dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": "parison-inflate",
      "type": "registry:ui",
      "title": "Parison Inflate",
      "description": "A capacity/fill-progress meter rendered as a glassblower's parison inflating: a thick-walled bubble grows from a small dense core toward a larger, thin-walled sphere on a self-limiting exponential curve that visibly decelerates as it grows, wall thickness thinning in proportion to the radius the way real blown glass thins, then holding at the 'about to over-blow' beat before deflating and reheating to blow again.",
      "files": [
        {
          "path": "registry/core/parison-inflate/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/parison-inflate.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "meter",
          "progress",
          "gauge",
          "glass",
          "parison",
          "canvas",
          "monochrome",
          "ambient"
        ],
        "instruction": "Build <ParisonInflate label? height? value? className?> as a card-scale <canvas> panel, a drop-in capacity/fill-progress meter that renders a glassblower's parison inflating instead of a bar or dial. SOURCE: standard hot-shop parison-forming sequence — after gathering, the gaffer blows a controlled breath of air down the blowpipe into the solid-but-workable gob, forming a thick-walled bubble (the parison) inside the glass; as it inflates, the bubble's wall thins in proportion to how much the radius has grown (wall volume is roughly conserved, so wall thickness falls off close to 1/r^2 as the bubble expands), and inflation is self-limiting — a thin-walled, over-blown parison chills and stiffens faster than a thick one, which is why gaffers reheat between blows rather than blowing continuously. GEOMETRY: two concentric circles (outer wall radius, inner cavity radius) centred in the canvas, both derived from dim = min(containerWidth, containerHeight) so the mechanic holds its shape at card scale regardless of panel aspect ratio: outer radius sweeps from R0 = 0.08*dim to R1 = 0.44*dim over the inflate phase. MATH: r(t) = r0 + (r1-r0)*(1 - exp(-t/tau)) with tau = 0.85s — fast initial expansion that visibly decelerates, the self-limiting curve, never a linear or ease-out cosmetic substitute. Wall thickness at outer radius r: w(r) = w0*(r0/r)^EXP where w0 = 0.22*r0 (thickness at r0). DEVIATION FROM THE SPEC'S STATED 1.8 EXPONENT, DELIBERATE: the spec's two literal endpoints ('22% of r0' at r0, 'roughly 4% of r1' at r1) are self-cancelling — 0.22*r0 and 0.04*r1 resolve to the same absolute thickness for r1 = 5.5*r0 — and satisfying both with a 1.8 exponent is impossible without w0 exceeding r0 itself; taken literally, 1.8 thins the wall under 1% of dim well before the inflate beat is half over and holds there, a growing circle with a flat rim, this component's own kill criterion. EXP = 0.75 is used instead: same decelerating, self-limiting character (thins fastest early, slowest late) but spreads a continuous ~3.5x thickness change across the full 2.6s beat with no floor engagement anywhere in the normal card-scale radius range. w(r) is still clamped to max(0.003*dim, 0.9px) (WALL_FLOOR_FRACTION, WALL_FLOOR_PX) as a sub-pixel-degenerate safety net only — the fraction-of-dim term alone would shrink along with a small container and drop under 1px into a faded partial-coverage line, so an absolute-px floor backs it up; neither term binds at the ~220px demo panel or above (~300px dim), so the visible thinning there is the raw power law, not the clamp. This is the piece that must be checked in light theme first (against --surface, not as a final pass). TIMING: inflate phase 2.6s (INFLATE_S), hold at the inflate-phase's end radius for 0.6s (HOLD_S, the 'about to over-blow' beat — this lands at ~95% of r1 given tau, not exactly r1, which reads correctly as the self-limiting ceiling), then a 0.5s eased (easeInOutCubic) deflate-and-reset back to r0 (DEFLATE_S) — a visible reset, not a jump-cut, so the loop reads as cyclical process. Total cycle 3.7s (CYCLE_S), repeats indefinitely with zero input. RENDER: each frame, clear, compute rOuter/rInner/wall for the current cycle time, build a canvas radial gradient from (cx,cy,rInner) to (cx,cy,rOuter) with colour stops muted-token at 0 and foreground-token at 1 (denser/brighter glass reads brighter toward the outer wall), then fill an annulus — outer circle arc clockwise, inner circle arc counter-clockwise, closePath, fill('evenodd') — so only the wall band paints; the cavity interior inside rInner is left untouched/transparent, reading as hollow rather than a filled disc. TOKENS: fg (--foreground) and muted (--ns-muted) are read once via getComputedStyle(document.documentElement) before the first paint, gated by a tokensReady flag that draw() checks structurally (rather than relying on call ordering or a 'currentColor' fallback reaching the gradient — an empty computed value passed to canvas addColorStop throws, unlike fillStyle, which silently no-ops) — no paint before a real token read; re-derived on a documentElement class MutationObserver so a theme flip repaints live; --border and --ns-accent are never touched by this component — completion at 100% reads via full inflation plus the hold beat, never colour, and the wall/cavity are never tinted with accent at any fill level. RESTING LOOP (ambient/uncontrolled state, value prop omitted): t0 — small, thick-walled bubble near the start of an inflate phase; t2.5s — ~2.5s into the 3.7s cycle, bubble near/at full inflation, wall visibly thin, distinctly larger than at t0; t5s — into the second cycle's early inflate phase (5s mod 3.7s ~= 1.3s), small-to-mid bubble again but at a different radius/thickness than the t0 frame, so all three frames are visibly distinct. REDUCED MOTION: freezes at 80% of the inflate phase (FREEZE_T = 0.8*2.6s = 2.08s into the cycle) — wall visibly thinned but not yet at the hold plateau, the frame that most clearly shows 'still actively inflating' without ambiguity about direction; checked via matchMedia('(prefers-reduced-motion: reduce)') at mount. CONTROLLED MODE: when the `value` prop (0..1) is provided, the loop stops entirely — the inflate curve maps monotonically to that value via r(value*INFLATE_S, ...) and holds there (value=1 lands at the same ~95%-of-r1 hold radius as the ambient loop's hold beat, read as full inflation; it does not deflate/reset), and no rAF loop, IntersectionObserver-driven resume, or reduced-motion freeze logic runs since the frame is already static — the canvas draws once per value/resize/token change. `autoplay: none` because in the ambient state the bubble animates on its own internal clock regardless of pointer/scroll/press input, and in controlled state it is driven by prop data, not synthetic input — there is nothing for a pointer/press/scroll driver to do either way. LIFECYCLE: canvas sized via getBoundingClientRect with devicePixelRatio (capped at 2) applied through ctx.setTransform, resize handled by a debounced ResizeObserver (150ms), the rAF loop paused by an IntersectionObserver when off-screen and by document.hidden via a visibilitychange listener (both resume with a fresh last-timestamp, no giant delta-time jump), and every rAF/observer/listener is torn down on unmount. A11Y: the canvas itself is aria-hidden (decorative raster); in ambient mode it is wrapped in a role=img element with an aria-label summarizing the reading in prose (a bubble inflating and thinning, decelerating as it grows, then deflating to reset) since the ambient loop has no single scalar to expose the way a determinate meter would; in controlled mode the wrapper is role=progressbar with aria-valuenow/min/max reflecting the value prop as a percentage, and the label row's right-hand cell shows the same percentage as a static Geist Mono caption. No canvas colour literal, no dependency: 2D canvas, DOM+canvas+CSS only. Props: label (accessible name and visible caption, default 'Fill level'), height (canvas panel height px, default 220), value (optional controlled fill fraction 0..1; omit for the ambient self-looping demo state), className."
      }
    },
    {
      "name": "passing-loop",
      "type": "registry:ui",
      "title": "Passing Loop",
      "description": "A progressive-rollout controller built as a funicular: two cars share one cable over a summit pulley, so the new version's share and the old version's share are geometric complements that can never disagree. A canary hold pins the midpoint until confirmed, and rollback releases the winch to fall back under damped physics.",
      "files": [
        {
          "path": "registry/core/passing-loop/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/passing-loop.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "deploy",
          "rollout",
          "traffic",
          "canary",
          "rollback",
          "slider",
          "ops",
          "dashboard",
          "mono"
        ],
        "instruction": "Build <PassingLoop newVersion oldVersion totalRequests defaultValue onValueChange? onCanaryConfirm? onRollback? ariaLabel? className?> as a funicular: one cable position `s` in [0,1] is the ONLY state that matters. new-version share = s, old-version share = 100 - round(s*100) (the complement of the SAME rounded integer, never an independently rounded 1-s, so the two percentages are structurally incapable of summing to anything but 100 — this is the falsifiable constraint the whole component exists to prove, and it rules out ever rendering two separate progress bars). Live request counts follow the identical rule: newCount = round(total*s), oldCount = total - newCount. GEOMETRY: a single incline from a base station to a summit pulley (pointAt(t) linearly interpolates between them); the new car sits at pointAt(s) (climbing toward the summit as s grows), the old car at pointAt(1-s) (descending toward the base as s grows) — same line, same t-parameterization, mirrored, so they are visibly one mechanism, not two. A counterweight tilt indicator near the base station rotates by (s-0.5)*24deg — also derived from s alone, nothing else, and stays a derived POSITION even under reduced motion (only its own transition is stripped, never the angle itself, since forcing it flat would quietly contradict the component's central claim that everything derives from s). THE PASSING LOOP: at t=0.5 the single track splits into two bulged rails (a quadratic-bezier lens either side of the line, ~10px control-point offset — since a bezier's midpoint sits at half that offset from the baseline, each car's own lateral position across the loop is a matching half-offset curve that tapers to 0 at the loop's edges, `railOffset(t) = d>=LOOP_HALF ? 0 : (LOOP_OFFSET/2)*(1-d/LOOP_HALF)` where d=|t-0.5`, so the new car rides one rail and the old car the other and they are visibly on separate tracks through the crossing rather than overlapping at exactly s=0.5, which is the whole reason a passing loop exists) and rejoins. A points blade at the loop's centre is drawn as an SVG line rotated via the `transform` attribute (`rotate(deg cx cy)`) with a CSS transition on that attribute (not a nested-group trick): closed = perpendicular to the cable (blocking further advance), open = flush with the cable direction, thrown between the two over ~200ms on a spring easing (cubic-bezier(0.34,1.56,0.64,1), one visible overshoot-and-settle — the 'mechanical throw'). MECHANISM: while `!canaryConfirmed`, any attempt to move s past 0.5 — arrow-key stepping, dragging, Home/End — is clamped to exactly 0.5; if the REQUESTED value actually exceeded 0.5 (a real attempt, not just resting there), both cars fire a one-shot 4px overshoot-then-settle bounce along the cable direction (opposite directions for the two cars, since they share one rigid cable) via a CSS keyframe applied to a remounted `key={nonce}` inner group, debounced to at most one restart per 240ms (just past the 220ms keyframe's own length) so a held drag or a held arrow key produces one clean overshoot-and-settle rather than the keyframe endlessly restarting into a buzz — the same one-shot-flash-via-remount idiom used elsewhere in this registry, never a persistent animation. A real `<button data-loop-confirm>` swings the points at any time regardless of current position (pressing it while parked below the loop is fully valid — it only ever needs to be pressed once; a repeat press while already open is a harmless no-op replay, matching a real switch that doesn't need re-throwing). Once confirmed, s is free to move all the way to 1 with no further gate. ROLLBACK: a real, ALWAYS-tabbable `<button data-loop-rollback>` that is never given a `disabled` attribute (unlike a normal fall-arrest control, this one must stay reachable even mid-incident) releases the winch brake and integrates s back toward 0 as a damped harmonic oscillator: accel = -omega^2*s - 2*zeta*omega*velocity, omega=4.2, zeta=0.85, semi-implicit Euler at the frame's real dt, settling (snapping to exactly 0 and clearing velocity) once |s| and |velocity| both fall under a small epsilon. zeta=0.85 is deliberate and load-bearing: at zeta near 0 the cars slam the lower stop and the rebound reads as a second, spurious deploy event; well above 1 the fall reads reluctant, which a rollback must never feel; 0.85 arrives fast with the velocity already bled off by the time it reaches the stop, so the hard clamp at s=0 is never visually a bounce. A rollback in progress disables the slider's own input (aria-disabled, tabIndex -1, pointer/keyboard handlers no-op) since the brake being released means nothing is steering it; the slider re-enables the instant it settles, and settling also resets `canaryConfirmed` to false — a full rollback re-arms the canary gate for the next attempt. A repeat press of rollback while one is already falling is a no-op replay, and pressing it again once already settled at s=0 with the gate re-armed is also a harmless no-op (nothing left to release). A11Y: the whole diagram (SVG track, both cars, points, counterweight) is aria-hidden; the interactive surface is a single `role=slider` div (the drag/keyboard target) with aria-valuemin=0, aria-valuemax=100, aria-valuenow=round(s*100), and aria-valuetext built as `'{newPct}% on {newVersion}, {oldPct}% on {oldVersion}'` (e.g. '35% on v2.4.1, 65% on v2.4.0'); ArrowLeft/Right/Up/Down step 5%; Home jumps to 0; End jumps to 1 if confirmed, else to the loop's 0.5 cap; Enter confirms the canary ONLY when parked exactly at s=0.5 and unconfirmed (a no-op otherwise, so Enter never fires the confirm action from elsewhere on the track). Two plain-text mono readouts below the diagram — 'v2.4.1 · N req' and 'v2.4.0 · M req' — are real DOM text, always legible and always summing to totalRequests; a second, redundant copy of the same version/percent/count triple floats beside each car (new label above its car, old label below, so the two never collide even at s=0.5 where the cars themselves are only ~10px apart on their separate rails) as the literal 'live request counts riding on each car' the brief asks for — both are real HTML text, aria-hidden since they duplicate the footer readouts rather than add new information. Rollback's assertive announcement fires exactly once at the instant it begins — e.g. 'Rolling back, 12% on new version and falling.' — into a permanently-mounted `aria-live=assertive` region, then stays silent until settling writes one final message ('Rolled back. 0% on v2.4.1, 100% on v2.4.0.'); a separate `aria-live=polite` region announces the canary confirmation. REDUCED MOTION: s still changes, just discretely — rollback runs as a `setInterval` geometrically decaying the remaining share (`s *= 0.4` each ~190ms tick) rather than the continuous rAF oscillator, snapping to exactly 0 once it's small enough, with the counters and the slider's aria-valuetext correct at every discrete tick; the points-throw transition, the bounce keyframe, the car-glide transition and the counterweight tilt transition are all forced to `none` via both an inline conditional and a `@media (prefers-reduced-motion: reduce)` guard on the bounce keyframe class, so the mechanism stays fully legible and operable with zero continuous motion. TOKENS: every stroke and fill is var(--background)/var(--foreground)/var(--ns-muted)/var(--border) — --ns-accent appears ONLY in the confirm and rollback buttons' own `focus-visible:outline-ns-accent` rings, never on the slider's own focus ring (which uses --foreground) and never as a status or progress color anywhere in the diagram; car identity is shape (filled vs hollow rounded rect), never hue. Pure DOM + SVG + CSS transitions/keyframes driven by React state and one rAF loop for the rollback physics only — no canvas, no dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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(--ns-muted, --ns-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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 `--ns-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 `--ns-accent`; an armed jack's border is a persistent `--ns-accent`; a patched-but-idle jack's label is `--foreground` rather than `--ns-muted`. Tokens only (`--background --foreground --ns-muted --border --ns-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": "pecked-ring",
      "type": "registry:ui",
      "title": "Pecked Ring",
      "description": "A geofence radius editor whose circle is drawn in pecked chart line-work where each dash is exactly 100 metres of ground circumference, so dragging the handle outward visibly grows the dash count and the radius reads by eye with no scale legend.",
      "files": [
        {
          "path": "registry/core/pecked-ring/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/pecked-ring.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "slider",
          "geofence",
          "radius",
          "map",
          "svg",
          "drag",
          "form",
          "data-viz"
        ],
        "instruction": "Build a geofence/alert-radius editor whose ring IS the scale bar, not a decorated slider with a legend beside it. One governing scalar, r (radius in metres). dashCount = round(2*pi*r / 100) — every dash subtends exactly 100m of ground circumference, never a fixed pixel length. The circle's stroke-dasharray is a [dashLen, gapLen] pair derived from dashCount so that dashCount*(dashLen+gapLen) equals the circle's pixel circumference EXACTLY, every render — the pattern always closes around the ring with no seam, because segment length comes FROM the integer dash count rather than being assumed; a fixed pixel dasharray is exactly the thing this forbids, since its dashes mean nothing and shear visibly wherever the radius doesn't divide evenly into it. Because circumference_m cancels out of dashCount*spacing, per-dash pixel spacing collapses to roughly 100/metersPerPixel regardless of r — spacing tracks the map's scale, not visual comfort: change metersPerPixel and every dash re-spaces, the stroke never re-spaces on its own for legibility. Below a 3px spacing threshold the individual dashes would be indistinguishable, so the ring switches to 10-dash bundles (each bundled dash = 1000m) rather than drawing a denser pattern that no longer means anything — same falsifiable-by-eye rule, coarser unit. On every dash-count crossing (continuous while dragging: a new dash is born roughly every 100/(2*pi) metres of radius) the segment pair eases to its new length over 120ms via a plain CSS transition on stroke-dasharray, guarded motion-reduce:transition-none — instant re-spacing shimmers like moire against the pecked pattern, slower detaches the ring visibly from the dragging hand. Every 10th dash gets a heavier radial tick line (independent geometry from the dash stroke itself) marking the 1km index normally, the 10km index once bundled. INTERACTION: a single fixed-bearing handle (north) whose distance from the ring's centre sets r; drag reads pointer distance from centre directly (magnitude only, any direction commits), pointer capture on the drag region. ACCESSIBILITY: role=slider on the drag region with aria-valuemin/max/now in metres and aria-valuetext formatted like '1.4 km radius, area 6.2 km²'; ArrowLeft/Right/Up/Down step ±100m (one dash, matching the announced unit), Shift+Arrow steps ±1000m (one tick index), Home/End jump to the rails. The SVG is aria-hidden throughout; a visible Geist Mono numeric input mirrors r for direct entry, sharing the slider's accessible name via aria-labelledby on a common label id rather than duplicating the string. The Geist Mono radius readout is positioned riding the handle (painted with a background-colour stroke halo for legibility over the ring); the derived area (pi*r^2, formatted km² or m²) sits dead centre in --ns-muted, clearly secondary to the governing radius. Only the handle's own border tints toward --ns-accent, and only on hover/focus/drag — the ring itself stays --foreground always. DOM + SVG + CSS only, no canvas, no WebGL; all ink from --background --foreground --ns-muted --border --ns-accent, nothing hardcoded."
      }
    },
    {
      "name": "peen-coverage",
      "type": "registry:ui",
      "title": "Peen Coverage",
      "description": "A card whose backing texture is a genuine shot-peening coverage process: a stream of uniform-random impacts stamping soft circular dimples into a coverage bitmap, saturating toward and past nominal 100% before fading back to a fresh unpeened pass and starting over.",
      "files": [
        {
          "path": "registry/core/peen-coverage/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/peen-coverage.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "ambient",
          "canvas",
          "coverage",
          "peening",
          "process",
          "monochrome",
          "hover"
        ],
        "instruction": "Build a card whose entire backing surface is a canvas-rendered shot-peening coverage simulation, sourced from real SAE/AMS peening process control, not a decorative dot field. Root is a `rounded-[14px] border border-border bg-background` card holding an absolutely positioned, pointer-active, `aria-hidden` canvas filling the card behind a `pointer-events-none` content stack (heading, mono body copy, optional underlined link that re-enables `pointer-events-auto` and carries a `focus-visible` ring in `--ns-accent`).\n\nSimulation state is a `Float32Array` coverage bitmap at grid resolution `cols = ceil(width/cell)`, `rows = ceil(height/cell)` where `cell = min(width,height)/48` (derived from the card's own smaller dimension via ResizeObserver, so a dimple always reads as roughly one grid cell regardless of card size). Impacts land at a fixed global rate of 90/second, position uniform-random across the full grid (`rand()*cols, rand()*rows`), using a fractional accumulator (`acc += rate*dt; count = floor(acc); acc -= count`) so the rate holds regardless of frame rate. Each impact stamps a soft circular indent of radius 1.3 cells: for every grid cell within that radius of the impact centre, compute `depth = smoothstep(0,1, 1 - dist/radius)` and set the cell to `Math.max(existingValue, depth)` — never add — so a cell hit twice re-confirms its coverage instead of darkening further, matching real peening where a second strike on already-dimpled steel doesn't compound. Do NOT derive coverage from a closed-form curve and paint it on: the saturating, increasingly-overlapping read is required to fall out of this literal random-stamp process, the same way a Poisson-disc process converges to `1-exp(-lambda*A*t)` coverage on its own — a rejection criterion if faked with even/tiled placement instead.\n\nEvery frame, compose the bitmap into a small offscreen canvas sized exactly `cols x rows` (one `ImageData` pixel per grid cell, colour = `mix(surfaceBase, dimpleColor, cellValue)`), then `drawImage` that buffer scaled up onto the main canvas with `imageSmoothingEnabled = true` — this keeps per-frame cost flat at O(cols*rows) regardless of how many impacts have landed, since nothing is drawn from impact history. A pass runs a fixed 16000ms virtual clock (independent of any user input): nominal (statistical) 100% coverage lands near t=8s, the pass is deliberately allowed to keep stamping to a visual 200%-equivalent density by t=16s (real shops explicitly re-run passes past nominal before calling a part done), then over 700ms every cell's rendered value fades from its last value to 0 (snapshot the grid at fade start, interpolate the RENDER only, then hard-reset the actual bitmap to zero and restart the clock once the fade completes) — a legible 'fresh part loaded' beat, the one point this mechanic is allowed a clean restart. On mount (and on every debounced resize, which rebuilds the grid at the new resolution), the virtual cycle clock starts already partway through a pass: compute `offsetMs = performance.now() % 16000` and instantly bulk-stamp `floor(90 * offsetMs/1000)` random impacts before the rAF loop starts, so different mounts land on visibly different coverage states and phase never resets to a blank card on every reload.\n\nColour is read via `getComputedStyle(document.documentElement)` for `--background`, `--ns-muted` and `--foreground` at mount and re-derived on a `MutationObserver` watching `documentElement`'s class — never a literal. Compute relative luminance of `--background` to pick the branch: dark theme sets `surfaceBase = background` and `dimpleColor = ns-muted` directly (peened reads as a step toward the muted token off a near-black backdrop). Light theme sets `surfaceBase = mix(background, ns-muted, 0.42)` (the base already leans toward muted so a further step still has legible room) and `dimpleColor = mix(ns-muted, foreground, 0.45)` (one more step in the same direction, checked to stay clear of `--border`-adjacent low-contrast values and clear of vanishing into the base) — check light theme first, it is where this reads flattest if the base is left at pure `--background`.\n\nInteraction: pointermove over the canvas tracks a hover point in grid coordinates and adds an EXTRA local impact stream — up to 90 more impacts/second (bringing the local rate to roughly 2x) inside a 6-cell radius, positions sampled uniformly inside that disc (`angle = rand()*2*PI, r = sqrt(rand())*radius`) — standing in for an operator dwelling the nozzle over one spot. On pointerleave, linearly decay that extra rate to zero over 500ms rather than cutting it instantly. This must NEVER touch the global 16000ms pass clock — dwelling somewhere changes local density only, it cannot stall or rush the reset — and must NEVER tint the hovered region with `--ns-accent`; the highlight is purely a denser patch on the same base->dimple luminance ramp everything else uses.\n\nDPR-capped (max 2) backing store sized off the card's own `getBoundingClientRect`, both the visible canvas and the small offscreen composite buffer resized together on `ResizeObserver`. The render loop pauses via `IntersectionObserver` (not scrolled into view) and `visibilitychange` (tab hidden), and is a plain `requestAnimationFrame` otherwise. Under `prefers-reduced-motion: reduce`, the grid is stamped synchronously, once, with exactly `floor(90 * 6)` impacts (t=6s of a cycle, named `FREEZE_PHASE = \"55pct-coverage\"`, exposed as `data-reduced-motion-freeze` on the root) and the loop never starts — dense enough to read as an active process, short of either a blank start or a saturated-flat finish. No dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 --ns-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, --ns-muted for dominated dots, connectors and captions, --border for the baseline and readout panel, --ns-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": "pin-register",
      "type": "registry:ui",
      "title": "Pin Register",
      "description": "A map layer panel drawn as registered acetate separations: each layer a translucent sheet in a shallow isometric stack that fans on hover, and turning one off parks it visibly off the pins instead of hiding it.",
      "files": [
        {
          "path": "registry/core/pin-register/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/pin-register.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "layers",
          "map",
          "gis",
          "toggle",
          "drag-reorder",
          "keyboard",
          "spring",
          "listbox"
        ],
        "instruction": "A map layer panel rendered as registered acetate separations instead of a checkbox list — the job a plain checkbox list hides is what layer ORDER even means. Mechanism: one governing scalar x, the panel's explode factor (0 = flat pile, 1 = fully fanned), driven by hover or focus landing anywhere in the panel (a damped spring, k=190 zeta=0.92, settles smoothly toward the target) — every sheet's transform derives from that single scalar: translateY = index * 14px * x. The whole stack sits inside one shared 2-axis CSS skew (skewX -6deg skewY 2deg) applied once to the stack wrapper for the isometric tilt; there is no per-sheet rotation, one skew plus one scalar is the entire motion budget. Two small pin-dot markers are drawn once, fixed in the panel (never repositioned by x, by toggling, or by reordering) — that fixed screen position IS the registration constraint: every ACTIVE sheet's translateX stays exactly 0 at any explode value, literally locked to the pins. Toggling a layer off never fades it to invisible (an overlay you can no longer see is a checkbox, not a separation) — it springs the sheet 24px right, off the pin line, into a parked slot at 35% opacity, still fully legible, still occupying its slot in the fan, tagged with a 'parked' badge. RENDERING: absolutely-positioned DOM rows inside one relatively-positioned, skewed stack container (no canvas); every row's transform (translateY, parkX, opacity) is written per-frame on a refs-only rAF loop — y is a real damped spring (k=240, zeta=0.86) so both the explode fan and any reorder read as one continuous physical settle, parkX/opacity are a simple exponential ease toward their target. ONE ARRAY: the same `order` array drives both the visual stack (translateY = index * 14 * x, z-index = higher for lower index — the sheet nearest the viewer occludes the ones behind it) and a small composite preview beside the panel, where only ACTIVE layers draw, in that same order, index 0 last (on top) — so the physical stack and the composited map can never disagree about what's showing or in what order. REORDER: drag directly on a sheet (4px threshold distinguishes a drag from a click-to-toggle) — the dragged sheet follows the pointer 1:1 on Y while every other row's target index is recomputed each move from a live rect-based hit test (comparing the pointer's clientY against each other row's current on-screen midpoint), so the reflow previews before the drop commits; releasing commits the order and every row springs the rest of the way. Alt+ArrowUp/ArrowDown on a focused sheet swaps it with its neighbor directly, no arm/drop step. A11Y: every sheet is one real `<button>` (`role=listitem` wrapper, panel is `role=list`) — `aria-pressed` carries visibility state, `aria-label` names the layer, its state, and its position ('Traffic layer, off, parked. Position 3 of 5.'); Space or a plain click toggles; a completed toggle or reorder announces via an `aria-live=polite` status region ('Traffic layer off, parked.', 'Labels moved above Boundaries, position 2 of 5.'). REDUCED MOTION: x is pinned at 0 (active sheets sit flush at the pins with no fan — the isometric read is pure presentation, never load-bearing) and parked sheets simply offset with no animation; dragging and Alt+Arrow reordering still work, they just commit their new position instantly instead of springing into it. Colors are --background/--foreground/--ns-muted/--border/--ns-accent only (--ns-accent appears only on focus-visible and on the sheet currently being dragged); DOM + SVG + CSS only, no canvas. Distinct from light-table: that component stacks film purely for visual comparison of content, with no z-order or on/off semantics; this one treats stack order as a draggable physical fact tied to real map draw order, and keeps a disabled layer parked-but-visible instead of gone."
      }
    },
    {
      "name": "pole-shy",
      "type": "registry:ui",
      "title": "Pole Shy",
      "description": "Live cursor name-labels that repel each other like matching magnetic poles, so the person actually typing makes bystander labels visibly yield and drift back.",
      "files": [
        {
          "path": "registry/core/pole-shy/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/pole-shy.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "presence",
          "collaboration",
          "cursor",
          "physics",
          "svg",
          "aria-live",
          "multiplayer",
          "typography"
        ],
        "instruction": "Live cursor name-labels that behave as like-pole magnets: every label carries the same polarity, so labels crowding one line shove each other apart along the baseline with inverse-square falloff, and the person currently typing carries a stronger field, so bystander labels yield and drift back — dominance is expressed by who moves out of whose way, never by size, glow or color; the currently-typing label is styled identically to every other label at all times. Props: `users` (required) is an array of {id, name, x (0..1 horizontal position of that user's TRUE caret within the container), row (integer line index — only users sharing a row ever interact, exactly like carets on different lines of a document never fighting for the same space), typing (boolean, true while that user is actively producing keystrokes), section? (optional human label used only in the typing announcement)}. MECHANISM: within a band of 2+ same-row users, a rAF loop throttled to 30fps computes each member's field strength F = 1 (base) times 3 while `typing` is true, or times 5 while the pointer is hovering that label (hover is meant to guarantee legibility on demand, so it's given a stronger pull than the ambient typing signal), each decaying independently back toward 1 over roughly 1.4s of measured idle/unhover time via the same exponential curve (1 + (mult-1)*e^(-idleMs/1400)) — hovering a crowded label is read by the physics exactly like that person typing: it, too, is a repulsion field, not a tooltip, it's just tuned stronger. Every pair in the band repels along the horizontal axis only with Fi*Fj/d^2 (d floored at 14px so near-overlap can't spike to infinity); that repulsion is summed as an external force into a critically-damped spring (k=46 s^-2, zeta=0.85) pulling each label back toward its true anchor at offset 0, clamped to +/-72px, integrated with semi-implicit Euler and written straight to a ref'd DOM node's `transform: translateX()` every tick — refs-only, no per-frame React state, so idle bands (fewer than two members sharing a row) cost nothing beyond a size check. The hovered label's own DOM node is also raised in paint order (z-index) above its band, so it draws over crowded neighbours rather than losing an arbitrary DOM-order tie — text is always in the accessibility tree regardless of paint order, so this is a purely visual legibility fix, not a semantics change. A hairline SVG line (stroke var(--border)) plus a small dot (fill var(--foreground)) always run from the label's current, possibly displaced position down to its true caret coordinate, so the anchor is never ambiguous even mid-displacement — a label can never be drawn somewhere its leader doesn't reconcile. Labels are bordered chips (border var(--border), background var(--background)) holding a small Geist Mono initials badge plus the collaborator's name as real, always-visible text — identity is carried by that text, not by color, and --ns-accent never touches a label. Under prefers-reduced-motion the spring is dropped entirely: crowded-row members render with zero horizontal displacement and instead stack vertically within a row that grows tall enough to hold them, each still leadered straight down to its own true caret — legible instantly, no motion required, and no physics loop runs at all in that mode. An adjacent role=status aria-live=polite region (visually sr-only) announces arrivals, departures, and '<name> is typing' (or '<name> is typing in <section>' when `section` is given) strictly on state change — never once per physics tick, since all repulsion is presentational and the leader guarantees the announced identity's visual position always matches its true one. Zero dependencies, DOM + SVG + CSS only, no canvas. Differs from avatar-stack-flock, which flocks a header's idle avatar row as decorative boids with no tie to document position: pole-shy only ever displaces to resolve a real collision between real caret positions, sleeps whenever a row has fewer than two members, and expresses 'who is typing' through motion deference rather than any static visual difference."
      }
    },
    {
      "name": "polyp-bud",
      "type": "registry:ui",
      "title": "Polyp Bud",
      "description": "A coral colony that accretes inward from one edge, budding new polyps only where five shadow rays against the colony's own skeleton clear a 0.55 exposure threshold, thickening every ancestor segment 0.4 units per bud, leaning as its light direction drifts, and occasionally bleaching a branch pale before it recovers or dies bare.",
      "files": [
        {
          "path": "registry/core/polyp-bud/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/polyp-bud.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "hero",
          "sidebar",
          "edge-treatment",
          "svg",
          "coral",
          "growth-simulation",
          "raycasting",
          "generative",
          "ambient",
          "frame"
        ],
        "instruction": "A coral colony that grows edge-first into whatever space `children` leaves open, rendered entirely in SVG with zero canvas. `seedCount` (default 5) polyps are planted along the container's `edge` (default \"left\"; also \"right\"/\"top\"/\"bottom\") at mount. Growth runs on a 60ms round-robin timer: each tick pops the next index off a FIFO queue of every live node (seeds and every bud since), and that node alone gets one bud attempt. Three candidate bud sites are proposed 15px out, fanned at -30/0/+30 degrees off the node's own heading (the direction it grew in from its own parent) plus up to +-8 degrees of random jitter for organic variety — jitter affects which sites are PROPOSED, never which one is ACCEPTED. Each candidate is scored by casting 5 shadow rays fanned across a 15-degree cone centred on the colony's current light angle; a ray counts as clear only if it reaches its full 46px length without crossing any existing skeleton segment (a real 2D segment-intersection test against the colony's own geometry, not a lookup table), and the candidate's exposure is the fraction of its 5 rays that came back clear. The candidate with the highest exposure buds only if that value exceeds 0.55; three rays produced visibly flickering, noisy shading during development, five did not. A node whose best candidate keeps failing simply sits in the queue, retried every future round, forever tucked in its own colony's shadow. `children` are measured via ResizeObserver on mount and every resize; any candidate within 48px of that measured box scores zero exposure unconditionally, before a single ray is cast — the copy is treated as a permanent occluder, the same mechanism that produces self-shading, not a clip-path laid over the result. The light angle itself drifts continuously at `lightDriftDegPerMin` (default 6) degrees per minute measured from mount, which is what makes the whole colony visibly lean over a session — every new bud's heading is inherited from its parent's already-drifted heading, so the lean compounds generation over generation. Reversing the drift direction (or the sign of `lightDriftDegPerMin`) visibly redirects which face of the colony keeps clearing its rays within roughly 10 further bud ticks, because acceptance is decided by the ray test at bud time, not baked into any existing geometry. Every successful bud walks its own parent chain to the root and adds 0.4 to every ancestor's `deposit`, and each segment's stroke width is `min(4.5, 1 + deposit * 0.4)` — a segment two ticks old stays hairline, a segment near the seed edge that fed dozens of descendants goes visibly fat, entirely from that walk, with no separate width pass. Independently of the bud timer, a random 2.6-5.4s interval picks any established (non-seed) node that isn't already bleached or dead and marks it bleached for 2.6s: every node in its subtree (checked by walking each node's OWN parent chain for a flag at render time, never copied down onto children) reads as `var(--ns-muted)` and stops budding for that span. On resolution roughly two times in three it recovers (flag cleared, colour and budding both resume); the other third it dies permanently, its whole subtree freezing as `var(--border)` bare skeleton and its tips dropped from the round-robin for good. Live tissue is `var(--foreground)`; both colours plus the accepting `<line>`/`<circle>` transition over 900ms so a bleach or recovery is visibly a fade, not a snap. Every currently-queued growth tip additionally renders a 3-4px \"cup\" `<circle>` (radius 1.5-2px, scaling slightly with its own deposit) marking the active growth front; dead tips render no cup, only bare line. The whole tree is capped at 400 nodes; once reached the bud timer keeps ticking harmlessly (bleaching continues) but nothing new is added. The SVG is `aria-hidden=\"true\"` with `pointer-events-none` and absolutely positioned under `children`, which render as completely ordinary, independently focusable DOM the colony only ever measures — it never wraps, clips, or intercepts them. `prefers-reduced-motion` runs the identical round-robin decision function synchronously up to 600 times at mount against a FIXED light angle, with the bleach interval never scheduled at all, then renders that one finished, fully static frame — same mechanism, no clock. Zero dependencies, DOM+SVG+CSS only, every colour a token (--background --foreground --ns-muted --border --ns-accent)."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "surface": "#171717"
        }
      },
      "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 --ns-muted --border --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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(--ns-muted)/var(--border) for rest state and var(--ns-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": "press-register",
      "type": "registry:ui",
      "title": "Press Register",
      "description": "An ambient conflict indicator staged as printing misregistration: a block with an unsynced collaborator prints a soft ghost plate of their version behind your text, offset by measured word-level divergence with matching SVG registration crosshairs at the corners, and resolving pulls the plates into register with a critically damped spring and a single crisp strike.",
      "files": [
        {
          "path": "registry/core/press-register/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/press-register.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "conflict",
          "collaboration",
          "diff",
          "dialog",
          "svg",
          "spring-physics",
          "aria-live",
          "word-diff"
        ],
        "instruction": "Build `PlateRegister({ text, theirText, collaboratorName, onResolve?, className? })`: a paragraph block that renders `text` (yours, always full opacity, always the only version in the accessibility tree) and, whenever it differs from `theirText` (the collaborator's unsynced draft of the same block), a ghost plate of `theirText` stacked directly behind it via CSS grid (`display:grid`, both `<p>`s sharing `grid-area:1/1` so the container auto-sizes to whichever is taller — no absolute positioning, no clipping). The governing scalar is `d`: word-level edit distance between the two strings (standard Levenshtein over `.split(/\\s+/)` tokens, insertions/deletions/substitutions each cost 1) normalized by your own word count, clamped 0..1. `d` maps linearly to a plate offset of 0-6px (`translate(d*6px, d*6px*0.4)`) plus `d*0.3deg` of `skewX` on the ghost only — hard-capped at 6px/0.3deg because past that the doubling stops reading as a measurable warning and starts reading as noise, and the offset is computed fresh from the two strings on every render that changes them, NEVER derived from time-since-desync (a collaborator parked on an identical draft for an hour must read as perfectly in register, not increasingly stale). The ghost renders `aria-hidden`, at 0.35 opacity, colored `var(--ns-muted)`. Four 8px SVG registration crosshairs — printer's alignment marks — sit just outside the block's four corners, each one a 'true' cross fixed in `var(--foreground)` plus a 'ghost' cross in `var(--ns-muted)` translated by that same (dx, dy) offset vector: the pressman's exact readout of how far out of true the plates are, rendered at full stroke strength (not softened like the ghost text) because it's the precise instrument, not ambient texture. All four marks and the ghost text disappear together once resolved — a single crisp block, no marks. When `d > 0`, render a real `aria-describedby` on the block pointing at sr-only text reading '{collaboratorName} has unsynced changes to this paragraph, {N} word(s) differ' (N = the raw edit-distance integer, not the normalized/capped d — magnitude must be legible as text, never left to the visual offset alone) and a real 'Review changes' button (`data-pr-review`) that opens a plain native `<dialog>` (`showModal()`, hand-rolled Tab-cycle focus trap, Escape/backdrop-click both close without deciding) showing their full paragraph text and three buttons: Keep mine, Take theirs, Merge. The dialog is explicitly centered with `position:fixed; top/left:50%; transform:translate(-50%,-50%)` rather than relying on the UA stylesheet's `margin:auto` default, which a host's own `margin:0` reset (Tailwind preflight and most resets apply one globally) silently defeats. It fades and scales in from `@starting-style` (a short honest transition under `prefers-reduced-motion`, opacity-only and not a frozen snap) rather than the UA's hard cut, and its `::backdrop` is a blurred, translucent wash of `var(--background)` — never `var(--foreground)` or a literal grey — so the scrim reads as a soft dim of the page in whichever theme is active instead of a flat grey plate. The button that opens the dialog must be idempotent (`showModal()` again if already open, never a toggle-closed), since the registry's autoplay/verify harness presses the first interactive control before checking any declared open-state gate. Any of the three buttons is a one-time, irreversible decision for the component's lifetime (guard with a ref, first call wins): 'Take theirs' swaps the displayed text to `theirText` (so the measured divergence naturally becomes 0 — texts are now identical); 'Keep mine' and 'Merge' leave the displayed text as `text` and instead flip an explicit `resolved` flag that forces the visual marks to clear regardless of any remaining textual difference (a decision here is about the block's sync state, not a promise the strings become byte-identical — real merge logic is the caller's concern via `onResolve(outcome, {mine, theirs})`, fired once at the moment of decision). Whichever button is pressed: close the dialog, then drive the offset from its current value to 0 with a critically-damped-feeling spring (stiffness 280, damping ratio 0.95, i.e. `c = 2*0.95*sqrt(280)`, integrated `accel = -k*x - c*v` every rAF frame) over roughly 450ms, and the instant it settles (or a 900ms safety deadline elapses) fire one 60ms 'strike': a `filter: contrast(1.2) brightness(1.06)` flash on the whole block via a retriggerable CSS class (force a reflow before re-adding it), then unmount the ghost/crosshairs entirely — the plates landing as one impression. A single `role=status aria-live=polite` sr-only region announces the outcome exactly once at decision time (e.g. 'Resolved — took Nadia's version, plates back in register'), independent of whether the spring is actually running. `prefers-reduced-motion: reduce` skips the spring outright: jump the offset straight to 0 and play only the 60ms strike frame (the strike itself is intentionally NOT gated behind reduced-motion — it's a single acknowledgement frame on already-rendered pixels, not a translate/scale motion, and the spec calls it out as the reduced-motion resolution in its own right). Idle rendering (before any resolve) is never animated regardless of reduced-motion, by construction: the offset is a plain per-render calculation, not a running loop, so there is nothing to gate. Zero dependencies; DOM + SVG + CSS only, no canvas — every ink comes from `var(--background)`, `var(--foreground)`, `var(--ns-muted)`, `var(--border)`, or `var(--ns-accent)` (accent reserved for hover/focus-visible states on the Review and dialog buttons, never a resting fill)."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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": "profilometer-trace",
      "type": "registry:ui",
      "title": "Profilometer Trace",
      "description": "A section divider rendered as a live contact profilometer readout: a fixed stylus near the right edge while the measured surface's roughness-plus-waviness trace scrolls continuously beneath it, right to left, at a constant real-time rate, peaks and valleys passing under the tip forever.",
      "files": [
        {
          "path": "registry/core/profilometer-trace/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/profilometer-trace.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "hr",
          "profilometer",
          "metrology",
          "surface-roughness",
          "iso-4287",
          "canvas",
          "monochrome"
        ],
        "instruction": "Build <ProfilometerTrace height? className?> as a <canvas> band wrapped in a <div role=\"separator\" aria-orientation=\"horizontal\">, a drop-in replacement for <hr>/border-top between page sections. SOURCE, NOT INVENTED: contact stylus profilometry (ISO 4287 / ASME B46.1 surface-roughness measurement) — a diamond stylus dragged across a surface at constant speed, its vertical deflection recorded as a trace that the standard cutoff-filter convention decomposes into roughness (short-wavelength) and waviness (longer-wavelength) components. THE MECHANIC: a stylus sits fixed at x = 82% of the band's width; the measured surface's trace scrolls continuously beneath it, right to left, at a constant 24px/s real-time rate (scrollX += (24/1000) * dt * speedMult every frame, dt = clamped real elapsed ms, never raw frame count — a rate tied to frame count reads as jittery on variable refresh and is an explicit kill criterion). THE TRACE IS NOT A RING BUFFER: it is a closed-form deterministic function of world-space x, height(x) = 0.6*valueNoise(x,18px) + 0.4*valueNoise(x,54px) [roughness, 2 octaves] + 0.4*sin(2*PI*x/240px) [waviness, amplitude 0.4x the roughness weight], normalized by the raw envelope (0.6+0.4+0.4=1.4) and scaled to a peak deflection of 0.35 * band height; valueNoise is a hashed-lattice value noise (mulberry32-style integer hash per lattice index, smoothstep-interpolated between neighbours) evaluated fresh at whatever world offset the clock has reached. Because the field is continuous and evaluated on demand rather than shifted through a fixed-size buffer, a wrap seam is structurally impossible — there is no buffer edge to wrap past. The band is drawn as a polyline sampled every SAMPLE_PITCH=4px across the visible width (visual resolution, not a data structure). REAL NUMBERS: scroll speed 24px/s (~6 new 4px samples/s at the sample pitch), stylus at 82% width, peak deflection 0.35 * height, roughness wavelengths 18px/54px, waviness wavelength 240px at 0.4x amplitude. THE STYLUS: a short vertical --foreground stroke from the zero line down to the trace's exact current height at the stylus's world x (the 'drop-off' read, recomputed every frame from the same height() function used for the polyline, never interpolated from the drawn path) plus a small filled --foreground circle at the tip. RESTING LOOP: t0 renders scrollX=0 already mid-trace (the closed-form field has no blank state to seed away from — every offset, including zero, shows real peaks and valleys); 2.5s later ~60px of new trace (24px/s * 2.5s) has scrolled through, so the segment under the stylus is a different peak/valley arrangement; 5s later a fully distinct excursion sits under the tip, proving continuous unbounded drift rather than a loop with a period a viewer could time. LEGIBILITY: the one thing to follow is the trace sliding past the fixed stylus tip, whose indicator line visibly rises and falls as peaks and valleys pass under it; cadence is a continuous constant-px/s drift (24px/s) rather than any discrete swap, so there is nothing to time a blink against — the eye tracks a smooth drift, not an event. INTERACTION: pointerenter on the band eases a single global speed multiplier toward 0.4 (a 60% slowdown, reading as 'zooming attention' on that stretch) over a 150ms time constant; pointerleave eases it back to 1.0 over a 400ms time constant per spec. This is a GLOBAL multiplier, not a per-x warp keyed to pointer position — the spec's 'radius around the pointer' language was implemented as attention-on-the-whole-band rather than a spatially localized warp, because slowing only the samples near the pointer's x while the rest of one continuous curve kept moving at full rate would tear the trace into two visibly different speeds across a single line, which is exactly the seam the kill criteria forbid. Scroll never fully stops at any multiplier value (floor 0.4x), preserving alive-at-rest. Hover must not, and does not, recolor the trace or stylus with --ns-accent. TOKENS: --foreground (trace stroke, stylus stroke+dot) and --ns-muted (zero-line baseline, drawn at 0.35 alpha) are read once via getComputedStyle(document.documentElement) before first paint and re-derived on a documentElement class MutationObserver; a `ready` gate (both values present) blocks every draw() call — including the very first resize(), the ResizeObserver callback, and the IntersectionObserver resume path — so nothing paints before the first successful token read. --border is deliberately NOT used for the zero-line: --border is a separator token measuring ~1.1:1 contrast in light theme and would make the required trace-vs-baseline separation (the spec's own light-theme legibility requirement) structurally impossible; --ns-muted at reduced alpha is used instead, a real token with real contrast that still reads as visually subordinate to the --foreground trace. --ns-accent never appears. GEOMETRY: derived from the band's own height (its only meaningful dimension at divider scale) via PEAK_RATIO; horizontal wavelength/pitch constants are fixed px values per the spec's real numbers and are not re-derived from width, so at a very narrow card the 240px waviness wavelength can read as a gentle tilt rather than a full period — acceptable, matches how real short-span waviness traces read, and roughness excursion alone stays well above the perceptual floor even at the minimum 48px band height (0.35 * 48 ≈ 16.8px peak-to-zero). Canvas uses devicePixelRatio-scaled backing store with CSS pixel style dimensions set explicitly (never relying on intrinsic canvas size). REDUCED MOTION: FREEZE_PHASE = deep-valley-lock — a deterministic search (since the field is seeded) over several waviness periods of scroll offset for the one where the trace directly beneath the fixed stylus x is at its minimum (deepest valley in the visible band), scrollX is set there once, draw() runs once, rAF never starts; hover listeners are disabled entirely under reduced motion. LIFECYCLE: a ResizeObserver re-measures the band on container size changes; an IntersectionObserver pauses the rAF loop while off-screen and resumes it with a fresh last-timestamp (no giant delta-time jump) on re-entry; a document visibilitychange listener does the same for tab backgrounding; unmount cancels the rAF, disconnects all three observers, and removes both pointer listeners. Direct-DOM rAF, zero React state on the hot path, zero dependencies. A11Y: role=separator carries divider semantics with no accessible name required; the canvas is aria-hidden decoration; there is no keyboard surface because there is nothing to operate — hover is a supplementary pointer-only affordance, not the mechanism that makes the component alive (the continuous scroll is), which is why autoplay.mode is 'none' rather than a pointer mode. Props: height (band height px, default 56, expected divider range 48-64), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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-ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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-ns-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-nlq-overstrike",
      "type": "registry:ui",
      "title": "Progress NLQ Overstrike",
      "description": "Determinate progress meter modeled on real 9-pin dot-matrix \"Near Letter Quality\" printing: the printed region is struck twice, the second pass offset by exactly half a dot pitch so it fills the gaps the first pass left, and the active strike column keeps dwelling in a two-phase cycle even when the value is held.",
      "files": [
        {
          "path": "registry/core/progress-nlq-overstrike/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/progress-nlq-overstrike.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "meter",
          "dot-matrix",
          "printer",
          "mono",
          "feedback"
        ],
        "instruction": "A determinate progress meter (value 0-100, controlled) sourced from a real dot-matrix printer mechanic: 9-pin \"Near Letter Quality\" mode, which faked higher resolution by striking every line TWICE — pass one lays dots on the head's native pitch, then the carriage returns and pass two strikes the identical line again with the head shifted by exactly half a dot pitch, so the second pass's dots land in the gaps the first pass left rather than on top of them, producing a visibly denser interleaved result. Every column left of the fill boundary already has both passes (solid double-density ink in --foreground). The boundary column is the one currently under the printhead: it runs a real, continuously repeating two-phase cycle off performance.now() — first pass alone, then both passes together (NLQ complete, the interleave clearly visible), then a brief carriage/paper-feed gap — independent of whether `value` is changing. That internal clock is what keeps a HELD, unchanging value alive at rest: the head is still sitting on the same line, still striking it, not idling. Columns right of the boundary are unprinted paper, drawn only as a sparse single-pass guide dot in --ns-muted, never doubled, so the unfilled track reads as track rather than absence. THIS IS DELIBERATELY THE OPPOSITE MECHANIC of the sibling component nav-overstrike-typewriter, which reproduces typewriter overstrike — stacking whole glyphs on top of one another in the SAME character cell, zero spatial offset. This component's signature is identical ink struck twice at a deliberate spatial offset of exactly half a dot pitch on the same row, a printer-hardware fact rather than a typography one — the half-pitch offset is drawn large enough (pitch/2, both lattices independently visible) that the two components can never be confused in a screenshot. Dot pitch is derived from the container's shorter dimension (height, since a progress bar is wide-and-short) folded through the same offscreen measureText \"@\"-advance-width convention the rest of the registry uses for cell metrics, clamped between 5px and 13px — below roughly 5px of pitch (about 40px of bar height) the half-pitch separation between a lattice-A dot and its lattice-B neighbor collapses below legible and the two passes visually fuse into one denser dot; the component still reads correctly as progress (dense vs. sparse) at that floor, it just stops demonstrating the NLQ interleave specifically, which is stated here rather than silently degrading. Colors read once via getComputedStyle on mount (a useLayoutEffect, before first paint) and re-read on a MutationObserver watching documentElement's class: --foreground for every struck dot, --ns-muted for the sparse unprinted guide dots, no --ns-accent anywhere — a progress fill is ambient state, not an interactive control, so accent chrome stays out of it entirely. role=progressbar carries aria-valuemin/valuemax/valuenow (rounded) plus an aria-label supplied by the caller (default \"Progress\"); the canvas itself is aria-hidden decoration on top of that real, announced control. prefers-reduced-motion (checked on mount and on every media-query change) cancels the rAF loop and freezes on a fixed non-zero fraction of the strike cycle — the midpoint of the \"both passes complete\" phase, not t=0 and not the cycle's blank feed-gap — so printed double-density ink, the frozen active-strike interleave, and the unprinted guide track are all simultaneously visible in the one static frame. The rAF loop itself pauses on IntersectionObserver (offscreen) and document.hidden (backgrounded tab), matching the rest of the suite's canvas host convention. Props: value (0-100, required), aria-label, className (size the bar here, e.g. \"h-16 w-72\" — the canvas fills whatever box it's given)."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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.",
        "rank": 286
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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.",
        "rank": 285
      }
    },
    {
      "name": "prompt-version-grain",
      "type": "registry:ui",
      "title": "Prompt Version Grid",
      "description": "A table of prompt blocks against releases: one filled cell wherever that block changed in that release, blank where it did not, plus each version's total token count.",
      "files": [
        {
          "path": "registry/core/prompt-version-grain/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/prompt-version-grain.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "agent",
          "llm",
          "prompt",
          "versioning",
          "history",
          "table",
          "data-viz",
          "mono",
          "dashboard"
        ],
        "instruction": "PromptVersionGrain renders a plain HTML table. Columns are prompt BLOCKS (`blocks: {id,label}[]` — system, persona, tools, rules, examples, format) as `<th scope=\"col\">`, rows are VERSIONS newest at the top (`versions: {id,label,tokens,changed}[]`) with the version label as `<th scope=\"row\">`. `changed` maps blockId to a boolean: true draws a 10px `--foreground` square in that cell, false leaves the cell blank. Every cell also carries a visually-hidden 'changed' / 'unchanged' word, so a screen reader gets the same facts as the swatch and nothing is conveyed by geometry alone. A trailing right-aligned column gives each version's total token count in Geist Mono tabular-nums. Rows are separated by a 1px `--border` hairline. A one-line legend under the table shows the same square next to the words 'changed in this release'. There is no hover state, no focusable element, no animation, no canvas, no SVG, no dependency and no hex literal — colours are only `--foreground`, `--ns-muted` and `--border`, so both themes come for free and `prefers-reduced-motion` is moot. Props: `blocks`, `versions`, `ariaLabel` (becomes the table's visually-hidden `<caption>`), `className`. The component is fully controlled and holds no state. Demo: twelve releases of a support-triage system prompt where `tools` changed in eight releases, `rules` in six, `persona` in two, `examples` in one, and `system` and `format` were never touched."
      }
    },
    {
      "name": "punch-figure",
      "type": "registry:ui",
      "title": "Punch Figure",
      "description": "An issued amount punched through the sheet as a dot matrix by a single left-to-right head pass: subtractive, permanent, and correctable only by a second pass punching VOID diagonally across it.",
      "files": [
        {
          "path": "registry/core/punch-figure/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/punch-figure.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "invoice",
          "payout",
          "amount",
          "checkwriter",
          "dot-matrix",
          "immutable",
          "micro-interaction",
          "accessibility"
        ],
        "instruction": "Render an issued amount (default $1,180.00, framed exactly as a checkwriter prints it) as a DOM dot-matrix punch card, never as flat text — the visible figure is a grid of 5x7-dot glyphs where a 'hole' is a cell whose background is set to var(--background) so it reads as cut clean through the sheet (the sheet itself is a slightly tinted card, `color-mix(in oklab, var(--foreground) 4%, var(--background))`, so a punched hole visibly differs from unpunched paper); unpunched cells render nothing at all, not a ghost/guide dot, because the card is physically blank there until the head arrives — never a fade-in or type-on, since typing implies backspace and the whole payload here is that a punched column cannot be un-punched.\n\nOne governing scalar, the print head's integer column position (headCol), derives everything: which columns are eligible to show their punched dots (columns after headCol render nothing, columns at or before it show whichever cells their glyph's bitmap marks), the sheet's per-column flinch (a 1px translateY dip with a slight spring-back overshoot, ~90ms, restarted via a classList remove/reflow/add trick on every column tick — never a React re-render of the whole sheet), and the 'carriage travel' strip (a thin 2px track under the figure with a fill and a small head marker whose width/position are `(headCol+1)/totalColumns`). The head steps at a firm 60ms per column via `setInterval` — faster and the flinch frames merge into vibration, slower reads as labouring; both are named failure modes, don't retune this without cause. Columns include a 1-column blank gap between glyphs, and the head steps through gap columns too (no flinch skip), giving the pass a slight breath between characters. Critically: one head, one pass, strictly left to right — column i's dots can never become visible before column i-1's, so drive headCol from a single incrementing interval, never compute it from multiple independent effects that could race.\n\nThe issuing sequence starts only from an explicit 'Issue' button click (real `<button>`, `data-pf-issue`, outside the punched grid — there is no interaction inside the mechanism itself). Before issuing, the button reads 'Issue'; mid-pass it reads 'Issuing…' and is disabled; once the head reaches the last column it reads 'Issued' and stays permanently disabled — an issued figure cannot be re-issued or edited, only voided. On completion, fire exactly one `role=status aria-live=polite` announcement: 'Invoice <id> issued for <amount>.' — not one announcement per column.\n\nCorrection is a second, independent punched pass: a 'Void' button (also outside the mechanism, disabled until issued) is confirm-gated — first click arms it (label becomes 'Confirm void', a `var(--ns-accent)` border cues the armed state, and it auto-disarms after 4s or on Escape or on blur); the second click while armed starts the void pass. That pass punches the literal word 'VOID' as its own 5x7-per-letter dot grid, rendered as a second absolutely-positioned layer centered over the amount grid and rotated -14deg, using a larger cell pitch than the amount grid so it reads as a stamp crossing it diagonally. It runs the exact same one-head/60ms-per-column mechanism as the issuing pass, on its own headCol, and shares the same sheet flinch. Voiding adds holes; it never removes or resets any hole already punched into the amount — the original figure stays fully intact underneath the VOID overpunch, because a void doesn't erase the record, it marks it dead. Once the void pass completes, both buttons are permanently disabled ('Issued' / 'Voided'), fire one more polite announcement ('Invoice <id> voided.'), and nothing in the component can change state again.\n\nAccessibility: the entire punched-grid mechanism (both layers, the flinch, the carriage track) is `aria-hidden`. The real amount always exists as plain text in the accessible tree via a `sr-only` node — `<label> <id>: <amount>` plus a short draft/void suffix reflecting current phase — present from first paint regardless of whether the head has punched anything yet, so a screen-reader user is never waiting on an animation for the number. A small plain-text status badge (Not yet issued / Issuing… / Issued / Voiding… / Void) sits next to the buttons for sighted users who don't want to parse the dot grid.\n\nColors: `--background --foreground --ns-muted --border --ns-accent` only, plus `color-mix()` built from those tokens for the sheet tint and track fill — no hex, no `rgb()/hsl()`, no Tailwind palette classes. `--ns-accent` appears exactly once, as the armed-Void border/text cue, and nowhere else — it is not used for the punch holes or the sheet itself. Radii: 16px on the outer sheet, full (9999px) on the punched holes, the carriage-track pieces, and the pill-shaped Issue/Void buttons. `prefers-reduced-motion: reduce` skips the per-column interval entirely on both passes: clicking Issue (or Void, once armed and confirmed) jumps straight from -1 to the final column in one paint, the flinch keyframes and track transitions are suppressed via `!important`, but the button/gate flow and the one-time announcements are unchanged — reduced motion is still fully usable, just without the physical theater. Pure DOM + CSS, no `<canvas>`/WebGL. Distinct from card-number-emboss: that component is additive (typed digits raise as embossed metal while the user is still mid-entry, and can be edited freely up to blur-validation) — this one is subtractive (material is punched OUT of the sheet in a single committed pass, and once punched the figure is permanently read-only, correctable only by overpunching VOID, never by editing)."
      }
    },
    {
      "name": "punch-patch",
      "type": "registry:ui",
      "title": "Punch Patch",
      "description": "A role x permission matrix rendered as a Jacquard card where authority is asymmetric material: granting punches a clean hole through the card stock, revoking never un-punches. It pastes a visible patch over the hole instead, so a cell that was ever revoked stays permanently distinguishable from one that was never touched.",
      "files": [
        {
          "path": "registry/core/punch-patch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/punch-patch.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "matrix",
          "permissions",
          "access-control",
          "grid",
          "checkbox",
          "admin",
          "svg",
          "aria-grid",
          "micro-interaction"
        ],
        "instruction": "Build a role x permission access matrix styled as a punch card whose material remembers its own history. DATA: roles (rows) x permissions (columns, some carrying a `requires` id naming another permission in the same set), plus defaultGranted, defaultRevoked (cells that start already patched — off, but previously held) and inherited (roleId+permissionId+from, rendered as sealed non-interactive eyelets) props. STRUCTURE: real ARIA grid — role=grid on the wrapper, role=row per row, role=columnheader for each permission, role=rowheader for each role name, role=gridcell wrapping each control. Every directly-editable cell is a real button role=checkbox aria-checked, accessible name '{role}: {permission}' (e.g. 'Editors: delete projects'). STATE PER CELL: two booleans, granted (current) and patched (sticky for the session — true forever once the cell has ever been revoked). RENDER LAYERS (all SVG, back to front): a faint dashed punch-site rim in --border; if patched, a patch group (a 1px-offset shadow rect in --foreground at low opacity, a solid --ns-muted patch rect 1px smaller than the rim on every side so the rim peeks through, and two small torn-corner tick marks in --foreground) that mounts once, ever, animating in with a 200ms ease-out translateX+opacity entrance the first time patched flips true, and stays mounted permanently after — this is what a re-grant punches through; a hole circle, always mounted, radius 8 on bare stock or 6 when patched (so it reads as a smaller void cut through the patch's centre), filled var(--background), scaled 0->1 and faded 0->1 on every grant/revoke via a 160ms cubic-bezier(0.3,0.9,0.4,1) transform transition with no overshoot (percussive, hard stop, not springy) — this single scale/opacity pair is what animates both a fresh punch and a punch-through-patch identically; a transient chad piece (small --ns-muted disc) that mounts only on a live user-triggered grant (never for seeded defaultGranted state) and plays a 200ms translateY(10px)+fade-out keyframe animation once via natural conditional mount/unmount, never replayed while the cell stays granted. TOGGLE: click or Space on a cell flips granted; going true->false sets patched=true (revoke, sticky) and announces '{permission} revoked for {role}. Previously granted.'; going false->true is a grant — if the permission has `requires` and the role doesn't yet hold that dependency, cascade-grant it in the SAME state update (both cells' hole transitions start together, not sequenced) and additionally fire one shared 'punch bar' — a thin --ns-accent bar measured from the two cells' real getBoundingClientRect()s via a ref map, positioned absolutely over the row, playing one 320ms scaleX(0->1)+fade keyframe connecting them — then announce '{permission} granted for {role}. Also granted: {dependency}.' via a single aria-live=polite status region; a plain grant with no cascade announces '{permission} granted for {role}.'. INHERITED CELLS: role=checkbox aria-checked=true aria-disabled=true aria-describedby pointing at a per-cell tooltip node reading 'Inherited from {parent role}' (kept in the accessibility tree at all times via opacity, not visibility/display, so assistive tech always gets it; shown visually on hover/focus via group-hover/group-focus-visible opacity), rendered as a sealed eyelet — solid rim circle stroke var(--foreground) plus a smaller inner ring, no punch/patch mechanics, not clickable (no onClick), but still reachable by grid navigation and Tab. PATCHED-CELL DESCRIPTION: every currently-off, ever-patched cell carries aria-describedby pointing at one shared sr-only node reading 'Previously granted, later revoked.' — the accessible description that keeps history honest even though aria-checked reads false. NAVIGATION: roving tabindex over the interactive+eyelet cells in a 2D roles x permissions array — only the focused cell has tabIndex 0, arrow keys move focus row/column-wise (clamped, no wrap), Home/End jump to the row's first/last column; real buttons already give Space/Enter the native click, no extra key handling needed for toggling. TOKENS: --background --foreground --ns-muted --border --ns-accent only, --ns-accent used exclusively for the transient cascade punch bar and focus rings (interaction-only). Reduced motion: every keyframe animation and the punch/patch CSS transition collapse to none via a top-level @media query plus a JS matchMedia read at mount, so grant/revoke/cascade apply their end states instantly with no chad and no bar. No canvas."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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, --ns-muted for secondary text, --ns-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": "quoin-lockup",
      "type": "registry:ui",
      "title": "Quoin Lockup",
      "description": "A bento grid whose only variable is pressure, not cell assignment: four quoins hold a fixed five-tile arrangement flush, pressure creeps down until a tile pies out of plane, and a quoin-by-quoin re-key — occasionally a full planer sweep — brings it back, forever.",
      "files": [
        {
          "path": "registry/core/quoin-lockup/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/quoin-lockup.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "grid",
          "layout",
          "bento",
          "letterpress",
          "print",
          "keyboard-navigation",
          "accessibility"
        ],
        "instruction": "Build <QuoinLockup tiles? planerEvery? className?> — a bento grid borrowed from letterpress lockup on the imposing stone, where the interesting variable is PRESSURE distributing a layout that never repacks (the line separating this from grid-bento-dense/grid-bento-ascii, both re-assignment problems). FIXED ARRANGEMENT: 5 tiles on a 3-content-column x 4-content-row track grid — hero 2x2 (cols1-2,rows1-2), one 1x2 tall (col3,rows1-2), one 2x1 wide (cols1-2,row3), two 1x1 (col3 row3; col1 row4) — with the one leftover cell (cols2-3,row4) rendered as a plain furniture block, no tile: the real process packs every empty area with furniture rather than leaving it inert. A literal 3x3 (9 cells) cannot hold this tile set: 2x2 + 1x2 + 1x2 + 1x1 + 1x1 = 4+2+2+1+1 = 10 cells of area, one more than 3x3 provides, so the base grid is extended to 3x4 (12 cells) rather than dropping a tile's area to force the fit. Below M=320px (M = min(width,height)) this collapses to 3 tiles on a 2x2 base (one 2x1 wide + two 1x1). Grid tracks interleave CONTENT/GUTTER/CONTENT on both axes; every grid-column/grid-row span is a module-level constant written once — pressure only ever changes gutter TRACK SIZE via CSS custom properties (--gw-gx1/gx2/gy1/gy2/gy3) set imperatively on the grid container in a single rAF loop, never React state, so a 60fps decay never re-renders the tree. PRESSURE MODEL, a pure function of absolute elapsed time (never integrated dt, so any instant reproduces byte-identically): P decays from 0.93 to 0.62 at 0.055/s (~5.64s). At P<0.62's lift threshold a re-key phase runs: 4 quoins turn in fixed order (right-top, foot-right, right-bottom, foot-left) 640ms apart, each rotating its key 90 degrees over 320ms and stepping P by +0.19 (clamped at 0.93) over the turn's final 180ms, critically damped (smoothstep here). Full cycle ~8.2s (5.64s decay + 4*640ms=2.56s re-key), unbounded, never terminating. Every `planerEvery` cycles (default 3, every ~24.6s) a planer bar sweeps left-to-right across the form during the re-key phase's first 700ms, forcibly zeroing each tile's sink the instant the bar's x-progress passes that tile's x-fraction — the component's striking moment. FURNITURE: gutter width in u = 0.0125*M ('one pica') is quantised to the nearest of {2,3,4,6,8}*u from a target that widens linearly as P falls — never a continuous value — rendered as a color-mix(in srgb, var(--background), var(--foreground) 40%) fill with a repeating-linear-gradient butt-joint line every 0.11*M using color-mix(var(--border), var(--foreground) 30%). PIEING: each tile has a fixed seeded hold H_i = P + 0.10*JITTER[i] (JITTER = [-0.62, 0.35, -0.88, 0.71, 0.08], hardcoded, never Math.random, so the same tile always loosens first); when H_i<0.62 the tile sinks translateY up to 0.028*M, scales to 0.978 and tilts 0.9deg about a fixed per-tile axis, gated entirely by CSS custom properties (--sink/--tile-scale/--tile-tilt) so the transform composites without layout thrash. Pie depth carries two independent value cues: a box-shadow toward --foreground scaling with sink fraction (a literal loss of local contrast under the tile, not a colour), and a border-top toward color-mix(var(--foreground) 22%) that fades as sink increases (loss of edge contrast against the still-flush neighbours) — deliberately NOT the spec's literal mix(bg,fg,0.10) hairline, which sits within 1% of the tile face itself and is invisible; the edge is pushed to ~22% foreground so it has contrast to lose in the first place. QUOINS: 4 opposing-wedge pairs (2 right edge, 2 foot), each a pair of divs skewed 14 degrees (skewY on the right edge, skewX on the foot) at two different color-mix stops so the direction of travel reads without hue; the sliding half translates by up to 1.0u, driven by the same rAF loop. A small bordered key marker docks at whichever quoin is mid-turn, rotating 0-90deg over its 320ms window, opacity 0 outside the re-key phase. INTERACTION: hovering or focusing a tile steps its 1-2 adjacent gutters down one legal size (a hardcoded per-tile adjacency table, e.g. the hero borders {gx2,gy2}) — the tile's own span never changes, only the furniture around it narrows, and the creep/re-key loop keeps running underneath. Tiles are real <a>/<button> elements with accessible names; roving tabindex plus ArrowLeft/Right/Up/Down cycle through the fixed tile order (never DOM reorder, since the arrangement never changes, focus order is the visual order by construction). COLOUR: everything is CSS var()/color-mix() consumed directly by Tailwind classes and inline styles — a `dark:[--x:Y%]` class flips a small custom property that a color-mix formula reads, so there is no getComputedStyle/MutationObserver pipeline and nothing can paint before a token resolves, because nothing ever reads a token in JS; matches grid-bento-dense, the nearest sibling in this collection. `prefers-reduced-motion` calls the same pure state(t) function once at STATIC_TIME=6.3s (mid re-key: some quoins keyed, one tile still pied, the key sitting in a socket) and never starts the rAF loop; IntersectionObserver and visibilitychange both stop it otherwise. M is measured in useLayoutEffect via ResizeObserver on the host so no frame ever paints the form at the wrong size."
      }
    },
    {
      "name": "rack-seat",
      "type": "registry:ui",
      "title": "Rack Seat",
      "description": "Step-up auth drawn as switchgear racking: a sensitive-actions group sits 12px back on a visible rail with its contacts unmade until fresh verification racks it in, then physically withdraws over the elevation window instead of expiring invisibly into a 401.",
      "files": [
        {
          "path": "registry/core/rack-seat/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/rack-seat.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "auth",
          "mfa",
          "step-up",
          "verification",
          "settings",
          "danger-zone",
          "svg",
          "aria-live",
          "accessibility",
          "mono"
        ],
        "instruction": "`<RackSeat actions={RackAction[]} expiresAt elevationMs? accelerateMs? label? title? onRequestVerification? onVerify? onActivate? className? />` renders a group of sensitive actions as switchgear racking rather than a disabled-and-dimmed button row. One governing scalar, rackTravel (0..1), computed purely from `expiresAt` (an owner-supplied epoch ms, or null) against the real clock, drives every visual: the action group's own `transform: translateX(-(1-rackTravel)*12px)` (12px of physical travel, racked out to the left at 0, flush/seated at 1), a paired contact-chevron gap in a fixed 36px rail zone to the group's left that closes over the same 12px as travel rises, and a 6-tick gauge on that rail's centerline that fills tick-by-tick from --border to solid --foreground as `Math.round(travel*6)` crosses each tick. RACKING IN: when `expiresAt` transitions from null/lapsed to a fresh future timestamp, travel is driven by the SAME per-frame clock computation but the group's transform additionally gets `transition: transform 450ms cubic-bezier(0.16,1,0.3,1)` (ease-out-expo) for exactly that 450ms window, so the seat reads as one deliberate mechanical motion, not a snap. WITHDRAWAL: for the rest of the window travel is recomputed every animation frame directly from elapsed-vs-elevationMs with no CSS transition at all (the continuous function itself is the motion) — a plateau phase drifts travel from 1.000 down to 0.995 (sub-pixel, purely for physical continuity: 'creeps back continuously through the window'), then the final `accelerateMs` (default 10s, clamped to elevationMs) plunges it from 0.995 to 0 on an ease-in cubic (`0.995 * (1-p**3)`), which is the visibly accelerating pull-back the brief calls for. THE INTERACTIVE FLIP — aria-disabled and what a click does — is derived from the same scalar via `seated = travel >= 0.995`: true for the entire plateau (so a verified group is genuinely usable for the whole window, not for one animation frame) and false for the entire final accelerate segment, so the group is already back to its locked behavior for the full length of the visible withdrawal the user is watching — elevation lapses in view, never invisibly at the last millisecond. Every action renders as a real, always-focusable `<button>` with `pointer-events` never touched and no disabled attribute — only `aria-disabled` (present only while `!seated`) and `aria-describedby` flip, pointed at one of two sr-only paragraphs: 'Requires recent verification. Activating starts verification.' while racked out, 'Verified. Activating performs this action.' once seated. Pressing a racked-out action calls `onRequestVerification(id)` and opens an inline step-up strip in place (never a dialog) with a 'Verify identity' button; pressing a seated action calls `onActivate(id)`, the real action. The strip is rendered BEFORE the rail/group in DOM source order but reflowed after it visually via `order-*` (head=order-1, strip=order-3, track=order-2) — deliberate: the demo driver and the verifier's own pre-gate press both walk the DOM in tree order and re-query `target` every cycle, so once the strip exists a union selector's first match becomes the Verify button instead of re-finding a rack action, letting one autoplay descriptor script open-then-complete step-up without special-casing it. Pressing 'Verify identity' calls `onVerify()` — the actual MFA belongs to the host app; this component only asks for it — and the strip self-closes the moment `seated` goes true, whatever produced that (an instant mock in the demo, a slower real flow in production). SEATED READOUT and STATUS: a Geist Mono line above the rail reads 'Locked · requires verification', 'Unlocked · 4:32 remaining' (live mm:ss), or 'Withdrawing · 0:07 remaining' once the accelerate phase starts, so the countdown itself narrates which phase is live. A visually-hidden `role=status`/`aria-live=polite` region announces exactly once per transition: 'Verified. Sensitive actions unlocked for 5 minutes.' on seat and 'Verification expired, sensitive actions locked.' on the window running out — both derived from the real clock crossing zero inside the same per-frame check, not from a prop merely changing, so a window that simply elapses without the owner touching `expiresAt` still announces correctly. The whole group is `role=region` with `aria-label` defaulting to 'Sensitive actions, verification required'. REDUCED MOTION (`matchMedia` with a live change listener) drops the per-frame creep entirely: a coarse 1s interval sets travel to a flat 0 or 1, the 450ms seat transition and tick/chevron fade transitions are removed, and the readout collapses to exactly two literal strings — 'Locked, requires verification' / 'Unlocked for 5 minutes' — so the state is carried by static position and plain text, never motion alone. No disabled-opacity veil anywhere: every action stays fully legible and physically present at every travel value, which is the falsifiable property this component is built to satisfy. Colours are `--background --foreground --ns-muted --border` only via Tailwind token classes and `currentColor`, plus a visible `--ns-accent` focus-visible ring on every control — `--ns-accent` never marks state. DOM + SVG + CSS, no canvas, zero dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "surface": "#171717"
        }
      },
      "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-ns-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.",
        "rank": 287
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 (--ns-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 --ns-muted --border --ns-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": "raft-moor",
      "type": "registry:ui",
      "title": "Raft Moor",
      "description": "A collaborator presence rail where each teammate's live scroll position becomes a jump-to chip, and teammates who land on the same stretch raft outward in arrival order instead of collapsing into a '+n' count.",
      "files": [
        {
          "path": "registry/core/raft-moor/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/raft-moor.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "presence",
          "collaboration",
          "nav",
          "scroll",
          "multiplayer",
          "avatar"
        ],
        "instruction": "Build <RaftMoor collaborators idleThresholdMs? railHeightVh? className?> as a plain-DOM component — no canvas, no SVG. COLLABORATORS is {id, name, initials?, fraction, sectionId, sectionLabel}[] where fraction (0..1) is the governing scalar: a collaborator's live position through the document, and it sets that chip's target y DIRECTLY against a fixed-position rail (left-4, height railHeightVh vh, vertically centered) — a 1px --border line down its left edge. Each chip is a real <button> 20×20 (rounded-sm, border-border, bg-background), showing 1–2 uppercase Geist Mono initials (derived from name when omitted), positioned absolute inside a <ul> of real list items inside a <nav aria-label=\"Collaborators' positions in this document\">. THE MECHANIC (raft/moor): every recompute, sort chips by target y and walk them into clusters wherever consecutive chips are within 24px of each other (chip height is 20px, so anything clustered is genuinely at risk of overlapping and anything NOT clustered — more than 24px apart — geometrically cannot overlap even flush). Within a cluster, rank members by arrival order: an id keeps its existing arrival timestamp as long as it shares at least one raft-mate with its immediately prior cluster (still rafted together); otherwise (newly joining this particular stretch, including from solo) its arrival timestamp resets to now. The earliest-arrived member gets raft slot 0 (x=0, flush to the rail); each subsequent member gets slot n at x = n × (chip width 20 + 6px gap) — moored one chip-width further out, never a '+n' badge, never a collapsed count. Solo chips (no cluster-mate within 24px) always render at slot 0 and have their arrival record cleared, so a future join at that stretch is correctly treated as a fresh arrival. MOTION: chip position is a per-id semi-implicit-Euler spring (stiffness 260, damping 30, just under critical) integrated in one requestAnimationFrame loop that writes only via el.style.transform (translate3d, no React state on the hot path) and sleeps once every chip's velocity and position error are both negligible; a first-ever appearance is placed directly at its target with no fly-in. IDLE: independently of the spring, a 1.5s interval (independent poll, not the rAF loop — an idle check is about elapsed wall time, not spatial motion) tracks the last moment each id's fraction or sectionId actually changed; past idleThresholdMs (default 60000) since that moment, the chip's opacity eases to 0.5 over a 300ms CSS transition — and that transition is NOT gated behind reduced motion, since easing opacity isn't spatial. REDUCED MOTION: prefers-reduced-motion swaps the spring for direct assignment — position snaps to target instantly on every recompute, no rAF loop runs — while the idle-opacity fade keeps easing exactly as above. INTERACTION: clicking a chip calls scrollIntoView (behavior 'smooth', or 'auto' under reduced motion) on the element with id sectionId, then moves focus to it — adding tabindex=\"-1\" first if it lacks one — after a 450ms delay (immediately under reduced motion) so focus doesn't yank mid-scroll. --ns-accent appears ONLY as the hover/focus-visible ring on a chip button — never as a resting fill, border, or any encoding of identity or raft order; a resting chip is border-border/bg-background/text-foreground only. A11Y: every chip's accessible name is a full sentence rebuilt whenever its data changes — 'Jump to Sam Okafor, 60% through, section Pricing' — so position is announced when focus lands on it and NOT streamed via any live region (a live region tracking scroll position would fire constantly and be unusable). Props: collaborators (required), idleThresholdMs (default 60000), railHeightVh (default 70, the rail's height as a percentage of viewport height), className."
      }
    },
    {
      "name": "range-light-transit",
      "type": "registry:ui",
      "title": "Range Light Transit",
      "description": "An ambient convergence moment modelled on maritime range lights: a near light and a far light drift independently on incommensurate periods, sliding apart and repeatedly back into vertical alignment, briefly brightening together each time they agree.",
      "files": [
        {
          "path": "registry/core/range-light-transit/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/range-light-transit.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "sync",
          "ambient",
          "svg",
          "maritime",
          "convergence",
          "monochrome",
          "connection"
        ],
        "instruction": "Build <RangeLightTransit label? height? className?> as a card-scale SVG panel modelled on maritime range lights (leading lights): a pair of navigation marks at different distances/heights that a pilot keeps vertically stacked ('in transit') to hold a safe channel course (USCG / Trinity House leading-line marks). Two discs drift on independent vertical guides inside the card — the front (near) light lower at 78% of card height, the rear (far) light higher at 24% — each offset horizontally from the card's own centre by its own sine of elapsed time: front amplitude = 18% of card width, period 6.2s; rear amplitude = 14% of card width, period 9.7s. The two periods are incommensurate on purpose so alignment moments recur aperiodically rather than on a fixed beat; the front and rear discs also render at different radii (rear = 0.75x front, both derived from min(card width, card height) and clamped 3-7px) so the pair reads as near/far, not as two identical dots. A single thin line is drawn every frame directly between the two disc centres — vertical exactly when their horizontal offsets match, visibly tilted otherwise — so the tilt straightening out on approach and reappearing on departure IS the 'converging' read, not a separate abstraction. Alignment is defined as |frontOffset - rearOffset| < 3% of card width. The line's opacity per frame is the sum of two effects: a continuous 'how close' glow — closeness = clamp(1 - gap/(3x threshold), 0, 1), opacity contribution = 0.12 + (0.55 - 0.12) * closeness^2, a small resting floor (0.12) rather than a bare closeness^2 term because at these amplitudes (18%+14%) the raw gap spends over half of any long run beyond the approach window entirely — a pure closeness^2 term left the connecting line fully invisible more than half the time, reading as two unconnected drifting dots rather than a converging pair — that climbs smoothly as the gap shrinks even before true alignment, plus, only while the gap has stayed continuously under the 3% threshold, an eased ramp (easeOutCubic) from that glow value up to full opacity over 250ms of continuous dwell. That 250ms gate is deliberate: two independent sines produce many fast, glancing zero-crossings of their difference (every ~1.5-4s at these periods) that touch the threshold only briefly; only a crossing with real dwell time — genuinely slowing near zero, not just passing through it — reads as an actual transit crossing and earns the full brighten. The SAME 250ms dwell gates the arrival cue itself: it fires only once the gap has already dwelled inside the threshold for 250ms+, then triggers at the true local minimum of the gap (detected by watching the gap shrink then start growing again while still inside the threshold, not by the ramp reaching 1, so it never re-fires on a lingering close pair): held at full for 300ms, then eased back to baseline over 600ms — never a single-frame flash. With this gate, full alignment events (guideline reaching full opacity, arrival cue firing) land roughly every 1.5-11s at these periods/threshold — irregular, not a fixed beat, but comfortably inside any few-second glance window; without the gate the raw zero-crossings of the two sines would fire a cue every ~1.5-4s and read as generic blinking. The cue itself is one CSS formula in both themes, no per-theme branch: `filter: drop-shadow(0 0 (6*pulseFactor)px var(--foreground))` on both discs plus a synchronized radius grow (+15% at peak) — because --foreground is already the token that flips per theme, this reads as a soft light-coloured halo blooming around the disc in dark theme, and as a deepening dark shadow around it in light theme, both from the same expression (a literal `brightness()` filter was tried first and clamped to a barely-visible +7.6% swing against near-white --foreground in dark theme, so the cue is carried by shadow + size instead of a filter that can clip — check this explicitly, it is the one place this cue's mechanism had to be rebuilt to work in both themes). All colour is read once via getComputedStyle(document.documentElement).getPropertyValue('--foreground') before the first paint and re-derived on a documentElement class MutationObserver (theme flip repaints live); disc fill and guideline stroke are both --foreground (so discs render as bright dots on the dark card in dark theme and as filled dark shapes on the light card in light theme, for free, from the same token) — --border and --ns-accent are never used, since the guideline is not a UI separator and this component has no interactive chrome. Geometry (disc radius) is derived from min(containerWidth, containerHeight); horizontal drift amplitude and the alignment threshold are fractions of container WIDTH specifically, so the component still reads correctly in a wide, short card. RESTING LOOP: t0 — discs at some nonzero offset, guideline at or near its resting floor; 2.5s — the front light alone has moved through roughly 40% of its own 6.2s period, both disc positions visibly different from t0; 5s — a different offset again, plausibly mid-approach to or departure from an alignment event. REDUCED MOTION: freezes at t = 1.753s via a single direct call to the render function (never by running the rAF loop once), so the dwell-ramp term is always exactly 0 for that frame and the guideline shows the continuous glow term alone — at that instant the gap is 1.32% of width (inside the 3% threshold and still closing, a genuine approach rather than an incidental touch), which puts the glow term at ~0.43 opacity: both discs visibly offset, the line visibly present but not fully bright, no arrival cue active. The full-alignment frame is deliberately avoided (the shadow/grow spike would read as blown-out or flat on a static frame) and so is a maximum-offset frame (the guideline sits at its 0.12 resting floor there, the least structured option). LIFECYCLE: the SVG is measured via getBoundingClientRect on mount and on a 120ms-debounced ResizeObserver; the rAF loop accumulates real elapsed time (capped at 100ms/frame) rather than assuming 60fps, pauses via an IntersectionObserver when off-screen and via a visibilitychange listener when the tab is hidden (both resume with a fresh last-timestamp, no time-jump), and every rAF/observer/listener is torn down on unmount. Under prefers-reduced-motion the render function is called exactly once at the fixed freeze time and no rAF loop ever starts. autoplay: none because the pair drifts on its own internal clock regardless of any pointer/scroll/press input — there is nothing for a synthetic-input driver to trigger, and this is explicitly an ambient-only surface (interaction: none). A11Y: the SVG is aria-hidden; it sits inside a role=img wrapper with an aria-label describing the reading in prose ('two lights drifting independently, briefly aligning as they converge') since there is no single scalar to expose. Below the panel, a static Geist Mono caption row reads the two REAL NUMBERS that never change — front and rear period in seconds — legible independent of motion. Zero dependencies, DOM+SVG+CSS only, no canvas, every colour a token. Props: label (accessible name and visible caption, default 'Sync transit'), height (svg panel height px, default 200), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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, --ns-muted, --border, --background, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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; --ns-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.",
        "rank": 289
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-muted --border), --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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 --ns-accent — the single, sole use of --ns-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.",
        "rank": 288
      }
    },
    {
      "name": "remnant-cut",
      "type": "registry:ui",
      "title": "Remnant Cut",
      "description": "A plan-change proration panel drawn as a draper cutting cloth: the used billing period dims and stays, the unused remnant slides into the new plan's row re-measured against its rate, and the cents lost to rounding render as a labelled sliver at the cut instead of vanishing into a footnote.",
      "files": [
        {
          "path": "registry/core/remnant-cut/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/remnant-cut.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "billing",
          "proration",
          "svg",
          "pricing",
          "confirm",
          "radio",
          "physics",
          "accessibility"
        ],
        "instruction": "Renders a plan-change proration readout (RemnantCut) as two horizontal SVG cloth bolts, one governing scalar driving every number and every shape on the first: `cutFrac = elapsedDays / currentPlan.periodDays`. The OLD STRIP is a woven-hatch rect (a small crosshatch `<pattern>`, `text-border` at low opacity) framed by denser 3-line 'selvage' rules at both ends; a dimmed `text-ns-muted` 'offcut' rect covers `[0, cutFrac]` of it (the used days) and a brighter `text-foreground` 'remnant' rect covers the rest (the unused days) until a plan is picked, at which point the remnant is replaced by the animated piece described below. The cut itself is drawn as a 2px `<polyline>` through 8 points with a small perpendicular jitter from a mulberry32 PRNG seeded by a FIXED constant (never derived from props) — so the waver is identical on every render of every input, deliberately: a dead-straight line reads as a CSS border, a wavered one reads as a blade that actually passed through. `creditRaw = (1 - cutFrac) * currentPlan.price` is computed from the exact same `cutFrac` that sized the remnant rect — never a second, independent computation — and is floored to the nearest `roundingUnit` (dollars, default 0.01) to get `creditedAmount`; the difference, `kerf`, is never absorbed silently — it renders as its own thin `text-foreground` sliver rect sitting between the offcut and the remnant at the cut, labelled 'kerf' in small SVG text, sized (with a legibility-only minimum width) from the same amount-to-pixels conversion used everywhere else. offcut + kerf + remnant reconstruct the old strip's full width exactly; credit (2dp) and charge, once rounded to what a bill actually shows, do not sum back to a clean total on purpose — the kerf sliver and its 4-decimal-place `$0.00XX` label are where the missing sub-cent fraction visibly lives. Bar HEIGHT on every strip maps to that plan's daily rate (`price / periodDays`) through one shared `pxPerRate` scale, and width maps to days through one shared `pxPerDay` scale (both computed from every plan in play, so any prop set stays legible) — the product of the two, `K`, is a fixed px-per-dollar constant, meaning a rect's pixel area IS its dollar value everywhere on the panel, not just conceptually but by literal arithmetic identity. Picking a candidate plan (real native `<input type=radio>` rows in a `<fieldset>`/`<legend>`, each a full label with a custom `text-foreground` dot indicator and a sr-only-clipped native input carrying real checked-state semantics) re-measures the SAME `creditedAmount` against that plan's daily rate: `creditedDays = creditedAmount / newDailyRate`, and the remnant animates from its old-strip position/size to a new rect at `x=0` on the new strip and `width = creditedDays * pxPerDay`, `height = newDailyRate * pxPerRate` — reading that final width off the new strip's day ticks gives the exact same day figure the confirm button states. The animation is a single 600ms ease-out-expo (`1 - 2^(-10t)`) progress value driving a `requestAnimationFrame` loop (no CSS transition, since height must be DERIVED not independently tweened): width is linearly interpolated between the start and end widths using the eased t, and height is computed every frame as `creditedAmount * K / width(t)` — since both the start and end rects already satisfy that identity, the rectangle's on-screen area is exactly `creditedAmount * K` at every intermediate frame by construction, not merely at the two endpoints; the vertical baseline and horizontal position are eased the same way so the piece visibly travels from the old row to the new one while it re-shapes. Re-selecting a different plan (or the same one again) always restarts the tween from the old strip's pristine cut position, never chains from wherever the previous tween ended, so every choice re-runs the same shear-and-slide. A permanently-visible (not sr-only) `role=status`/`aria-live=polite` paragraph states the full sentence in plain text on every change — raw credit, days unused, rounded credit, the kerf amount, and once a plan is picked, the day count applied against that plan's rate and the new period's start date — so sighted and assistive-tech readers get identical figures at the identical moment; nothing on the cloth (both SVGs are `aria-hidden`) is interactive or is the source of truth for any number. The Confirm button is a real `<button>` whose visible text states the derived figures outright (e.g. 'Confirm: $8.41 credit, 6.5 days applied, $0.0094 rounding, new period starts Aug 17'), stays disabled with its own accessible 'Select a plan to continue' label until a plan is chosen, and calls `onConfirm` with the full numeric summary. `prefers-reduced-motion: reduce` (via `matchMedia` with a live listener) skips the rAF loop entirely and renders the remnant at its final reflowed rect immediately on selection — fully legible, no motion. Every ink is a token class — `text-foreground`/`text-ns-muted`/`text-border`, `--ns-accent` used solely for the sibling-selector keyboard focus ring on the hidden native radio inputs — no hex, no canvas; pure DOM + SVG + CSS."
      }
    },
    {
      "name": "remontoire-rewind",
      "type": "registry:ui",
      "title": "Remontoire Rewind",
      "description": "A sync/upload progress strip driven by a constant-force remontoire: a coiled spring glyph slowly winds tight over a 2.9s creep, then trips and dumps one fixed, calibrated 6% kick into the bar in 0.3s (the escapement's flat repeated release, not the mainspring's decay), rewinding forever, unbounded.",
      "files": [
        {
          "path": "registry/core/remontoire-rewind/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/remontoire-rewind.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "upload",
          "sync",
          "loader",
          "spring",
          "svg",
          "mechanical",
          "ambient"
        ],
        "instruction": "A progress `role=progressbar` strip whose motion is a constant-force remontoire, not a plain glide. A coiled-spring SVG path (a 100x24 viewBox, `preserveAspectRatio=\"none\"` so it stretches to the container's width while its height stays fixed like a sibling bar's track) is resampled every animation frame as a plain sine wave — `y = 12 + amplitude * sin((i/samples) * turns * 2*PI)` across 49 samples — where `turns`, a continuous float, is the ONE value driving the whole glyph: fewer turns at the same horizontal span reads as coils bunched/compressed together (wound), more turns reads as the loose, fully relaxed spring (just tripped). One real-time cycle is a fixed 3.2s (2900ms wind + 300ms trip), compressed ~9.4x from a typical 30s precision-clock remontoire rearm interval, run on a single rAF loop keyed off real elapsed time via `performance.now()` (never frame count, never a CSS keyframe animation), unbounded and self-driven — nothing here waits on pointer/press/scroll input, hence `autoplay: none`. During the wind phase, `turns` eases from 5 down to 2 via `5 - 3 * t^1.6` (t = elapsed/2900, a power-based ease-in: slow to start, visibly accelerating into the trip, approximating a spring's rising resistance near full wind) and the uncontrolled fill accumulator creeps forward at a fixed 0.3%/s (barely perceptible on its own). During the 300ms trip phase, `turns` eases back from 2 to 5 through a hand-solved cubic-bezier(.2,1.4,.4,1) — solved via 20 iterations of bisection on the bezier's x(u)=t before reading y(u), since the y control point (1.4) sits above 1 and produces exactly the spec's 'one small overshoot' past 5 before settling — while the fill accumulator, independently, jumps forward a fixed 6% (roughly 20x the creep rate) via `1 - (1-t)^3` ease-out so the trip reads as a distinct kick, never a continuation of the creep; the accumulator wraps at 100% back to 0% with no pause at the seam (an idle/demo loop, not a terminating transfer). The instant a trip completes, a small leading-edge `<div>` (absolutely positioned at the fill's own right edge, `bg-foreground` at full opacity) flashes to opacity 1 with `transition: none`, then on the very next frame gets a `150ms ease-out` transition back to opacity 0 — a pure luminance lift on `--foreground`, never `--ns-accent`, matching the binding rule that accent stays interaction-only chrome. Optional controlled `value` (0-100): when supplied, the fill `<div>`'s width tracks `clamp(value, 0, 100)` directly every frame instead of the internal accumulator, while the spring glyph and its trip flash keep running their own 3.2s wind/trip clock untouched — pure 'work is happening' chrome layered over a real reading, exactly as the spec requires; `aria-valuenow`/`aria-valuetext` mirror the same controlled value, and omit `aria-valuenow` (falling back to `aria-valuetext=\"Syncing\"`) when uncontrolled, since the internal accumulator is decorative, not a real percentage. Track is `border border-border` (separator only) over a `bg-ns-muted/20` base fill — never `--border` itself as the empty-track fill, per the binding token rule that `--border`'s ~1.1:1 light-theme contrast is invisible as a fill. `prefers-reduced-motion` skips the rAF loop entirely and freezes on turns=3.5 (deliberately mid-wind — neither the 5-turn 'just tripped' start nor a fully wound 2-turn end) with the bar sitting at a static 46% (uncontrolled) or the live controlled value, and the leading-edge flash held at a static opacity 0.55 with no transition — reads as 'a mechanism mid-cycle', not a single fired moment. An IntersectionObserver pauses the rAF loop whenever the strip scrolls offscreen and resumes it on return; cleanup cancels the rAF and disconnects the observer on unmount. Props: `value` (0-100, optional, uncontrolled ambient loop when omitted), `label` (default \"Sync progress\"), `className`. Zero dependencies, DOM+SVG+CSS only, no canvas, every colour a token (`--background --foreground --ns-muted --border`), `--ns-accent` unused entirely since this component has no interaction chrome to spend it on."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": "retrieval-chunk-sieve",
      "type": "registry:ui",
      "title": "Retrieval Chunk Sieve",
      "description": "RAG retrieval as a literal sieve: one hard rule across the frame with the chunks that cleared the similarity cutoff resting solid above it and the rejects falling as faint stubs below, and a connector rail that separates what was merely retrieved from what the answer actually used.",
      "files": [
        {
          "path": "registry/core/retrieval-chunk-sieve/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/retrieval-chunk-sieve.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "agent",
          "llm",
          "rag",
          "retrieval",
          "provenance",
          "threshold",
          "data-viz",
          "mono",
          "slider",
          "grounding"
        ],
        "instruction": "Build <ChunkSieve chunks cutoff? defaultCutoff? onCutoffChange? height? label? className?> as a fixed-height bordered frame that draws RAG retrieval as an actual sieve, so the idle frame states the result with no caption. GEOMETRY: a 2px --foreground rule spans the full width at exactly 42% of the frame height (LINE_PCT 0.42, height default 440px, so the rule sits at 184.8px and the composition is top-heavy on purpose). Chunks are {id, doc, span:[start,end], score, used?, preview}; they are sorted by score DESCENDING and laid out as equal 1fr columns in a flex row, so x is RANK and y is SCORE — the two axes a retrieval panel usually collapses into a sorted list. RETAINED (score >= cutoff): a solid hairline card, 52px tall, border --border on --background, whose BOTTOM edge sits on the rule and is lifted (score − cutoff) × 240px above it, clamped so its top never crosses the answer rail. The card carries three things and nothing else — the doc id in 10px Geist Mono, the char span rendered '[1200-1480]' in 9px --ns-muted, and a 34px score bar (a --border track with a --foreground fill at score × 100%). REJECTED (score < cutoff): no card at all, just a bare 1px --foreground rule with the doc id under it at 0.38 alpha (STUB_ALPHA — the floor at which a 1px hairline and 9px mono stay legible in both themes, ~2.6:1 light / ~3.0:1 dark; anything near 0.14 composites to ~1.35:1 and the whole rejected half of the sieve vanishes from the resting frame), sunk (cutoff − score) × 300px BELOW the rule and tapering its column width from 1fr down to 40% in proportion to how far it has fallen — so the rejects read as dust thinning out toward the bottom of the frame and most of the lower half stays empty. ANSWER RAIL: a 1px --border rule runs across the frame 24px from its top, labelled 'answer' in 9px uppercase mono at the right. A chunk with used:true draws a 1px --foreground connector from its card's right edge straight up to the rail, terminating in a 3px filled square; a retained chunk WITHOUT used draws nothing at all. That absence is the whole argument: 'retrieved but never cited' — the actual RAG failure mode — becomes legible as an empty column above a solid card, with no badge, no second color, and no legend. INTERACTION: the rule is the control, not a decoration. It is a real role=slider (aria-orientation=vertical, aria-valuemin 0 / aria-valuemax 1 / aria-valuenow, aria-valuetext 'cutoff 0.742, 6 of 12 chunks retained') inside a 40px-tall grab strip with cursor:ns-resize and a --ns-accent focus-visible ring drawn at a NEGATIVE outline-offset, because the strip runs edge to edge and an outward offset puts its left/right caps under the frame's overflow-hidden. pointerdown captures the pointer and a vertical drag maps 1px = 0.004 score, DOWNWARD lowering the bar (clamped 0…1); every chunk that crosses the new cutoff reflows between the two zones on one shared 240ms cubic-bezier(0.22,1,0.36,1) transform + opacity transition — the reflow IS the feedback, there is deliberately no separate readout animation. Release snaps the value to 3 decimals and fires onCutoffChange (a drag on a controlled instance still reflows live via an internal override, and only commits on release). ArrowUp/Down (and Left/Right) step 0.01, PageUp/Down step 0.1, Home/End jump to 0 and 1. A live mono readout 'cutoff 0.742 · 6/12 retained' sits just above the rule at the right; it is aria-hidden, because the slider's own aria-valuetext already announces the identical string and an aria-live copy would double-announce on every arrow key and every frame of a drag. HOVER: every chunk, retained or rejected, is a real button with a full aria-label ('api-ref.mdx, characters 1200 to 1480, score 0.903, retained, cited in the answer'). Hovering or focusing one lifts it 2px, raises it to full alpha, dims every other chunk (retained to 0.35, rejects to STUB_ALPHA_DIM 0.15) and opens its text preview in a mono strip pinned to the frame's bottom edge over 180ms ease-out; pointerleave/blur relaxes all of it over the same 180ms. AMBIENT: the rule breathes ±0.5px vertically on a 4s ease-in-out loop so it reads as suspended rather than printed, and any chunk that newly crosses INTO the retained zone pulses opacity 0.6 → 1 once over 900ms on arrival. COLORS: --foreground for the rule, card text, score-bar fill, stubs and connectors; --border for the answer rail, card borders and the score-bar track; --ns-muted for the char spans, the readout and the preview strip; --ns-accent is spent ONLY on focus rings, never on the data. REDUCED MOTION: a prefers-reduced-motion media block in the component's own <style> kills the breathe, the arrival pulse and the reflow transition — the drag still works and values snap. Implementation is DOM + CSS only: no canvas, no SVG, no ResizeObserver and no measurement pass, because the card height, rail offset and rule position are absolute constants, which is what lets each connector's length be computed as lineY − lift − 52 − 24 at render time. Props: chunks (required), cutoff (controlled), defaultCutoff (0.5), onCutoffChange, height (440), label ('Similarity cutoff'), className."
      }
    },
    {
      "name": "return-aviso",
      "type": "registry:ui",
      "title": "Return Aviso",
      "description": "An @mention chip that behaves like registered post: a stub tears off along a dashed perforation on send and only returns (filled, timestamped) once the mentioned person actually looks.",
      "files": [
        {
          "path": "registry/core/return-aviso/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/return-aviso.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "mention",
          "chip",
          "read-receipt",
          "delivery-status",
          "link",
          "micro-interaction",
          "accessibility"
        ],
        "instruction": "A radius-6 bordered @mention chip, name set in Geist Sans with no fill, whose entire visual life is a pure function of one controlled scalar prop, deliveryState: 0 (sent), 1 (delivered) or 2 (seen) — there is no other animation input, and the component never advances this itself. A thin 3px stub sits along a dashed perforation cut into the chip's trailing edge, always visible as a faint scored line even at rest. While deliveryState is 0 or 1 the stub is detached: translated 12px clear of the chip, unfilled (border-only), holding at 40% opacity — sent and delivered render byte-identical on purpose, because the mentioned person hasn't looked either way and the chip has nothing new to report until they do. The instant deliveryState reaches 2, the stub travels back over 220ms cubic-bezier(0.22,1,0.36,1) and docks flush against the chip's own corner radius, widening and filling solid with --foreground — a permanent filled notch, not a temporary highlight — while a 10px Geist Mono HH:MM timestamp unfurls beside it via an animated CSS grid track (0fr to 1fr) rather than a hard reflow, so the receipt eases into place instead of popping. The HH:MM text is supplied pre-formatted by the caller (a seenAt string prop, not a Date) precisely so the same receipt can't read a different clock time on the server that renders it than on the browser that hydrates it — formatting is the caller's timezone/locale call, not this component's. Hard constraint: deliveryState is fully controlled and driven only by confirmed events from the sync layer; nothing inside this component ever sets or infers a seen state optimistically on send, since a receipt that fires before the fact is decoration wearing a receipt's clothes. The chip itself is a real <a> to the mentioned person — its accessible name is the plain-text \"@name\" content — carrying aria-description \"mentioned, seen 14:22\" or \"mentioned, not yet seen\" so the exact same information the shape carries is always available as text; the stub, the perforation and the inline timestamp are all aria-hidden, since they would otherwise just duplicate that description rather than add to it. A visually-hidden aria-live=\"polite\" region announces the not-seen -> seen crossing (\"@name saw your mention at 14:22\"), but only when the optional authoredByViewer prop is true and only past the first render — a chip that mounts already seen (a reloaded page, say) has no fresh news to announce, and a receipt is only news to the person who sent the mention, not to every other viewer of a thread that happens to mention them. prefers-reduced-motion removes the stub's travel and width change entirely — it sits permanently docked and the three states crossfade through opacity and fill alone, still fully legible, just without the physical journey. Pure DOM/CSS, no canvas; colors are --background, --foreground, --ns-muted and --border only, with --ns-accent reserved strictly for the focus-visible ring. Differs from badge-unread-tarnish on purpose: that component ages the CURRENT user's own unread state over elapsed clock time; this one tracks a discrete, event-driven round-trip acknowledgment of someone ELSE's attention, and never touches a clock on its own."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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/--ns-accent/--foreground/--surface/--ns-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": "ring-stain",
      "type": "registry:ui",
      "title": "Ring Stain",
      "description": "A loader built on the coffee-ring effect: 90 particles drift outward on a capillary-flow velocity profile and pin at the rim, so waiting visibly deposits a denser and denser ring. Determinate progress reads as coverage angle, indeterminate progress reads as density, and completion contracts the residue onto a checkmark.",
      "files": [
        {
          "path": "registry/core/ring-stain/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ring-stain.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "progressbar",
          "svg",
          "determinate",
          "indeterminate",
          "particles",
          "accumulation",
          "accessibility"
        ],
        "instruction": "Build <RingStain value? complete? size? label? className?> as a plain SVG + CSS progressbar, zero dependencies, no canvas. MECHANISM: 90 small <circle> particles (r 0.5-1.5, deterministic per-index via a golden-ratio sequence so sizes vary without touching Math.random during render) sit inside a <g transform=\"translate(50 50)\"> clipped to a circle of radius 44, alongside one thin stroke=\"var(--border)\" circle at radius 36 (the rim, drawn once, always visible — the meniscus). Each particle is simulated in polar coordinates relative to that translated center: it spawns near r=0 at a random angle, then every animation frame integrates an outward radial velocity v(r) = V_BASE + (V_RIM - V_BASE) * (r/RIM)^2.6 (V_BASE=5, V_RIM=46, units/sec) — slow near the center, sharply accelerating near the rim, the same shape as the outward capillary flux that pins real coffee grounds to a drying drop's contact line — plus small per-frame Brownian jitter on both theta and r. Position is written as a CSS `transform: translate(x px, y px)` on the circle's own inline style every frame (never as cx/cy, and never a CSS transition on that property during normal drift — it is manually integrated at 60fps, not eased), so the whole simulation is transforms-only, matching the no-canvas/no-filter constraint. ELIGIBILITY AND FREEZING: when a particle's r reaches the rim it either freezes there — pinned, r clamped to RIM, fill flipped from var(--ns-muted) to var(--foreground) via a `ns-rs-deposit` class whose CSS transition on `fill` and `opacity` gives the brighten a soft 220ms fade, and it is never touched again — or, if ineligible, respawns at r≈0 with a fresh random angle and speed multiplier and keeps circulating. In DETERMINATE mode (value 0-100) eligibility is a contiguous rim ARC starting at 12 o'clock and sweeping clockwise for `value/100` of the circle — recomputed live from the current prop on every arrival, so a particle that misses an earlier, narrower arc can still land once the arc has grown to include its angle. Coverage angle is therefore the progress reading; there is no separate progress arc drawn, the dot pattern itself is the readout. In INDETERMINATE mode (value omitted) every angle is eligible but the total number allowed to freeze is capped by `1 - exp(-elapsedSeconds / 9)` (an asymptotic curve, checked against a running frozen-count ref) — so density alone encodes elapsed wait time, never resolving to a full ring on its own. COMPLETION: triggered when `value` reaches 100 or the `complete` prop is set (the latter is the escape hatch for an indeterminate wait that resolves without ever carrying a percent). On the transition into completion, every still-drifting (non-deposited) particle fades its opacity to 0 over 200ms — the interior clears — while every deposited particle eases, via a 460ms cubic-bezier(.34,1.56,.64,1) spring transition newly applied to its transform, onto a point sampled with `getPointAtLength` along a short checkmark path (evenly distributed across however many particles actually deposited) — the residue itself rearranges into the check rather than being swapped for a separate glyph. A thin var(--foreground) stroke of that same path draws in underneath via the standard pathLength=1 / stroke-dasharray '1 1' / stroke-dashoffset technique (420ms cubic-bezier(.34,1.56,.64,1), 90ms after the particle motion starts) so the mark reads clearly even when only a few particles deposited. Falling back out of completion (a reused loader starting a fresh wait) fully resets the particle field — new random spawns, cleared deposits, dashoffset back to 1 — rather than resuming stale state. PREFERS-REDUCED-MOTION: the entire drift simulation is skipped. The same 90 circles are placed once at fixed, evenly spaced rim-slot angles and never move again (`transform` is set once at init and never touched afterward) — only their `ns-rs-deposit` class toggles at a coarse ~800ms interval (determinate: whichever fixed slots fall inside the current progress arc; indeterminate: a golden-ratio-ordered permutation of slot indices sliced by the same density-cap formula, so the reveal scatters rather than sweeping like a wipe). Only opacity/fill transition — the same 180ms CSS rule under the media query — so the accumulation metaphor survives with zero position change. The completion spring is also suppressed under reduced motion (particles jump straight to their checkmark points, the stroke reveals instantly). ARIA: the SVG is aria-hidden; a wrapping div carries role=\"progressbar\", aria-valuemin=0, aria-valuemax=100, aria-label from `label`, aria-valuenow set to the rounded percent only in determinate mode (omitted entirely while indeterminate, the correct indeterminate signal) and pinned to 100 once settled regardless of the exact value that triggered completion, and aria-busy=true while indeterminate and not yet settled. A separate visually hidden `role=\"status\" aria-live=\"polite\"` span carries a plain-language status line ('Uploading files, 42 percent.' / 'Syncing workspace, ring 63 percent filled.' / 'Uploading files complete.') refreshed on a 1500ms interval, never per animation frame. TOKENS: every fill/stroke is var(--ns-muted) (undeposited particles), var(--foreground) (deposited particles, the checkmark stroke), or var(--border) (the meniscus ring) — no --ns-accent anywhere, since nothing here is interactive. Props: value (0-100, omit for indeterminate), complete (force the completion sequence), size (px, default 120), label (default 'Loading'), className. Display-only: no pointer or keyboard interaction, no focusable control, correctly exempt from the tabbability check."
      }
    },
    {
      "name": "riso-drum-pass",
      "type": "registry:ui",
      "title": "Riso Drum Pass",
      "description": "A loader / background ambient texture reproducing a risograph duplication run: three sequential drum passes reading the same shared stencil field, each a rotation sweeping the sheet top-to-bottom, cumulatively drifting a few pixels out of register against the pass before it, with per-rotation drum-pressure banding baked into each row as it prints.",
      "files": [
        {
          "path": "registry/core/riso-drum-pass/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/riso-drum-pass.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "background",
          "ambient",
          "riso",
          "risograph",
          "print",
          "canvas",
          "monochrome"
        ],
        "instruction": "Build a loader / background ambient texture, canvas-rendered, reproducing a risograph duplication run sourced from real riso GR/MZ drum duplication (a perforated cylindrical drum wrapped in a stencil master, ink forced through the stencil's micro-perforations onto paper as the drum makes one full rotation per printed sheet; multi-colour riso work is always run as sequential separate drum passes, one physical drum per colour). Root is a `relative h-full w-full overflow-hidden bg-background` element holding an absolutely positioned, `aria-hidden` canvas filling the host — no card copy, no text-protection carve-out, this is a pure ambient surface a page places behind or beside content, not a card.\n\nStencil grid pitch is derived from the host's own smaller dimension via `ResizeObserver`: `clamp(minDim/40, 5, 10)` px, dot diameter `0.8 * pitch` (radius `0.4 * pitch`). Run three sequential passes per cycle, each pass occupying a 2000ms slot budgeted as a 1400ms live top-to-bottom sweep (one drum rotation) followed by a 600ms pause standing in for the physical drum swap — three slots back to back total exactly 6000ms (`SWEEP_MS=1400, PAUSE_MS=600, PASS_SLOT_MS=2000, CYCLE_MS=6000`), then the cycle restarts immediately with a freshly cut stencil, no invented rest period. Track cycle time as `performance.now() - cycleBase`, incrementing a `cycleIndex` and rebasing `cycleBase` every time the cycle wraps, so every lap reseeds the stencil field (`seed = cycleIndex*997.13`) rather than replaying an identical loop.\n\nCritical to the mechanic actually being visible: all three passes read from ONE SHARED value-noise stencil field per cycle (`hash01(i*0.37+seed, j*0.41+seed)` sampled once per grid cell, reused by all three passes), not three independently-seeded fields — an uncorrelated random field shifted by a couple of pixels shows no perceptible change, while a SHARED field shifted a couple of pixels visibly doubles up and drifts. Each pass nudges the shared field's open/closed cutoff by a small per-pass delta (`threshold = 0.5 + (passIndex-1)*0.04`) standing in for that colour's own stencil-cutting tolerance. A pass only draws a row once its sweep front (`progress * rows`, progress = `clamp((cycleTime - passStart(k)) / SWEEP_MS, 0, 1)`, `passStart(k) = k * PASS_SLOT_MS`) has reached or passed that row — this is what makes the pass visibly sweep top-to-bottom rather than pop in fully formed. Each pass's dot centre is offset from the shared grid by a CUMULATIVE registration drift of `(passIndex * 1.3px, passIndex * 0.7px)` — the same sheet drifting further through each successive nip, not three independent misalignments — which is the core visible mechanic: pass 2's dots land 1.3/0.7px off pass 1's shared dots, pass 3's land 2.6/1.4px off pass 1's.\n\nDrum pressure banding: multiply each dot's base alpha (0.55) by `1 + 0.08 * sin(2*PI*(row/(rows-1)) + passIndex*2*PI/3)`, using the ROW's position as a proxy for rotation angle around the drum and phase-shifting a third of a turn per pass so the three passes' bands beat against each other instead of stacking into one static gradient. This value is computed once, at the moment that row's sweep front passes it, and never re-modulates afterward (a real drum's pressure at a given rotation angle was fixed the instant that row printed). Cap the combined alpha where multiple passes land on the same shared dot at 0.82 via a running Porter-Duff union per cell per frame: `alpha_k = min(bandedAlpha, (0.82 - prior) / (1 - prior))`, `prior = prior + alpha_k * (1 - prior)` — in practice this only visibly thins a genuine triple-overlap dot's third layer, letting the first two passes render at their full nominal alpha so the registration drift stays legible as separate ink layers rather than flattening into one solid tone.\n\nAlso draw a faint, luminance-only sweep-front 'nip line' (a soft foreground-tinted linear gradient band, peak alpha ~0.05, spanning roughly 4 pitches tall) at whichever pass is currently mid-sweep, giving the drum's current contact line a visible presence without ever using `--ns-accent`.\n\nInk colour is `--foreground` read via `getComputedStyle(document.documentElement)` at mount and re-derived on a `MutationObserver` watching `documentElement`'s class — never a literal — so ink polarity is automatically correct in both themes; verify at card scale in light theme that the 0.82 alpha cap still reads as layered ink rather than a flat dark block. `--ns-accent` is never touched anywhere in this component. DPR-capped (max 2) backing store sized off the host's own `getBoundingClientRect`. The animation loop is a plain `requestAnimationFrame`, paused via `visibilitychange` when the tab is hidden and via `IntersectionObserver` when scrolled offscreen, and driven purely by elapsed time (never frame count) so a slow machine still completes each sweep in real seconds. `prefers-reduced-motion: reduce` stops the loop entirely and freezes at the spec's explicit `STATIC_TIME_MS = 4200` — passes 1 and 2 fully complete (their sweeps end at 3400ms), pass 3 already sweeping (its sweep starts at 4000ms) — the most structurally dense, most legibly drifted static frame short of the whole cycle finishing. No dependencies."
      }
    },
    {
      "name": "roller-break-reduce",
      "type": "registry:ui",
      "title": "Roller Break Reduce",
      "description": "An ambient multi-stage pipeline indicator built as a flour mill's break system: a stream of particles shrinks by roughly a third every time it crosses a nip between two differentially-spinning corrugated rollers, three or four stages left to right.",
      "files": [
        {
          "path": "registry/core/roller-break-reduce/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/roller-break-reduce.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "progress",
          "pipeline",
          "status",
          "canvas",
          "generative",
          "ambient",
          "stepper",
          "processing"
        ],
        "instruction": "Build <RollerBreakReduce activeStage? className?> as an ambient multi-stage pipeline indicator whose mechanism is a flour mill's break system: grain is worked down through a sequence of paired corrugated rollers (B1..B4), each pair spinning at a fixed 2.5:1 differential (fast roll 1.5 rev/s over slow roll 0.6 rev/s, the real mill ratio) so the nip SHEARS the stock rather than crushing it, narrowing size by roughly 35% at every pass. Stage count and roller diameter derive from the container's SMALLER measured dimension via a ResizeObserver: 3 stages if min(width,height) < 280px, else 4; roller diameter = min(w,h) x 0.14. Rollers are plain DOM: two rounded-full divs per stage (fast on top, slow on bottom) separated by a per-stage nip gap that narrows stage to stage, each painted with a base fill of var(--ns-muted) and a repeating-conic-gradient rim of 60 fine var(--foreground) ridges (a corrugation pattern), rotated purely by writing a JS-accumulated angle straight to style.transform every animation frame — the rotation rate is our own accumulator (rev/s times elapsed ms), never tied to a real display or timer frequency, so it cannot alias into a strobe at any paint rate. A single 2D canvas overlay (w-full h-full, JS-set style + backing-store dimensions scaled by a clamp-2 devicePixelRatio) draws the particle stream: particles spawn at the left edge at 3/s with an 8-10px random diameter, travel each inter-stage gap (feed-to-nip0, nip-to-nip, last-nip-to-exit) in a fixed 900ms eased transit, and every crossing of a nip triggers a 150ms compression — during that window the particle's canvas ellipse widens on x and flattens on y (a real squish, peaking at the window's midpoint) while its diameter interpolates smoothly from the pre-stage size down to pre-stage x 0.65, so the size loss reads as a deformation under load rather than an instant swap. A nip crossing also splits the particle 1:2 (a kernel breaking into fragments): the passing particle spawns a sibling at the same new stage with fresh vertical scatter, capped so total on-screen particles stay in the 25-35 steady-state band the spec calls for; particles that finish the final travel segment past the last roller pair are simply dropped, so the feed-spawn-split-exit cycle runs forever with zero settle state. All particles carry a small constant per-particle vertical jitter around the shared nip centreline while travelling, which the compression window eases to exactly zero as they thread the gap, so the stream reads as scattered flow that narrows to a point at every nip. Colour discipline: the canvas reads var(--foreground) via getComputedStyle(document.documentElement) once at mount into a ref and again only from a MutationObserver on the documentElement's class attribute (a theme flip) — never per frame, and there is no ctx.fill before that first read resolves; the DOM roller/hairline colours are plain CSS var() references (--ns-muted body, --foreground ridges, --border for the thin per-stage boundary hairline, which is a separator only and never a roller fill). An optional activeStage prop (0-indexed) marks one stage's roller pair as the current step via a CSS filter: brightness(1.4) luminance boost on that stage only — never var(--ns-accent), and it is synced by its own effect so it updates even while the reduced-motion frame is frozen or the rAF loop is paused off-screen. Growth/paint loop is driven by a single requestAnimationFrame accumulator using real elapsed time (not fixed 900ms wall assumptions per frame), paused via an IntersectionObserver when the component scrolls off-screen and a visibilitychange listener when the tab backgrounds, and every rAF/ResizeObserver/IntersectionObserver/MutationObserver/listener is torn down on unmount. Under prefers-reduced-motion the rAF loop never starts at all: the component renders one deliberately non-t0 static frame — rollers frozen at an arbitrary non-zero rotation (not phase 0), one particle resting mid-travel in every segment so the full large-to-small size gradient is visible across the whole pipeline at once, and the stage-index-1 particle ('stage 2') caught exactly at its compression window's peak squish, mid-nip between its roll pair. Zero dependencies, DOM + CSS transforms for the rollers, canvas only for the particle stream, every colour a token (--background --foreground --ns-muted --border), no literal anywhere including canvas fallbacks."
      }
    },
    {
      "name": "roller-occlusion",
      "type": "registry:ui",
      "title": "Roller Occlusion",
      "description": "A determinate-feel loader ambient drawn as a real peristaltic pump: a three-roller rotor spins above a horizontal tube, each roller pinching the lumen fully flat as it passes and driving a visible fluid slug ahead of it while the tube wall rebounds viscoelastically in its wake. Quasi-continuous flow, never a percentage bar.",
      "files": [
        {
          "path": "registry/core/roller-occlusion/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/roller-occlusion.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "ambient",
          "canvas",
          "pump",
          "peristaltic",
          "mechanism",
          "hover"
        ],
        "instruction": "Build <RollerOcclusion label? className?> as a Canvas 2D component that fills its parent container (h-full w-full, no intrinsic size of its own — the demo wraps it in a sized box). GEOMETRY, derived every resize from minDim = Math.min(containerWidth, containerHeight): tube length = minDim*0.7, tube diameter = minDim*0.18 (tubeRadius = diameter/2), rotor radius = tubeDiameter*1.4, roller radius = tubeDiameter*0.62 (deliberately larger than a literal 12% reading of the source spec, which would be too small to ever close the lumen — sized instead so the roller circle genuinely overlaps and flattens the tube at closest approach). Rotor pivot is horizontally centered above the tube's midpoint with its bottom-most orbit point exactly on the tube centerline (rotorCenterY = tubeCenterY - rotorRadius), so 'tangent to the tube centerline' is a geometric fact, not an approximation. MECHANISM: 3 rollers at 120° spacing, each roller's position on the rotor circle is rollerX = pivotX + rotorRadius*cos(angle), rollerY = rotorCenterY + rotorRadius*sin(angle) (canvas convention, angle in degrees, 90° = straight down = closest approach to the tube). A roller occludes only within a ±70° window around its own 90° approach (140° contact arc); within that window, occlusion 0→1 eases over a FIXED 60ms of wall-clock time as the roller enters (converted to an angular fraction of the window using the CURRENT rotor speed, since wall-clock viscoelastic response doesn't scale with pump RPM), holds at 1 for the remainder, then eases 1→0 over a fixed 90ms as it exits — this asymmetry (open-time 90ms > close-time 60ms, both fixed regardless of the rotor's instantaneous speed) is load-bearing: dropping it or making them equal makes the loop read as a generic ball-on-a-track and is a stated kill criterion. Tube lumen half-height at any x-sample = tubeRadius * max(0.04, 1 - occlusionAt(x)*0.96), where occlusionAt(x) is the max, across all 3 rollers, of that roller's own occlusion value times a Gaussian falloff (sigma = rollerRadius*0.9) in x-distance from the roller's CURRENT x-position — this is what makes the pinch visibly travel along the tube as the rotor turns rather than squeezing one fixed spot. Render by sampling ~96 columns across the tube length each frame: fillRect the wall (half-height = the value above) and a thinner fillRect on top for the fluid lumen (half-height = wall half-height * 0.6), then stroke (never fill) a traced envelope of the same wall half-heights in --border for the outline. FLUID SLUG: one slug per roller gap, length = rotor circumference / 3, painted as a repeating sawtooth luminance ramp along x (leadFrac = 1 - (((x - slugOffset) % period + period) % period) / period, color = mix(--ns-muted, --foreground, leadFrac) — a color gradient, never an opacity gradient, so it composites correctly under both themes). slugOffset is an unwrapped px value that advances by 1.08 tube-lengths per rotor REVOLUTION (not 1.0 — an integer ratio locks the slug pattern to the same tube pixels every revolution and the loop visibly freezes every ~5s, a stated kill criterion). REST STATE (t0): rotorDeg starts at 90 (roller 0 dead-bottom, fully occluding, lumen at 4%) and slugOffset starts at tubeLength/2 (a slug boundary — the sawtooth's brightest point — sits exactly at the tube's midpoint). Both rotorDeg and slugOffset are unwrapped monotonic accumulators driven by dt each rAF frame, so 2.5s and 5s in are genuinely different frames (a different roller occluding, the previous roller's wall mid-rebound, the slug pattern advanced a non-integer number of tube-lengths) rather than a repeat. TOKENS: --foreground, --ns-muted, --border, --background read once via getComputedStyle(document.documentElement) as hex strings before the very first draw call (no paint before that first read — verified on the rAF start, the ResizeObserver resume path, AND the IntersectionObserver resume path, all of which call the same wake()/draw() functions that assume tokens are already populated), and re-read on a MutationObserver watching documentElement's class attribute. THEME: `document.documentElement.classList.contains('dark')` branches the wall fill — dark theme is a flat --ns-muted fill; light theme ramps --background → --ns-muted across the tube's x-axis (checked first: an all---muted wall on a light page nearly disappears at low opacity, so light theme never uses a flat muted fill). --border is stroke-only on the outline trace, never a fill. --ns-accent is never read or used anywhere in this component — nothing here is interaction chrome. INTERACTION: hovering or focusing the root nudges rotor speed to a flat 1.6x for the duration (an immediate snap up, reading as 'spinning up'), then decays back to 1x over 400ms (smoothstep) on pointer leave or blur; it changes ONLY the numbers feeding the same occlusion/slug math (never pauses the rotor, which would read as broken, and never touches color). PERFORMANCE: refs-only hot path, DPR backing store clamped to 2 with explicit canvas.style.width/height (a canvas is a replaced element that ignores CSS inset for sizing), rAF loop guards zero-size containers and sleeps on document.hidden / IntersectionObserver leaving viewport, ResizeObserver re-measures geometry on container resize, all four observers plus the media-query listener and pointer/focus listeners are torn down on unmount. REDUCED MOTION: matchMedia('(prefers-reduced-motion: reduce)') swaps the entire rAF path for a single static draw at a deliberately chosen structured frame — FREEZE_PHASE = 35deg-into-occlusion: roller 0 held at 55° (35° into its 140° contact arc), tube ~60% occluded there (not fully pinched, not fully open, so the mechanic still reads mid-motion), with a slug boundary parked at the tube's 1/3 mark; this is a hardcoded snapshot state, not a paused version of the live easing function. Root is a focusable `role=\"group\"` div with `aria-label` from the `label` prop (default 'Data pump active'); the canvas itself is `aria-hidden`. Zero dependencies. Props: label (string, default 'Data pump active'), className."
      }
    },
    {
      "name": "ropewalk-lay-twist",
      "type": "registry:ui",
      "title": "Ropewalk Lay Twist",
      "description": "Ambient card background modeling rope laying in a traditional ropewalk: three strands spiral together at a fixed convergence point into one rope, continuously hauled off and wound onto a take-up drum whose layer count keeps climbing, never stopping and never resetting.",
      "files": [
        {
          "path": "registry/core/ropewalk-lay-twist/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ropewalk-lay-twist.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "canvas",
          "rope",
          "cordage",
          "ropewalk",
          "ambient",
          "monochrome",
          "generative"
        ],
        "instruction": "Build <RopewalkLayTwist height? label? className?> as a fixed-height ambient canvas background. SOURCE, NOT INVENTED: rope laying in a traditional ropewalk — three pre-twisted yarns are drawn together at a fixed convergence point ('the top') where a counter-rotating closing twist locks them into one rope, torque-balanced against kinking because the yarn-twist and rope-lay directions oppose each other; the finished rope is continuously hauled off and wound onto a take-up drum. TWO DELIBERATELY DIFFERENT ROTATION RATES driven by the SAME monotonic haul-off clock (feedDist, px hauled off, never reset): (1) the LAY rotation at the convergence point, period = L / FEED where L is the lay length (a helical-period constant, 64px at the component's nominal 160px min-dimension, scaled by container size); (2) the WRAP rate at which finished rope winds around the drum's own circumference C = 2*PI*baseRadius, period = C / FEED — these two periods are NOT equal, because L and C are unrelated lengths; conflating them (assuming the same period governs both, as an earlier draft of this component's spec did) produces a drum that takes tens of seconds to show a single wrap at card scale, which fails the round's 'alive at rest within 5s' requirement. GEOMETRY: baseRadius = min(width,height) * 0.12; FEED = 26px/s and L = 64px, both scaled by clamp(min(width,height)/160, 0.55, 2.2) so the lay-rotation PERIOD itself (L/FEED, ~2.5s) stays constant across container sizes even though the pixel values scale. STRANDS: 3, each a travelling cosine wave y = midY + strandR*cos(2*PI*x/L - 2*PI*feedDist/L + i*2*PI/3) drawn from x=0 to the convergence point at x = min(width*0.3, drum-clearance); z-order/opacity is set by cos(phase) at the convergence point — the strand closest to +1 (nearest the viewer) draws last, at the highest opacity, lerped between --ns-muted (furthest) and --foreground (nearest). ROPE: a single stroked --foreground path from the convergence point to the drum, with a faint periodic --background-alpha ripple at wavelength L riding the rope body (twisted structure as luminance banding, never a separate hue). DRUM: a ring-recycling system — every completed layer (WRAPS_PER_LAYER=3 full wraps around the current radius) gets an absolute integer index; only the MAX_LAYERS=4 most recent layers are ever rendered, each easing (rate 6/s) toward its current display radius, and a layer aging out of that window eases its opacity to 0 over a LAYER_FADE_S=2s window while the remaining layers ease one radial slot inward, which is the continuous version of 'oldest layer fades out to make room.' The layer still being wound sits one slot further out than the newest completed layer: its FIRST wrap draws as a genuine sweeping arc (0 -> 2*PI over one wrap period), and every wrap after that in the same layer raises the ring's opacity instead of re-sweeping, since further wraps at an unchanged radius are visually identical to the first and a re-sweep would read as a blink/reset. COLOUR: every value read via getComputedStyle(document.documentElement) at mount (no paint before that first read) and re-read on a documentElement class MutationObserver — --foreground, --ns-muted, --border (with its own parsed alpha, used only for the drum's static outer-rim outline, never a load-bearing fill), --background (used only as the rope-ripple's alpha-blended highlight, still zero literals). RESTING LOOP: feedDist accumulates every frame and never resets; the lay rotation alone (period ~2.5s at nominal scale) is the fast, primary alive-at-rest signal (t0, t+2.5s and t+5s land the 3 strands at visibly different phases), and the drum's ring set climbing/recycling is the slower secondary confirmation. Standard canvas host lifecycle: DPR-aware backing store capped at 2, ResizeObserver on the canvas, IntersectionObserver (threshold 0) pausing the rAF loop off-screen, visibilitychange pausing on a hidden tab, full cleanup (cancelAnimationFrame, disconnect both observers, remove the visibilitychange listener) on unmount. REDUCED MOTION: freezes on a named LAY_QUARTER_PHASE frame, computed (not hardcoded) at mount by solving for the feedDist value that is simultaneously congruent to L*0.25 (the 3 strands at their most visually separated moment, pi/2 apart) and lands wrap-progress at roughly 2 full layers plus a half-filled 3rd, so both the twist mechanism and the drum's accumulation read clearly in one static frame; no rAF loop starts. The root is role=img with an accessible `label` (default 'Rope forming'); the canvas itself is aria-hidden decoration. Zero dependencies, direct-canvas rendering only, no React state beyond refs."
      }
    },
    {
      "name": "router-tier-cascade",
      "type": "registry:ui",
      "title": "Router Tier Cascade",
      "description": "Model routing drawn as a cascade of weirs: one hairline row per model tier, each with a spill notch at its cost ceiling, so a request that a cheap tier can't hold visibly spills down a row and every escalation and its price are readable in one still frame.",
      "files": [
        {
          "path": "registry/core/router-tier-cascade/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/router-tier-cascade.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "agent",
          "llm",
          "routing",
          "cost",
          "model",
          "meter",
          "data-viz",
          "mono",
          "dashboard",
          "escalation"
        ],
        "instruction": "Build <RouterTierCascade tiers requests decayMs? onCeilingChange? formatCost? ariaLabel? className?> as a weir cascade: one 34px row per model tier from `tiers: {id,label,ceiling,pricePer1k}[]`, cheapest first, laid out as a 112px / 1fr / 132px grid (tier label + price · track · cost gutter). GEOMETRY: each row's track carries a 1px --border baseline that is CUT at x = ceiling — two absolutely positioned segments leaving a 10px gap — and that gap is flanked by two 1px --foreground vertical lips 4px tall, one hanging above the line on the left edge and one below on the right, which is the weir notch and the only thing that distinguishes one tier from another (no color coding anywhere; tier identity is row position and notch x, never hue). CASCADE: a request from `requests: {id,difficulty,tokens,acceptedTier?}[]` enters row 0 as a 3px --foreground square at x=0 and travels right at 90px/s (converted to normalized track units against a ResizeObserver-measured width, so the speed is real px/s at any container size). If it reaches its row's notch x while difficulty > that row's ceiling it SPILLS: translateY(+34px) over 260ms on cubic-bezier(0.33,0,0.15,1), overshooting by 3px and easing that overshoot back out over the final 60ms, then it resumes rightward on the next row. It comes to rest at x = difficulty on the first row whose ceiling can hold it (last row otherwise) and becomes a settled tick. All in-flight glyphs share ONE rAF loop writing style.left/transform directly on refs — React state holds only the settled ticks — and the glyph layer lives inside row 0's track with z-index 5 so the same element can translate down across every row below it. DECAY: a settled tick's opacity runs 1 -> 0.25 linearly over `decayMs` (default 45000) as a pure CSS animation on a wrapper element, so it needs no timer and no re-render; ticks are capped FIFO at 40 per row. The FIRST batch of requests is treated as history rather than live traffic — it lands settled with no travel, and each tick gets a negative animation-delay spread along the decay ramp in arrival order, so the resting frame reads as accumulated traffic (a dense scatter thinning down the rows) rather than forty simultaneous arrivals. Sparsity is the design: the frame is n hairlines plus a scatter, never a fill. COST: the right gutter is font-mono tabular-nums showing the accepted count and cumulative spend (sum(tokens)/1000 * pricePer1k) plus a 1px --foreground bar scaled to the busiest row's spend, so routing and price sit on the same geometry. INTERACTION: pointerover on a row (via bubbling pointerover/pointerout, not enter/leave, so the autoplay driver's hit-tested dispatch onto a child still reaches the row) expands that row's ticks 3px -> 5px and its notch lips 4px -> 10px while every other row eases to 0.18 opacity on a 0.16s constant, and swaps that row's gutter from cumulative spend to the per-request MEDIAN; pointerleave on the container relaxes everything back over the same constant. Each notch is a real role=slider (tabIndex 0, aria-valuemin 0.02 / aria-valuemax 0.98 matching the real clamps / aria-valuenow / aria-valuetext in percent, aria-describedby pointing at that row's sr-only summary, visible focus-visible outline in --ns-accent) that can be dragged horizontally with pointer capture or arrow-keyed at 0.01 steps (PageUp/Down 0.1, Home/End to the 0.02..0.98 clamps), snapped to 0.01, firing onCeilingChange(tierId, ceiling). Because each settled tick stores its difficulty and its row is DERIVED from the live ceilings on every render rather than frozen at arrival, moving a notch re-cascades every tick that now falls on the wrong side of it, animated by a CSS transition on the same 260ms cubic-bezier(0.33,0,0.15,1) curve the live spill uses. `acceptedTier` on a request is advisory and deliberately ignored for that reason. AMBIENT MOTION: only the newest settled tick breathes, opacity 0.55 -> 1 on a 3.4s ease-in-out alternate loop with a deterministic ±180ms delay jitter seeded from its sequence index; the cascade animation plays only when a request actually arrives in props. prefers-reduced-motion: arriving requests are placed directly at their resting x on their accepting row with zero travel, the rAF loop never starts, and every transition, the decay fade and the pulse are all disabled — routing, hover, drag and keyboard all still work, they just resolve instantly. Every stroke and fill is a token (bg-foreground, bg-border, text-ns-muted, outline-ns-accent) — no hex, no canvas, DOM + CSS only. Each row also carries an sr-only sentence spelling out its ceiling, accepted count and spend, because a scatter of squares says nothing to a screen reader. Props: tiers, requests, decayMs (default 45000), onCeilingChange, formatCost (default $ with 4 decimals below 1), ariaLabel (default 'Model routing cascade'), className."
      }
    },
    {
      "name": "routing-slip",
      "type": "registry:ui",
      "title": "Routing Slip",
      "description": "A multi-party approval ledger styled as an interoffice routing slip: names print in fixed routed order, signing presses an ink chop beside your name, and signing out of turn lands visibly skewed with a written note rather than being hidden or blocked.",
      "files": [
        {
          "path": "registry/core/routing-slip/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/routing-slip.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "approval",
          "sign-off",
          "quorum",
          "table",
          "ledger",
          "workflow",
          "confirmation",
          "aria-live",
          "accessibility"
        ],
        "instruction": "Build RouteSlip, a controlled-once approval ledger for a fixed chain of approvers. Props: `approvers` (an array of `{id, name, role}` in ROUTED order — index 0 signs first in sequence; this array's order is the row order and never changes, regardless of who actually signs when), `initialSignatures` (optional array of `{approverId, at}` seeding already-signed rows before mount, in whatever order they actually happened), `quorum` (signatures required before Publish unblocks, clamped to [1, approvers.length], default approvers.length — this can be LESS than the full roster, which is a deliberately different claim from \"everyone must sign\"), `currentApproverId` (the one approver whose row gets a real, clickable Sign button — every other row is read-only text regardless of its own signed state, because this is one person's view of a shared slip, not an admin panel that can sign on anyone's behalf), `docLabel`, and `onSign`/`onPublish` callbacks. Signing and publishing are each one-shot and irreversible: once `currentApproverId` has a timestamp there is no unsign path, and once Publish fires there is no unpublish path — the same terminal-state discipline as approval-inline-diff's decided guard. MECHANISM: the component renders a real `<table>` with a `<caption>` (sr-only) and four `<th scope=\"col\">` columns — Approver, Role, Status, Time — one `<tr>` per approver in fixed routed order. The Approver cell prints the name followed by a small stamp box (1px dashed `--border`, ~42x26px). Before that approver signs the box is empty. The moment `currentApproverId` signs (Sign is a real `<button>`, present ONLY on that one row, reading \"Sign as {name}\"), the box fills with their initials in Geist Mono and plays a 260ms ease-out-expo press-in: `scale(1.15)` to `scale(1)` while settling into a `rotate(3deg)` tilt — a stamped, not typeset, mark. OUT OF TURN is the component's actual payload and is computed purely from final state, never from click order: approver i (by routed index) is out of turn if any earlier-routed approver j<i is still unsigned, or signed with a LATER timestamp than i's. Row 0 can never be out of turn — there is no one earlier to have skipped ahead of. A flagged chop's settle transform becomes `rotate(2deg) skewX(8deg)` instead of the plain 3deg tilt — noticeably irregular at a glance, still legible — and the row additionally renders a plain-text \"out of turn\" note beneath the name, so the distinction is carried by real text, not only by the skew. This is deliberately NOT a checkmark/avatar-overlay list: two approvers can both show \"Signed\" with identical green-free, color-free status text while only one of their stamps carries the skew, because a checkmark row has no way to encode routed-vs-actual order at all. GOVERNING SCALAR: signed count over quorum drives three derived surfaces — the header's \"{n} of {total} signed\" progress line (against the full roster, independent of quorum), the Status cell's \"Now\" label on the first unsigned row in routed order (independent of who `currentApproverId` is — \"Now\" can land on a row nobody present is allowed to sign), rendered with a crawling dashed underline (a `repeating-linear-gradient` sweeping via `background-position`, `--foreground`-only, never `--ns-accent`), and Publish's gating. Publish is a real `<button>` carrying `aria-disabled` (never the native `disabled` attribute, so it stays focusable and its reason stays reachable) plus `aria-describedby` pointing at a visible paragraph with the actual written reason, e.g. \"Publish disabled — needs 1 more signature, waiting on Release Mgmt.\" once quorum is met the reason reads \"Quorum met — ready to publish.\" and the button both loses `aria-disabled` and gains the enabled accent styling. A11Y: a polite `aria-live` region (sr-only, separate from the visible reason paragraph) announces on every genuine transition — never on the initial seeded mount, only on a sign or publish that happens in-session — phrased \"{n} of {total} signed; waiting on {role}.\" (naming the blocking approver's ROLE, matching the brief's own example almost verbatim) or \"...; all signed.\" once nobody remains, with \"Publish unlocked.\" appended the moment quorum is crossed, and a final \"Published.\" once Publish fires. Every signed row's timestamp is a real `<time datetime=...>` element. COLOR: strictly `--background` / `--foreground` / `--ns-muted` / `--border` / `--ns-accent`. `--ns-accent` appears nowhere but the Sign and Publish buttons' own hover/focus-visible/active states (border tint + a `color-mix(in srgb, var(--ns-accent) …%, var(--background))` wash, never a hex/rgb literal) — never on a chop, never as a persistent fill, and Publish's disabled variant overrides hover/active back to `--border`/`--background` and swaps its focus ring to `--foreground` so accent reads as an enabled-only signal exactly as specified. Under `prefers-reduced-motion`, the press-in keyframe and the \"Now\" underline's crawl animation are both stripped via a CSS media query (belt-and-suspenders alongside the JS `matchMedia` check that gates whether the press-in class is applied at all) — the final tilt/skew and the dashed underline still render at rest, fully legible on first paint, since a fixed tilt is not itself motion. Pure DOM and CSS, zero dependencies, no canvas or SVG."
      }
    },
    {
      "name": "running-belay",
      "type": "registry:ui",
      "title": "Running Belay",
      "description": "A deploy pipeline drawn as a climbing pitch: stages are protection anchors bolted up a vertical line, the current deploy is the climber-end of a rope clipped through every passed anchor, and rollback is a fall arrest that catches at the last healthy anchor with visible rope stretch.",
      "files": [
        {
          "path": "registry/core/running-belay/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/running-belay.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "deploy",
          "pipeline",
          "rollout",
          "status",
          "canary",
          "rollback",
          "ops",
          "dashboard",
          "mono"
        ],
        "instruction": "Build <RunningBelay stages headroom onArrest? ariaLabel? className?> as a vertical climbing pitch. DATA: `stages: {id,label,status,timestamp?}[]` in pipeline order (e.g. build, canary, 10%, 50%, 100%), status one of pending|active|passed|failed; `headroom: number` 0..1, the caller-aggregated canary-health margin. GEOMETRY: one 96px row per stage, a fixed 40px SVG rail on the left carries a vertical line at its centre — a faint --border hairline runs its full length as the permanent conduit; --foreground solid segments overlay it between every pair of ADJACENT passed anchors (the already-clipped chain). Anchors are small circles on that line: filled --foreground when passed, an open --foreground ring when active, plain --border when still pending, and an X (two crossing --foreground strokes, never a color) when failed — status is shape, never hue, matching the rest of the registry's no-color-coding rule. A passed anchor also carries a faint carabiner ring (an ellipse, opacity 0.5) that flashes to full opacity for one 140ms beat the instant that anchor newly becomes passed, then settles back — a one-frame clip, not a celebration. THE LIVE ROPE: from the greatest passed anchor to the current leader position (the active stage, or the furthest passed stage once nothing is active) is drawn as a single quadratic bezier, `M lineX,y0 Q lineX+offset,midY lineX,y1`, where offset = clamp(headroom,0,1) * 24, hard-capped at 28px — past that cap the curve would cross back over its own anchor line and stop reading as clipped through. headroom near 1 reads as visible slack; headroom near 0 pulls the rope visibly taut, legible before any automated abort actually fires. No live segment is drawn once the leader sits exactly on the greatest passed anchor (nothing left to bow). THE LEADER: a small ringed dot translated to the current stage's y with a single CSS transform transition — ease-out-expo (cubic-bezier(0.16,1,0.3,1), 600ms) for ordinary forward advancement between renders, cubic-bezier(0.34,1.56,0.64,1) 700ms (one overshoot-and-settle spring, never a stage-by-stage reverse walk) specifically for an arrest. ARREST: a real <button data-belay-arrest> labeled 'Arrest rollout', disabled only when there is no passed anchor yet to fall back to, carrying aria-describedby pointing at a permanently-mounted sr-only span reading the exact target, e.g. 'Rolls back to 10% cohort, deployed 14:02.' On click the target is computed ONLY as the greatest passed anchor (recorded-healthy) at that instant — this is a fall-arrest catch, not a negotiated multi-step rollback — the leader makes one spring-eased translate straight there (cubic-bezier(0.34,1.56,0.64,1), 700ms), the target anchor gets a one-shot ring flash (500ms), and onArrest(stageId) fires. For that same 700ms a second, dashed rope segment is drawn between the target anchor and wherever the leader fell from, bowed to the identical 28px hard cap as the live rope and fading to transparent over the spring's own duration — the catch shows the rope taking the load, not just a dot relocating with nothing attached to it. The arrest position is a local optimistic override that is released automatically the moment the caller's own `stages` prop independently confirms the deploy is back at that same stage (a real rollback landing), so the component never fights a parent that is the actual source of truth. A repeat press while already arrested at the same target is a harmless no-op replay, not a toggle — arrest has no 'undo' click, matching a real fall-arrest catch. RENDER + A11Y: stages render as a real <ol> (not the SVG) with each stage's name and a 'status · timestamp' line as plain text, aria-current='step' on the stage the leader is currently at — the active/furthest-passed stage at rest, or the arrest's landing anchor once arrested, so the list never goes silent about where the deploy actually is; the rope/anchor SVG is aria-hidden. A permanently-mounted <p aria-live='polite'> (sr-only) announces every stage status change in plain language ('canary is now passed, 13:52.'); a separate <p aria-live='assertive'> (sr-only) announces only the arrest ('Arrested. Rolled back to 10% cohort, deployed 14:02.'). REDUCED MOTION: the leader's transition is removed entirely (teleports to its resting position), the live rope's bow offset is forced to 0 regardless of headroom (always redraws straight), and arrest is instant — the target still gets its one-shot ring flash, which is a single state change, not a repeating animation, so it stays inside prefers-reduced-motion. Every color is var(--background)/var(--foreground)/var(--ns-muted)/var(--border)/var(--ns-accent) — no hex, no rgb()/hsl(), --ns-accent used only for the arrest button's own focus-visible ring, never as a status or health indicator. Pure DOM + SVG + CSS transform transitions, no canvas, no rAF loop. Props: stages, headroom, onArrest(stageId), ariaLabel (default 'Deploy pipeline'), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 `--ns-accent` and the label from `--ns-muted` to `--foreground`, and the isolated node's border stays `--ns-accent` persistently. Tokens only (`--background --foreground --ns-muted --border --ns-accent`), applied as Tailwind utility classes (`bg-background`, `border-border`/`border-ns-accent`, `text-ns-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": "schedule-ascii-freebusy",
      "type": "registry:ui",
      "title": "Schedule ASCII Free/Busy",
      "description": "A meeting-time finder: one row of block glyphs per attendee and an underlined accent window marking the earliest slot where everyone required is free. Toggling a person optional recomputes the answer instantly.",
      "files": [
        {
          "path": "registry/core/schedule-ascii-freebusy/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/schedule-ascii-freebusy.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "scheduling",
          "calendar",
          "availability",
          "ascii",
          "mono",
          "grid",
          "keyboard-navigation",
          "meetings"
        ],
        "instruction": "Build <ScheduleAsciiFreebusy attendees slots dayStartMinutes defaultDuration durations title className> as a pure-DOM availability matrix over slots=18 half-hour columns spanning 09:00–18:00, one row per attendee (default six: Dana, Priya, Marcus, Inés, Tobias, Wei). GRID: each attendee carries a SlotStatus[] of 'free' | 'tentative' | 'busy' | 'out', rendered strictly glyph-encoded and never color-encoded — free is · in text-ns-muted/50, tentative is ▒ in text-ns-muted, busy is █ in text-foreground (never text-border — --border is a 1px-hairline token and is invisible as type on the light theme, which would leave the resting grid blank), and a slot outside that person's working hours renders as a blank cell. SOLVER (the actual mechanism, not decoration): isBlocked = status is 'busy' or 'out'; freeMask[k] is the AND over every REQUIRED attendee of NOT isBlocked, i.e. the bitwise intersection of the inverted busy masks, computed only over attendees not currently marked optional. A single left-to-right run-length scan over freeMask collects every MAXIMAL run of set bits whose length is >= durationSlots (duration/30). Every qualifying run is drawn on a result row as └ + ─×(len−2) + ┘ in text-ns-muted (a length-1 run degenerates to ┴); the BEST run — earliest of the longest — is drawn in text-ns-accent, and an aria-live answer row seated under that window prints '10:30 → 11:30   all 6 free'. If no run qualifies the component never goes blank: it falls back to a minimum-conflict scan over every window of durationSlots, picking the one blocking the fewest required attendees, and prints 'no window — best is 10:00 → 11:30 with Dana busy'. A single ├────┤ rule separates the attendee rows from the result rows. There is no aggregate or density row of any kind: the six calendars are the only glyph rows. CONTROLS: a segmented role=radiogroup of 30 / 60 / 90 min (1 / 2 / 3 slots) re-runs the scan on click and on ArrowLeft/Right/Up/Down, which wrap around and move the group's single tab stop (roving tabindex parked on the checked radio); each attendee's name is an aria-pressed toggle button (data-attendee-toggle=\"<id>\") that flips them required↔optional — an optional attendee is excluded from the AND, is parenthesised, and their entire row dims to text-ns-muted/30, so with the default data dropping Dana visibly widens the accent window from 10:30 → 11:30 to 09:30 → 11:30. That jump is the component's whole argument and is what the landing-card autoplay presses. HOVER: entering any cell sets the hovered column, which tints that column bg-ns-accent/[0.10] and swaps the glyph to ┊ in every row where that person is not blocked — a dotted plumb line drawn through the free cells of the column, leaving the █ blocks intact — while a readout below crossfades (150ms opacity, motion-reduce:transition-none) from the resting hint to '14:00 — 2 of 6 busy: Dana, Priya', with an out-of-hours attendee listed as 'Wei (off)'. pointerleave on the grid clears it and the readout eases back. A11Y: role=grid on the matrix with role=row per line, role=columnheader on the hour ruler (each hour label carries aria-colspan=2 and an explicit aria-colindex, since it spans two half-hour columns), role=rowheader on the name/label column, role=gridcell on every slot with aria-selected true inside the best window and an aria-label naming attendee + time + status; a roving tabindex (exactly one cell with tabIndex 0) moves with ArrowUp/Down/Left/Right, Escape clears the column hover, and Space on a name button toggles required/optional through native button semantics. Every focusable control — cells, name toggles, duration radios — carries a visible focus-visible:ring-2 focus-visible:ring-ns-accent ring. COLOR: token-only (--foreground / --ns-muted / --border / --surface / --background / --ns-accent through Tailwind utilities and bg-ns-accent/[0.06|0.10|0.14] arbitrary-opacity tints), no hex anywhere, so both themes follow the tokens with nothing to re-derive. There is no canvas, no rAF loop and no animation beyond the 150ms color/opacity transitions, all of which are suppressed under prefers-reduced-motion via motion-reduce:transition-none."
      }
    },
    {
      "name": "screen-flood-stroke",
      "type": "registry:ui",
      "title": "Screen Flood Stroke",
      "description": "A card-scale loader modelled on the real screen-printing flood/print stroke cycle: a squeegee alternates a light flood pass that spreads ink across the mesh and a firm print pass that forces it through, printing one fading impression per cycle.",
      "files": [
        {
          "path": "registry/core/screen-flood-stroke/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/screen-flood-stroke.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "canvas",
          "print",
          "ambient",
          "monochrome"
        ],
        "instruction": "Build a card-scale canvas-2D loader whose entire surface reproduces one screen-printing flood/print stroke cycle, not a percentage bar or generic spinner. Root is a `relative h-40 w-40 overflow-hidden rounded-[10px] border border-border bg-background` element (`role='img'`, caller-supplied `aria-label`) holding an absolutely positioned `aria-hidden` canvas at `w-full h-full`, sized off the root's own `getBoundingClientRect` via `ResizeObserver`.\n\nThe cycle is a fixed 1800ms loop: a 600ms FLOOD stroke (left-to-right, low pressure), a 900ms PRINT stroke (right-to-left, firm pressure, blade angled 18deg from vertical), then a 300ms dwell where the blade lifts clear before the next flood pass. The blade's local x position at row `cy` is `baseX + (cy - h/2) * sin(18deg)` so the angle is geometrically real, not a CSS skew. During flood, `baseX` runs `0 -> w` over 600ms; during print it runs `w -> 0` over 900ms; during dwell it holds at 0 while the blade's own render alpha fades to 0.\n\nA persistent per-cell Float32Array (grid pitch = `clamp(min(w,h)/60, 4px, 9px)`, resized with the canvas) tracks print-through ink. Every frame during the print stroke, any cell whose centre the blade has already crossed (`cx >= localX(cy)`, since the blade travels right-to-left and 'already crossed' means to its right) jumps straight to alpha 0.9 of `--foreground`. Every frame, every cell with ink > 0 decays via `ink = FLOOR + (ink - FLOOR) * exp(-dt/2400ms)` toward a floor of 0.2 — residual staining that never reaches 0, so a resting frame always carries the ghost of at least one prior impression. Untouched cells stay exactly 0 (never drift toward the floor) until their first impression. The flood stroke does NOT write into this persistent field: it is drawn live, every frame, as a flat `rgba(fg, 0.15)` wash over whichever cells sit behind the blade's current flood position, and it has zero persistence once the flood pass ends — the flood/print distinction has to be legible as two different effects on the surface, not two speeds of the same wipe.\n\nMesh fabric is drawn first, every frame, as a fixed grid of 1px lines at the same pitch, stroked at a flat, non-decaying `rgba(--foreground, 0.08)` — never `--border`, which is a near-invisible separator token in light theme and would defeat the always-faintly-visible-mesh requirement. Ink and the flood wash draw on top of the mesh at their own alpha (never fully opaque), so the grid lines still show through everywhere.\n\nAn optional `progress` prop (0-1) makes the loader determinate without ever letting it read as finished-and-stopped: when set, a print-stroke deposit is only allowed onto a cell if `cx / w <= progress`, capping how far into the mesh fresh ink can land, while the blade itself keeps sweeping the full width and the flood wash keeps animating every cycle regardless of `progress`'s value — even at `progress = 1` the flood/print cycle keeps running unforced, satisfying the 'alive at rest, unbounded loop' requirement rather than settling into a static finished frame.\n\nColour is `--foreground` only, read via `getComputedStyle(document.documentElement)` at mount and re-derived on a `MutationObserver` watching `documentElement`'s class attribute; `--ns-accent` is never touched anywhere in this component, since there is no pointer/press interaction to give it interaction-chrome duty. DPR-capped (max 2) backing store sized off the root's own rect. The rAF loop pauses on `document.visibilitychange` and on an `IntersectionObserver` (threshold implicit, any intersection keeps it awake) so an offscreen or backgrounded loader costs nothing, and both observers are disconnected and the frame cancelled on unmount. Under `prefers-reduced-motion: reduce` the clock freezes at a computed instant equivalent to 60% through the print stroke (`FLOOD_MS + 0.6 * PRINT_MS` = 1140ms into the 1800ms cycle, exposed as `data-freeze-phase='print-stroke-60pct'` on the root) — squeegee visibly angled mid-pass, roughly 60% of the mesh printed and 40% still bare mesh, the single most legible 'process caught in the act' frame; the frame is computed by running a single deposit step directly at that instant against a fresh, zeroed ink field (never left at whatever state the live loop happened to be in when the media query flipped), so the freeze is deterministic on every mount and re-mount. No 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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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-fine-register",
      "type": "registry:ui",
      "title": "Scroll Fine Register",
      "description": "A divider/footer band whose streaming motion is built from a real tilemap-PPU scroll: a fine register sweeps 0..cellPx-1 sub-pixel offsets across 480ms before a coarse register ticks a tile over, with both registers exposed as a live numeric coarse/fine readout beside the strip.",
      "files": [
        {
          "path": "registry/core/scroll-fine-register/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/scroll-fine-register.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "scroll",
          "register",
          "tilemap",
          "hardware",
          "ppu",
          "divider",
          "footer",
          "mono",
          "readout"
        ],
        "instruction": "A horizontal band with no props beyond `className`, split into a flex-1 overflow-hidden tile strip and a shrink-0 numeric readout separated by a `border-border` rule (that rule, plus the band's own `border-y`, are the only uses of `--border` — always a structural separator, never the motion's paint). The strip is built entirely by direct DOM manipulation inside a single effect: on mount (and on every ResizeObserver-driven resize, debounced 120ms), `cellPx = clamp(round(minDim/24), 8, 16)` is derived from the container's smaller dimension (minDim = min(rect.width, rect.height), almost always height for a horizontal band). `tileCount = clamp(ceil(rect.width/cellPx), 24, 40)` is the COARSE WRAP PERIOD — `totalSteps` wraps at `tileCount*cellPx` — and a fixed-seed `mulberry32(0x5f3a11)` PRNG draws one glyph choice per tile index into a `tileCount`-length `pattern` array once per build, so the strip's true visual repeat period is the full wrap (11.5-19.2s at cellPx 8-16), not a short arithmetic ramp. `renderCount = visibleTiles + tileCount + MARKER_LEAD + 1` cells are actually painted (glyph at index `j` = `pattern[j % tileCount]`), one full coarse period wider than the viewport so the strip fully covers the container at every reachable translate offset within a wrap cycle, not just near t=0. Each cell's `fontSize` is not simply `cellPx`: a hidden probe span (same technique as marquee-ticker-glyph) measures the real monospace glyph advance at `cellPx`, and every cell's font is scaled by `cellPx / advance` (capped at 2.5x) so the glyph's rendered width fills the cell instead of leaving gaps between tiles; each cell also gets `overflow: hidden` to clip the resulting taller glyph. One integer, `totalSteps` (in px), is the entire state: `fine = totalSteps % cellPx` and `coarse = floor(totalSteps/cellPx) % tileCount`. A rAF loop accumulates elapsed ms and ticks `totalSteps` by 1 every `480/cellPx` ms — so a full fine sweep across a tile always takes exactly 480ms regardless of cellPx — wrapping `totalSteps` from `tileCount*cellPx` back to 0 the instant one full coarse period has scrolled past (landing on content pixel-identical to the wrap target, since the glyph pattern already repeats with that period). Every tick writes `track.style.transform = translate3d(-totalSteps px, 0, 0)` directly, and writes `coarse`/`fine` as plain zero-padded/unpadded text into two `tabular-nums` readout spans — no React state on the hot path. The marker is not a strip cell: it is a separate, absolutely-positioned full-band-height overlay div (`border-x border-foreground`, `cellPx` wide) drawn on top of the strip so it stays legible over any glyph beneath it, including a solid block. Every tick its transform is set to `translate3d(MARKER_LEAD*cellPx - fine, 0, 0)` (MARKER_LEAD = 4 tiles ahead of the live coarse register), so it visibly slides left by `cellPx` px over the 480ms sweep and snaps back to its start position the instant coarse ticks over — the sawtooth made literally visible on the one thing a viewer's eye is meant to track, tying the numeric coarse tick directly to a visual snap. The marker starts `visibility: hidden` in JSX and is flipped to `visible` on the first `paint()` call, so there is no flash of an unpositioned overlay at x=0 between mount and the first frame. `prefers-reduced-motion: reduce` skips the rAF loop entirely and calls `freeze()` once, which sets `totalSteps = min(tileCount-1, 12) * cellPx` — fine exactly 0, coarse fixed at a mid-strip tile — the one instant a tile boundary sits flush with the viewport's left edge, the most structured resting frame. At t0 (non-reduced) `totalSteps` starts at 3, a few px into the first sweep, so the component reads as already in motion rather than freshly booted; over 5s (~10 sweep cycles at cellPx=8, 480ms each) the coarse counter visibly advances roughly 10 and the strip has scrolled several tile widths, with fine cycling through a different phase than t0 at every checkpoint. An IntersectionObserver stops/resumes the rAF loop when the band scrolls off/on screen; a ResizeObserver rebuilds strip geometry (and the reduced-motion freeze target) whenever the band's own box changes size. No interaction: the strip does not accept drag/scrub, since the mechanic is a fixed-rate hardware register cadence, not a carousel. Ink is `text-foreground` / `text-ns-muted` for glyphs and readout, the `border-foreground` utility for the marker overlay, `border-border` only for the band's dividing rules — zero colour literals, zero `--ns-accent` use, both themes read identically. Pure DOM, zero dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": "seal-roll",
      "type": "registry:ui",
      "title": "Seal Roll",
      "description": "A single-quote testimonial rotator built as a Mesopotamian cylinder seal: a small barrel rolls across the card and the quote presses into the surface word by word in its wake, then the impression re-clays as the barrel's return trip rolls in the next testimonial.",
      "files": [
        {
          "path": "registry/core/seal-roll/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/seal-roll.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "testimonial",
          "carousel",
          "rotator",
          "quote",
          "svg",
          "impression",
          "auto-advance"
        ],
        "instruction": "Build a single-quote testimonial rotator staged as a cylinder-seal impression, not a fading carousel. Layout: a bordered card holds a `stage` (overflow-hidden, relative) containing an absolutely positioned barrel and, below it, a `<blockquote>` whose text is pre-split into one <span> per word (plus a real text-node space between spans, not a nbsp, so line-wrapping stays natural); below the stage a `<footer>` holds the real `<cite>` author and an optional role, both present as real DOM text from the moment a cycle starts.\n\nThe barrel is an SVG group: a pill-shaped body (rounded-rect, rx = height/2) with a clip-pathed layer of faint mirrored glyph marks at opacity 0.2 (deterministic per-cycle placement, alternating rows mirrored, reading as struck-in cuneiform texture) — all fill/stroke from `var(--foreground)` / `var(--border)` / `var(--background)`, no hex. One rAF loop per active phase drives it: over `rollMs` (default 4000ms, eased with a cubic ease-in-out) the barrel's translateX moves from just off the stage's left edge to just off its right edge while its rotation is computed every frame as `(x_px / radius_px) * (180/Math.PI)` — radius = half the barrel's own height — so it visibly rolls rather than slides or spins independently of its travel.\n\nWord reveal: word positions are measured once per cycle (after the new quote's spans mount) as `offsetTop * trackWidth + offsetLeft`, which unwraps a multi-line quote into one continuous left-to-right coordinate so the reveal still reads as a single unbroken sweep across wrapped lines. The SAME rAF loop that moves the barrel compares each word's measured offset against the barrel's current eased travel fraction times the quote's total measured span, and the instant a word's offset falls behind the barrel, sets `data-revealed` on that word's span directly via `element.setAttribute` (no React re-render on this hot path — gating is opacity via a CSS attribute selector, never conditional content). CSS gives `[data-revealed]` an `opacity:1` plus a 400ms `animation-fill-mode:both` keyframe: it starts as `--foreground` ink with a 1px `--background` text-shadow over a `color-mix` `--ns-muted` background patch (the debossed, freshly-struck read) and settles to plain `--foreground` ink with no shadow/patch by the animation's own end — never JS-timed class removal.\n\nArrival (roll progress reaches 1): every word is force-marked revealed as a safety net, the barrel does a discrete CSS-transitioned lift (translateY up + fade to opacity 0 over 260ms — this one IS a plain CSS transition, since it's a one-off settle, not a hot per-frame write) and the phase becomes `stamped`, which is also what fades the `<footer>`'s cite/role in (opacity 0 -> 1, translateY 4px -> 0, plain CSS transition keyed off a `data-phase` attribute — the underlying DOM text is unconditionally present throughout, only opacity/transform gate its visibility).\n\nAuto-advance: while `stamped`, a dwell timer (default 2200ms) is what would move the phase to `clearing` — hover, focus-within, or an explicit Pause control (a real button toggling `aria-pressed`) suspends only this timer, so the quote holds indefinitely for as long as any one of those is true, and resumes counting down fresh once none are. `clearing` is a second rAF loop: the barrel drops back in (a blended translateY/opacity ramp over the loop's first ~22%) and rolls the return trip back to its start position over 800ms (same distance/radius rotation formula, direction reversed) while the quote block and footer blur (filter blur(6px)) and fade to opacity 0 over the same 800ms via a CSS transition keyed off the same `data-phase` attribute — the barrel's return trip IS the transition into the next testimonial, not a separate crossfade. On completion the index advances (wrapping) and a fresh `rolling` cycle starts for the next quote, resetting the barrel to its start position and mounting fresh (unrevealed) word spans.\n\nReal Prev/Next buttons (each with an accessible name) jump the index immediately and restart the cycle from scratch, discarding whatever was in flight — the same index-keyed effect that starts a fresh roll on auto-advance handles a manual jump identically. Pause, when it turns ON mid-roll, does not merely gate the future auto-advance — it calls back into the in-flight roll loop to finish it immediately (all words revealed, barrel lifted, phase `stamped`) rather than freezing on a half-struck quote, which is also what makes pausing a reliable way to reach the fully-stamped, fully-legible resting frame on demand.\n\nprefers-reduced-motion (media query, live-updated): the barrel is not rendered at all, every word is marked revealed instantly on mount, phase jumps straight to `stamped`, and the dwell/auto-advance timer still runs (still pausable by hover/focus/Pause) but the `clearing` phase becomes an immediate index swap with no roll, no lift, and no blur — quotes change instantly at the same interval. The rotation itself never uses an `aria-live` region (nothing here is a status update to announce — the quote and attribution are already real, present text the instant a cycle starts, and gating is purely visual). Colors throughout are `--background --foreground --ns-muted --border`, with `--ns-accent` used only for the Prev/Next/Pause buttons' `:focus-visible` ring. No dependencies, no canvas — SVG for the barrel, everything else DOM/CSS."
      }
    },
    {
      "name": "seam-gild",
      "type": "registry:ui",
      "title": "Seam Gild",
      "description": "A kintsugi payoff for consequential actions: confirming cracks the panel with a biased-random-walk SVG path in ~400ms, then immediately re-fills it from both ends as a bright raised seam. The scar stays for the rest of the browser session, so repeated confirmations accumulate as ornament instead of fading. Before any confirmation, a faint stress hairline draws in, holds, and dissolves on its own every few seconds, so the panel never sits as a dead frame.",
      "files": [
        {
          "path": "registry/core/seam-gild/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/seam-gild.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "confirm",
          "success",
          "payment",
          "svg",
          "persistence",
          "session",
          "repair",
          "aria-live"
        ],
        "instruction": "A self-contained confirm panel: a bordered card with a description block, a real <button> (default label 'Confirm payment') and an aria-hidden, pointer-events:none SVG overlay absolutely positioned over the whole panel. Clicking the button calls the optional onConfirm callback — awaited if it returns a Promise, pendingLabel shown meanwhile — and the instant it settles, two things fire independently of each other: a role=status aria-live=assertive region receives confirmedMessage (this is the real confirmation, and it fires even if the visual below has nowhere to draw), and a visible role=status aria-live=polite counter ('N seams this session') updates from a running total kept in sessionStorage. Geometry: one seed point is picked on the panel's edge, retried up to 20 times against a measured exclusion list — every element carrying data-seam-avoid (the content block, the button, the counter) — read via getBoundingClientRect so the crack never starts inside one of them. A biased random walk grows inward from there: each step nudges the previous heading by a small jitter, and a step that would land inside an excluded rect (padded 6px) resamples its angle up to 6 times before the walk simply stops early — a shorter valid crack rather than an invalid one. Partway along (35-65% of the walk), one branch forks off at roughly a 60deg turn and walks a shorter distance under the same avoidance. Both polylines render as SVG <path>s with pathLength=1, so real pixel length is never measured: dasharray '1', dashoffset animated 1->0 over 400ms in --border draws the crack outward from the origin, with the branch on the same 400ms clock but starting 42% of the way through so it visibly forks mid-propagation rather than growing from t=0. The instant the crack's 400ms finishes, the gild pass starts: the same polyline once forward and once with its points reversed, both pathLength=1, dashoffset animated 1->0.5 over ~450ms in --foreground at 1.5px — forward reveals the first half from the origin, reversed reveals the first half of the flipped path (the original's second half) from the far end, so the bright seam visibly closes toward the middle from both directions at once. A drop-shadow(0 -1px 0 color-mix(in srgb, var(--foreground) 55%, transparent)) filter on that same stroke is the raised highlight, an offset duplicate of exactly the currently-revealed geometry rather than a separately animated path; the original hairline crack stays underneath as the seam's groove. Finished seams are normalized to fractions of the panel box and appended (capped at maxSeams, default 5, oldest dropped) to sessionStorage under ns-seam-gild:<storageKey>, alongside the running count under the same key plus ':count' kept separately so the tally stays accurate even on the rare panel too crowded to fit a new seam. Seams loaded from storage, or any seam past its own mount, render at their finished gilded state directly with no crack-then-gild replay. prefers-reduced-motion skips the propagation entirely for every new seam and renders it settled immediately, the same code path storage-loaded seams already use. IDLE STRESS HAIRLINE: while the panel has zero seams (nothing confirmed yet), it isn't a still frame — every few seconds the same buildSeam geometry engine draws a faint trunk-only hairline in --border at ~42% opacity (no branch, no --foreground, no drop-shadow highlight, so it can never be mistaken for a finished or in-progress real seam), holds briefly, dissolves, and a new one starts elsewhere at random after a pause. It measures the same data-seam-avoid exclusion rects as a real crack, is fully independent of sessionStorage (nothing about it persists), stops the instant a first real seam exists (the panel has its own trace by then and doesn't need the hint), and prefers-reduced-motion disables it outright. Props: actionLabel, pendingLabel, confirmedMessage, description, onConfirm, storageKey, maxSeams, resetKey, className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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/--ns-muted/--border/--background/--ns-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": "seep-lattice",
      "type": "registry:ui",
      "title": "Seep Lattice",
      "description": "A feature-flag rollout picker where blast radius is computed as literal site percolation: user segments are cells in a declared-adjacency lattice, the rollout percentage wets a fixed-order prefix of cells, and union-find continuously finds the largest connected wet cluster. Its traced perimeter and percentage are the honest readout a plain rollout number hides, with a cliff at the lattice's own measured containment threshold rather than a smooth curve.",
      "files": [
        {
          "path": "registry/core/seep-lattice/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/seep-lattice.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "slider",
          "percolation",
          "union-find",
          "feature-flag",
          "rollout",
          "svg",
          "graph",
          "risk",
          "form"
        ],
        "instruction": "Build a rollout slider whose entire content is a phase transition, not a smooth mapping from percentage to outcome. LATTICE: at mount, build a fixed rows x cols grid of cells (default 16x24 = 384, kept comfortably under ~600 so union-find stays cheap every frame) representing user segments, with DECLARED adjacency — an explicit edge list of each cell's up/down/left/right grid neighbours, built once and reused for the lattice's entire lifetime, standing in for shared-infra/shared-tenancy proximity. Separately, a seeded Fisher-Yates shuffle (mulberry32 PRNG, fixed seed prop) produces a permutation `order` of all cell indices, ALSO fixed for the component's lifetime. MECHANISM: there is exactly one governing scalar, the rollout percentage `p` (0-100). At any moment the wet set is defined as `order.slice(0, round(p/100 * n))` — a literal prefix of the fixed permutation — so p directly determines the wet set with no randomness at read time: dragging p up wets cells in exactly that fixed order, dragging back down un-wets in EXACT reverse, deterministic and replayable, never re-rolled mid-drag. On every value change (drag frame, keystroke, or external prop change) run union-find fresh over the wet cells using ONLY the declared adjacency edge list — never a radial distance-from-slider-value approximation and never a rectangular zone guess; that shortcut is the specific wrong version this component must not become, since it would fabricate containment the topology does not guarantee. From the union-find result, find the single largest connected component among wet cells (by cell count) and trace ONLY that cluster's outer boundary into one SVG path — walk the grid's boundary edges (an edge exists between a wet cluster cell and any non-cluster neighbour or the lattice edge, oriented consistently so cluster interior stays on one side) and stitch them tail-to-head into closed loops, rendered as a single `<path>` with evenodd fill (handles a cluster with a hole correctly) — never per-cell borders/strokes on every wet cell, the perimeter is drawn once around the whole blob. THRESHOLD: once at mount, binary-search over k (0..n) for the minimal prefix length at which the wet cells connect the lattice's top row to its bottom row (spanning is monotonic in k since the prefix only ever grows) — this measures THIS specific seeded lattice's own containment threshold, not the textbook infinite-lattice value 0.5927, because a finite grid with this declared adjacency has its own number. Render that threshold as a static tick mark on the slider track. HEADLINE FIGURE: the largest cluster's cell count as a percentage of total cells is the number that matters, always shown, e.g. 'largest connected exposure 41% of traffic' — never just the rollout percentage alone. RENDERING: cells are SVG `<rect>`s in a single `viewBox=\"0 0 cols rows\"` (one unit per cell, small inset gap, aria-hidden, non-focusable — they are derived from p, never independently choosable), coloured `var(--border)` when dry, `var(--ns-muted)` when wet but outside the largest cluster, `var(--foreground)` when inside the largest cluster; the traced perimeter path is stroked `var(--foreground)` with `vector-effect=\"non-scaling-stroke\"` and a faint `var(--foreground)` fill for legibility. `--ns-accent` never appears on the lattice itself — only on the slider handle/focus ring while focused or dragging, matching this registry's interaction-only accent rule. CAPILLARY WETTING: when p increases, run a small multi-source BFS restricted to just the cells newly added this step, seeded at depth 0 from any of them that touch an already-wet cell (or, if an entirely disconnected new patch appears with no wet neighbour yet, seed all of them at depth 0), and set each newly-wet cell's fill-transition delay to `min(depth * frontSpeedMs, capMs)` before the fill colour itself changes (a layout effect run before paint, so the delay is in effect for the very transition it targets) — the result reads as water flooding outward from existing wet regions in visible fronts, not a uniform flash across scattered cells. Un-wetting (dragging p down) is instant with no stagger. INTERACTION: the ONLY interactive, focusable element is a horizontal `role=\"slider\"` track below the lattice and the live readouts (drag anywhere on it to jump/scrub, matching this registry's click-jumps-to-position convention; ArrowLeft/Right step by roughly one cell's worth of percentage, PageUp/PageDown step 10, Home/End go to 0/100); `outline-none` is paired ONLY with `focus-visible:ring-2 focus-visible:ring-ns-accent`, never with `focus-visible:outline-*` on the same element. ACCESSIBILITY: `aria-valuenow` is the rounded rollout percentage; `aria-valuetext` is composed every change as e.g. 'rollout 35% — largest connected exposure 41% of traffic, above containment threshold' (or 'below') so a screen reader gets the honest readout, not just the input percentage; a permanently visible mono caption below the slider duplicates the threshold percentage, the largest-cluster percentage, and the above/below containment-threshold state in plain text at all times; a `sr-only` `aria-live=\"polite\"` region announces exactly once per genuine threshold crossing in each direction ('Crossed containment threshold — largest connected exposure now spans the lattice.' / 'Back below containment threshold.'), driven by a React effect on the above/below boolean so it never fires on every drag frame, only on an actual crossing. REDUCED MOTION: `prefers-reduced-motion` (detected via `matchMedia`, not just a CSS variant) skips the BFS stagger and disables the fill transition outright (`motion-reduce:transition-none` on every cell) — cells appear and disappear instantly, while the traced perimeter, which was never animated to begin with (it's a direct attribute set every change, not tweened), keeps drawing exactly as before. PROPS: `rows`/`cols` (lattice dimensions), `seed` (fixed PRNG seed — same seed reproduces the identical lattice and threshold), `rollout`/`defaultRollout` (controlled/uncontrolled 0-100, standard pattern), `onRolloutChange`, `segmentLabel` (noun used in readouts, default \"traffic\"), `className`. PERF: wet-set membership, cluster detection, and perimeter tracing are all pure functions of `(p, lattice)` recomputed via `useMemo` on every value change — cheap at this cell count (a few hundred cells, one union-find pass, one boundary walk) — only the capillary stagger's per-cell transition-delay is set imperatively outside React render, in a layout effect, for correct timing. DOM + SVG + CSS only, no canvas."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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 --ns-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": "serving-mallet-wind",
      "type": "registry:ui",
      "title": "Serving Mallet Wind",
      "description": "A thin ambient loader modelled on marlinspike seamanship's serving pass: a serving zone travels along a rope, tightly winding twine turn after turn so covered rope trails behind it and bare rope always waits ahead, told through a fixed served/bare boundary, a fast scrolling wrap texture, and one slow turn-lock highlight that snaps into place every 0.9s.",
      "files": [
        {
          "path": "registry/core/serving-mallet-wind/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/serving-mallet-wind.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "canvas",
          "ambient",
          "rope",
          "marlinspike",
          "texture",
          "industrial",
          "progress"
        ],
        "instruction": "Build <ServingMalletWind label? className?> as a self-contained 2D canvas widget inside a bordered card (rounded-md border border-border bg-surface, fixed 5:2 canvas aspect-ratio — wide enough to read as a thin ambient bar while still giving the rope's diameter real pixels to work with, geometry derived from the container's smaller dimension). Default label is a product-domain string (\"Serving in progress\"), not the mechanic's name — callers slot in their own copy (\"Provisioning workspace\", \"Syncing index\") so it reads as a real loader on a real surface. MECHANIC: the rope is drawn as a horizontal cylinder (diameter = min(w,h)/8, luminance-only vertical sheen gradient across its diameter using --ns-muted at the edges and --foreground at the centre, no hue) rendered as a continuous horizontal feed — bare rope conceptually enters from the right, covered rope exits to the left — so the served/bare boundary sits at a FIXED screen x (the canvas's horizontal centre) forever, and the loop never runs out of work. The rope also carries a 'lay': a coarse, aperiodic luminance mottle (three superposed sine waves with incommensurate periods — 53px, 89px, 137px — summed and clamped to roughly [-1,1], drawn as a low-alpha --foreground overlay in 6px-wide columns) sampled in MATERIAL coordinates (screen x + accumulated feed distance) across the WHOLE rope, bare half included — this is what gives the rope an identity, so a fixed boundary position genuinely reads as different material over time rather than two static halves with only a highlight moving. TWO DECOUPLED CLOCKS then drive the rest, per the round's decoupling rule (a real cadence far faster than followable must never be animated 1:1 against a followable one): (1) a fast, purely textural wrap — a continuous scrolling helical hatch (diagonal lines slanting across the rope's diameter) at WRAP_PITCH=4px spacing, phase driven by elapsed-time * 22px/s (the rope's continuous feed rate), rendered at full contrast (0.22 alpha --foreground base fill plus 0.5 alpha diagonal strokes) in the fully-served region left of the boundary, and RAMPING IN linearly across a ZONE_W=46px working band immediately right of the boundary (both the base-fill alpha, via a linear gradient, and each individual hatch line's stroke alpha) before giving way to the bare region beyond it, which stays flat, textureless sheen plus the lay mottle only — no single turn in this texture is claimed as individually trackable; and (2) a slow, deliberately followable turn-lock highlight — a soft point-light in --foreground (never --ns-accent, built as three concentric filled circles at descending alpha, never a radial-gradient-to-background which would punch a hole instead of glowing) that travels LINEARLY (no easing — a viewer must see it cross the whole rope diameter over the whole sweep, not jump most of the way in the first frame and then look stationary) from the rope's top edge to its bottom edge over 0.63s (representing one trip around the rope's circumference, viewed side-on), then holds at the bottom edge with a brief brightness peak that decays through the remaining 0.27s of a fixed 0.9s cycle before resetting — this is the one thing the spec wants a viewer's eye to actually track, and both the sweep and the arrival must be visible, never a blink. A 1px hard-edge stroke at the boundary itself marks the literal luminance step where the working zone finishes and rope becomes fully served. RESTING LOOP: at t0 the boundary sits mid-frame with a lock highlight starting a fresh sweep from the rope's top edge; by 2.5s roughly two to three more lock events have fired at the 0.9s cadence and the rope has drifted ~55px, so both the wrap-texture phase and the lay mottle read as entirely different material framing, while the boundary's screen position never moves; by 5s further drift and additional lock events make all three marks visibly distinct — unforced, unbounded, zero input. TOKENS: every stroke/fill reads var(--foreground) or var(--ns-muted) exclusively (plus the literal CSS keyword \"transparent\" as a gradient stop, never a colour) via getComputedStyle(document.documentElement), no literal fallback of any kind; the mount routine retries on the next rAF until both resolve and paints nothing before that; re-reads on every documentElement class mutation via a MutationObserver, and a ResizeObserver keeps canvas pixel dimensions and DPR in sync. No --ns-accent anywhere — the turn-lock highlight is explicitly luminance-only, matching the recipe's pointer-highlight rule even though this highlight has no pointer behind it. REDUCED MOTION: freezes on LOCK_SETTLED — elapsed time pinned to a point inside the hold portion of a lock cycle (highlight fully snapped at the rope's bottom edge with its brightness peak still visible, not mid-sweep) with the served (ridged) and bare (smooth) halves both clearly visible either side of the boundary — no rAF loop, no timers, no observers driving motion. An IntersectionObserver pauses the rAF loop when the card scrolls off-screen and re-arms it (re-checking tokens for a theme flip that happened while hidden) on re-entry. Cleanup on unmount cancels every pending rAF and disconnects all three observers. No pointer interaction — this is a resting ambient loader with no value prop and no finished state."
      }
    },
    {
      "name": "shearer-advance",
      "type": "registry:ui",
      "title": "Shearer Advance",
      "description": "A \"loading more rows\" strip for an infinite-scroll or virtualised list, built on real longwall coal-mining mechanics: a shearer traverses the face and drops cut material onto the armoured conveyor while hydraulic roof supports advance behind it in sequence, rather than an invented spinner shape.",
      "files": [
        {
          "path": "registry/core/shearer-advance/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/shearer-advance.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "list",
          "infinite-scroll",
          "pagination",
          "status",
          "svg",
          "accessibility"
        ],
        "instruction": "Build <ShearerAdvance loading exhausted aria-label height className /> as a small, wide status strip for the bottom of an infinite-scroll or virtualised list, sourced from real longwall coal-mining mechanics rather than an invented spinner. A shearer (a small rectangular body with two circular cutting drums) traverses back and forth along a coal face on a continuous triangle-wave sweep; its drums spin independently of the traverse, always at the same rate, reading as \"cutting\" rather than \"rolling\". Cut material drops onto an armoured face conveyor (AFC) running the width of the strip, drawn as a dashed line whose dash offset animates in one constant direction at all times, independent of the shearer's current travel direction, exactly like a real AFC. Behind the shearer, a row of hydraulic roof supports (a vertical prop plus a horizontal cap, evenly spaced along the face) release from the roofline, nose forward, and re-set in sequence as the shearer's pass reaches each one — a travelling wave of advances, not a simultaneous flip, and not a plain bouncing-dots loader wearing mining paint: the cap visibly drops off the roofline before the prop noses forward and the cap re-seats, one support at a time, timed to the shearer's own sweep so the sequence visibly follows the cutting head rather than running on an unrelated clock. `loading` (default true) keeps the face working — shearer sweeping, drums spinning, conveyor flowing, supports advancing in sequence — and sets `aria-busy`. `exhausted` (default false) overrides `loading` and parks the machine in a deliberately different, deliberately static geometry: the shearer sits at the worked-out end of the panel, drums stopped, every support fully re-set with no advance pulse, the conveyor idle, and a stop line marks the end of the panel — an honest \"this panel is worked out\", not a machine that runs forever implying infinite content, and not a machine that simply freezes mid-motion as if broken. The whole strip is wrapped in `role=\"status\"` with a real accessible name (an sr-only string, default \"Loading more rows\" / \"No more rows to load\" depending on state, overridable via `aria-label`) so a screen-reader user is told a new batch is loading or that the list has ended without needing to see the mechanism; the SVG itself is `aria-hidden`, purely decorative on top of that announced state. Geometry comes from a `ResizeObserver` on the host element (plain px coordinates, no viewBox scaling) so the strip stretches to any list width; drum, body and support-cap geometry scale down with `height` (default 40) and were designed but not visually confirmed in a browser below roughly 28-32px in this build — verify at your target row height before shipping it that small. Colors are `var(--foreground)` for the shearer body, drums and support caps, `var(--ns-muted)` for support props and the end-of-panel stop line, and `var(--border)` only for the roofline, a structural boundary rather than machinery — no `--ns-accent` anywhere, since this is ambient status, not interaction chrome. One mount-time `requestAnimationFrame` loop writes transform/dash-offset attributes directly to a handful of refs every frame — never React state per frame — and pauses via `IntersectionObserver` when the strip scrolls offscreen and `visibilitychange` when the tab is hidden, resuming from live elapsed time rather than a stored frame count. `prefers-reduced-motion` skips the loop entirely and paints one fixed, deliberately non-t0 frame instead, chosen mid-traverse with several supports caught at different stages of their advance pulse simultaneously, so the mechanism is legible in a single still frame; an `exhausted` component under reduced motion shows its own already-static parked geometry instead. Zero dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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/--ns-muted/--border/--background/--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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": "shutter-telegraph-board",
      "type": "registry:ui",
      "title": "Shutter Telegraph Board",
      "description": "An ambient multi-stage status board of six louvred shutters, modelled on the Admiralty six-shutter telegraph: each shutter independently flips edge-on (open, near-invisible) or face-on (closed, a solid opaque square) in a staggered cascade that forms a new symbol every couple of seconds.",
      "files": [
        {
          "path": "registry/core/shutter-telegraph-board/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/shutter-telegraph-board.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "loader",
          "grid",
          "dom",
          "telegraph",
          "signal",
          "ambient",
          "generative"
        ],
        "instruction": "A 2-column x 3-row grid of shutters filling a card, sized entirely from the container's smaller dimension (cellSize = minDim/4, measured via ResizeObserver on the root) so it holds its identity at card scale. Each shutter is two stacked absolutely-positioned divs inside a relative cell: a fixed 1px `border: 1px solid var(--foreground)` frame at `opacity: 0.25` (the shutter's edge, deliberately NOT `--border`, since at ~1.1:1 contrast in light theme that token would be invisible and this line is load-bearing for reading which cells are open) sitting behind an opaque `backgroundColor: var(--foreground)` panel whose only animated property is `transform: perspective(600px) rotateY(0deg | 90deg)` — 0deg is closed/face-on (the panel fully covers the frame, reading as a solid black square), 90deg is open/edge-on (the perspective projection alone collapses the panel to a near-invisible sliver, revealing the low-opacity frame and the background behind it; no opacity animation is involved in the open/closed transition itself). One of 8 fixed 6-bit boolean patterns (SYMBOLS, row-major reading order) is applied at a time via React state; on every symbol change each of the 6 shutters that needs to flip gets a `transitionDelay` of `index * 60ms` in reading order (left-to-right, top-to-bottom) with a shared 320ms ease-in-out `transitionDuration`, so the change reads as a wave of flips crossing the board rather than a simultaneous snap — this is the one thing to visually follow, at a cadence of roughly one full symbol change every 2000ms (DWELL_MS 1700 + 5 x STAGGER_MS 60), giving an 8-symbol, ~16s unbounded loop that simply repeats. Colours are plain `var(--foreground)` references directly in inline styles, not JS-read RGB values — pure DOM/CSS with no per-pixel colour math, so (matching the shipped flag-hoist-run sibling) the CSS cascade already repaints on a theme class change for free and there is nothing for a getComputedStyle+MutationObserver dance to do. The interval driving symbol advancement is started/stopped by an IntersectionObserver on the root so nothing runs off-screen, and is never started at all under `prefers-reduced-motion: reduce` — instead the board is pinned, synchronously in that effect (before any timer would otherwise fire), to symbol index 4: an exact checkerboard of 3 open / 3 closed shutters, the maximum structural contrast of any symbol in the set, deliberately distinct from the degenerate all-closed (index 0) and all-open (index 5) symbols that also exist elsewhere in the sequence and which would read as a blank board if chosen as the freeze frame; the CSS transition itself is also stripped via a `prefers-reduced-motion` media query as a second guard. The root is `role=\"img\"` with an accessible label (default \"Signal board\"); the grid itself is `aria-hidden`. `autoplay: none` — the board advances on its own internal 2s timer with no pointer, press, or scroll input in the loop at all. Zero dependencies, DOM + CSS only, no canvas, every colour a token (--foreground only)."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 --ns-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 (--ns-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": "sinkhole-ravel",
      "type": "registry:ui",
      "title": "Sinkhole Ravel",
      "description": "A destructive-action confirm modeled on cover-collapse sinkhole formation: a void chimneys upward through a canvas grain field as its ceiling grains individually ravel into the cavity below, until the crust can no longer bridge its own weight and drops in one fast collapse, backfilling partway before renucleating at a new spot. Click-arm-then-confirm gates a forced collapse.",
      "files": [
        {
          "path": "registry/core/sinkhole-ravel/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/sinkhole-ravel.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "confirm",
          "destructive",
          "canvas",
          "granular",
          "geotechnical",
          "arm-then-confirm",
          "ambient",
          "simulation"
        ],
        "instruction": "A card-scale canvas component whose grain field is a cross-section of granular overburden above a bedrock void. On mount and on every debounced ResizeObserver tick, the container is measured and a lattice of grains is built: cell size = sqrt(area / maxGrains) where maxGrains = clamp(round(minDim^2 / 340), 1800, 2400) (minDim = the container's smaller dimension), grains placed at jittered lattice positions with radius ~0.32x the cell's smaller edge. Each grain carries a rowFrac in [0,1] (0 = bedrock/bottom row, 1 = surface/top row). A single scalar voidFrac tracks how far the chimney has migrated upward, advancing at 0.09/s ambient with a +/-15% multiplicative jitter recomputed every 400ms so the advance never reads perfectly linear. A narrow chimney footprint is centered at a randomized chimneyX with radius = minDim*0.17*(0.6 + 0.4*rowFrac) (narrower at bedrock, widening toward the surface, mimicking real stoping-chimney bulking). Every 250ms tick, any alive grain inside the chimney footprint whose rowFrac sits within one grain-row of voidFrac (ahead of the front, not yet engulfed) rolls an independent 1-in-6 chance to ravel; grains the front has already passed by more than a few rows are force-removed as a safety net so nothing is left floating mid-void. A raveling grain gets its own 180ms fall+fade animation (quadratic ease, radius-scaled fall distance) rather than an instant disappearance, and a faint --ns-muted glow (radius ~2.1x grain radius, oscillating alpha) marks any grain currently eligible to ravel, so the agitated band is visible ahead of the front. Once voidFrac crosses 0.94, the top 6% of the overburden inside the chimney footprint (the remaining crust) triggers a single 220ms collapse event with a small per-grain stagger (<=40ms) so the drop reads as one fast ballistic event distinct from the ambient trickle, not ravel-sped-up; voidFrac jumps to 1 at that instant. 220ms after collapse starts, backfilling begins: for 3.5s, one new grain is spawned roughly every 140ms at a randomized position within the crater's rim radius (1.25x the surface chimney radius) and a rowFrac drawn from [0, 0.55] (the 55% refill target), each arriving with a 260-380ms fade/slide-in tween so the crater is never fully re-solidified going into the next cycle. 900ms after backfilling completes, the chimney renucleates: a new chimneyX at least 22% of the container width from the previous one is chosen, voidFrac resets to 0, and phase returns to ravel — prior backfilled grains stay in the field as a visible scar. The confirm control is a bottom-right button using the arm-then-confirm pattern (mirrors bitting-cut's evict control): first click arms (only while phase is 'ravel'), which ramps an internal armEase value toward 1 over ~1/3.4s, multiplying the ambient ravel rate by up to 3.5x (a visibly accelerating tease); a 3.2s window timer runs, and Escape, blur, or the timer expiring de-arms, easing the rate back down at the same rate rather than snapping. A second click inside the window force-triggers the collapse immediately regardless of the current voidFrac — every alive grain in the chimney footprint (not just the top 6%) is included in that one 220ms drop — and onConfirm fires exactly as backfilling begins (i.e., as the crater finishes dropping), never at the click itself. All colour is read via getComputedStyle(document.documentElement) for --foreground, --ns-muted, and --background before the first paint, re-read on a MutationObserver watching the root element's class; --ns-accent appears nowhere in the simulation, only as the confirm button's own border/text colour while armed and its focus-visible outline. Canvas is sized via ResizeObserver-driven width/height plus devicePixelRatio, never relying on intrinsic sizing. Under prefers-reduced-motion, the whole ravel-to-collapse sequence runs once synchronously at mount in fixed 16ms steps and freezes immediately after the collapse fires, before backfilling starts (crater fully open, walls sharp, maximum void visible) — no further motion, no arm/confirm styling change beyond focus states. autoplay is 'none' because the ambient ravel/collapse/backfill/renucleate loop runs entirely on its own internal clock with zero pointer/scroll dependency; the only pointer-driven state (arm/confirm) latches an irreversible collapse on its second activation, which per the round's autoplay rule must not be synthetically driven. Zero dependencies: canvas 2D, React state only for the armed boolean and the aria-live announcement string, everything else in a single mutable ref object updated on the rAF hot path."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 `--ns-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 `--ns-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 --ns-muted --border --ns-accent), with --ns-accent appearing only on the copy button's focus ring."
      }
    },
    {
      "name": "slate-gauge",
      "type": "registry:ui",
      "title": "Slate Gauge",
      "description": "A testimonial wall built as real double-lap slating: each quote is a slate hung on a batten, and only its computed gauge — the exposed margin — is ever visible at rest. The wall's geometry never changes; the only motion is one slate lifting on its nail to read.",
      "files": [
        {
          "path": "registry/core/slate-gauge/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/slate-gauge.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "testimonial",
          "wall",
          "social-proof",
          "roofing",
          "occlusion",
          "spring"
        ],
        "instruction": "Build <SlateGauge items? columns? heading? className?> as a DOM/CSS testimonial wall modelled on double-lap slating, no canvas. GEOMETRY: five diminishing courses, heights [132,120,110,101,94]px at slate width 168px, lap 44px, head allowance 8px; each course's gauge (its exposed margin) is computed, never chosen: gauge = (h - 44 - 8) / 2, giving [40,34,29,24.5,21]px. Testimonials fill row-major (course 1 left-to-right, then course 2, ...) into `columns` slates per course (default 2), cycling back through the five courses if there are more than five rows. Alternate courses are offset laterally by 84px (half a slate) to break the side lap. AT REST every slate's rendered face is exactly its own gauge tall — courses are laid edge to edge with zero overlap, so nothing needs a clip-path to hide the covered nine-tenths of a slate: it is simply never painted at that height. The quote text is always fully in the DOM inside a <button aria-expanded> wrapping an <article> — never display:none — so a screen reader, Ctrl-F and text selection reach the whole sentence regardless of what height is currently rendered; only the SHORT box clips it visually. READING: clicking a slate (or Enter/Space on its button) grows that one article from gauge to its true height h and raises it to z-index 1000, which is the only overlap this layout ever has, while it also rotates -34deg about a nail line 18px below its head (transform-origin) on an underdamped spring integrated in an rAF loop with k=210, c=19 (semi-implicit Euler, mass 1) — the spring's own underdamped ratio (c/c_crit ≈ 0.66) is what produces the single ~3deg overshoot as an emergent property rather than a hand-authored keyframe, and relaxing the SAME spring back to target 0 on release/Escape/blur is what produces the return clatter, so lift and return are one mechanism read in two directions. Only one slate is ever lifted; lifting a second sets both aria-expanded values in the same state commit, and Tab order is DOM order (course 1 left-to-right, then course 2, ...), not visual stacking order. HIT TARGET: each slate's button is padded upward (never downward, which would intrude on the course it's about to cover) to a 44px minimum height when its gauge is smaller, via fully transparent extra box — the painted <article> inside is untouched, so the drawn lap never changes. ALIVE AT REST, two unforced processes, neither ever touching the wall's own layout: (1) a 190px-wide gust sweeps left to right every 4.8s (natural, unscaled design units); a deterministic 3-in-17 subset of slates (index % 17 < 3) is centre-nailed, and whenever a centre-nailed slate's tail sits inside the gust it lifts 2.5-5px (per-slate deterministic amplitude via a sine-based hash of its index) on a second, snappier underdamped spring (k=1200, c=24) applied as a CSS `translate` (a separate transform channel from the read-rotation's `transform: rotate()`, so both apply independently on the same element without fighting). (2) a light azimuth swings ±14deg on a 22s cosine, written ONCE per frame as a single `--wall-azimuth` custom property on the wall root and read by every slate's own `box-shadow: inset calc(var(--wall-azimuth) * 0.4px) ...` — one style write drives the whole wall's cast-shadow relief inverting together, rather than touching N DOM nodes per frame. The azimuth NEVER changes any slate's own face luminance, only the cast shadow's horizontal offset and hence which side it falls on. `prefers-reduced-motion` skips the rAF loop entirely and instead composes ONE fixed, byte-stable frame: azimuth -6deg, one centre-nailed slate held at --gust-lift:5 (its max), and one representative slate forced into the lifted state at exactly -34deg with no spring residue — every state the component has, shown at once, with no timers running afterward. TOKENS AND COLOUR: only --background, --foreground, --ns-muted, --border (unused — no separator lines anywhere on the wall) and --ns-accent (interaction chrome only, the read-button focus ring) are read, via getComputedStyle on documentElement, re-read on a MutationObserver watching its class. Every derived colour keeps a token's own hue/saturation and only overrides HSL lightness: slate face = --foreground forced to L 0.34 (light theme) / 0.52 (dark); the exposed margin is a separate tint LAYER (not a different base colour) at the same hue, +0.06L light / +0.05L dark, drawn only over the top `gauge` px so the sky-lit strip reads lighter regardless of whether the slate is at rest or lifted; the tail shadow (the box-shadow colour) is the margin shifted -0.12L light / -0.10L dark; the riven fracture texture is a percentage-stop background gradient alternating face and face+0.03L in 3-9px bands seeded deterministically per slate index (byte-stable, never animated), rendered on a layer pinned to the slate's FULL design height regardless of its current gauge/lifted height so the pattern never needs re-scaling — only the enclosing article's overflow-hidden crops it. Quote ink is --foreground in light theme, --background in dark (darker than the slate face in both, no glow inversion). FIT: the component measures its host's width AND height (ResizeObserver) and scales all geometry by min(hostW/naturalW, hostH/naturalH), clamped so the smallest course never renders under 44px (scale floor) and never above 1.4x — deriving from the smaller dimension is what keeps a short, wide card from producing an unreadably short wall. No box-shadow colour literal fallback is given anywhere (an unset custom property makes the whole box-shadow declaration invalid and therefore simply unpainted, which is the desired pre-token-read state, rather than painting a placeholder hex)."
      }
    },
    {
      "name": "sleeper-renewal-relay",
      "type": "registry:ui",
      "title": "Sleeper Renewal Relay",
      "description": "An ambient row-level 'processing/refreshing' indicator built from mechanised sleeper-renewal trains: a crawling gantry crane lifts one old sleeper clear, swings a new one into the gap, and clips the rail down before advancing to the next tie. Old-vs-new is a continuous wear gradient across the 8-row stack, not a binary flag, so it never blinks.",
      "files": [
        {
          "path": "registry/core/sleeper-renewal-relay/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/sleeper-renewal-relay.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "list",
          "row",
          "table",
          "svg",
          "railway",
          "mechanical",
          "ambient",
          "sync",
          "refresh",
          "monochrome"
        ],
        "instruction": "A card (heading + mono description over a bordered panel) whose panel renders an 8-row 'track' as an SVG, sourced from mechanised sleeper/tie-exchange trains used on mainline railway renewal. Panel geometry is measured via ResizeObserver on the panel div (not the SVG): `rowSize = Math.min(panelW, panelH) / 8` (the binding smaller-dimension rule), and the 8-row stack is vertically centred in whatever extra height remains. A thin gantry line runs down the left edge (x = panelW*0.08) the full height of the stack; two rail lines run the same height at x = panelW*0.3 and panelW*0.7; each row's sleeper is an SVG `<rect>` spanning x = panelW*0.14..0.86, height = rowSize*0.46, vertically centred in its row band, drawn in front of the rails so gaps between sleepers read as visible track.\n\nEach row cycles on a fixed 1300ms period, all 8 rows offset by their own index so exactly one row is ever mid-cycle: 0-250ms straight vertical lift (still directly over its own gap), 250-450ms sideways kick out toward a side wagon (dx up to panelW*0.22, slight rotation up to 12deg, fading toward 15% opacity) — a combined 450ms 'departure arc'. 450-650ms the new sleeper swings in from the gantry side, elevated, fading in over the first quarter of that window; 650-800ms it lowers into the gap with a small sinusoidal clip-down bounce (`dy = rowSize*0.1*sin(pi*q)`, a soft overshoot-and-settle, not a hard stop) — a combined 350ms 'arrival arc'. 800-1300ms is a settled dwell during which the crane's arm rests on the newly clipped sleeper; the final 150ms of the dwell eases the crane carriage's Y position from the current row's centre to the next row's centre (wrapping from row 7 straight back to row 0) so the carriage already sits at the next row the instant that row's own 250ms lift begins — the literal 'advances to repeat, wraps to the top' mechanic, unbounded, 10.4s per full lap (8 rows x 1.3s).\n\nColour is never a two-state flag. Every row's fill is `rowWear(row, elapsedMs)`, a value that is 1 (fully `var(--ns-muted)`, worn) the instant the crane has genuinely never reached that row yet (only possible in the first ~10.4s), and otherwise 0 (fully `var(--foreground)`, crisp) the instant its most recent drop completed, ramping linearly back toward 1 over the following 10.4s (one lap) until the crane is due back — computed purely from elapsed time via `Math.floor` lap arithmetic, so no per-row state is stored and the value is trivially exact after a resize, a tab switch, or a pause/resume. Fill is set every frame as `color-mix(in srgb, var(--ns-muted) X%, var(--foreground) (100-X)%)`. The row currently being lifted reads this exact same continuous formula for its departing colour (so it is already wherever it had smoothly drifted to — never an instant colour flip at the cycle boundary); during the arrival arc only, the moving sleeper's fill is explicitly eased from that same starting wear value down to 0 across the 350ms, so the newly dropped sleeper visibly becomes crisp while it swings in rather than snapping on arrival. Small clip-mark tick paths (two short strokes per row, at the rail x-positions) use `opacity = clamp01((0.85 - wear) / 0.85) * 0.6`, so they fade in as a row freshens and fade back out as it wears back toward due-for-service — a secondary cue riding the same continuous value, no separate flash state machine. A dashed outline rect at the row's rest position (`stroke var(--foreground)`, `stroke-dasharray 3 3`) fades in during the departure arc and back out during the early arrival arc, reading as the empty gap in the track bed while no sleeper physically occupies it. The crane itself — carriage rect, arm line, hook circle — is drawn in `var(--foreground)` stroke/fill only, at opacities scaled by scene phase; `var(--ns-accent)` is never referenced anywhere in the component, matching the spec's explicit note that the crane and clip marks are foreground-only.\n\nThe animation clock is seeded `5*CYCLE_MS + RISE_MS + 20` (6770ms) in the past at mount (`INITIAL_OFFSET_MS`), landing 20ms into row 5's 200ms sideways-swing sub-phase, so the very first live frame already shows a distinct wear gradient across rows 0-4 and the crane mid-swing on row 5, rather than opening on a uniform fully-worn stack — deliberately a different frame from the reduced-motion freeze below.\n\nAll stroke widths and the carriage/hook sizes are authored at `REF_ROW = 32` and multiplied by `scale = rowSize / REF_ROW`, so the whole scene holds proportion at other panel sizes. An optional `paused` boolean prop (default false) supports binding this to a real data-refresh state: when true, the render loop keeps running normally until the active row's `localT` crosses into its dwell phase (>= 800ms into that row's own cycle — i.e. only once the sleeper is fully settled and clipped), then freezes the clock exactly there; unfreezing recomputes the internal start time so the loop resumes with zero jump. This guarantees a pause never lands mid-arc (open claw, sleeper mid-flight), matching the spec's explicit 'must not' constraint. Because the loop is otherwise fully unconditional and `paused` is a data-bound prop rather than pointer/press/scroll input, `autoplay.mode` stays `\"none\"`.\n\n`prefers-reduced-motion: reduce` renders a single static frame at `elapsedMs = 3*CYCLE_MS + 200` (row 3, 200ms into its 250ms rise sub-phase): the old sleeper on row 3 is lifted straight up, still directly above its own gap (pre-swing), rows 0-2 already show a fresh wear value from having been processed earlier in the same lap, and rows 4-7 have never been reached (`rowWear` returns 1, fully worn) — old-out, new-in-pending, and clip-pending are all visible in the one frame, named via `data-reduced-motion-freeze=\"mid-lift\"` on the root. Standard cleanup: ResizeObserver on the panel, IntersectionObserver (default threshold) pausing the rAF loop off-screen, `visibilitychange` pausing when the tab is hidden, all disconnected and the rAF cancelled on unmount. Zero dependencies, DOM+SVG+CSS only, no canvas."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-muted)`; the bead is a --border-ringed dot that swaps to --ns-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-ns-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 --ns-muted --border --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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 --ns-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-ns-accent, fill-ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": "slump-mould-drape",
      "type": "registry:ui",
      "title": "Slump Mould Drape",
      "description": "A content-reveal/settle moment built on kiln-slumping in warm glass work: a flat profile sags into a mould's contour under gravity, the centre drooping first and the drape spreading outward toward the edges over several seconds before holding, exactly conformed to the mould.",
      "files": [
        {
          "path": "registry/core/slump-mould-drape/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/slump-mould-drape.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "canvas",
          "reveal",
          "settle",
          "glass",
          "kiln",
          "lab-instrument",
          "mono",
          "ambient",
          "generative"
        ],
        "instruction": "Build <SlumpMouldDrape loop? className?> as a card-scale 2D-canvas content-reveal mechanic modelled on kiln-slumping: a flat glass profile sags under gravity into a mould's contour, centre first, edges last. THE PROFILE IS A PURE FUNCTION OF TIME: N_POINTS=32 samples span the container width; each sample's y-offset is y_i(t) = mould(x_i) * (1 - exp(-(t - delay_i)/TAU_S)) for t in the DRAPE_S=4.5s drape phase, where mould(x) = depth * (1 - cos(2*pi*x)) / 2 (a shallow bowl, 0 at both rims, depth=MAX_DROOP_FRAC=0.3 * containerMinDim at centre), delay_i = DELAY_EDGE_S=1.8s * (|x_i - 0.5| / 0.5) (0 at centre, 1.8s at the rim), and TAU_S=1.0. Nothing here carries state frame-to-frame — the whole animation is draw(phaseT) called with whatever phaseT the clock produces, which is exactly why the reduced-motion freeze frame is a single function call at STATIC_TIME_S=2.5 rather than a replay loop. PHASES: 0..DRAPE_S is the staggered drape above; DRAPE_S..DRAPE_S+HOLD_S=1.5s snaps every sample to mould(x_i) EXACTLY (full conformity, motionless — the asymptotic exp() value never quite reaches 1, so this snap is what makes the hold read as \"matches the mould\" rather than \"almost there\"); DRAPE_S+HOLD_S..CYCLE_S=6.4s (only when loop=true) lifts every sample back toward 0 over RESET_S=0.4s with a smoothstep ease (frac*frac*(3-2*frac)) — fast relative to the drape, since a real reset is a discrete offstage event (swapping blanks), not a slow process. loop=true (default) repeats the cycle indefinitely for the ambient/idle use; loop=false clamps phaseT at DRAPE_S+HOLD_S forever once reached, for a one-shot reveal (e.g. content finishing loading) whose settled state must persist rather than looping into a reset. RENDER: both the mould reference contour (drawn first, fixed, never animated) and the glass profile are stroked as a smoothed polyline — quadraticCurveTo through each pair of sample points' midpoint — never raw per-point dots, so the sheet reads as one continuous surface. TOKENS: profile line is --foreground; mould contour is --border only, and specifically NEVER the \"arrived\" signal (that reads from the profile's geometric conformity to the mould line, not colour) and never --ns-accent. Because --border is ~1.1:1 contrast in light theme, the mould's stroke WEIGHT (not a different token) is multiplied by 1.6x when documentElement lacks the dark class, so the target line the profile drapes toward stays legible in the harder theme. HOST: ResizeObserver on the root recomputes width/height/dpr(capped 2)/depth/baseline/stroke-weights (and the dark/light read they depend on) — never window.resize; IntersectionObserver(threshold 0) and visibilitychange both pause the single rAF loop; unmount cancels the frame and disconnects every observer/listener. Tokens are read via getComputedStyle(document.documentElement) plus documentElement.classList.contains('dark') at mount, before any paint, and re-read on a MutationObserver watching class/data-theme plus a prefers-color-scheme change listener. prefers-reduced-motion (matchMedia, live via a change listener) replaces the rAF loop with one draw(STATIC_TIME_S) call — centre points ~92% conformed, outer points ~50% conformed, the frame that shows the mechanic's signature centre-first/edges-last asymmetry that neither the flat start nor the fully-settled hold can show alone."
      }
    },
    {
      "name": "sorter-pocket-route",
      "type": "registry:ui",
      "title": "Sorter Pocket Route",
      "description": "An ambient processing/organizing loader modeled on a Hollerith tabulating-machine card sorter's chute: cards drop single-file down a center track, a gate flap swings to a heading and each card curves off with it into one of five numbered pockets below, the pockets filling unevenly forever as a rolling 14-card window with the oldest card fading on overflow.",
      "files": [
        {
          "path": "registry/core/sorter-pocket-route/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/sorter-pocket-route.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "process",
          "queue",
          "sorting",
          "mechanical",
          "dom",
          "css-animation",
          "aria-live",
          "monochrome"
        ],
        "instruction": "Build <SorterPocketRoute label? paused? className? style?> as a pure DOM + CSS ambient loader (no canvas) modeling a Hollerith card-sorter CHUTE — the routing/pocket mechanism, deliberately NOT a card reader or needle bank (round 9's jacquard-card-chain already owns that surface; this component never renders a 'read' event, a needle bank, or punched pips — the only visible subject is the gate swinging to a heading and a card curving into a pocket). LAYOUT: root is a relatively-positioned box (default h-[220px] w-full, ResizeObserver-driven) with a single-column drop track centered horizontally at the top, a gate line at height*0.32, and a row of 5 pocket bins occupying the bottom ~58% of the height. GEOMETRY (recomputed on every ResizeObserver tick): pocketWidth = containerWidth / 5.5; cardHeight = pocketWidth * 0.4; cardWidth = pocketWidth * 0.72; the 5 pockets are laid out flush-left-to-flush-right with equal gutters (gutter = (containerWidth - 5*pocketWidth) / 4), matching flex space-between spacing, computed analytically rather than measured. ROUTING SEQUENCE: a FIXED period-17 array (never Math.random per-card) cycled by an incrementing counter — ROUTE_PATTERN = [2,0,3,2,4,1,2,0,3,2,1,4,2,0,3,1,2] — indices are pocket numbers 0-4; pocket 2 (center) appears 6 times in the 17, each outer pocket 2-3 times, giving the required 'roughly 2x' center weighting while staying long enough not to visibly repeat within a short viewing window (17 cards * 1.1s feed ~= 18.7s per cycle). TIMING: setInterval feeds one new card every 1100ms (plus one immediate spawn on mount so the loader is never empty at t0). Each card is tracked in React state through two phases with setTimeout, not rAF (this is a discrete-event mechanism, not a continuous simulation): 'drop' for 300ms — CSS transition on transform: translateY, linear easing, from the track top to the gate y — then 'route' for 380ms — CSS transition on transform: translate(dx,dy) rotate(-6deg), cubic-bezier(0.4,0,0.2,1) (ease-in-out), from the gate position to the assigned pocket's center x and its current stack's landing y (bin bottom - cardHeight/2 - currentStackLength * rise). 680ms total per card journey, safely inside the 1100ms feed interval so exactly one card is ever mid-flight — this is the binding legibility constraint, do not shrink the feed interval or grow the journey past it. GATE FLAP: a single 2px --foreground line, transform-origin left, pivoting at the gate y-position; when a card enters its route phase the flap eases (same 380ms/ease-in-out transition) to GATE_ANGLES[pocket] (-60,-30,0,30,60 degrees for pockets 0-4); between cards (gatePocket === null) it eases back to 0deg — this is what sells 'mechanism deciding', not just the card animating on its own. POCKET STACKS: each pocket holds a rolling array of landed cards (id, removing flag), capped at 14 visible. On the card that would be the 15th, the bottom (index 0, oldest) card is flagged removing=true immediately (its opacity CSS-transitions to 0 over 260ms) and is spliced out of state via a matching 260ms setTimeout, while the new card is already visible on top of it — a continuous rolling window, never a hard reset/clear. Stack rise per card = min(3px, (binHeight * 0.7) / 14) so the tallest possible full pocket never exceeds 70% of the bin's height regardless of container size; each landed card is positioned bottom: index * rise within its pocket bin, index 0 = oldest/bottom. TOKENS (all via var()/color-mix(), nothing read through getComputedStyle since nothing here needs a resolved numeric value): flying/landed card fill is --background with a color-mix(in srgb, var(--foreground) 60%, transparent) border; pocket bin borders are var(--border) only, never a fill; stacked cards inside a pocket use color-mix(in srgb, var(--foreground) X%, var(--background)) with X interpolated 25 to 55 across the visible stack (oldest=25%, newest=55%) so pocket depth reads as a value gradient. --ns-accent never appears; the gate's heading is conveyed by angle and the card's curved path only. REDUCED MOTION / paused: skip the feed interval entirely and render a hand-authored STATIC_PHASE instead — five pockets at a fixed, distinctly uneven card-count array (STATIC_POCKET_COUNTS = [4,9,3,11,6], none equal, none zero) and one frozen card whose transform is the SAME translate/rotate interpolation used at runtime, evaluated at t=0.6 between the gate position and pocket 4's landing position (STATIC_FROZEN_POCKET = 4), with the gate flap frozen at the matching 0.6-interpolated angle — the single frame reading 'mechanism mid-motion, system in an uneven working state', never an all-empty or all-equal t0. HOST: role=\"status\" aria-live=\"polite\" on the root with a sr-only label (default 'Sorting'); the entire visual tree is a sibling aria-hidden div — nothing here needs individual card semantics since no card content is ever rendered, only the mechanism. Clean up: clearInterval and every pending setTimeout on unmount and whenever paused/reduced-motion flips."
      }
    },
    {
      "name": "spall-face",
      "type": "registry:ui",
      "title": "Spall Face",
      "description": "A card-scale background texture panel modelling frost (freeze-thaw) weathering of a porous rock face: the sampled field conveys slowly upward, and every second or two a flake somewhere lifts, tips outward, and falls off, exposing a fresh patch that brightens then weathers back into the surrounding stone over the following seconds.",
      "files": [
        {
          "path": "registry/core/spall-face/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/spall-face.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "canvas",
          "texture",
          "geology",
          "weathering",
          "ambient",
          "generative",
          "conveyor",
          "grid"
        ],
        "instruction": "Build <SpallFace className?> as a full-bleed <canvas>, w-full h-full. GRID: cell size cellPx = max(3, min(width, height) / 48) so geometry is derived from the container's SMALLER dimension; cols = ceil(width / cellPx), visibleRows = ceil(height / cellPx). Rows live in a buffer array one row taller than visibleRows (index 0 exits first at the top, the last index is the incoming buffer row entering at the bottom); each row is an array of cells { grain: a fixed per-row-band tone offset plus a small +/-0.02 per-cell jitter, sinceExposure: seconds since that cell last freshly spalled, initialised to 9999 = never }. BANDING: grain is not independent per-cell noise — every row is stamped once at creation with band = 0.09*sin(seq*0.34) + 0.05*sin(seq*0.13+2.1), where seq is a monotonically increasing row-generation counter, so adjacent rows correlate into horizontal strata a few cells wide (real rock faces have bedding) and the eye has a coherent feature to track as the conveyor scrolls, rather than a flat mottled field with no legible motion cue. CONVEYOR: a persistent conveyorPix accumulator increases by (48/70) * cellPx px/s (a full nominal 48-cell pass takes ~70s of illustrative compression, documented in-source against the real 0.1-2mm/yr field recession rate — fast enough that a band visibly advances within 2.5s), rows render at y = i*cellPx - conveyorPix so the whole field visibly slides upward continuously; once conveyorPix crosses a full cellPx the top row is array-shifted off (rowsBuf.shift(), permanently exits the visible frame), a brand-new unweathered row is pushed onto the bottom with the next band value (rowsBuf.push(), seq++), and every in-flight flake's stored row index is decremented in lockstep so it never desyncs from the cell it is exposing — this is real material transport, not a fixed field that fills and stops, which is what keeps the loop genuinely unbounded. TONE: every cell's rendered tone is t = clamp(0.14 + exp(-sinceExposure/9)*0.6 + grain, 0, 1) lerped across a single 3-stop ramp --background -> --ns-muted -> --foreground (t<0.5 lerps background->muted, t>=0.5 lerps muted->foreground); because both themes share the same two-token ramp, a freshly exposed cell (sinceExposure near 0, t pushed high) automatically reads brighter in dark theme (toward --foreground = lighter there) and automatically reads as a darker, more structured patch in light theme (toward --foreground = darker there) with no theme branch anywhere — the spec's required light-theme inversion falls straight out of the token roles rather than being hand-coded. Cells are bucketed into 16 tone buckets each frame and each bucket is filled with one ctx.beginPath()/ctx.rect() batch + one ctx.fill(), never a per-cell fillStyle write. SPALL EVENTS: on a random uniform 1300-2000ms interval (drawn fresh after each event, up to 3 concurrent), one cell address is picked at least EDGE_MARGIN_ROWS=2 rows clear of the buffer's top/bottom edge and a flake object is created carrying: its row/col indices (its pixel position is recomputed from those indices plus the live conveyorPix every frame, never frozen at spawn, so it rides the same coordinate system as the grid beneath it through any mid-flight row shift), a size of minDim*(0.04 + rand()*0.03) px (4-7% of the container's smaller dimension), a random lateral tip direction, and capturedT — the patch's CURRENT tone computed and frozen at spawn time, which is what the flake itself renders with as it departs (the old, pre-event surface, not the fresh colour). The flake runs three stages against elapsed time since spawn: lift (0-120ms, rises to 35% of its size, rotates up to 8deg), tip (120-220ms, rises to 50% of its size, rotates to 38deg — lift+tip together read as a single unmistakable departure arc, comfortably over the 200ms no-blink floor), and fall (220-350ms, accelerates away with an eased quadratic rise in offset, rotates on to ~93deg, and fades alpha to 0 as it exits off-frame). The instant elapsed crosses 220ms (the fall stage beginning) — checked once via an `exposed` flag on the flake — every cell within a small radius (round(sizePx / (2*cellPx)) cells) of the flake's origin cell has sinceExposure reset to 0, so the freshly exposed patch brightens (or, in light theme, darkens/structures) at the exact instant of departure, closing the visible beat; from there the exp(-sinceExposure/9) glow decays back toward the ambient base tone over the following several seconds, i.e. the patch visibly ages toward its own eventual next spall. Flakes older than 350ms, or whose row index has scrolled off the top edge, are dropped from the array. RESTING LOOP: at mount, ~2.5% of cells are seeded with sinceExposure = rand()*22s so t0 already shows several patches at varied weathering ages rather than a uniformly blank face; by 2.5s at least one spall event has completed (a new bright/dark patch exists where an older one used to be) and the banded conveyor offset has visibly advanced (roughly one full cell of travel); by 5s further events have fired at different locations and the t0 patches have aged further or already spalled again — unbounded, never converging on a static frame. INTERACTION: none. This is ambient background texture and the pointer must never trigger or accelerate a spall (the process is thermal/mechanical fatigue, not touch-driven) — no pointer listeners are bound at all. TOKENS: --background, --ns-muted and --foreground are read once via getComputedStyle(document.documentElement) at mount and re-derived on a MutationObserver watching documentElement's class attribute (which also force-repaints one frame immediately if the animation loop happens to be paused at that moment, e.g. tab hidden or the canvas scrolled out of view, so a theme flip never leaves a stale-theme frame sitting); there is no colour literal anywhere in the source, --border is never touched (it is a separator token, never a fill), and --ns-accent is never touched (there is no interactive climactic moment here). REDUCED MOTION: prefers-reduced-motion freezes on a single composed still — one flake at elapsed=280ms (inside the fall stage: visibly departed, mid-fade, well past lift+tip) whose own patch has already been exposed (sinceExposure=0.15, brightening/structuring), plus four other scattered cells manually set to sinceExposure of 1.4s/5.5s/13s/21s so the still shows a departing flake, a just-exposed patch, and several older weathering patches at once — no rAF loop and no pointer listeners are ever bound in this mode, and the still is redrawn (not re-animated) on resize and on theme change. Cleanup: cancels the rAF, disconnects the ResizeObserver, the theme MutationObserver, and an IntersectionObserver that pauses the loop when the canvas scrolls out of view (and resumes it on return, paired with the existing visibilitychange pause on tab-hide). The canvas is aria-hidden — it is decoration and carries no content a screen reader should announce. Props: className only."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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, --ns-accent, --ns-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": "specie-clip",
      "type": "registry:ui",
      "title": "Specie Clip",
      "description": "A partial-refund control drawn as a struck coin with a reeded edge: dragging a chord across the disc clips off a sector whose true circular-segment area, not the chord's linear position, is the refund amount, so the coin visibly diminishes by exactly what it pays out.",
      "files": [
        {
          "path": "registry/core/specie-clip/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/specie-clip.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "refund",
          "input",
          "form",
          "slider",
          "svg",
          "currency",
          "admin",
          "geometry",
          "clip-path"
        ],
        "instruction": "Render the outstanding charge as a struck coin: an SVG disc (viewBox 200x200, radius 78) with a reeded edge of 120 short radial ticks every 3deg, and the formatted charge amount as Geist Mono face text centered on it. The coin is drawn TWICE, once per side of a vertical chord, each copy a full disc + full reeded edge + full face text, each wrapped in its own <clipPath> rect driven by the same chord x-position — together the two halves are pixel-identical to one undivided coin, and refunding means separating them. The one governing scalar is `refundFrac` in [0,1]. Geometry and money are bound by the real circular-segment area formula: `fracAtD(d) = (acos(d) - d*sqrt(1-d^2)) / pi` for d in [-1,1] (d = the chord's signed offset from the disc centre in radius units), which is exact and monotonic 1->0, so live dragging reads the pointer's pixel position on the coin and evaluates this FORWARD, directly, on every move — the printed live figure beside the coin ('Refund $30.00 of $120.00 · 25%') is always `chargeAmount * fracAtD(currentChordOffset)`, never a cached or approximated value. The INVERSE — placing the chord at the position that pays out an exact target fraction (a keyboard step, a typed dollar amount, a snapped detent, a controlled-prop sync) — has no closed form, so it is solved by walking a 256-sample lookup table of fracAtD(d) precomputed once at module load, binary-searched and linearly interpolated between the two bracketing samples, never re-derived per call. Half the chord's drag travel is emphatically NOT half the refund anywhere except the exact geometric centre (a diameter chord always bisects a circle by area regardless of angle) — everywhere else the mapping is genuinely nonlinear, which is the entire point: this must never degrade into a linear slider wearing a coin skin. INTERACTION: the whole coin box captures pointer drag (not just a thin handle) and projects the pointer's x directly onto the chord offset; while dragging, the raw position snaps to the nearest of {25%, 50%, 100% of the charge, or one currency-appropriate round-dollar amount (steps of $1/$5/$10/$25/$100 scaled to the charge size)} whenever that candidate's chord position is within 4 real screen pixels, because support refunds cluster on round numbers. A native `role=slider` handle (aria-label 'Refund amount', aria-orientation horizontal, aria-valuemin/max 0/chargeAmount, aria-valuenow the live dollar figure, aria-valuetext like 'refund $30.00 of $120.00, 25%') sits over the chord; ArrowLeft/Right and Up/Down step $1, Shift steps $10, Home/End jump to $0/full charge. A paired native `<input type=number>` in the same row writes the exact same refundFrac for direct typed entry — dragging, the slider's keys, and the input all funnel through one commitFracChange path, so they can never disagree. NOTHING moves money by itself: a separate real `<button>` ('Refund $X.XX', disabled while the refund is $0 or a commit is in flight) is the only path that fires `onRefund` — the screen-reader story is complete with just the input and the button, drag is a convenience. COMMIT: clicking the button imperatively tears the far-side <g> free with a single one-shot 280ms translate+fade tween along the chord's normal, eased by a genuine `1 - 2^(-10t)` ease-out-expo curve (not a CSS approximation — driven by requestAnimationFrame writing the SVG `transform`/`opacity` attributes directly in the same user-space units as the ticks it carries with it, sidestepping the px-vs-viewBox ambiguity CSS transforms have on inline SVG), reading entirely from that geometry — no separate flash or highlight marks the tear. When the tween lands: the outstanding charge drops by the committed refund, `onRefund(refundedAmount, remainingAmount)` fires, refundFrac resets to 0, and the coin re-strikes — both clip groups immediately reflect the new, smaller struck amount, chord back at the disc's edge, ready for the next partial refund from the new balance. A `role=status aria-live=polite` line below the button announces 'Refunded $30.00. $90.00 remaining.' after each commit. REDUCED MOTION: the 280ms tween is skipped outright — the far side jumps straight to its fully-torn transform and zero opacity in one frame (geometry intact, no translate motion played), then the same re-strike/reset/announce sequence runs immediately; dragging, keys, and the input are unaffected either way. Colors are tokens only: disc fill `var(--background)`, disc rim, reeded ticks and the live chord line `var(--border)`/`var(--ns-muted)`, face text `var(--foreground)`, the keyboard focus ring `var(--ns-accent)` — correct in both themes, no canvas, no dependencies."
      }
    },
    {
      "name": "spectrogram-ascii-bands",
      "type": "registry:ui",
      "title": "Spectrogram ASCII Bands",
      "description": "A scrolling spectrogram in monospace ink: a real 512-point FFT grouped into log-spaced bands, each with its own adaptive noise floor so quiet bands stay dark and only the actual harmonic structure prints as bright glyph runs.",
      "files": [
        {
          "path": "registry/core/spectrogram-ascii-bands/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/spectrogram-ascii-bands.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "spectrogram",
          "audio",
          "fft",
          "ascii",
          "canvas",
          "signal",
          "data-visualization",
          "monochrome"
        ],
        "instruction": "Build <SpectrogramAsciiBands source? cellSize? label? className?> as a scrolling ASCII spectrogram over a <canvas>, with a left gutter of frequency labels and a focusable readout strip below. ANALYSIS: a 512-point radix-2 Cooley–Tukey FFT, inlined with no dependency, over a Hann-windowed frame (w[n] = 0.5 - 0.5*cos(2*pi*n/511)) taken from a modular ring of the newest 512 time-domain samples; magnitude is normalised by N/4 and converted with dB = 20*log10(mag + 1e-9). BANDS: 32 log-spaced groups covering bins 2..200, band k starting at floor(2 * (200/2)^(k/32)) and running to the start of band k+1, each band's value being the max dB over its bins. Because 100^(1/32) is under 1.16 the raw formula's first ten starts collide on bins 2..5, which prints ten byte-identical rows and makes a max() over an empty range yield -Infinity, so each start is forced strictly above its predecessor — the lowest bands end up one bin wide, which is honestly the FFT's own resolution limit down there, and log spacing takes over above it. ADAPTIVE FLOOR — this is the sparsity control and the whole reason the field is readable: every band runs its own slow AGC, floor += (dB - floor) * (dB < floor ? 0.02 : 0.002) per 50 ms hop, so it falls onto dips fast and climbs out of them ten times slower and therefore tracks the band's QUIET baseline rather than its mean. A steady hiss or a leakage skirt sinks into its own floor and vanishes instead of producing the uniform medium-density wash that would make the field unreadable; the floor is clamped at -35 dB so it can never climb onto the subject itself. The floors are settled by 300 analysis-only warmup hops with a 12x boosted rise coefficient before any visible column is committed — without that pre-roll every floor is still at its -70 dB seed and the first frame the viewer sees is exactly the wash the AGC exists to prevent. GLYPH: excess = dB - floor; anything under floor + 12 dB prints NOTHING, so most of the frame is empty by construction, and above it idx = clamp(round(excess/32 * 9), 1, 9) indexes the shared ' .:-=+*#%@' density ramp. SCROLL: one new column per 50 ms hop appended into a Uint8Array(cols * 32) ring behind a moving write head, so the field scrolls right-to-left with zero per-frame reallocation and the newest column is always at the right edge. DRAW: two-pass, the same discipline as background-ascii-caustics — pass one walks the ring filling the ramp-index buffer and buckets every non-empty cell into one of 6 alpha buckets; pass two sets ctx.globalAlpha once per bucket and fills only that bucket's cells. Ink is read once from getComputedStyle(canvas).color and the accent from --ns-accent on documentElement, both re-derived on a documentElement class MutationObserver so a theme flip is live; the mono cell width is measured with an offscreen canvas measureText. Monochrome ramp only — no viridis/jet/inferno, no glow. PITCH TRACK: the argmax band of each column is drawn as a single '·' in the accent at that band's OWN row, painted after the field so it reads as a contour riding over the dominant ribbon — put on a separate one-row strip above the field it could not encode which band actually won and degenerated into a solid accent rule. AXIS: a left gutter of frequency labels every 8 bands in muted ink, computed from the source's real sample rate rather than baked in. SOURCE: the source prop takes either an AnalyserNode (read via getFloatTimeDomainData) or a (out: Float32Array) => void sample provider; omitted, it falls back to a deterministic synthetic signal — formants at 320 Hz and 1.9 kHz sharing a ±8% vibrato at 0.4 Hz, an exponential 200→4000 Hz sweep looping every 9 s, and pink-ish noise well below both — so nothing ever calls getUserMedia and a screenshot gate never meets a permission prompt. At rest that reads immediately as a spectrogram: two steady horizontal ribbons crossed by a slow rising diagonal against a mostly empty field. INTERACTION: hovering a column parks a '┊' cursor on it and prints that column's peak as '1.9 kHz  −22 dB  0.8 s ago'; the field itself is never distorted by the pointer, because the cells are measurements and bending them would lie. On pointer-out the cursor and its line fade out on a 0.4 s time constant while the running peak summary for the whole visible window crossfades back in — an ease, not a hard cut. The readout region is focusable with a visible accent focus ring, and ArrowLeft/ArrowRight step the cursor column by column through the history; a visually hidden aria-live=polite span is written ONLY on a deliberate key step, never from the 20 Hz running summary, which would be a screen-reader firehose. prefers-reduced-motion renders exactly ONE static frame of a pre-seeded 240-column history and skips the rAF loop and the pointer listeners entirely, keyboard stepping still redrawing on demand. The loop pauses on document.hidden and resumes on visibilitychange. Direct-DOM rAF, zero React state on the hot path. Props: source (AnalyserNode or sample provider, default the synthetic signal), cellSize (row height px, default 16; the field is 34 rows tall), label (accessible name for the readout region), className."
      }
    },
    {
      "name": "spindle-strike",
      "type": "registry:ui",
      "title": "Spindle Strike",
      "description": "Payment history as a diner-receipt spindle: settled transactions pile up impaled on a shared 1px spine, and a refund doesn't remove the card. It pulls the same pierced node a few px back up the spine with its puncture elongated into a slot.",
      "files": [
        {
          "path": "registry/core/spindle-strike/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/spindle-strike.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "billing",
          "payment-history",
          "receipt",
          "transactions",
          "list",
          "refund",
          "spring",
          "hash-rotation",
          "mono",
          "aria-live",
          "accessibility"
        ],
        "instruction": "A payment-history list rendered as a diner-receipt spindle. Each transaction is a small card (rounded-md, border-border, bg-background) impaled on a shared 1px vertical spine (--border) running down the middle of the stack, newest closest to the top. Every card carries exactly one governing scalar, `rawY` — its recency index (0 = newest, computed among ALL transactions including refunded ones, so a refunded card's old slot stays reserved and reads as a gap in the pile) times an 8px CARD_GAP, minus a fixed 48px REFUND_LIFT (kept as an exact 6x multiple of CARD_GAP) when its status is 'refunded'. translateY, z-index (`3000 - rawY`, so whichever card sits highest wins the stack order automatically) and depth-proportional ink (color-mix from --foreground toward --ns-muted, ramping to 0 over the first 112px of *unshifted* depth so a lifted refund never reads as 'handled') all derive from that one number — none of the three is set independently. Rotation is a djb2-style hash of the transaction id clamped to +-3deg, not Math.random(), so SSR and re-renders agree and the tilt never jitters. A newly-settled transaction starts 90px above its landing spot (14px under prefers-reduced-motion: reduce) at opacity 0; clearing that offset one frame after mount lets the card's own transform/opacity transition — one shared rule, cubic-bezier(.34,1.56,.64,1), 560ms (160ms linear under reduced motion), identical on every card, no per-card delay — carry it down past rest once and settle, which is also what makes the rest of the pile appear to shove down in sync, since every other card's rawY shifted by exactly the same CARD_GAP and is animating under the exact same rule: there is no separate 'shove' animation choreographed per card. A 3px puncture dot (color-mix from --foreground toward --ns-muted) sits at each card's rotation origin (50% 16px) and pops in shortly before the drop settles, timed to read as the strike landing rather than the card just fading in whole. A refund is a prop update on the SAME transaction object — the caller flips `status` to 'refunded' and supplies `refundedAmount` — not a removal-and-reinsert, so the existing <li> (same React key) is the node that moves: it eases up the spine by REFUND_LIFT under the identical shared transition, its puncture widens from a 3px dot into a 13px rounded slot (its own transition, decoupled from the position spring since it's a shape change, not a move), and its ink resets toward full --foreground since darkening reads off the unshifted depth. Filtering the refunded card out of the list and re-rendering a restyled copy with a badge would break the entire premise — the pierce has to survive the reversal. Fully controlled: `transactions` is the only data in, always in caller-supplied chronological order (oldest first), which is also the <li> DOM order inside the <ol> — independent of the visual stacking, so assistive tech always gets the story in time order even though sighted users see newest-on-top. Each <li> is a real listitem containing a <dl> (date, amount, status — status reads 'Refunded, $30.00 returned' for a refund, plain 'Settled' otherwise) plus an aria-label summarizing the same three facts, is individually focusable, and lifts 2px clear of the pile on focus via translateY + z-index only (JS-tracked focus state composed into the same transform string, so it stacks correctly with the rotation) — no scale or opacity change, so the receipt's text never resizes under focus. A refund fires one aria-live=polite announcement naming the amount returned. prefers-reduced-motion shortens the entrance drop and every transform/opacity/color transition to a brief linear pass rather than dropping them — a press still visibly moves, just without the spring overshoot or the full-distance travel; rotation, being static in the first place, is unaffected either way. Props: `transactions` (`{id, date, amount, status: 'settled'|'refunded', refundedAmount?}[]`), `ariaLabel` (default 'Payment history'), `className`. Pure DOM + inline SVG-free CSS — no canvas. Demo: an 8-receipt history with one already refunded (so the elevated, slotted card is visible at rest, not only after interaction), a 'Settle a payment' button that drops a new receipt onto the spike, and a 'Refund newest settled' button that pulls the current top-of-pile receipt back up the spine."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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": "starch-shear",
      "type": "registry:ui",
      "title": "Starch Shear",
      "description": "Film-strip thumbnail scrubber made of a shear-thickening material: drag slowly and each thumbnail visibly lags the one before it down the chain like a liquid; flick fast and the strip instantly locks into one rigid slab that moves and stops as a single piece.",
      "files": [
        {
          "path": "registry/core/starch-shear/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/starch-shear.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "listbox",
          "media",
          "carousel",
          "drag",
          "keyboard",
          "accessibility",
          "physics"
        ],
        "instruction": "Build <StarchShear items value? defaultValue? onValueChange? label? className?> where `items: { id, label, caption? }[]` is the ordered set of thumbnails, `value`/`defaultValue` is the active index (controlled/uncontrolled, defaulting to 0), `onValueChange(index)` fires whenever the active index commits, and `label` names the listbox (default \"Film strip\"). STRUCTURE: an outer relative wrapper contains one role=\"listbox\" div (tabIndex 0, aria-label from `label`, aria-activedescendant pointing at the active option's id) that is a fixed-height, overflow-hidden viewport; inside it a track div sized to items.length * (88+8) - 8 px holds each thumbnail as an absolutely-positioned role=\"option\" div (aria-selected, aria-label from item.label, left: index * 96, width 88, height 68, rounded-[6px] border border-border bg-background) at its rest slot — the option never moves through layout, only through an imperative transform written on its own ref. MECHANISM — the drive value: a single ref (`driveRef`), the raw px scroll target, is always instantaneous — set directly and synchronously from the pointer position during a drag (1:1, no rounding, no easing), or jumped directly to a computed target on a keyboard step or a controlled `value` change. It is never itself animated; it is the thing every thumbnail's visual position is chasing. MECHANISM — the chain: one requestAnimationFrame loop, direct-DOM writes only (no per-frame React state), advances an array of per-thumbnail rendered positions. Each frame it first updates a velocity envelope from |drive_now - drive_prev| / dt with a fast attack (envelope jumps up almost immediately when the instantaneous speed exceeds it) and a slow release (envelope bleeds down gradually when speed drops below it) — a classic asymmetric envelope follower. The envelope, normalized against a reference speed and clamped to [0,1], sets a relaxation rate (lambda) that interpolates between a very low value (soft/fluid: each node closes only a small fraction of its gap per frame, so a lag is visibly still cascading down the row several frames after the pointer stops) and a very high value (stiff/locked: every node closes essentially its whole gap within a frame or two, so the whole strip reads as one rigid transform even though each node is still computed independently). The chain itself is a follow-the-leader relaxation, not a spring: thumbnail 0 relaxes toward `drive` at the current rate, thumbnail 1 relaxes toward thumbnail 0's just-updated position at the same rate, and so on down the row — each node chases the one immediately before it, so the lag compounds with distance from the point of contact. This is a deliberately overdamped, monotonic settle (no overshoot/bounce) because a liquid flows into place, it does not spring into place. SELECTION: aria-selected / the active option's persistent 2px var(--ns-accent) box-shadow ring (the component's only accent use) is derived straight from `drive` every frame via nearest-thumbnail-to-viewport-center, independent of how far the visual chain has caught up — the committed selection can lead the visible settle, exactly like a viscous material registering a hit before it finishes flowing into its new shape. POINTER: pointerdown on the listbox begins a drag (setPointerCapture, records start x and the current drive); pointermove writes drive = clamp(startDrive - dx, 0, maxDrive) every sample; pointerup/pointercancel ends the drag, and if total travel stayed under 6px (a tap, not a scrub) the thumbnail under that point is selected directly through the same jump path used by keyboard, forced into the soft/fluid regime for its settle. KEYBOARD: ArrowLeft/ArrowUp and ArrowRight/ArrowDown step the active index by one and force the chain's envelope to its minimum for that jump — the settle is always a visible one-item cascade, regardless of any residual velocity from a prior drag — while Home and End jump to the first/last thumbnail and force the envelope to its maximum, so the whole strip visibly leaps as one locked piece; a controlled `value` change from outside a drag is treated the same as Home/End (locked), since a caller setting the index directly is not a scrub gesture. REST STATE: every thumbnail also carries a static translateY sag — SAG_PX * (2*sin(pi * index / (n-1)) - 1), a literal -2px at both ends of the strip rising to +2px at the center — applied unconditionally alongside the chain's translateX, never animated on its own and never driven by velocity; it is the constant visual tell that the material is fluid even when nothing is being dragged. IDLE MOTION: with nothing being dragged, node[0]'s chase target is a slow ±1.6px sine offset (7s period) instead of the exact drive value — the real `drive` and the committed selection never see it, only the visual settle target does — so the same overdamped coupling that answers a real drag carries a gentle, continuously cascading lag down the strip at rest, unprompted, with no risk of the selection drifting. TOKENS: all ink is var(--border)/var(--foreground)/var(--ns-muted)/var(--background); var(--ns-accent) appears only on the active thumbnail's ring; no gradients. REDUCED MOTION: prefers-reduced-motion removes the chain, the envelope, the sag and the idle wobble entirely — every thumbnail's transform is written to the current drive value directly, every frame, with translateY always 0, which reads as plain instant scrolling with a still, non-cascading strip. Zero dependencies; DOM + CSS only, no canvas, no SVG beyond a small decorative corner-bracket glyph per thumbnail (aria-hidden), open strokes rather than a filled silhouette so it can't misread as a play icon. DEMO: a 12-item strip in a card frame with a caption line explaining the two regimes; fully interactive, no synthetic input of its own — the landing-page card's own self-demonstration is driven by the site's shared autoplay descriptor below, not by anything baked into the demo."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-muted to --ns-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/--ns-muted/--border/--ns-accent (fill-ns-muted, stroke-ns-muted, stroke-border, fill-ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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→--ns-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/--ns-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-metaball-merge",
      "type": "registry:ui",
      "title": "Status Metaball Merge",
      "description": "Group membership drawn as an implicit surface: each item is a charge in an inverse-square field, and a marching-squares isoline at F = 1 is stroked as a bare outline, so items in the same group share one necked contour and an item that leaves visibly pinches off into its own closed loop.",
      "files": [
        {
          "path": "registry/core/status-metaball-merge/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/status-metaball-merge.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "canvas",
          "metaball",
          "marching-squares",
          "isoline",
          "grouping",
          "membership",
          "aria-live"
        ],
        "instruction": "Build <MetaballMerge items? label? onChange? className?> as a bare-outline metaball grouping widget: a <canvas> field box above a row of chip buttons. FIELD: every item is a charge, and the scalar field is the classic inverse-square metaball sum F(x, y) = Σᵢ rᵢ² / ((x − xᵢ)² + (y − yᵢ)² + 1e-3) — the 1e-3 softening is what keeps F finite at a charge centre. Because the threshold is exactly 1.0, rᵢ is literally the radius a lone charge's contour would have, and two equal charges merge once they are closer than 2√2·r ≈ 2.83·r; membership is therefore not a colour or a badge, it is whether the surface connects. ISOLINE: marching squares on a 4 px lattice over the panel's field box. F is sampled into a reused Float32Array (reallocated only when the grid size changes), then per cell a 4-bit corner mask (TL=1 TR=2 BR=4 BL=8) is built against the threshold and indexed into the standard 16-case segment table, edges numbered 0=top 1=right 2=bottom 3=left. Every segment endpoint is placed by LINEAR interpolation along its edge, t = (1 − Fa) / (Fb − Fa) clamped to [0,1] — never at the edge midpoint, or the contour comes out visibly stair-stepped at this lattice size. The two saddle cases (mask 5 and 10) are disambiguated by re-evaluating F at the cell centre: if the centre is inside, the two inside corners are joined through the cell and the outside corners are isolated instead, so a neck resolves as a genuine pinch rather than an X. All segments accumulate into one Path2D and are stroked ONCE per frame at lineWidth 1.25 in the canvas's own colour, globalAlpha 0.85 — one stroke call, no fill, no shadow, no glow, which is what keeps the frame almost entirely empty. LAYOUT: each item has a chip button below the canvas and an anchor around the cluster centroid; a member's anchor sits at 0.5·Rc (well inside the merge distance) and a non-member's at 1.35·Rc (well outside it — the gap is about 1.5× the merge distance even with the ambient breathing at its peak), with Rc = 0.47·min(w,h) / (1.35 + r/Rc) so the outermost loop plus its own radius always stays in the box. Members are packed onto consecutive slots of an arc one ARC_STEP = 2π/(n+2) apart, centred on the members' own mean direction and kept in their original circular order — that spacing guarantees the group is always ONE connected chain (never two 'grouped' items too far apart to merge) and that the arc always keeps a three-step gap, so it can never close into a ring with a hole punched through the middle. Non-members keep their own direction. The charge radius is derived from that constant member-to-member chord S rather than picked by eye: since the waist half-width between two equal charges is √(2r² − S²/4), r = 0.4·S puts the neck at about 0.66·r — thin enough to read as a deliberate waist, thick enough that the ambient breathing never severs it. AMBIENT PULSE: each charge's centre orbits its anchor on a 3 px circle at 0.11 Hz with an FNV-hash-derived per-item phase, and rᵢ breathes ±6% at 0.07 Hz on a second hash-derived phase, so the neck between merged blobs visibly thins and thickens on a slow beat with jitter and nothing ever holds a dead pose. STATE: toggling a chip retargets that item's anchor and the charge glides there on a critically damped spring (k = 120 s⁻², ζ = 1, so c = 2√k), so the contour necks, thins and pinches into a separate closed loop over roughly 500 ms — at exactly the moment the item stops belonging. HOVER/FOCUS: pointerenter or keyboard focus on a chip raises that item's rᵢ by 18% on the same spring, so its blob swells and its neck to the cluster fattens — the identity cue is 'which lobe grew', which needs no legend; pointerleave/blur springs it straight back. TOKENS: the canvas is the medium, so ink is read from getComputedStyle(canvas).color at mount (the canvas carries text-foreground) and re-derived on a documentElement class MutationObserver, so a theme flip is live and no colour literal is ever baked in; chips use --border/--foreground and --ns-accent appears only on the chip focus ring. PERF/A11Y: dpr clamped to 2, a ResizeObserver with a zero-size guard, one rAF loop that is the sole canvas writer and pauses on document.hidden, and a sleep condition that stops the loop once every spring is under epsilon AND the ambient pulse is disabled. The canvas is aria-hidden; the chips are real <button aria-pressed> controls inside a role=group, each with a visible accent focus ring, and a visually hidden aria-live=polite region announces 'N items grouped' on every toggle, so the canvas is never the only channel. prefers-reduced-motion: no rAF loop at all — no ambient orbit, no breathing and no pointer/focus swell (the chip's own focus ring carries that cue instead), and a toggle jumps the anchors to their new positions and redraws exactly one frame — the merged-versus-split contour still reads perfectly with zero motion. Props: items ({ id, label, member? }[], default a four-person approver set), label (names the group for assistive tech), onChange (member ids), className. Zero dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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, --ns-muted, --ns-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": "steam-trap-batch-flush",
      "type": "registry:ui",
      "title": "Steam Trap Batch Flush",
      "description": "An inline sync-status glyph modelled on a float-and-thermostatic steam trap: condensate (pending events) fills a small chamber, a float trips a discharge valve at 88% height, the chamber blows down fast on an eased-out curve, and refill starts immediately, forever.",
      "files": [
        {
          "path": "registry/core/steam-trap-batch-flush/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/steam-trap-batch-flush.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "status",
          "indicator",
          "sync",
          "svg",
          "motion",
          "steam-trap",
          "loading",
          "icon",
          "buffer"
        ],
        "instruction": "An inline sync-status glyph, 24-64px, meant to sit beside a text label the way a spinner or cloud-sync icon would. It renders a rounded-rect chamber outline (stroke var(--ns-muted), strokeWidth 1.25 with vector-effect=\"non-scaling-stroke\" so the outline never drops below roughly 1 physical px at any size, including the smallest supported 24px in light theme) and, clipped inside that same rounded rect via an SVG clipPath keyed with useId (safe for multiple instances on one page), a fill rect (var(--foreground) at 0.7 opacity) whose y/height are updated every animation frame via direct setAttribute calls on a ref rather than React state, avoiding a re-render per frame. A small circle (also var(--foreground)) rides the top edge of the fill as the float. The whole cycle runs on a requestAnimationFrame loop paused via IntersectionObserver while off-screen: fraction fills linearly at a constant rate of 0.275/s (derived directly from the spec's '3.2s from empty to the 88% trip line' — 0.88 / 3.2), and once fraction reaches the 0.88 trip point a 340ms blowdown begins, interpolating fraction from 0.88 down to 0.06 on an eased-out cubic curve (fast, front-loaded — the real mechanical blowdown is far quicker than the fill it releases). During that same 340ms the float is pushed an extra 0-2 viewBox units below the fill line (also eased-out), and for a further 180ms after blowdown ends that extra offset eases back to zero while the chamber has already resumed filling from 6% — the overshoot-then-settle read that makes the trip feel like a mechanical snap rather than a sawtooth reset. Because refill always resumes from 6% rather than 0% at the same constant rate, the steady-state loop is genuinely periodic at roughly 3.32s (fill 6%->88% takes ~2.98s, plus the 340ms blowdown), which the component treats as authoritative over the spec's rounded '~3.6s' figure since it preserves the spec's stated fill rate exactly rather than padding the cycle. Each mounted instance starts at a random fraction between 6% and 88% rather than always at empty, so multiple glyphs on one page (e.g. a document list) are visibly out of phase with each other from t0. Under prefers-reduced-motion no rAF loop starts at all; the component sets one static frame directly — fraction at 0.88 (the trip point) with zero float overshoot, chosen because it is the single most information-dense frame available: full chamber, the trip threshold, and the float all clearly legible at once, rather than an arbitrary mid-fill or empty frame. Default render is a non-interactive `role=\"img\" aria-label={label}` span (label defaults to \"Syncing\") since the fill-and-flush motion itself carries no discrete state worth announcing via aria-live. Passing `interactive` renders the same glyph inside a real `<button>` instead, with a focus-visible ring using var(--ns-accent) as the only accent-eligible pixel in the whole component; pressing it calls an internal forceFlush (exposed via a ref-held closure, not React state, to stay off the render path) that starts a blowdown immediately from whatever fraction the chamber currently holds — it reuses the identical 340ms eased-out curve and never changes the 0.88 trip threshold or the 340ms duration, only the moment blowdown starts; an optional `onFlush` prop fires once per blowdown start, forced or natural. Props: size (px, default 24, legible 24-64), label (accessible text, default \"Syncing\"), interactive (boolean, default false), onFlush (callback), className. Zero color literals — chamber outline, fill, float and focus ring all read tokens via CSS var(), and cleanup on unmount cancels the rAF and disconnects the IntersectionObserver. Pure DOM/SVG, no canvas, 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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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, --ns-muted labels, --ns-accent only on the keyboard focus ring).",
        "rank": 298
      }
    },
    {
      "name": "stencil-fill",
      "type": "registry:ui",
      "title": "Stencil Fill",
      "description": "A reusable pattern-masked input: the full template renders at rest as a muted stencil with literal separators already in place, each typed character inks its own cell, and a refused character prints faint, shakes, and falls away instead of vanishing silently.",
      "files": [
        {
          "path": "registry/core/stencil-fill/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/stencil-fill.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "input",
          "form",
          "mask",
          "validation",
          "license-key",
          "accessibility"
        ],
        "instruction": "Build a reusable pattern-masked input driven by a `mask` prop string ('X' = editable cell, any other character = a literal separator, e.g. \"XXXX-XXXX-XXXX-XXXX\"). At rest, render the ENTIRE mask verbatim as a muted stencil: every 'X' cell shows the literal glyph 'X' in `var(--ns-muted)` at ~50% opacity, and every separator cell shows its literal character (also muted) already in place — the format is visible before the first keystroke, never auto-inserted as-you-type. A single native `<input>` (transparent text and caret via `color:transparent; caret-color:transparent`, absolutely positioned to exactly overlay the decorative cell grid beneath it — same split as card-number-emboss: real input drives state/focus/label/autocomplete, `aria-hidden` decorative layer is what's actually seen) owns all interaction. Because the mask model is append-then-backspace (not free mid-string cursor editing, matching the rest of the registry's structured-entry components), intercept every printable keydown yourself: `preventDefault()` always, validate the character against a `validate(char)` predicate (default `/^[A-Za-z0-9]$/`), and if it passes, transform it (default: uppercase) and append it to an internal typed-character array, advancing to the next open cell. If the character equals one of the mask's own literal separator characters, silently no-op — that is the 'auto-skip', never a rejection. Backspace/Delete removes the last typed character. On accept, the cell's glyph steps from stencil to inked with a 90ms transition (opacity 0.5->1, `var(--ns-muted)` -> `var(--foreground)`, font-weight 400->600) — a state change, not a spring. The emitted `value` (via `onValueChange`) is built by walking the mask and interleaving typed characters with literal separators, so it keeps the separators exactly where the mask puts them — never raw digits without them.\n\nThe rejection beat is the signature interaction: when a keystroke fails `validate`, do NOT just drop it. Show it: in that character's target cell (the next open one), render an absolutely-positioned overlay printing the refused raw character at `var(--ns-muted)`, animate it through a CSS keyframe — appear to ~40% opacity, a 3px-amplitude decaying horizontal shake (a few decreasing-amplitude translateX steps standing in for a spring, not a linear tween), then fall (translateY down, fade to 0 opacity) over roughly 550ms — and clear the overlay on `animationend` (with a small timeout fallback in case the event is missed). Simultaneously fire a polite `aria-live` announcement built from a `rejectMessage(char)` callback (default: \"Letter X not allowed\" for a letter, \"Character \\\"x\\\" not allowed\" otherwise) — clear the live region text and re-set it on a short delay so two consecutive rejections of the same character still both get announced, since an unchanged live-region string doesn't reliably re-fire in every screen reader. Reduced motion: replace the shake+fall keyframe with a plain 260ms fade (print in, fade out, no transform) via a `prefers-reduced-motion: reduce` media query on the same class, so the rejection is still visibly communicated without motion.\n\nPaste: intercept `onPaste`, prevent default, filter the clipboard text through the same `validate`/separator-skip logic (invalid or over-capacity characters are silently dropped, not individually shaken — the shake is a keystroke-time beat), then commit the accepted characters into the typed array via a short staggered cascade (~45ms per character via sequential `setTimeout`s, collapsed to no stagger under reduced motion) so the paste visibly inks left-to-right rather than jumping to its final state in one frame. Clear all pending stagger timers on unmount and whenever a new paste starts.\n\nThe caret is a kerf-thin (2px) `var(--ns-accent)` bar, vertically centered, rendered only while the real input is focused, positioned at the start of the next open editable cell (never inside a separator cell) — it blinks via a `steps()` keyframe, suppressed (solid, no animation) under reduced motion. Accessibility: a real `<label>` (visually `sr-only`, associated via `htmlFor`) supplies the accessible name; `aria-describedby` points at a `sr-only` span stating the format contract up front — auto-derive it from the mask when groups are uniform (\"Format: N groups of M characters, separated by <sep>\"), overridable via a `formatDescription` prop for irregular masks. The entire decorative glyph layer is `aria-hidden`. No dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa",
          "error": "#ea001d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717",
          "error": "#ff6369"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-muted tooth path (a single continuous path built once from an array of tooth positions, not one polygon per tooth), and a small --ns-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/--ns-muted, --foreground is reserved for the value readout above it and --ns-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(--ns-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 --ns-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": "storey-pole",
      "type": "registry:ui",
      "title": "Storey Pole",
      "description": "Indoor level switcher drawn as a true architectural cross-section: floors are horizontal slabs at their real relative heights (a double-height lobby is visibly taller than a mezzanine), a spring-loaded datum line points at the selected slab's elevation, below-grade floors carry a 45-degree hatch fill, and every label prints its elevation in metres.",
      "files": [
        {
          "path": "registry/core/storey-pole/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/storey-pole.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "radio-group",
          "navigation",
          "picker",
          "wayfinding",
          "physics",
          "drag",
          "svg"
        ],
        "instruction": "A floor/level switcher for indoor maps, office floorplan viewers, mall/airport/hospital wayfinders, or parking apps — anywhere a bare vertical list of B2/G/M/3 hides the fact that floors are not evenly spaced. STRUCTURE: real native input type=radio per level, one shared radiogroup, arrow-key roving and Tab order native to the browser; each radio's accessible name is generated, not authored: 'Level 3, 12.4 m above ground, Engineering; directly above Parking, below Food Court' — label, elevation phrase, optional name, and both immediate neighbors. GEOMETRY: each level supplies elevation (metres above ground, negative below grade) and height (storey height in metres). Slab pixel height = height x one shared px-per-metre scale used for every level in the drawing — the falsifiable constraint that forbids equal-height list rows: a double-height lobby renders roughly twice as tall as a normal floor, a short mezzanine renders shorter, because the drawing is a real section, not a styled list. Slabs are stacked contiguously in elevation order and never move; only the datum line moves. Below-grade slabs (elevation less than 0) carry a 45-degree --border hatch fill down their left margin, the standard section-drawing convention for earth, so 'below ground' reads before any label does. MECHANISM: one governing scalar, the selected level's elevation in metres, drives four things together: the datum line's y position, which slab is highlighted, the plan-viewport crossfade pairing, and the readout text. Selecting a level (click, keyboard, or a controlled value change) moves ONLY the datum line via an rAF mass-spring: stiffness is constant, damping ratio is derived from how many floors were crossed — one floor settles near-critically-damped in about 280ms with almost no overshoot, an eight-floor jump underdamps further and visibly overshoots before settling, because a fixed overshoot would make a 1-floor and an 8-floor jump feel identical. DRAG: pointer-down anywhere in the cross-section starts a continuous scrub — the line magnetizes to the nearest slab's datum with a deadband 30% of THAT slab's own height (snapping when close, free-following the pointer outside every deadband), and the nearest slab drives a live PREVIEW of the highlight, the plan glyph and the readout without calling onValueChange or moving the committed radio selection until pointer-up, when the nearest slab is committed and the line makes a small critically-damped settle correction. PLAN VIEWPORT: a small deterministic abstract floor-plan glyph (seeded per level id, so it's stable across renders, not random each time) crossfades to the previewed level's glyph on every change — outgoing glyph fades over 220ms via the Web Animations API while the incoming one is already in place underneath. ANNOUNCEMENTS: every committed change (never a drag preview) pushes a spoken elevation delta into an aria-live=polite region, e.g. 'up 3.6 m to Level 3'. REDUCED MOTION: the datum line repositions instantly with no spring, and the plan viewport swaps instantly with no crossfade; both remain fully functional. STYLING: the highlighted slab and its floor code step up from --ns-muted to --foreground; --ns-accent appears only as the keyboard focus-visible ring, never as a resting-state color. Pure DOM + SVG + CSS, no canvas — every color is one of --background --foreground --ns-muted --border --ns-accent, read as CSS custom properties already in scope so both themes restyle for free. Distinct from pricing-scale: pricing-scale stacks CONTENT by weight as an information hierarchy; StoreyPole stacks REAL BUILDING GEOMETRY, and the metre values behind every slab's proportion live in each radio's accessible name, not just in the pixels."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 `--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-muted --border --ns-accent, surface for the ghost chip); --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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": "strip-station",
      "type": "registry:ui",
      "title": "Strip Station",
      "description": "Turn-by-turn directions unbent into one vertical straight-line diagram: row spacing is proportional to real ground distance, not step count, with a 44px floor that visibly breaks (equation-station zigzag, skipped chainage) rather than silently lying about distance.",
      "files": [
        {
          "path": "registry/core/strip-station/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/strip-station.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "directions",
          "navigation",
          "list",
          "svg",
          "scroll",
          "chainage",
          "route",
          "instrument"
        ],
        "instruction": "A directions panel rendered as a DOT-style straight-line diagram (SLD). Core data: an ordered array of maneuvers, each with instruction text, an optional road label, distanceM (length of the leg that STARTS at this maneuver), a signed turnAngleDeg (0 straight, negative left, positive right, ±180 U-turn) and an absolute headingDeg for the leg that follows it. LAYOUT: row pitch = max(44px, distanceM * scalePxPerM, default scale 0.09 px/m) — cumulative real distance drives vertical position directly, so a 34km highway leg renders as ~3000px of scrollable strip while a 90m downtown turn collapses toward the 44px floor. FALSIFIABLE FLOOR: when true proportional spacing would fall under 44px, the strip does not silently clamp — it renders the standard equation-station break glyph (a small zigzag polyline) in place of the connector and the chainage numbering visibly skips (no interstitial ladder ticks in that segment), so compression is a legible mark, never a quiet lie about distance. CHAINAGE: DOT-format station labels (e.g. 1+50 = 150m) sit at every maneuver plus a 1-2-5-ladder of intermediate ticks (auto-picked so pixel pitch lands 24-56px) inside any non-broken connector; minor ticks are unlabeled lines, major ticks (5x the minor step) carry the mono chainage label. TURN GLYPHS: each maneuver is a single SVG polyline per glyph tracing entry -> vertex -> exit at the maneuver's ACTUAL turnAngleDeg plus a small arrowhead, so a 15° fork and an 80° hard left are literally different drawings, not different icon names; depart renders a start dot, arrival a diamond waypoint marker. COMPASS ROSE: a small SVG rose lives in its own non-scrolling gutter column (a fixed-width flex column beside the scroller, border-separated, aria-hidden — never an overlay floating on top of row content) and counter-rotates its needle group to -headingDeg of the CURRENT leg (the leg containing positionM) with a 500ms ease-out CSS transition, plus a tiny mono degree readout — this is the bearing the strip gave up by straightening the road, paid back visibly rather than discarded. MOTION: one governing scalar, positionM (route progress in meters), drives both the marker and the scroll target. The marker's own y is never smoothed — it is always the honest proportional position via the same piecewise cumulative-distance mapping used for chainage; only the viewport SCROLLS toward keeping the marker in the top third, via a persistent rAF loop applying an ease-out-expo catch-up (next = cur + (target-cur) * (1 - 2^(-10*dt))), which lets scattered real GPS updates read as continuous glide. The follow loop yields the instant it detects a scrollTop delta it did not itself write (user scrolled by hand) for 2.5s, and again while any row button holds focus — it never hard-locks the viewport against the user's own scroll or keyboard review. Passed maneuvers (index <= the current leg) render in --ns-muted; the upcoming maneuver is --foreground and carries aria-current=\"step\". A11Y: the directions are a real <ol> of <li>, each wrapping one <button> whose accessible name is the composed sentence '{instruction}, {road}, in {distance}, step {i} of {n}' — screen-reader users get the same information sighted users get from the proportional gaps, since the ribbon (ticks/connectors/break glyphs/turn glyphs) and the compass rose are all aria-hidden and purely presentational. Roving tabindex: exactly one button carries tabIndex 0 at a time (the focused/current row), ArrowUp/ArrowDown/Home/End move focus row-to-row and scrollIntoView (block: 'nearest') to match, so Tab always reaches something and keyboard review never fights the mouse. A single polite aria-live region announces '{instruction}, in {distance remaining}, step {i} of {n}' only when the current leg actually changes, never per animation frame. REDUCED MOTION: the scroll-follow loop snaps instead of easing, the compass rotation transition is removed (instant snap), and the demo freezes positionM at one representative frame instead of animating — the layout, chainage, break glyphs and turn angles are all still fully legible statically. Zero dependencies, DOM + inline SVG only, no canvas."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "error": "#ea001d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "error": "#ff6369"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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-ns-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-ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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.",
        "rank": 297
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 = --ns-accent mixed 20% into --ns-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(--ns-accent, --ns-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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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/--ns-muted for label state, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-muted at rest, --foreground with a hover transition when active or hovered; focus uses focus-visible:outline-ns-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": "tacho-disc",
      "type": "registry:ui",
      "title": "Tacho Disc",
      "description": "Today's security activity as one rotating polar disc under a fixed stylus: sign-ins, permission grants and revocations scribed at their true time-of-day angle, radius by category, older days stacked behind as dimmer discs. Dragging or arrowing it scrubs a real, bound event list.",
      "files": [
        {
          "path": "registry/core/tacho-disc/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tacho-disc.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "security",
          "activity-log",
          "svg",
          "slider",
          "feed",
          "aria-live",
          "keyboard",
          "accessibility",
          "dashboard"
        ],
        "instruction": "Build <TachoDisc events historyDays? now? label? className?> where `events: SecurityEvent[]` is `{ id, time /* ms epoch */, category: \"sign-in\"|\"grant\"|\"revocation\", actor, action, result: \"success\"|\"failed\"|\"revoked\" }[]`. STRUCTURE: a flex row (column on narrow viewports) of an SVG disc on one side and a real event list on the other — the list is the primary semantic surface, the disc is aria-hidden geometry plus one role=\"slider\" control. GEOMETRY / THE ONE GOVERNING SCALAR: 24 hours maps to 360 degrees (angleForMs = minutesOfDay/1440*360, clockwise from 12 o'clock). Compute exactly one rotation value, `discAngle = -angleForMs(now)`, and apply it as a single SVG <g transform=\"rotate(discAngle cx cy)\"> wrapping every day's marks — every mark inside is positioned at its OWN true, unrotated timestamp angle; the shared group rotation is what makes \"now\" line up under a fixed stylus glyph drawn OUTSIDE that group at 12 o'clock, which itself never moves. This is the falsifiable constraint: marks sit at true angular time on the shared clock. Do NOT lay marks out evenly by array index — a burst of failures seconds apart must pile into a visibly dense arc because it geometrically is one; index-based spacing would hide exactly the pattern the component exists to reveal. HOUR GRADUATION: inside that same rotated group, under every event layer, draw a faint hour ring — a tick at each of the 24 true hour angles, a slightly longer major tick plus a `00`/`06`/`12`/`18` numeral (Geist Mono, var(--ns-muted), counter-rotated in place so the glyph itself stays upright as the disc turns) at the cardinal hours — so the dial reads as a day at a glance instead of a bare fan of event ticks; it shares discAngle rather than sitting fixed under the stylus, because a fixed graduation would drift out of true-hour alignment with the events the moment \"now\" moved off the hour. It is chart paper: thinner and dimmer than every event mark, never var(--foreground), never mistakable for one. RADIUS BY CATEGORY: sign-ins draw as a radial line segment at the outer radius band, grants at a middle radius band, revocations at the innermost radius but as a punched HOLE — a small circle filled var(--background) with a var(--border) ring stroke, appearing whole in a single frame with no draw-in animation (it is an absence being marked, not an arrival). Ordinary sign-in/grant events (result \"success\") draw as a 3px hairline (var(--foreground), round cap). Any event with result \"failed\" draws at 8px stroke width regardless of category. OLDER DAYS: group events by calendar day (0 = today); render days oldest-first inside the SAME rotated group so they share discAngle, each day's own sub-group scaled slightly smaller (~13% per day back, applied to that day's radius numbers, not a DOM-level CSS scale/transform-origin trick) and faded down toward ~0.16 opacity, so older nights sit behind today as visibly smaller, dimmer, concentric discs, with the same time-of-day landing at the same angle across all of them for at-a-glance night-over-night comparison. LIVE ARRIVALS: track which event ids were present at first mount; any id that appears afterward on a TODAY, failed, sign-in/grant mark gets a 90ms stroke-dashoffset draw-in (dasharray = own line length, offset animated length\\u21920 via an imperative ref effect on mount of that mark, one real transition, never a CSS keyframe percentage hack) — skip the animation entirely under prefers-reduced-motion, drawing it fully formed instead. Revocations never scribe. THE DISC AS INDEX, NOT DECORATION: keep one shared piece of state, `activeIndex` into today's chronologically-sorted events, plus a transient `scrubbing` boolean. Hovering a list row, or arrow/Home/End-ing within the list (roving tabindex, one row's button has tabIndex 0 at a time, ArrowUp/Down moves both the DOM focus and activeIndex, Home/End jump to first/last), sets activeIndex and — inside the disc's rotated \"today\" sub-group only — draws a low-opacity var(--ns-accent) wedge (fillOpacity ~0.1, a wide radial line from center outward) at that event's angle: this is the one and only place --ns-accent appears, purely interaction-driven, never a resting/decorative color. The disc SVG itself is role=\"slider\" tabIndex 0, aria-valuemin 0, aria-valuemax 1439 (minutes in a day), aria-valuenow the active (or, if none, \"now\") event's minute-of-day, aria-valuetext formatted `scrubbing HH:MM, N failed sign-ins nearby` while a pointer drag is live, `HH:MM, actor, action, result` when a row/step is focused but not dragging, or `now HH:MM, N failed sign-ins in the last hour` at rest. ArrowLeft/Down and ArrowRight/Up on the focused disc step activeIndex to the previous/next event chronologically (event-to-event, never a fixed-minute increment) and Home/End jump to the day's first/last event; every activeIndex change (from either the list or the disc) calls scrollIntoView({block:'nearest'}) on the corresponding list row, so scrubbing the disc scrolls the list to the nearest event and vice versa. Dragging directly on the disc (pointerdown/move/up, pointer capture held for the gesture) converts the pointer's angle-from-center back through the current discAngle into a true time-of-day, picks the category band whose radius the pointer is nearest, and sets activeIndex to the angularly-nearest event within that band (falling back to nearest across all categories if that band is empty for the day) — this is a live scrub, not a discrete click. LIVE-FAILURE ANNOUNCEMENT: a visually-hidden role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" node is the ONLY place new failures are spoken. Batch every failed arrival behind a ~700ms debounce and announce once per batch — \"4 failed sign-ins in the last minute\", pluralized — never one live-region update per mark; this is a hard constraint, not a nicety, since a real credential-stuffing run would otherwise spam a screen reader with dozens of individual announcements. TOKENS: every disc mark, ring, and hole uses var(--foreground)/var(--border)/var(--background) only; var(--ns-accent) is reserved solely for the interaction-driven highlight wedge and the slider/list focus-visible rings — never a resting color, never the failure or revocation encoding itself (severity is stroke weight and hole shape, not color). REDUCED MOTION: the rotating group's 60s linear CSS transition (the mechanism that makes discAngle glide smoothly as the internal clock ticks once a minute) is disabled outright, so the disc jumps to each new minute's angle rather than sweeping — the update cadence itself doesn't need to change, only whether it's animated; the scribe draw-in is likewise skipped, marks simply appear complete. DEMO: three days of baseline sign-ins/grants, today seeded with a tight nine-failure credential-stuffing run (~95s apart) followed by a session-key revocation around 03:10\\u201303:27, then a real setInterval appends a fresh 4-failure burst to the SAME today array a beat after mount and again every ~9s, driving the live scribe-in and coalesced announcement through actual state updates rather than a scripted replay."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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, --ns-muted and --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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.",
        "rank": 296
      }
    },
    {
      "name": "tally-cleave",
      "type": "registry:ui",
      "title": "Tally Cleave",
      "description": "An org invite list where each invite is a split wooden tally: a jagged seam cut from the invite's own token, the org keeps a solid stock, and accepting slides the matching foil home so the grain lines cross the seam and prove the match.",
      "files": [
        {
          "path": "registry/core/tally-cleave/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tally-cleave.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "invite",
          "team",
          "settings",
          "list",
          "svg",
          "aria-live",
          "accessibility",
          "spring"
        ],
        "instruction": "`<TallyCleave initialInvites={Invite[]} roles? defaultTtlMs? maxInvites? ariaLabel? onCreate? onAccept? className? />` renders a Settings > Invites list where every invite draws itself as a physically split wooden tally: a 119x30 inline SVG per row, aria-hidden, with a solid stock piece on the left and a foil piece on the right separated by an 11px gap while pending. THE SEAM IS DETERMINISTIC: `seamOffsets(invite.id)` fnv1a-hashes `id#i` for the five interior rows of a seven-row sample (the first and last rows are flush, offset 0, so the cut reads as a stick sawn straight-in-straight-out with a jag only in the middle) and quantizes each to a signed offset in [-7,7]px — a pure function of the invite's own id/token, memoized once per row, identical on every render and every mount. That SAME offsets array is used twice: added to a fixed baseline for the stock's right edge, and used as-is for the foil's left edge — drawn once, mirrored, not two independently-hashed edges — so when the gap closes the two edges are a geometric fact, not a coincidence. A decorative zigzag shared across every invite is the explicitly forbidden alternative, because identical seams on different invites would fake the matching claim the component exists to make. WHILE PENDING the foil is a GHOST: `fill=none`, dashed `var(--foreground)` stroke at reduced opacity — nothing solid, because the invitee hasn't produced their half yet. ACCEPT is a real per-row button (`data-action=\"accept\"`, accessible name `Accept invite for {email}`) that flips the invite's `status` to 'accepted' synchronously — text, `data-status` on the row's `<li>` (plus a session-only `data-matched=\"true\"` used by the verifier's gate, since a seeded already-accepted demo row must NOT satisfy the same check a live accept does), and the CSS custom properties driving the art all update in the same commit. Two CSS transitions, no JS choreography, produce the two-phase animation the brief describes: the foil group sits at `translateX(STOCK_W + gap)` always — `gap` is the only animated term, 11px pending -> 0px accepted, over 480ms on `cubic-bezier(.22,1.7,.36,1)` (this registry's spring-approximating overshoot, same family as logo-cloud-settle) so it overshoots slightly past 0 before settling — the exact moment the shared grain lines (two horizontal lines per piece, each one deliberately terminating AT that row's own seam-offset x, not at a fixed edge) become numerically continuous across the seam, because stock's line ends at `STOCK_W+offset` and foil's line starts at `offset` translated by the same closing gap; when the gap reaches 0 those two x-coordinates are identically equal — and the seam-highlight polyline's `opacity` transitions afterward (`transition-delay:460ms`, scoped by `li[data-status=\"accepted\"]` in plain CSS, no timers) from its pending value down to a fixed 0.22 hairline, while the grain lines' opacity stays comparatively strong (0.75) because the grain, not the cut mark, is the thing that should stay legible as evidence once matched. EXPIRY has no separate branch: `t`, the TTL fraction elapsed (`(now-createdAt)/ttlMs`, clamped 0..1, computed from a `now` that starts `null` and is set from `Date.now()` in a mount effect on a 60s interval — this repo's SSR-safe clock pattern, matching bitting-cut's `now`), is the one governing scalar for every non-accepted row. `erosionScale = 1-t` multiplies the SAME memoized offsets before either path or seam-polyline is built, so the jag itself shrinks toward a flat line as expiry approaches — not a new random-looking shape, an eroded version of the same one — while seam and ghost opacity fall linearly with `t`. At `t=1` the row's derived status (never a separately stored field) reads 'expired', the Accept button is not rendered, and the seam is flat and faint enough that two different invites' expired tallies would be visually indistinguishable — which is the component's own explanation for why an expired invite needs a fresh one, not a renewal. Accepted rows fix `t` at 0 forever, ignoring elapsed time entirely, because a sealed match doesn't keep eroding after the fact. STATE LIVES IN TEXT: each row shows `{email} — {role}` plus a status line that is the actual source of truth — 'Pending, expires in {N days|hours|minutes}', 'Expired — send a fresh invite to reconnect', or 'Accepted by {localpart}@ on {Month Day}' — never the SVG; the tally graphic is `aria-hidden` redundant encoding of exactly what the text already states. The list is a real `<ul role=\"list\">`/`<li>` structure with an accessible name; accept completion fires a `role=status aria-live=polite` announcement once ('Invite matched, {Name} added as {role}.') — creation is silent, mirroring this repo's existing add/remove asymmetry. The header is a real `<form>`: an email input and a role `<select>` (each with a visually-hidden `<label>`) plus a 'Send invite' submit button, disabled until the email matches a basic address pattern or the list reaches `maxInvites` (default 8); a created invite starts fully pending (`t=0`, crisp seam, full ghost) with no entrance animation. REDUCED MOTION (`matchMedia` with a live change listener, plus a `.ns-tc-reduced` class as a static-render fallback) sets every transition's duration to zero, so acceptance joins the two halves with no travel and no overshoot — the row lands directly on its finished, already-matched state. Colors are `--background --foreground --ns-muted --border --ns-accent` only (the last confined to focus-visible outlines on the form controls and the accept/send buttons — interaction-only, never decorative), no hex/rgb/hsl anywhere including inside the SVG paths. DOM + SVG + CSS, no canvas, zero dependencies."
      }
    },
    {
      "name": "tamper-tine-squeeze",
      "type": "registry:ui",
      "title": "Tamper Tine Squeeze",
      "description": "An indeterminate 'still working' loader modeled on mechanised ballast tamping: a satellite plunges a paired tine either side of a rail sleeper, squeezes it shut to pack the ballast beneath, lifts, and steps to the next sleeper along an infinite treadmill row.",
      "files": [
        {
          "path": "registry/core/tamper-tine-squeeze/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tamper-tine-squeeze.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "indeterminate",
          "svg",
          "railway",
          "industrial",
          "physical-metaphor",
          "ambient"
        ],
        "instruction": "`<TamperTineSqueeze progress? totalSleepers? ariaLabel? className? />` renders a card-scale `role=\"status\"` (or `role=\"progressbar\"` when `progress` is supplied) region containing one inset-0 SVG: two thin static `--ns-muted` rail lines near the top, a pool of 8 sleeper `<rect>`s (var(--foreground)) each paired with a sparse-scatter ballast `<path>` (var(--ns-muted)) and a packed-grid ballast `<path>` (var(--foreground)) that crossfade by opacity, and two `<line>` tines (var(--foreground)) that plunge/squeeze/lift at a single fixed screen x. AMBIENT MODE (no `progress` prop, the default): a monotonically increasing `cycleIndex = floor(elapsedMs / 1600)` — never wrapped or reset — names which sleeper is 'current' at the fixed tine x; this is an infinite treadmill, not a bounded loop, so the row is materially further along at every later timestamp with zero input. Each 1600ms cycle runs four sub-phases entirely off local time within the cycle: plunge (0-300ms, tine bottom endpoint eases 0->full depth via easeOutQuad while its top endpoint stays fixed, gap held at the open half-width, opacity ramps in over the first 90ms); squeeze-close (300-550ms, half-gap eases from the open width to the closed width via easeOutQuad — this 250ms close is what visibly reads as 'squeeze shut'); squeeze-hold (550-800ms, gap stays at the closed width and a `sin(t * 2*pi*6Hz) * jitterAmp` offset — CAPPED at 6Hz, an explicit decoupling from the real ~35Hz tine vibration per the round-9 rule that a near-paint-rate real frequency must never be animated 1:1 — is added to both tine x-positions, its amplitude shaped by a `sin(holdProgress * pi)` envelope so the jitter fades in and out at the hold's edges instead of switching on/off abruptly); lift (800-1100ms, the tine's bottom endpoint eases back up to its top via easeOutQuad, opacity fades out over the final 30% once mostly retracted). Ballast packing for the CURRENT sleeper is driven directly off the live half-gap value (`(gapOpen - gapHalf) / (gapOpen - gapClosed)`, clamped 0..1) rather than a separate timer, so the packed-dot cluster's opacity rises in lockstep with the tines visibly closing and holds at 1 through lift; every sleeper behind the current index renders fully packed (packed opacity 1, sparse opacity 0) and every sleeper ahead renders fully loose (sparse opacity 1, packed opacity 0). The shift sub-phase (1100-1600ms) hides the tines (opacity 0) and translates every rendered sleeper slot left by `easeOutQuad(shiftProgress) * spacing` px, so by t=1600ms the row has visually arrived exactly at the position the next cycle's `cycleIndex + 1` frame expects — zero visual discontinuity across the cycle boundary. Only 8 DOM slots are ever mounted; each frame maps slot `s` to sleeper index `cycleIndex - 1 + s` (one sleeper of context behind the satellite, six ahead), positions it at `fixedX + (index - cycleIndex) * spacing - shiftOffsetPx`, and looks up its ballast dot paths from a `Map<index, {sparse, packed}>` cache built lazily via a seeded `mulberry32` PRNG the first time an index enters the visible window (never rebuilt per frame) and pruned once an index falls more than 2 behind the current one, so cache size stays bounded regardless of how long the loop has been running. Sparse dots are 7 points placed by a pure random scatter (loose, no structure); packed dots are a 4x5 jittered grid (±20% of cell size) at slightly smaller size (structure and count carry the 'consolidated' read, never colour alone, so it survives light theme where --ns-muted sits close to --background). CONTROLLED MODE (`progress` 0-100 supplied): the loop does not run on its own clock at all. `totalSleepers` (default 24) sets a fixed row length; the target index is `round(progress/100 * totalSleepers)`. On mount the component settles instantly (no animation) to that index with tines fully retracted (depth 0, opacity 0) — there is no 'from' state to animate from at first paint. On every subsequent change to `progress`, the effect re-runs (both `progress` and `totalSleepers` are real dependencies, not refs, specifically so a change restarts the one-shot pass rather than being silently swallowed) and plays exactly ONE plunge->squeeze->lift->shift pass from the previously-settled index to the new target — reusing the identical `tineStateAt` phase function the ambient loop uses for the first 1100ms, then during the 500ms shift sub-phase scaling the row's translate distance by the ACTUAL index delta (`shiftProgress * (toIndex - fromIndex) * spacing`) so a multi-sleeper jump in `progress` still crosses in one 500ms pass rather than replaying multiple cycles — then stops the rAF loop entirely and re-settles statically at the target. If `progress` does not change, nothing animates: an indeterminate loop is never faked as looping once a real percentage exists, and a real percentage never keeps animating once the component has caught up to it — this is the one condition under which the component is allowed to stop looping (`autoplay.mode` stays `\"none\"` regardless, since neither mode is pointer/press/scroll-driven; ambient motion is self-clocked and controlled motion is driven by the `progress` prop, not by any input the site's synthetic-input driver could produce). REDUCED MOTION (`matchMedia`, read once at mount): ambient mode freezes on cycle index 1 with tines fully shut and holding (depth 1, half-gap at the closed width, zero jitter, opacity 1) — sleeper 0 already fully packed to its left, sleeper 1's tines mid-hold around it, sleepers ahead still loose — the frame that shows the mechanism's purpose (the compacted state), never an open or in-transit frame. Controlled mode under reduced motion renders the same squeeze-closed look at whatever the current target index is, still with no animation. Geometry — sleeper width/height, tine gap widths, ballast cluster half-width, dot size, jitter amplitude — is all multiplied by `clamp(min(w,h)/200, 0.65, 2)`, the binding 'derive from the container's smaller dimension' rule, so the squeeze reads at card scale instead of shrinking to a sub-pixel wobble; sleeper spacing is instead driven off container WIDTH (`max(28*scale, w/6.2)`) so roughly six to seven sleepers stay visible regardless of aspect ratio. Sizing is read via `getBoundingClientRect()` inside a debounced `ResizeObserver` (root renders `opacity-0` until the first successful layout read, then fades in over 200ms) and the render loop pauses whenever the card scrolls offscreen via an `IntersectionObserver` (default threshold; there is no separate `visibilitychange` listener, only this one offscreen check), resuming the ambient clock fresh (`ambientStart` reset to 0, so time-off-screen is never counted as elapsed progress) or resuming an in-flight controlled pass, whichever mode is active. Colors are `--background --foreground --ns-muted --border` only, all read as CSS custom properties directly in SVG `fill`/`stroke` attributes and Tailwind classes — no JS colour reads or `getComputedStyle` calls exist because nothing here draws to canvas, and `--ns-accent` never appears anywhere: the loose-to-packed motion is density/geometry only, never accent-tinted, per the brief's 'never the climactic moment' rule. `--border` is used only for the container's own 1px hairline via a Tailwind `border-border` class, never as an SVG stroke or fill. DOM + SVG + CSS, no canvas, zero dependencies."
      }
    },
    {
      "name": "team-grid-timezone-rail",
      "type": "registry:ui",
      "title": "Team Grid Timezone Rail",
      "description": "A team grid that answers the question a team grid is actually asked (who is reachable right now) by running a 24-hour UTC rail above the cards with the team's shared working-hours overlap inked into it, while each card shows that person's live local time and dims when they're outside their own hours.",
      "files": [
        {
          "path": "registry/core/team-grid-timezone-rail/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/team-grid-timezone-rail.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "team",
          "grid",
          "about",
          "timezone",
          "availability",
          "clock",
          "marketing"
        ],
        "instruction": "Build <TeamGridTimezoneRail members? workingHours? viewerOffset? className?> as a plain DOM component — no canvas, no rAF, zero dependencies. MEMBERS are {id, name, role, initials, utcOffset} where utcOffset is hours ahead of UTC and may be fractional (5.5 renders as 'UTC+5:30'); workingHours is a local [start, end) window applied to everyone, default [9, 18]; viewerOffset defaults to -new Date().getTimezoneOffset()/60 but is adopted in a mount effect rather than at render time, so the server and the first client render stay byte-identical instead of throwing a hydration mismatch. THE CLOCK: exactly one timer, aligned to the real minute boundary — setTimeout(60000 - (Date.now() % 60000)) and then setInterval(60000) — and it writes through DIRECT DOM mutation, not React state: on each tick it moves the rail caret, rewrites every card's local time, flips its status glyph, sets the card's opacity and rebuilds its aria-label. This is a clock, not an animation, so there is deliberately no requestAnimationFrame anywhere and no per-second re-render. THE RAIL: an aria-hidden mono track of 24 cells at 1ch pitch where the index IS the UTC hour. Base glyph is '·' in --border, with '│' in --ns-muted at indices 0/6/12/18 and an absolutely positioned label row ('00 06 12 18') beneath at the same ch pitch so the ticks line up exactly; the current UTC hour carries a '▮' caret in --ns-accent, positioned as a single absolutely placed span whose style.left is set to `${index}ch` on the minute tick — one element moves rather than 24 being re-rendered. THE OVERLAP BAND is the mechanic: a cell is live for member m when ((hour + m.utcOffset) mod 24) falls inside [workingHours[0], workingHours[1]), and the run inked in --ns-accent is the INTERSECTION across the entire team plus the viewer — so the resting rail, with nothing hovered, already answers 'when can we all meet'. Each cell is two stacked layers: the '·' base and an accent '━' overlay, cross-faded by opacity over 180ms on cubic-bezier(0.22, 1, 0.36, 1), so cells leaving the band fade back to --border while cells still shared simply hold with no animation at all. On pointerenter or focus of a member card the intersection drops to viewer ∩ that one member, which is necessarily a superset of the team-wide run, so the band visibly OPENS OUT from rest — the resting run is the tightest the rail ever shows, and hovering is how you see what a single pairing would buy you; on pointerleave/blur a 220ms relax timer (cleared by any re-entry) pulls it back to the team-wide window, so sweeping across the grid does not strobe. If the intersection is empty the rail renders no accent cells and a mono line reads 'no shared hours — nearest overlap 07:00 UTC (Ana +2h)', computed by scanning all 24 UTC hours for the minimum total circular distance between each person's local hour and their working window, then naming whoever is furthest out. THE CARDS: each member is a real <button data-member aria-pressed> — pressing PINS that person's band so it survives pointer-out and pressing again unpins, which is a genuine function and also makes every card keyboard-reachable with a visible --ns-accent focus ring. A card carries an initials mark, name, role, a tabular-nums mono local time '14:32' derived as UTC-now + utcOffset, the printed offset, and a status glyph '●' inside working hours or '○' outside; cards outside their own hours render at opacity 0.5, so half the grid is visibly asleep at a glance. The aria-label is a full sentence — 'Ana Reyes, Design Lead, local time 14:32, UTC+2, currently available' — rebuilt on every tick, and a polite live region below the rail states the shared window in words since the rail itself is aria-hidden. RESPONSIVE: a ResizeObserver on the root switches the grid to 1 / 2 / 3 columns at 480px and 860px, and below 30ch of root width the rail collapses to a compact 12-cell track at 2 hours per cell, with the caret index becoming floor(utcHour / 2) and the band recomputed on the coarser grid. REDUCED MOTION: the band snaps between states with no crossfade (motion-reduce disables the opacity transitions), but the minute clock keeps ticking, because a stopped clock would be wrong rather than calm. TOKENS ONLY — --border, --foreground, --ns-muted, --surface, --ns-accent, with accent used on the caret, the overlap band and focus rings and nowhere else, so both themes invert cleanly. Props: members (default six-person team spanning UTC-5 to UTC+3:30), workingHours (default [9, 18]), viewerOffset, className.",
        "rank": 295
      }
    },
    {
      "name": "tendril-cast",
      "type": "registry:ui",
      "title": "Tendril Cast",
      "description": "Two or three climbing-tendril shoots circumnutate out from the pane's bottom edge in live SVG, searching a widening helix until a growing tip actually touches a measured support rect, then switch to a tightening spiral coil with a chirality-flipping perversion kink before stiffening and budding again elsewhere.",
      "files": [
        {
          "path": "registry/core/tendril-cast/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tendril-cast.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "ornament",
          "hero",
          "card-frame",
          "svg",
          "vine",
          "tendril",
          "circumnutation",
          "generative",
          "decoration",
          "plant"
        ],
        "instruction": "An aria-hidden SVG overlay of two or three (`tendrilCount`, clamped 2-3, default 3) climbing-tendril shoots that grow in from the wrapper's bottom edge and genuinely search the empty pane for the host page's own structure, then physically wrap onto whatever they find. Each shoot starts asleep (staggered ~2.6s apart so all three are never mid-cycle in lockstep), wakes into a `searching` state, and every 30Hz tick advances its tip by ELONGATE_RATE=52px/s along `heading`, where `heading` is integrated forward each tick by a curvature term `A(t)*sin(2*pi*t/T)` (T = `nutationPeriod`, default and governing scalar 9000ms — under ~5s this reads as a drill bit, not a searching shoot) whose amplitude A widens linearly from a near-straight 0.05rad probe up to a 1.3rad cap as search time grows, so the very first stretch of a shoot is nearly direct and only later stretches sweep wide enough to reach sideways — a widening helix, not a fixed-radius orbit. A polyline point is recorded every 4px of arc length. Every tick the live tip position is tested with a point-to-segment distance check against every edge of every DOM element carrying `data-tendril-support` inside the component (measured live via getBoundingClientRect, kept current by a ResizeObserver on the root AND on each support element for size changes, plus a 500ms poll so a support that moves without resizing is still caught); the instant the tip comes within 6px of some support's edge, growth switches from open nutation to arc-length-parameterised wrapping along that exact edge, sampled far more densely (pitch/5) than the search phase because the coil's own wavelength (pitch = 1.6x the 1.5px stroke width, ~2.4px) is narrower than the search phase's 4px sampling and would alias into a flat line at that resolution. The wrap's lateral envelope is two mirrored half-arches: it blends in from zero at the contact point, pinches flat exactly at the wrap's midpoint, then reopens with the OPPOSITE sign for the second half before tapering flat again as the shoot finishes — the chirality-reversing perversion kink real climbing tendrils tie partway along a coil, not a fixed spiral. Once 64px of edge has been wrapped (or the shoot runs off the end of that edge's segment, whichever comes first), the shoot goes `stiff`: geometry stops changing and its stroke steps discretely from var(--ns-muted) to a var(--foreground) 60% mix, holds for 6s, then fades to opacity 0 over 420ms and respawns from a fresh origin point on the bottom edge to search again — so a support's occupant tendril is never permanent, and which support (if any) a given shoot ends up gripping is decided fresh each cycle by where the live sweep actually lands. A shoot that exhausts a 42s search budget or 420 points without ever touching anything gives up quietly and respawns the same way, no coil, no stiffen. Rendering is three `<path>` elements total (one per shoot slot); each tendril tracks its own dirty flag so a path's `d` attribute is only rewritten on the tick a point actually lands (roughly every ~77ms per shoot, not every animation frame), and its `stroke`/`opacity` are only written on the tick the value actually changes, keeping steady-state DOM writes far below one per frame per path. The overlay `<svg>` is `aria-hidden` and `pointer-events-none` and is painted AFTER the children in DOM order specifically so a completed coil stays visible in full against an opaque card fill rather than half-clipping under it — pointer-events is what keeps it from ever intercepting a click, not paint order, so stacking it on top costs nothing. `prefers-reduced-motion` (checked at mount and re-checked live via a matchMedia change listener) runs the identical per-tick step function synchronously in a bounded fast-forward loop until every shoot has reached its stiff, fully-coiled state, paints once, and starts no timers or rAF loop at all — a permanently pre-grown, already-gripping frame, with no further motion ever. `autoplay: none` because the whole piece runs on its own internal clock regardless of pointer, scroll, press or type input; there is nothing for the site's synthetic-input driver to add. Consumers mark whatever the tendrils should search for and grip — a headline rule, a CTA card's outer border, any anchor `<div>` — with `data-tendril-support`; nothing else about that element's own focus, keyboard or screen-reader behavior is touched. Zero dependencies, DOM+SVG+CSS only, no canvas, every colour a token (--background --foreground --ns-muted --border --ns-accent)."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-muted for the duration of a rise via a class toggle removed on a matching timeout, and separately shows --ns-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, --ns-muted, --border, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "error": "#ea001d",
          "success": "#2d7a2d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "error": "#ff6369",
          "success": "#47a447"
        }
      },
      "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(--ns-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(--ns-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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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 (--ns-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, --ns-muted rings, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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 --ns-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 --ns-accent, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa",
          "error": "#ea001d",
          "warning": "#7a5200",
          "success": "#2d7a2d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717",
          "error": "#ff6369",
          "warning": "#f5a623",
          "success": "#47a447"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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> (--ns-accent text, the only place --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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-ns-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": "tonearm-skate",
      "type": "registry:ui",
      "title": "Tonearm Skate",
      "description": "A read-only tracking-quality gauge built from real pivoted-tonearm geometry: a headshell sweeps a 215mm-pivot arm across a spinning record from lead-in to run-out while a small error needle reads the actual geometric tracking-error function, centering exactly at the two Baerwald null radii (66mm, 120mm) and drifting off zero everywhere else.",
      "files": [
        {
          "path": "registry/core/tonearm-skate/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tonearm-skate.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "gauge",
          "meter",
          "svg",
          "rotary",
          "geometry",
          "instrument",
          "quality",
          "tracking",
          "ambient",
          "status"
        ],
        "instruction": "Renders one autonomous, data-free instrument (no props drive its motion) built on the real geometry of a pivoted phono tonearm: LP_MM=215 (pivot-to-spindle) and L_EFF_MM=225 (effective pivot-to-stylus length) are real constants for a standard 9\" arm, and the arm's absolute rotation angle for any target stylus radius r is derived from the true law-of-cosines relationship r^2 = Lp^2 + l^2 + 2*Lp*l*cos(alpha) (alphaForRadius, solved via acos, not fit or approximated) — the same geometry that places this pivot distance's two Baerwald alignment nulls at 66mm and 120mm. A single rAF loop (no per-frame setState; every mutation is a direct ref.setAttribute call) derives an elapsed-time value from the frame's own timestamp (never accumulated per-frame deltas, so a backgrounded tab cannot drift the arm out of sync with the disc) and drives two independent clocks off it: the record disc rotates continuously at 1 rev/4000ms via a rotate() transform on a <g> around the spindle point SP, regardless of what the arm is doing (a real turntable motor never stops between sides); and the arm runs a 50400ms cycle (stateForCycleT) — a 48000ms linear sweep of its target radius from the 146mm lead-in groove down to the 60mm run-out groove, a 700ms hold at run-out, a 1400ms eased lift-and-return (easeInOutCubic on the radius, plus a sin(pi*p)-shaped vertical hop capped at LIFT_PEAK_VB and a matching opacity dip, so the pickup/swing/set-down reads as continuous motion, never a jump-cut) back to the lead-in, and a 300ms settle before the next side begins — unbounded, no dead stop. alphaForRadius(targetRadius) converts that virtual radius into the arm's absolute pivot angle every frame; stylusPoint(alpha) places the stylus in scene coordinates; the counterweight (a filled circle rigidly opposite the headshell across the pivot at a fixed CW_LEN_VB radius) and a dashed anti-skate thread (from a fixed post near the pivot to a point 72% along the current arm-tube line) are recomputed from that same alpha every frame, so they swing in lockstep with the arm rather than being static decoration. The headshell <rect> is rotated to the tube's angle plus a constant SKATE_LEAN_DEG=3 cant that never changes with value or time — the skating force is a standing offset, not a discrete event — while the small stylus-contact <circle> stays exactly on the tube's true endpoint, unrotated by the lean, since that circle is the actual groove-contact point. A second, independently driven readout is the tracking-error strip below the disc: errorForRadius(r) walks a five-point keyframe table (146mm:+2deg, 120mm:0, 93mm:-0.7deg, 66mm:0, 60mm:-2deg — anchored to the real null radii and the documented +/-2deg edge magnitudes, with the 93mm interior lobe set to roughly a third of the edge magnitude so the needle visibly leaves and returns to center between the nulls rather than reading as a flat line for the 63% of the sweep that separates them) and interpolates between adjacent keyframes with a smoothstep(t) (zero derivative at every keyframe, so the needle visibly decelerates into and pauses at each null before departing again — that pause is what makes the crossing legible as 'it centered' rather than a blip) rather than a straight lerp. The needle itself is a single <line> whose x position maps error from [-2,2]deg linearly to viewBox x in [20,240]; only its stroke-width (1.5 to 3.1) and opacity (0.55 to 1.0) scale with |error|/2 — it is stroke-current text-foreground always, never text-ns-accent, since a higher error reads as heavier/darker, not as interaction chrome. Disc, arm tube, headshell, counterweight, stylus dot and the fixed pivot mount are all stroke-current or fill-current classes on text-foreground; the disc's groove-ring circles (at the lead-in, both null, and run-out radii) and the anti-skate thread are text-border — pure separator use, never load-bearing fill; the paper label circle is text-ns-muted at low opacity. The disc's rotation would otherwise be visually silent (four concentric circles have full rotational symmetry) so a single short radial notch just outside the paper label is drawn text-foreground specifically so it stays visible against a light theme and is the one asymmetric mark that proves the disc is turning. Under prefers-reduced-motion (matchMedia with a live 'change' listener; the rAF loop keeps running so the disc-independent-of-arm relationship never has to be reasoned about statically) the whole scene freezes on a deliberately chosen non-t0 frame: the arm pinned at exactly the 66mm inner null (needle reading 0.0deg dead center) while the disc is held at a fixed 135deg rotation rather than its start angle — chosen because that single frame shows both 'the geometry is exactly correct here' (centered needle) and, by the arm's visibly non-radial position against an already-turned disc, that error exists everywhere else along the sweep. Geometry is derived from the SVG's own viewBox (300x300, fixed internal units) scaled from real millimetres via SCALE = 96/150 (record radius in viewBox units over the real 150mm LP radius), and the whole scene sits in a wrapper with aspect-square and max-w-[280px] so it reads at card scale regardless of container width. Every point painted per frame (stylus, counterweight, anti-skate thread end, tube-axis extension, headshell rotation, needle x, numeric error) comes from one `geometryFor(radius, lift)` function shared by the rAF loop and a module-scope `T0 = geometryFor(R_OUTER_MM, 0)` constant that is inlined directly as the initial JSX attributes on every ref'd element (arm tube, headshell transform, stylus dot, counterweight, thread, needle, readout text) — without it, every ref'd SVG primitive would default to (0,0)/zero-length until the first rAF tick, and since these are client components under App Router that wrong frame would sit in the pre-hydration server HTML, not just flash for one frame; this is the geometric form of the binding rule against painting before the first read. No canvas anywhere, no color literal anywhere, no getComputedStyle/MutationObserver machinery — nothing here is a raster surface, every paint is a token Tailwind class, so a theme flip repaints for free. Cleanup cancels the single rAF and removes the matchMedia listener on unmount. The root element carries no role/aria-label of its own — every SVG is aria-hidden, but the visible plain-text label row ('TONEARM' / null radii), the live numeric error readout (a plain HTML span, not SVG text, updated via textContent each frame), and the lead-in/run-out/null caption below the disc are ordinary DOM text nodes an AT already reads correctly; wrapping the root in role=img would collapse all of that into a single opaque string, which is why it's deliberately absent (matching barograph-drum-week, which also leaves its root unlabelled and aria-hides only the non-text layers). Props are `className` only — this is an autonomous instrument, not a data-bound meter; it takes no `value` and exposes no drag interaction (the spec's optional scrub-to-inspect affordance was deliberately dropped, since it is the only path by which text-ns-accent could leak onto the needle, and the passive resting loop already carries the full legibility requirement on its own). The anti-skate thread and its post, and the pivot mount circle, are rendered as fixed siblings outside the lifted arm group — only the thread's far end (recomputed from the arm's live position minus the current lift offset) moves, so the thread never visibly detaches from its anchor during the lift-and-return phase. The 3deg skate lean would be sub-pixel on the headshell rect alone, so a second, uncanted hairline continues the true tube axis a further 16vb past the stylus — the two nearly-parallel lines' visible divergence is what makes the constant lean legible, not the rect's rotation by itself. During the 1400ms lift-and-return phase (airborne=true) the tracking-error needle fades to a fixed 0.2 opacity and the numeric readout shows an em dash instead of a number, since the stylus is off the groove and the formula's output during that phase is not a real reading — without that suppression the return sweep would manufacture two fast, meaningless null-crossing-looking events every 50.4s loop. Under prefers-reduced-motion the rAF loop paints the frozen frame once and then stops rescheduling itself (no wasted per-frame work animating an unchanging scene); the matchMedia 'change' listener re-arms a fresh rAF loop if motion is turned back on mid-session."
      }
    },
    {
      "name": "toner-fuse-streak",
      "type": "registry:ui",
      "title": "Toner Fuse Streak",
      "description": "A loader / progress surface that prints its content in the way a laser printer images a page: a top-to-bottom expose/develop wipe with toner visibly catching behind it, fine edges thinning for 400ms before filling to full density, and a persistent starvation streak on any large fill area, locked down by a single fuse-roller highlight pass. An unbounded charge/wipe/hold/reset loop, not a spinner.",
      "files": [
        {
          "path": "registry/core/toner-fuse-streak/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/toner-fuse-streak.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "progress",
          "print",
          "xerographic",
          "canvas",
          "ambient",
          "monochrome",
          "skeleton"
        ],
        "instruction": "A card-scale canvas-2D loading surface, `w-full h-full` inside a relatively-positioned root, sourced from the standard xerographic imaging cycle (charge/expose/develop/transfer/fuse) and its two documented failure artifacts (edge deletion at small-feature boundaries, toner starvation on large solid fills). Optional `children` are mounted off-screen (`position:absolute; visibility:hidden; pointer-events:none`) purely to donate a silhouette — they are never displayed. On mount and on every debounced ResizeObserver callback, the root is measured; if children are present, a `document.createTreeWalker` pass over the hidden container rasterizes them into an offscreen canvas: every non-whitespace text node gets a `Range.selectNodeContents` + `getClientRects()` pass (exact per-line boxes, no manual wrap simulation) and each line box is painted as a thin bar (trimmed to ~92% width, ~55% of line height, offset 15% down) — this is what donates the fine 'serif' edge feature; every childless leaf element whose tag is IMG/SVG/CANVAS/VIDEO or whose computed background-color alpha exceeds 0.05 or border-width is nonzero is painted as a solid filled rect at its `getBoundingClientRect()` — this is what donates large fill regions. If no children are passed, or the DOM pass paints nothing, a built-in placeholder (a heading bar, three text lines with a short last line, and one media block spanning 80% of the width) is used instead and is on its own sufficient to trigger both artifacts, so the component ships as `core` without requiring a caller snapshot. The raster is done at one toner cell per ~3px of mask (a `cols*3 x rows*3` offscreen canvas, alpha channel read as coverage), then downsampled by block-averaging into a `cols x rows` density grid where `cols`/`rows` are sized so ~140 cells span the container's SMALLER dimension (`cellSize = min(w,h)/140`). A cell is flagged an 'edge' cell if any neighbour within Chebyshev distance 2 has density below the ink threshold (0.15) — this is the literal reading of 'gradient over <2 cells'. Toner-starvation regions are found by 4-connectivity flood-fill over cells at or above a solid-fill threshold (0.6); any region whose bounding width is at least 18% of the grid's smaller dimension gets one streak band, deterministically hashed (never Math.random) from its seed cell index — a rectangle 8-14px tall (converted to cells) positioned in the trailing 30% of the region's height (the wipe travels top-to-bottom, so 'trailing' is the bottom of the region) spanning 50-90% of the region's width at a hashed x-offset, with a hashed 18-30% opacity reduction. Playback runs on a fixed 2600ms unbounded loop with no external clock dependency: 900ms expose/develop wipe (a horizontal line sweeping top to bottom, `wipeLineY = progress * height`), 1500ms hold at full density, 200ms reset fading every toned cell back to the un-toned baseline before the next cycle's wipe restarts at t=0 (never a hard cut). Cells whose row the wipe has not yet reached render only a 4%-opacity `--border`-derived fill (`alpha = 0.04 * density`) — the un-toned whisper of the shape, never fully invisible. Once the wipe passes a row, that row's cells ramp from 0 to full density over 150ms (toner 'catching' behind the line, the legibility anchor a viewer's eye follows); edge-flagged cells render 15-25% under-toned (hashed per-cell within that range) for 400ms after the wipe passes them, then ease to full density over a further 150ms; cells inside a streak band have their density multiplied by `1 - reduction * min(1, sincePass/400)`, so the dip fades IN over 400ms and then holds steady (a persistent artifact, not a transient) — reset-phase fading is applied after both. 220ms after the wipe completes, a single soft highlight — the fuse-roller 'nip' contact line — sweeps once top to bottom over 300ms and decays over a further 180ms, adding at most +8% luminance (never touching --ns-accent). The wipe line itself is a soft ~6%-luminance band riding `wipeLineY` while `t < 900ms`. All ink is `--foreground` (toned density, wipe band, fuse flash) or `--border` (un-toned baseline) read via `getComputedStyle(document.documentElement)` at mount and re-read on a `MutationObserver` watching `documentElement`'s class; no paint happens before that first successful read. Full toned density reads at 95% `--foreground` alpha in dark theme and 85% in light theme (checked first — light theme needs headroom under the streak's opacity reduction so the dip stays a visible relative dip rather than clipping toward indistinguishable near-white). Cells render as small squares at 84% of cell pitch, bucketed into ~100 discrete alpha levels each accumulated into a shared `Path2D` so a frame costs at most 100 `fill()` calls rather than one per cell. Backing store is DPR-capped at 2, sized off the root's own `getBoundingClientRect` on `ResizeObserver`, which also triggers a full mask rebuild (children layout can reflow with the container). The rAF loop pauses on `document.visibilitychange` and via `IntersectionObserver` when scrolled offscreen, and stops entirely under `prefers-reduced-motion: reduce`, which freezes the clock at 1700ms into the cycle — full density, both artifacts visible and settled, safely clear of the transient fuse flash — rather than the blank t0 frame. Purely decorative and non-interactive: the root is `role=\"img\" aria-label=\"Loading\"`, the canvas is `aria-hidden`, and it must never respond to hover/press beyond default focus-visible chrome should it happen to wrap a control. No dependencies."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 `--ns-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 `--ns-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 --ns-muted --border --ns-accent`, `--ns-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": [],
      "cssVars": {
        "theme": {
          "color-surface": "var(--surface)"
        },
        "light": {
          "surface": "#fafafa"
        },
        "dark": {
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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 --ns-muted --border --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 `--ns-accent` and the fill ink from `--ns-muted` to `--foreground`; focus additionally gets a `--ns-accent` focus-visible outline. Tokens only — `--background --foreground --ns-muted --border --ns-accent`, applied as Tailwind utility classes (`bg-background`, `border-border`/`border-ns-accent`, `text-ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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.",
        "rank": 294
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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, --ns-muted/--foreground for the control, --ns-accent only as the focus ring). Pure DOM/CSS, zero dependencies, no canvas.",
        "rank": 293
      }
    },
    {
      "name": "tufting-gun-loop-pile",
      "type": "registry:ui",
      "title": "Tufting Gun Loop Pile",
      "description": "A card whose backing texture is a genuine tufting-gun loop-pile build: a carriage sweeping row by row across a backing grid, plunging a loop of pile at each cell just behind it, the finished field feeding continuously off the top edge as new rows tuft in below.",
      "files": [
        {
          "path": "registry/core/tufting-gun-loop-pile/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tufting-gun-loop-pile.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "card",
          "ambient",
          "canvas",
          "texture",
          "tufting",
          "carpet",
          "process",
          "monochrome",
          "empty-state"
        ],
        "instruction": "Build a card whose entire backing surface is a canvas-rendered tufting-gun loop-pile simulation, sourced from real hand/machine tufting gun operation (textile/carpet manufacture), not a generic 'loading tiles' fill. Root is a `rounded-[14px] border border-border bg-background` card holding an absolutely positioned, `aria-hidden`, `pointer-events-none` canvas filling the card behind a `pointer-events-none` content stack (heading, mono body copy, optional underlined link that re-enables `pointer-events-auto` and carries a `focus-visible` ring in `--ns-accent`); the demo wraps it with an explicit minimum height (`min-h-[280px]`) so the field has room to read as a building carpet rather than a sliver behind the copy.\n\nGrid geometry is derived from the container's own smaller dimension via ResizeObserver: cell pitch = `clamp(6, min(width,height)/26, 14)` px in both axes (a 240px-tall card lands on the spec's ~9px pitch, ~26 rows), and the column count is `ceil(width/pitch) + 2` so the field overhangs both edges rather than ending on a visible half-cell, centred by offsetting the draw origin by `-(fieldWidth - width)/2`. A hollow-needle gun head sweeps left to right across one 'active' row at a time: the whole row-crossing takes a fixed 1100ms regardless of column count (`cellInterval = 1100 / cols`, so a wider card ticks faster per-cell but crosses the row in the same wall-clock time — the resulting ~24-36 cells/second figure falls out of that division, never hand-tuned per width), with a crosshair drawn at `--foreground` whose x-position is continuously interpolated from elapsed sweep time (not snapped to the cell grid) so a viewer can track the head's position at any instant. Cells behind the head pop from backing to filled loop over a fixed 90ms — decoupled per the round 9 rule from the gun's real 800-1500 punches/minute rate, which would alias against paint; the 90ms pop is a legible discrete event, never simultaneous across the row. The row only hands off to the scroll transition once the LAST cell's own 90ms pop has actually finished (`rowElapsed >= ROW_SWEEP_MS + POP_MS`, not just the sweep clock), so the final cells never snap into place. Once that's true, the whole tufted field scrolls up by exactly one row-pitch over a 260ms ease-out — completed rows climbing toward the top edge, culled from the draw loop once fully above the canvas — then the active row resets empty at the same fixed canvas slot and the next row's sweep begins; this never stops, restarts, or caps, so the specific filled/empty pattern visible at t0 is guaranteed to have scrolled entirely off-frame by t=5s. The gun head crosshair stays visible through the scroll transition too, riding up at the row-end x with the row it just finished, so the handoff shows departure and arrival rather than blinking out for the 260ms (round 9's cadence rule). A fixed strip of `EMPTY_ROWS_BELOW = 2` not-yet-reached backing rows always rests below the active row's slot and never itself scrolls (the active slot is pinned; the field feeds up through it) — this is what reads as 'empty backing weave below the head' in the resting frame. On mount only (not on later resizes), the field is seeded already mid-flight rather than blank: completed rows filled up to the canvas top, the active row a third filled, per the spec's own t0 description — a resize afterward clamps existing progress to the new geometry instead of re-blanking it. Completed and active-row loops are rendered as a short vertical ellipse (`rx = 0.34*pitch, ry = 0.42*pitch` at full pop) filled solid `--foreground`, with a lighter rim arc along the top-left stroked in the SAME `--foreground` token nudged by a fixed additive delta (+46 per channel, clamped to 255) in luminance only — never a literal colour, never `--ns-accent` — to sell the loop's rounded top catching light. Unfilled cells render a faint two-line diagonal crosshatch (a separator token used correctly as a separator, never a fill of solid shapes): in dark theme that's `--border` directly, but `--border` alone is only ~1.1:1 against `--background` in light theme, so in light theme the crosshatch stroke is `--border` mixed 70% toward `--ns-muted` (branch chosen from `--background`'s own relative luminance) to stay legible — light theme was checked first, not as a final pass.\n\nColour is read via `getComputedStyle(document.documentElement)` for `--foreground`, `--background`, `--border` and `--ns-muted` at mount, before any canvas paint (nothing paints, not even a fallback, until that first read resolves all three required tokens), and re-derived on a `MutationObserver` watching `documentElement`'s class. DPR-capped (max 2) backing store sized off the card's own `getBoundingClientRect`, resized on `ResizeObserver` (which also re-derives cell pitch, column count and the active row's fixed y-slot) and re-laid-out once more after `document.fonts.ready` (guarded against firing post-unmount). The render loop pauses via `IntersectionObserver` (not scrolled into view) and `visibilitychange` (tab hidden), and is otherwise a plain `requestAnimationFrame` driven by real elapsed time (never frame-count), so sweep speed and pop/scroll durations hold regardless of frame rate. Interaction: none — this is a genuine unconditional rAF loop with no pointer response and no autoplay-mode dependency, `autoplay.mode` is `\"none\"` because nothing about it depends on synthetic pointer/press/scroll input. Under `prefers-reduced-motion: reduce`, the loop never starts: layout bakes the head paused 50% through the active row (`filled = round(cols*0.5)`, every one of those cells' 90ms pops already resolved), 4 completed rows resting above it, and the fixed empty strip below, named `GUN_MIDROW` and exposed via `data-reduced-motion-freeze` on the root — the most structured single frame (mixed filled/unfilled state, head visibly mid-sweep, not at a row boundary). No dependencies."
      }
    },
    {
      "name": "turbidite-graded-bed",
      "type": "registry:ui",
      "title": "Turbidite Graded Bed",
      "description": "A card-scale ambient timeline where each irregular flow-pulse event deposits one new sedimentary bed at the top of a growing stratigraphic column: coarse grains settling first at a sharp, erosively-scoured base and fining continuously upward to a fine cap, stacking pulse by pulse forever with the oldest beds slowly compacting and scrolling off the bottom.",
      "files": [
        {
          "path": "registry/core/turbidite-graded-bed/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/turbidite-graded-bed.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "timeline",
          "canvas",
          "ambient",
          "generative",
          "activity-log",
          "grain",
          "stratigraphy",
          "raf"
        ],
        "instruction": "Build <TurbiditeGradedBed pulseIntervalSeconds? paused? className? style?> as a card-scale ambient vertical strip reproducing a submarine turbidite's stratigraphic column: one <canvas> (w-full h-full, DPR-capped at 2, resized only via a ResizeObserver on the wrapper, never window.resize) inside a role=\"img\" wrapper. STATE: an array of `Layer` objects, index 0 always the newest/topmost, each holding baseHeight (deposited height, never mutated), a compaction-reduced `height`, `yTop` (recomputed every pulse by summing heights from index 0), and a fixed array of colour-free `Dot` descriptors {x, y, radiusFactor, alphaFactor} generated ONCE at deposit time via a seeded mulberry32 PRNG — dots run from y=0 (the layer's fine cap) to y=baseHeight (its coarse base), radiusFactor/alphaFactor both scaling with that same 0..1 position so density and grain radius are both highest at the base and lowest at the cap (the fining-upward signature). PULSES: fire on setTimeout at an irregular interval averaging `pulseIntervalSeconds` (default 3.2s, exponential-ish via -mean*ln(1-rand()*0.98), clamped to bounds proportional to the documented 1.8-5.5s range) — never a metronome. Each pulse: sample a layer height from a skewed distribution (Math.pow(rand(),3) so most events are modest, occasional ones large) in the 14-34px range scaled by the strip's own width (clamp(width/220, 0.6, 1.6) — geometry is derived from the container's smaller dimension so it still reads at any card size), bake its dot field into an offscreen <canvas> sized to the strip's current width, shift every existing layer's yTop down by the new height, unshift the new layer to index 0, and arm the PREVIOUS top layer's basal scour (10 sampled depth points 2-4px via a jittered sine, never a straight line) with a start timestamp. Cull any layer once its yTop exceeds the strip height by 48px so memory stays bounded while the loop stays visually unbounded (older beds genuinely scroll off the bottom). COMPACTION: every pulse, any existing layer whose current span falls in the bottom 20% of the visible strip has its compression increased by 0.3% (capped 40% total) and height recomputed as baseHeight*(1-compression) — a slow, continuous squeeze from the growing overburden, applied every pulse but only visible over many. RENDER LOOP (rAF, only while visible/awake): compositing is drawImage calls of the cached per-layer bitmaps, never dot recomputation — the only per-frame work is (a) a reveal clip on the currently-depositing layer (0-120ms: only the bottom ~28% base slice is revealed; 120-450ms: the reveal boundary sweeps linearly from that 28% line up to the layer's very top, so the coarse base visibly appears first and the fine cap resolves last) and (b) an overpaint on a freshly-buried layer's cap tracing its scour depth points scaled by elapsed/80ms, filled in the `--background` token (a genuine erasure of already-drawn grains, not a fill token misuse) with a 1px `--border` stroke along the same scalloped curve marking the erosive contact line — `--border` is used ONLY as that stroke, never as a fill or the layer's own background (which is `--background`). TOKENS: colours read once via getComputedStyle(document.documentElement) before any paint and re-read (repainting every cached layer bitmap from its stored dot descriptors, not regenerating the dots) on a MutationObserver watching documentElement's class attribute — theme toggles never lose accumulated history. No `--ns-accent` anywhere; this is a pure ambient record with no interactive chrome. LIFECYCLE: IntersectionObserver (threshold 0) and visibilitychange both pause the rAF loop and clear the pulse timer (no catch-up scheduling on wake, just a fresh randomized interval); prefers-reduced-motion and the `paused` prop (polled every 140ms) both freeze on a synchronously-built deterministic still — six complete graded beds stacked with the freshest fully formed, settled, and its own basal scour fully baked in, no deposit or scour animation in flight, the single most-structured frame. A resize invalidates every cached layer bitmap (baked at the old strip width) so the whole column resets and restarts cleanly rather than attempting to rescale finished beds in place. Pure DOM + 2D canvas, zero dependencies."
      }
    },
    {
      "name": "turntable-stall",
      "type": "registry:ui",
      "title": "Turntable Stall",
      "description": "Org/tenant switcher as a locomotive roundhouse turntable: the bridge slews on a real spring, and org-scoped nav stays visibly disconnected through the entire unaligned middle, reconnecting only when the bridge is exactly on the stall angle AND the server has confirmed.",
      "files": [
        {
          "path": "registry/core/turntable-stall/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/turntable-stall.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "switcher",
          "organization",
          "tenant",
          "turntable",
          "svg",
          "spring",
          "physics",
          "listbox",
          "multi-tenant",
          "navigation"
        ],
        "instruction": "Build an org/tenant switcher whose popover contains an SVG roundhouse turntable: a pivoting bridge (the current session) and a fixed fan of stalls (the tenants), each stall a real <button role=\"option\"> carrying the org's initial and name, arranged along a 24-degree-stepped arc. One governing scalar drives everything: bridge angle theta, integrated on a single underdamped spring (k=114 s^-2, zeta=0.75) so a hop overshoots by a couple degrees and settles in roughly 500ms — critical damping was rejected because it reads as a digital snap and erases the sense that switching exclusive authority has mass. Rail continuity between the bridge tip and each stall's fixed spur is a dash-junction line computed live from theta every frame: solid only within +/-1.5 degrees of exact alignment, dashed and fading outside that, absent past ~11 degrees — so mid-swing, when theta is between stalls, no junction renders at all and the geometry itself shows 'aligned with nothing.' Selecting a different org immediately (before any motion) drops the bundled org-scoped nav strip to 40% opacity with a hairline gap opening under each label (a broken two-segment underline replacing the continuous one), announces 'Switching to {name}…' via a role=status region, and springs the bridge not to the exact stall angle but to a point held HOLD_SHORT_DEG (~2.4deg, deliberately outside the 1.5deg join tolerance) short of it — rails stay visibly unjoined and the bridge visibly parked, so 'almost switched' has a face instead of a spinner. A confirmSwitch(id) promise (default: a simulated 950-1350ms network round trip, deliberately slower than the ~700-900ms the hold-phase spring itself takes to settle, so the parked/unjoined state has time to actually be seen rather than being raced through; injectable for a real backend) runs concurrently; only once it resolves does the bridge spring the remaining short hop to the exact angle, and only at that exact settle does the component flip connected, re-enable the nav strip, announce '{name}, connected.', and fire a 1px translate impulse on the popover panel — a physical clunk marking the exact moment of rail-mate, not a fade. This is the falsifiable constraint: nav re-enables only when theta equals the stall angle AND the server has confirmed, never earlier — no optimistic re-enable, no crossfade between org contexts pretending continuity that was never there. Selecting a second org before the first confirmSwitch resolves cancels the stale wait via a sequence counter (the late resolution is a no-op), and a rejected confirmSwitch leaves the bridge parked short indefinitely with an explicit 'holding' status rather than silently retrying. The stalls form a composite listbox: the popover panel owns role=listbox (tabIndex -1, focused programmatically on open), each stall button is tabIndex -1 with aria-selected on the pending-or-committed org, arrow keys move an active-option highlight via aria-activedescendant without committing, and Enter/Space commits the active stall exactly as a click does. The trigger button is idempotent-open (click always opens, never toggles closed — only Escape, an outside pointerdown, or picking a stall's own path lets the swing finish uninterrupted) so a repeated press can never undo the open state mid-demonstration. Reduced motion teleports theta directly (no spring) but preserves the identical two-phase hold-then-final structure — still parks short awaiting confirmation before jumping to the exact angle — and the org-scoped nav's opacity transition stays a plain 150ms opacity-only fade regardless of the motion preference, because the disconnected middle is a correctness signal, not decorative motion. All ink is drawn from --background/--foreground/--ns-muted/--border via inline var() on the SVG elements and Tailwind utilities bound to the same tokens elsewhere; --ns-accent appears only on the trigger's focus-visible ring. No canvas — the turntable is plain SVG with one rotated <g> for the bridge and per-stall <line> refs for the junctions, updated by a single refs-only rAF loop that sleeps whenever the spring is idle."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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(--ns-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.",
        "rank": 292
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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/--ns-muted/--border/--ns-accent, plus --surface for the list card), --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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/--ns-muted/--border/--ns-accent, plus --surface for the list card), --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1",
          "surface": "#fafafa",
          "error": "#ea001d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717",
          "error": "#ff6369"
        }
      },
      "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.",
        "rank": 291
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "error": "#ea001d"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "error": "#ff6369"
        }
      },
      "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), --ns-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.",
        "rank": 290
      }
    },
    {
      "name": "vellum-scrape",
      "type": "registry:ui",
      "title": "Vellum Scrape",
      "description": "A version-history rail styled as scraping vellum: dragging a vertical depth slider reveals each paragraph's overwritten text exactly where it lived, in real selectable DOM, with a per-run restore control to re-ink an old passage as the document's true content.",
      "files": [
        {
          "path": "registry/core/vellum-scrape/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/vellum-scrape.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "version-history",
          "diff",
          "slider",
          "range",
          "scrub",
          "document",
          "revision",
          "restore",
          "accessibility"
        ],
        "instruction": "Build VellumScrape, a document viewer where a single vertical rail (a real input[type=range], min 0, max versions.length-1) scrubs the whole document's edit history at once. Props: `versions` (NEWEST FIRST — index 0 is depth 0, the current save), `runs` (the document as an ordered flat list of `{id, paragraph, current, ghost?, revealedAt?, overwrittenBy?}` — a run with no `ghost` has never been overwritten and renders as a plain span, untouched by any of this), controlled/uncontrolled `depth`/`defaultDepth`, `onDepthChange`, `onRestore`, `label`, `className`. MECHANISM: every run carrying a ghost gets a static per-run CSS custom property `--rd` (its `revealedAt` threshold); the document root carries the one value that actually changes on drag, `--scrape-depth`. Each run computes its own reveal fraction in pure CSS as `--show: clamp(0, calc(var(--scrape-depth) - var(--rd) + 1), 1)`, which is always exactly 0 or exactly 1 for any integer depth vs. any integer threshold — a clean step with no per-run JS bookkeeping. Setting the single `--scrape-depth` var on the doc root is what makes every run's opacity/color/filter (ordinary animatable properties, each with its own `transition: … 420ms`, not a transition on the custom property itself) pick up the step and ease across it together: current text crossfades toward `--ns-muted` at 35% opacity while the ghost fades in at reduced weight with a 0.5px blur. This is a CSS-driven reveal, not a JS-computed diff view or a strikethrough overlay — the revealed past is real DOM text, selectable and readable, never an image. RESTORE: a per-run button (never a descendant of the faded ghost element, so it never inherits its opacity/blur) opens an inline confirm strip beside it (Restore/Cancel), never a modal. Confirming sets that run's `restored` flag, which a `data-restored` attribute on the run wrapper uses to permanently pin the ghost to full `--foreground` ink (no blur, no muted crossfade) and hide the superseded current text, animating over 500ms cubic-bezier(0.16,1,0.3,1) — the \"one wet stroke\" re-inking, independent of the rail's depth from that point on. The confirm trigger itself is never removed from the DOM; only the confirm strip beside it toggles, and opening it is idempotent (`setConfirmingId(id)`, never a toggle), so a second click on an already-open trigger cannot close it. A11Y: the rail carries `aria-valuetext` naming the version, its author and save time — standard slider semantics, so arrow keys/Home/End work for free with no extra keydown handling. A separate visible + `aria-live=\"polite\"` summary line announces what the current depth revealed (\"Showing version N of total: k paragraphs differ\"), independent of whether the rail has focus. Every transition this component declares is zeroed under `prefers-reduced-motion`, a pure CSS switch with no JS branching in the drag or reveal path. Colors are strictly --background/--foreground/--ns-muted/--border/--ns-accent, read as CSS custom properties, never hardcoded hex. Zero dependencies, DOM+CSS only, no canvas."
      }
    },
    {
      "name": "venturi-ejector-draw",
      "type": "registry:ui",
      "title": "Venturi Ejector Draw",
      "description": "A processing/analyzing loader shaped as an industrial Venturi vacuum ejector: a particle stream necks through a 22%-of-inlet throat and visibly speeds up there, a second entrainment stream merges in from a side port just before the constriction, and one marked tracer crosses the whole nozzle every 2.4s at full opacity against the ambient field's 55%.",
      "files": [
        {
          "path": "registry/core/venturi-ejector-draw/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/venturi-ejector-draw.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "loader",
          "loading",
          "spinner",
          "processing",
          "canvas",
          "particles",
          "fluid-dynamics",
          "ambient",
          "card"
        ],
        "instruction": "Build <VenturiEjectorDraw label? className? style?> as a self-contained canvas-2D component, zero dependencies, no SVG. The root is a `role=\"status\" aria-live=\"polite\"` div holding a `w-full h-full` canvas (aria-hidden) plus a visually-hidden text node carrying `label` (default 'Processing') as the only thing announced. GEOMETRY: on mount and on every ResizeObserver callback, measure the root's content box and derive a `Geom` object from it — duct spans the width minus a 5%-of-width margin on each side; inlet half-width = min(container's smaller dimension * 0.34, height * 0.46); throat half-width = inlet half-width * 0.22 (throat = 22% of inlet width); exit half-width = inlet half-width. The throat zone's LENGTH is not a guess: it is solved (CONE_FRAC = 1/(2 + 3*(TRACER_THROAT/TRACER_APPROACH)) with the tracer's own 0.7s/1.0s/0.7s timing, ~16% cone, ~68% throat, ~16% diffuser) so the tracer's speed is continuous across both seams — a long narrow mixing tube, not a pinch point, which is also closer to a real ejector's proportions. The nozzle silhouette is two polylines (40 samples each, mirrored above/below the centreline) plus two short end caps at the inlet and exit, all read via widthHalfAt(x) — purely cosmetic, nothing in the tracer's schedule reads it: a smoothstep ease from inlet half-width down to throat half-width spread across the whole converging cone, a flat throat half-width across the throat zone, then a smoothstep ease back out to exit half-width across the diffuser. A short perpendicular stub line marks the entrainment side port on the nozzle wall just before the throat zone. PARTICLES: two independent emitters accumulate `dt * rate` per frame and spawn a particle each time the accumulator crosses 1 — 14/s at the inlet (x = ductLeft, a random lane in [-0.9, 0.9] off the centreline, via a seeded mulberry32 PRNG) and 6/s at the side port (t = 0, travelling a smoothstep-eased straight line from the side port to a merge point just before the throat over 0.4s, then handed into the main particle array at the merge point/lane, trail carried over so the merge reads continuously). Every main particle's x advances by speedAt(x)*vScale*dt each frame — vScale is a per-particle 0.85-1.15 multiplier drawn at spawn so the field doesn't march in visible lockstep — where speedAt shares the same three zone boundaries as widthHalfAt but its own cubic-in/flat/smoothstep-out shape over real px/s numbers: 40 at the inlet, 260 flat across the throat zone, 90 at the exit (kept cubic on purpose, independent of widthHalfAt's cosmetic smoothstep taper, so particles stay slow and bunched near the inlet — the density gradient the resting loop needs). Side-stream particles merge in with a small lane jitter (mergeLane ± 0.15) rather than single file. A particle's y = centreY + lane * widthHalfAt(x) * 0.82, keeping it inset from the outline; particles are removed once x passes the duct's right edge. TRAIL: every particle (main, side, and the tracer) accumulates its own 0.055s timer and, each time it fires, pushes its current {x,y} onto a capped 4-entry trail array (oldest dropped first). Rendering draws the trail oldest-to-newest at alpha = baseAlpha * [0.1, 0.2, 0.35, 0.6][age-from-newest], then the current position at baseAlpha — 3-4 fading historical dots standing in for a motion streak, no shader. MARKED TRACER: exactly one, always live, riding the centreline (lane 0) on its OWN fixed authored schedule, decoupled from the ambient speed field but geometrically solved to be continuous with it at both seams (see GEOMETRY) — phase runs 0 to 2.4s and wraps; 0-0.7s an ease-in cubic (starts at rest) from inlet to throat-zone-start, 0.7-1.7s LINEAR across the throat zone at the exact speed the cubic ramped up to (a full 1.0s crossing, versus a real ejector's 15-40ms — deliberately decoupled ~30x so the speed-up is a followable event, not a strobe), 1.7-2.4s a mirrored ease-out cubic (enters at throat speed, eases to rest) to the exit. On wrap, the tracer's trail array is cleared immediately — otherwise a ghost cluster lingers at the exit for ~220ms while a fresh dot appears at the inlet, reading as two tracers. Rendered at full --foreground opacity (alpha 1) against the ambient streams' 0.55, drawn last so it sits on top; NEVER accent-tinted and NEVER a different size — distinguished purely by that luminance difference. TOKENS: getComputedStyle(document.documentElement) read for --ns-muted and --foreground, assigned verbatim to strokeStyle/fillStyle (whatever colour syntax the token actually holds) with alpha modulated via ctx.globalAlpha per dot rather than a parsed-and-rebuilt rgba() string, so nothing depends on hex specifically; read synchronously before the first resize/paint, and re-read on a MutationObserver watching documentElement's class attribute, re-drawing immediately if currently frozen. WARM-UP: a fresh mount (or a real resize) never renders an empty outline — resize() resets the simulation then runs a deterministic fixed-1/60s-timestep warm-up (1-2 randomized tracer periods) BEFORE the first paint/rAF, so ambient particles are already distributed across the whole nozzle at varying local density and speed the instant the component becomes visible. Every resize() call first checks the measured size against the last one it actually applied and returns early if unchanged, since ResizeObserver fires an initial callback on observe() even with no real change — without that guard the field would silently re-warm-up and teleport to a new random configuration ~100ms after every mount. RESIZE/VISIBILITY: a ResizeObserver (100ms debounced) recomputes Geom on an actual size change, dpr-clamps the canvas backing store to 2, sets canvas.style.width/height explicitly, cancels any in-flight rAF, and re-runs the same reset+warm-up path; an IntersectionObserver pauses the rAF loop while off-screen and resumes it the instant it re-enters viewport (resuming the existing simulation state, not a fresh warm-up); a visibilitychange listener does the same for a backgrounded tab. REDUCED MOTION: on prefers-reduced-motion (checked at mount and on a matchMedia change listener) the component runs the identical step()/emit()/speedAt() simulation forward with the same fixed 1/60s warm-up helper, deterministically (same seeded PRNG), for exactly 2 full 2.4s tracer cycles plus one additional 1.2s (TRACER_APPROACH + TRACER_THROAT/2) — landing the tracer's phase exactly at throat centre by construction (the linear throat segment's 50% point), i.e. the frame of maximum density/speed contrast between the constriction and the open cone/diffuser — draws that single frame, and schedules no further ticks; a resize while reduced re-runs the same deterministic freeze at the new geometry. Cleanup cancels the rAF, disconnects the ResizeObserver/IntersectionObserver/MutationObserver, and removes the visibilitychange and matchMedia listeners. Props: label (aria-live text, default 'Processing'), className, style. No interaction — it is a pure ambient loader; nothing in it responds to pointer or keyboard input."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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-ns-muted, text-foreground, focus ring in --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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": "warp-knit-tricot-lapping",
      "type": "registry:ui",
      "title": "Warp Knit Tricot Lapping",
      "description": "An ambient divider/background texture whose guide bar shogs a short lateral distance every course, lapping each needle's thread diagonally across its neighbours to build a continuously-scrolling zigzag chevron field. The diagonal wale structure of warp knitting, distinct from a weft shuttle's full-width crossing.",
      "files": [
        {
          "path": "registry/core/warp-knit-tricot-lapping/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/warp-knit-tricot-lapping.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "background",
          "divider",
          "canvas",
          "knit",
          "textile",
          "generative",
          "chevron",
          "ambient"
        ],
        "instruction": "Build <WarpKnitTricotLapping className?> as a card-scale <canvas w-full h-full>, ambient/autoplay only with no pointer interaction. GEOMETRY: needle pitch is derived from the container's smaller dimension, clamped to 8-14px around a ~11px target (pitch = clamp(min(w,h)/27, 8, 14)); needle columns = round(w/pitch)+2 so the field slightly overhangs both edges; course height = pitch * 9/11 (~9px at ~11px pitch), matching the spec's real-numbers ratio. STATE MACHINE: every needle in a row shares one guide-bar offset (in px), tracked as offsetPrev/offsetTarget. A course cycle is exactly 500ms: for the first 200ms the guide bar (a bright full-width horizontal line pinned at a fixed frontier y = height - courseHeight, the 'where the work is happening' indicator) eases from offsetPrev to offsetTarget via easeOutCubic; at the 200ms mark the course COMMITS in one instant redraw (never a gradually-growing line) — a CourseRecord {k, offsetPx: offsetTarget} is pushed to history and every needle's diagonal lap segment for that course becomes part of the settled texture. At the 500ms mark the course advances: offsetPrev = offsetTarget, direction flips sign, offsetTarget = offsetPrev + direction * 2 * pitch — a fixed 'shog 2 needle-spaces right, then 2 left' tricot chain, so offset is a triangle wave (0, +2*pitch, 0, +2*pitch, ...) rather than unbounded drift. RENDER: settled lap texture is one stroked path built by iterating consecutive history pairs and, for every needle column n, drawing a line from (baseX_n + courseA.offsetPx, screenY(courseA)) to (baseX_n + courseB.offsetPx, screenY(courseB)) — this produces the parallel-line chevron field, reversing direction every course. A field-scroll clock, decoupled from the course-commit cadence but running at the same real rate (one course-height of upward scroll per 500ms, via a continuous ms-based formula rather than a discrete jump), maps each committed course's fixed world position to a screen y; courses are culled from history once they scroll more than one course-height above the top edge. Stroke is `--foreground` via `currentColor` (canvas className carries text-foreground, colour read once with getComputedStyle(canvas).color and re-read on a MutationObserver watching documentElement's class attribute — no colour literal anywhere), uniform 1.15px line weight, no fill; the settled texture strokes at 0.62 alpha and the guide-bar indicator strokes at 0.95 alpha so the eye can find the active course at a glance in both themes. No `--border` on the lap/chevron itself and no `--ns-accent` anywhere. RESTING LOOP: at any instant the canvas shows a zigzag chevron mid-build with the guide bar mid-shog or freshly settled; over 2.5s roughly 5 courses commit with at least 2 visible chevron reversals, and by 5s the chevron pattern visible at t0 has scrolled off the top edge and been culled from history — alive and unbounded with zero input. LIFECYCLE: dpr clamped to 2, a debounced ResizeObserver re-measures pitch/courseHeight/frontier and restarts the course clock, an IntersectionObserver pauses the rAF loop when the canvas scrolls off-screen and resumes it (re-checking the elapsed-gap guard) when it re-enters, and a >20-course real-time gap (e.g. a long-backgrounded tab) triggers a silent resync of the field instead of replaying every missed course. prefers-reduced-motion synchronously replays 8 course commits (guaranteeing at least 2 direction reversals) at mount and freezes on LAP_SETTLED — the frame immediately after the 8th shog's lap segments have landed, guide bar at rest, never mid-slide — with no further ticking. Cleanup cancels the rAF, disconnects the MutationObserver/ResizeObserver/IntersectionObserver, and clears the resize debounce timer. The canvas is aria-hidden — it is decoration, no content a screen reader should announce. Props: className only."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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/--ns-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": "welt-channel-close",
      "type": "registry:ui",
      "title": "Welt Channel Close",
      "description": "A full-width section divider modeled on Goodyear-welted shoe construction: a needle locks one welt stitch at a time at a single working point while, a few stitches behind it, the channel flap that exposed the last batch of stitches folds shut on a real CSS 3D hinge and hides them for good. The lockstitch is never drawn; only a plain flush seam is ever visible once a flap closes.",
      "files": [
        {
          "path": "registry/core/welt-channel-close/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/welt-channel-close.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "core",
        "tags": [
          "divider",
          "separator",
          "hr",
          "welt",
          "stitch",
          "leather",
          "fold",
          "3d",
          "monochrome"
        ],
        "instruction": "Build <WeltChannelClose height? className?> as a drop-in replacement for <hr>/border-top between page sections, rendered as a <div role=\"separator\" aria-orientation=\"horizontal\"> holding a pool of real absolutely-positioned DOM elements (no canvas, no SVG) plus one CSS custom-property-driven <style> block. SOURCE: Goodyear-welted shoe construction — a channel is skived into the insole rib to expose it; a curved awl and needle lockstitch a welt to the upper and insole rib through that open channel; immediately behind the working point, the lifted channel flap that exposed the rib is folded back down and pressed flush, permanently concealing the just-completed stitching under the insole surface, so the outer edge only ever shows a plain seam. TIMELINE: one continuous clock, elapsedMs from mount, currentStitchFloat = elapsedMs / STITCH_INTERVAL_MS (STITCH_INTERVAL_MS = 1050, comfortably above the 1s legibility floor), currentIndex = floor(currentStitchFloat) is the stitch the needle is actively locking, feedDistance = currentStitchFloat * CELL_SPACING (CELL_SPACING = 14px, ~6-7 stitches/inch welt gauge at card DPI) is how far the whole strip has fed left — deliberately locked in lockstep with the stitch count (not a separate speed constant) so the working point never drifts more than one cell width. The working point sits at a FIXED screen fraction workingX = width * 0.45; any stitch index i's screen x = workingX + i*CELL_SPACING - feedDistance, continuously sliding left as elapsedMs advances (material scrolls off the left edge forever, an unbounded loop). CLASSIFY EVERY STITCH POSITION PURELY BY ageMs = elapsedMs - i*STITCH_INTERVAL_MS (negative for a stitch not yet reached), no per-stitch persisted React state: ageMs < FLAP_LAG_MS (FLAP_LAG_STITCHES=3, FLAP_LAG_MS=3150) keeps its flap fully lifted (CSS custom property --lift=1) — this single condition covers both a not-yet-worked stitch (channel already skived open ahead of the needle) and a just-locked stitch still waiting its turn; FLAP_LAG_MS <= ageMs < FLAP_LAG_MS+FOLD_MS (FOLD_MS=260) is the fold itself, --lift eased from 1 to 0 via easeOutCubic over that 260ms window — an explicit continuous rotateX hinge (transform: perspective(260px) rotateX(calc(var(--lift,0) * -32deg)), transform-origin: right center, the trailing/already-closed edge is the hinge line), never an opacity blink; ageMs >= FLAP_LAG_MS+FOLD_MS is flush-closed, --lift=0, and that flap element is simply hidden behind the single continuous seam hairline described below — the lockstitch itself is never drawn at any point, matching the real mechanic where it's concealed the instant it's finished. Because ageMs is linear in i, the boundary between still-open and already-flush trails the working point by a FIXED, precomputed number of stitches ((FLAP_LAG_MS+FOLD_MS)/STITCH_INTERVAL_MS), so the flush seam's length is one constant px offset from workingX recomputed once per frame (seamWidth = workingX - CELL_SPACING*(FLAP_LAG_MS+FOLD_MS)/STITCH_INTERVAL_MS, clamped >=0) rather than a per-stitch loop — the seam is a single absolutely-positioned div with only its width animated, height 1px, background var(--foreground) via the bg-foreground utility class (a genuine hairline stroke, never --border, which is a separator token invisible as a fill/stroke in light theme). FLAP POOL: a fixed pool of 200 pre-rendered divs (POOL_SIZE), each frame the visible index window [iMin,iMax] is recomputed from feedDistance/workingX/width and mapped onto pool slots 0..count-1 (slots beyond count get display:none); because nothing here relies on a stitch's DOM node identity persisting (--lift is a pure function of (i, elapsedMs), not accumulated), reassigning which stitch a pool slot represents as the window slides never restarts or loses an in-flight fold. Each flap div is CELL_SPACING wide, height = max(10, height*0.5) (derived from the strip's own smaller dimension, the height prop), background color-mix(in srgb, var(--background), var(--foreground) calc(var(--lift,0)*5%)) for a faint tonal lift, and box-shadow 0 calc(var(--lift,0)*2px) calc(var(--lift,0)*6px) color-mix(in srgb, var(--foreground) calc(var(--lift,0)*24%), transparent) for the lifted-edge cast — the ONLY cue distinguishing open from flush in both themes, verified to survive light theme where shadow contrast is naturally weaker (no hue change, luminance/alpha only). NEEDLE: one element (never pooled), present only during its own stitch's NEEDLE_MS=420ms crossing window inside each 1050ms interval (needleLocalMs = elapsedMs - currentIndex*STITCH_INTERVAL_MS, active when <= 420), depth = sin(clamp(needleLocalMs/420,0,1) * PI) drives scaleY(0.25 + 0.75*depth) for an in-lock-out poke and opacity toggles 1/0 with activity; rendered in --foreground via the bg-foreground utility class ONLY — --ns-accent must never appear anywhere in this component, this is an ambient divider with no interaction chrome. LEGIBILITY: the eye follows the single working point where the needle currently pokes, once every 1.05s (well above the 1s floor); three stitches behind it, the flap that was open folds flush over 260ms with a visible eased lift-to-flush hinge arc, giving a fast anchor (needle) and a slower confirmation event (fold) to track, exactly as specified. HOST: ResizeObserver on the wrapper (not window.resize, 120ms debounce) remeasures width, a single requestAnimationFrame loop paused via IntersectionObserver (threshold 0) when scrolled offscreen and via visibilitychange when the tab is hidden, full cleanup (cancelAnimationFrame, disconnect both observers, remove the visibilitychange listener) on unmount. REDUCED MOTION: prefers-reduced-motion renders exactly once at a fixed elapsedMs = 8*STITCH_INTERVAL_MS + NEEDLE_MS/2 (MID_LOCK) — the needle frozen mid-crossing (depth=1, fully inserted) with the full FLAP_LAG=3 window of lifted flaps visible ahead of the working point and the flush closed seam clearly visible behind it, so open/closing/closed all read in one static frame; no rAF loop starts. TOKENS: every color is a CSS custom-property reference (var(--foreground), var(--background), var(--border)) via Tailwind's token-backed utility classes (bg-foreground) and inline color-mix() expressions in the embedded <style> block — zero JS color reads, zero literals, zero hex, so both themes and any live token change repaint for free through the normal CSS cascade with no MutationObserver needed (this component paints no canvas/WebGL surface, so the getComputedStyle+MutationObserver token-read rule that applies to raster contexts elsewhere in the registry doesn't apply here — consistent with every other pure-DOM/CSS-var component in this registry, e.g. tag-input-tear, button-cooldown-heat). A11Y: role=separator carries the divider's semantics and needs no accessible name; every rendered element is aria-hidden or purely decorative; there is no keyboard surface because nothing here is operable. Props: height (band height in px, default 44) and className. Zero dependencies, DOM + CSS only."
      }
    },
    {
      "name": "wind-regulator-bellows",
      "type": "registry:ui",
      "title": "Wind Regulator Bellows",
      "description": "A streaming media buffer-ahead strip modelled on a pipe organ's wind supply: a feeder bellows pumps discrete strokes into a reservoir whose weighted lid smooths that into a steady reading, and a spring-loaded spill valve bleeds off the excess whenever the lid threatens to overshoot, replacing the flat grey \"buffered\" overlay most players use.",
      "files": [
        {
          "path": "registry/core/wind-regulator-bellows/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/wind-regulator-bellows.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "meter",
          "buffer",
          "media",
          "scrub-bar",
          "svg",
          "bellows",
          "status",
          "ambient",
          "industrial"
        ],
        "instruction": "Build <WindRegulatorBellows label? className?> as a self-contained DOM/SVG horizontal strip (rounded-md border border-border bg-surface card) split into a narrow left feeder panel (border-r border-border, ~1/4 width) and a wider right reservoir panel, both driven by one fixed 30Hz simulation tick (rAF with an accumulated-real-elapsed-time step, never frame-rate dependent). SIMULATION: a lid fraction (0..1 of max height, floored at 0.08 so the strip never reads fully empty) drains continuously at 4%/s every tick, independent of and concurrent with feeder strokes; every 1.4s a stroke fires and steps the lid up 6% of max height instantly (not eased) — net drift across one 1.4s interval is +0.4%, so the lid climbs stroke over stroke until a spill valve intervenes. RELIEF VALVE: the instant the lid would exceed 92% of max height, a flap hinged at the reservoir's top-right corner cracks open (rotates 0 to 28deg over 180ms), then the excess bleeds back down to exactly 92% over a 260ms eased interpolation (overriding the continuous drain/stroke terms for that window), then the flap reseats (28 to 0deg over 200ms, not separately specced by the source mechanic) — this sequential crack/bleed/reseat state machine is the only route back down once drift alone would overshoot. Faint two-tick 'hiss' marks near the flap hinge fade in during the crack/bleed phases only. RESERVOIR RENDERING: a lid line (var(--foreground), thicker stroke) spans the panel at its current height between two static side-wall lines (var(--foreground)) and a floor line (var(--border), a genuine separator use); between the lid and the floor, six evenly-spaced pleat fold lines are re-drawn every tick as a shallow 4-segment zigzag whose amplitude scales with the current fold spacing (var(--ns-muted) — --border alone reads too faint for these to carry structure in light theme, per source spec) — so pleats visibly compress into tight zigzags as the lid rises and spread into loose ones as it falls, never a static pattern just sliding. FEEDER RENDERING: a small pump cylinder (var(--border) outline) with a vertical stroke-arm (var(--foreground) line, filled circle head) that rises fast (280ms, ease-out) on every 1.4s stroke drive, then eases back down over the remaining ~1.12s to sit idle at the cylinder floor before the next drive — the visual cue that a discrete injection just landed. GEOMETRY: each panel's own SVG viewBox is set to its measured pixel rect (getBoundingClientRect via a shared ResizeObserver on both panel wrappers) on every resize, and every stroke-width/hinge-length/amplitude is derived from that panel's own smaller dimension so the strip rescales cleanly at card width. TOKENS: colours are read once via getComputedStyle(document.documentElement) (--foreground, --ns-muted, --border) with no literal fallback of any kind; the mount routine retries on the next rAF until all three resolve and paints nothing before that; re-applies on every documentElement class-attribute mutation via a MutationObserver. Never tints the reservoir, feeder arm, or relief flap with --ns-accent — that token stays reserved for the actual scrub playhead/controls elsewhere in the player chrome, per source spec; this strip is a passive read only. An IntersectionObserver resets the frame-delta clock on re-entry (avoiding a large simulated dt jump) rather than fully pausing, since the underlying supply/demand simulation is meant to read as continuous. `autoplay: none` because the whole thing runs on its own internal 1.4s stroke / 4%-per-second drain clock with zero pointer/scroll/press input — there is nothing for a synthetic-input driver to trigger. Under `prefers-reduced-motion` no rAF loop, timer, or observer-driven redraw starts; instead a single deterministic STROKE_PEAK frame is drawn once at mount and on every resize/token-mutation: lid parked just below the 92% relief threshold (a stroke has just landed and the lid sits at its local peak, pleats visibly compressed, flap fully seated at 0deg) with the feeder arm frozen at the top of its stroke — the most structured frame available, not t0's arbitrary mid-drain sample. Cleanup on unmount cancels every pending rAF and disconnects all three observers. No hover/pointer interaction is required for the ambient read; if placed adjacent to a real scrub bar a consumer may layer a hover tooltip on top using their own buffered-seconds data, this component owns only the bellows read itself."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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/--ns-accent/--background/--surface/--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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|--ns-muted|--ns-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": "zipper-stall",
      "type": "registry:ui",
      "title": "Zipper Stall",
      "description": "Merge resolution staged as closing a zipper: two columns of timestamped edit ops, meshed by dragging a handle down the spine that physically jams at the first same-offset conflict until you pick a side.",
      "files": [
        {
          "path": "registry/core/zipper-stall/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/zipper-stall.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "core",
        "tags": [
          "merge",
          "diff",
          "conflict-resolution",
          "slider",
          "range",
          "drag",
          "ordered-list",
          "version-control"
        ],
        "instruction": "Build a merge-resolution control where two edit streams (ours, theirs) render as two columns of 'teeth' down a shared spine, one operation per tooth, and a single scalar — closureY, px down the spine — is the ONLY thing that drives every tooth's meshed/open state, the drag handle's position, the merged-op count, and which conflict (if any) is currently live; there must be no per-tooth animation clock, since independently-timed teeth could mesh out of arrival order and misrepresent causality. DATA: ops are {id, side, t (arrival order), timestamp (pre-formatted display string, never Date-derived, so server/client render identically), offset (document position), label}. Sort by t, then group into rows keyed by offset: the first op seen at an offset creates a row at the next spine position; a second op arriving later at the SAME offset joins that row on the other side and marks it a conflict — this is the only conflict condition. GEOMETRY: rows are fixed-height (rowHeight, default 44px) list items; row i's mesh threshold (center) is i*rowHeight + rowHeight/2; spineHeight is rows.length*rowHeight. MESHING: a non-conflict tooth is 'meshed' (flush, translateX 0) exactly when closureY >= its row's center, else it sits at a resting column offset of +-16px; that boolean flip drives a 90ms ease-out-expo (cubic-bezier(0.16,1,0.3,1)) CSS transition on transform/opacity/color — nothing else times it, so scrubbing the handle backward deterministically re-opens every tooth in reverse. A conflict row's teeth stay at their +-16px resting offset forever, regardless of closureY, until resolved. THE STALL: the native <input type=range> driving closureY always has min=0, max=spineHeight (constant, never rescaled) so its pixel position always corresponds to the real rows drawn underneath it; the COMMITTED value is clamped in the onChange/keydown handler to at most (firstUnresolvedConflict.center - 8px). Never shrink the native max to fake this — that would silently rescale the whole track and break 'the handle's position IS the row it's stuck at'. The SAME change event's raw (pre-clamp) value drives a 0-3px 'jam' bump on a decorative handle-cap element via a critically-adjacent spring (k=500 s^-2, zeta=0.5, semi-implicit Euler, rAF loop that tears down once settled) so pushing past the wall reads as resistance, not a control that silently stopped responding. RESOLUTION: once jammed, two real <button>s ('keep yours' / 'keep theirs') render inline at that row (a relatively-positioned wrapper at z-index above the full-height transparent input, since the buttons sit inside the same narrow center column the drag control spans); clicking one sets that row's resolved side, fires onResolve(kept, dropped), returns focus to the range input, and announces the decision through a role=status aria-live=polite region. The chosen op then meshes exactly like any normal tooth (closureY vs its row's center, same transition); the OTHER op immediately and permanently transitions to a folded state — scaleY(0.5), reduced opacity, its label in a <s> strikethrough, captioned 'not taken' — that never depends on closureY again and is never removed from the list, since it's a genuine one-way decision, not something scrubbing should undo. Resolving unblocks the max for whatever conflict comes next, if any. KEYBOARD: the input's own onKeyDown intercepts ArrowDown/Right (+1 row), ArrowUp/Left (-1 row), PageDown/PageUp (+-3 rows), Home/End (0 / spineHeight), each going through the exact same commit()-and-clamp path as pointer dragging — native default stepping is preempted so 'down' always means 'more closure' regardless of the control's visual rotation. A11Y: aria-label 'Merge closure', aria-valuemin/now explicit, aria-valuemax explicitly set to the CURRENT reachable max (not the nominal spine total) so assistive tech reports the real boundary, aria-valuetext formatted as '{merged} of {total} ops merged' or, while jammed, '{merged} of {total} ops merged, stopped at conflict {n} of {N}'. The spine is a plain <ol> — one <li> per row — each with an aria-label naming its op(s): 'theirs, 14:02, replaced heading' for a normal row, both sides plus resolution status ('conflict, unresolved, stalls the merge here' or 'resolved, kept ours') for a conflict row. VISUAL: the native input is full-height, fully transparent (opacity 0, outline none) and rotated 90deg via a centered CSS transform (translate(-50%,-50%) rotate(90deg) on a box whose pre-rotation width equals spineHeight) so native click-to-position and drag physics work correctly against the real rotated geometry; every visible pixel (rail, fill, handle cap, focus ring) is a separate aria-hidden decorative layer computed straight from closureY/bump/focus/hover/press React state, tokens only (var(--foreground) resting, var(--ns-accent) only while focused/hovered/pressed, var(--border) for rails and idle tooth borders, var(--ns-muted) for idle tooth text and folded rows). REDUCED MOTION: the spring loop is replaced outright by a single ~150ms border-color flash on the handle cap (no rAF, a setTimeout toggle) and all tooth transitions become instant via a prefers-reduced-motion media query zeroing their transition property — the merge is fully readable and resolvable with zero animation."
      }
    },
    {
      "name": "agar-starve",
      "type": "registry:ui",
      "title": "Agar Starve",
      "description": "A bacterial colony grown from a live, depleting nutrient field: a rich plate spreads into a smooth disc, a starved one forks into dense-branching fingers that never merge because the gap between them was eaten first, and a spent plate dries to interior tone before a fresh colony sprouts from another corner and grows through the ghost of the last one.",
      "files": [
        {
          "path": "registry/loud/agar-starve/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/agar-starve.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "canvas",
          "simulation",
          "cellular-automaton",
          "reaction-diffusion",
          "monochrome",
          "ambient",
          "decorative",
          "organic"
        ],
        "instruction": "Build <AgarStarve c0? diffusion? speed? paused? children? className? style?> as a full-bleed canvas colony whose shape is decided by a live nutrient field it is currently consuming, not by noise or a fixed growth rule. Two coupled lattices run at roughly 1/6 viewport resolution (coarsened further under an internal cell budget so huge panes stay cheap): OCCUPIED, a permanent biomass grid that cells never leave once colonized, and N, a nutrient concentration that diffuses and gets eaten. Every simulation tick, in order: DIFFUSE — N is relaxed via 5 Jacobi iterations solving the implicit diffusion equation (Stam-style: each iteration reads the previous iteration's whole field, never itself mid-sweep) with a no-flux boundary at the plate edge; fewer iterations leaves the solve visibly biased along the two grid axes and branches snap to 90 degrees, the classic lattice-diffusion artifact. REACT — every occupied cell eats 0.02 nutrient from its own cell each tick; once its local nutrient drops under a starvation threshold it stops eating and stops dividing (\"dies to inert interior\"), and that check is against the LIVE field rather than a sticky flag, so a cell a rising diffusion front reaches genuinely wakes back up. DIVIDE — every still-alive occupied cell picks ONE random empty Moore (8-connected) neighbour and divides into it with probability equal to that neighbour's own nutrient, clamped at 0.25; a 4-connected rule would grow a visible diamond instead of a disc. Nothing here ever points a finger on purpose: a neighbour facing untouched plate simply has more nutrient and wins the coin flip more often over many ticks, which is what makes fingers statistically steer toward what is still fresh, and the same diffuse-then-eat step pulls nutrient OUT of the gap between two fingers advancing at each other before either tip physically reaches it, so the seam starves first and the two never touch — the render pass paints that gap as permanent exhausted shadow because its nutrient truly never recovers on its own. `c0` (initial nutrient concentration, default 0.34) is the governing scalar and the real axis of the Ben-Jacob morphology diagram: a high value keeps most of the plate above the 0.25 probability cap long enough that the front advances near-uniformly into a smooth Eden disc, a low one exhausts locally almost immediately behind the leading edge and growth channels into whichever few directions still have supply, producing dense-branching fingers. `diffusion` (default 0.18, cell^2/step) is read live every tick rather than only at mount, so raising it at runtime visibly heals an already-branched colony: faster relaxation refills the starved gaps between fingers before the boundary cells there die for good, and cells that are still viable resume dividing into territory the branching had shut. When no occupied, still-alive cell has an empty neighbour left for a sustained stretch, the plate is spent: every occupied cell is snapped to its oldest age band in one beat (\"the colony dries to interior tone\"), and after a pause a fresh inoculation is planted at the next corner in rotation, nutrient is refilled everywhere including inside the previous colony's shadow gaps, and the new colony grows outward free to fill territory the old one's depletion had sealed off — occupancy is never cleared, so the old colony's silhouette stays legible underneath while the new one grows through its ghost. RENDER: the two lattices are painted into an ImageData at grid resolution — occupied cells ramp from --ns-muted at the newly-divided rim toward --border at full age, empty-but-depleted cells get a faint --border wash standing in for exhausted shadow, everything else is fully transparent over the bg-background wrapper — then drawn upsampled onto the visible canvas with a 1.5px canvas blur so the result reads as biomass texture rather than visible grid cells. --ns-muted and --border are read via getComputedStyle(document.documentElement) at mount and re-read on a documentElement class mutation (theme toggle); the color ramp never leaves that --ns-muted..--border range, so biomass can never get darker in dark mode or busier in light mode than --border, which is what keeps overlaid text safe in both themes. There is no pointer or keyboard interaction anywhere: the canvas is aria-hidden, the wrapper renders `children` as ordinary accessible DOM over the plate, and the whole point is a slow ambient process meant to reward an occasional glance rather than a stare — the front advances at roughly 8px/s. `paused` freezes the current frame without unmounting (polled via a ref rather than an effect dependency, so toggling it does not tear down and reseed the plate); `speed` is a global tick-rate multiplier, also read live. prefers-reduced-motion runs the same tick function 3000 times synchronously at mount and paints one static, fully-grown frame instead of ever scheduling a rAF loop, and a live change to the media query freezes or resumes growth from wherever it currently is. ResizeObserver rebuilds both lattices (debounced) on a real size change; IntersectionObserver and visibilitychange stop the single rAF loop off-screen or in a hidden tab. A deterministic xorshift PRNG (not Math.random) drives every random neighbour pick and division roll, so the shipped resting frame is reproducible mount to mount rather than drifting between screenshots."
      }
    },
    {
      "name": "arc-ladder-climb",
      "type": "registry:ui",
      "title": "Arc Ladder Climb",
      "description": "A full-bleed section-divider background modelled on a Jacob's ladder: two rails diverge from a narrow base gap, a bright plasma arc strikes there, climbs the widening gap as it burns, dies near the top, and a fresh arc restrikes at the base almost immediately. This runs continuously, at a re-randomized ~1.6-2.2s cadence.",
      "files": [
        {
          "path": "registry/loud/arc-ladder-climb/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/arc-ladder-climb.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "divider",
          "hero",
          "canvas",
          "electric",
          "arc",
          "plasma",
          "ambient",
          "background"
        ],
        "instruction": "Build <ArcLadderClimb children? className?> as a full-bleed Canvas 2D section-divider background modelling a Jacob's ladder. GEOMETRY: computeGeo(W, H) derives baseGapHalf = (minDim * 0.014) / 2 and maxClimb = H * 0.85 from the container's own dimensions, plus a scale factor clamp(minDim / 640, 0.6, 1.6) applied to stroke widths, jitter magnitude and halo blur so the same numbers read from a slim divider strip up to a 100vw section break. Two straight rails are stroked once per frame in --ns-muted at 0.8 alpha from (cx -+ baseGapHalf, H) at the bottom up to (cx -+ railOffsetAt(H), 0) at the top, where railOffsetAt(ch) = baseGapHalf + ch * tan(7deg) — a fixed 7-degree half-angle divergence, so the gap between the rails at any climb height is purely a function of how far the arc has risen from the base. STATE MACHINE: a single ArcState carries phase ('climbing' | 'dark'), phaseStartMs, phaseDurMs, a per-climb random vertex count (5-7) and an incrementing rank used to vary the jitter hash between successive arcs; advanceArc(state, nowMs, rand) is called every frame and walks the phase machine forward through however many completed phases fall between the last check and now (a for-loop with a 64-iteration guard, not a single if) — the same idiom plasma-filament-wander uses for advanceFilament, letting a background tab or the reduced-motion freeze jump to an arbitrary elapsed time without ever appearing to teleport. TIMING: each climb draws a fresh period uniformly from 1600-2200ms (re-randomized every cycle, never a fixed metronome) and climbs for 78% of that period, eased via easeInQuad from a climb height of 4px*scale up to maxClimb (0.85 * container height) — real ladders accelerate as the buoyant plume speeds up, so the climb visibly speeds up toward the top rather than gliding at constant rate; on completing its climb the phase flips to 'dark' for a freshly-rolled 120-220ms (ionization redeveloping before the next strike, never instantaneous), after which a brand new climb begins with a freshly-rolled period, vertex count and rank. JITTER: the arc itself is a polyline spanning the current left-rail to right-rail contact points at its live climb height, pinned exactly at both endpoints (the rail contact points), with interior vertices displaced vertically by hashJitter(rank, timeBucket, vertexIndex) * (3-7px scaled, derived per-rank from a golden-ratio hash so consecutive arcs get visibly different jitter magnitudes) * a sin(pi*t) taper so displacement is zero at the pinned rail contacts and freest at mid-span; timeBucket = floor(nowMs / (1000/24)), a fixed 24Hz resample rate deliberately decoupled from the 60Hz paint loop per the round-9 aliasing rule, so the redraw itself can stay at full frame rate while the perceived flicker sits at the real HV-arc-adjacent rate. AFTERGLOW: while climbing, the live climb height is pushed onto a max-3-entry trail array every ~130ms (capturedAt timestamp retained); every frame, every trail entry younger than 400ms is redrawn underneath the live arc at alpha = (1 - age/400ms) * 0.55^(distance-from-newest) with no halo, giving a decaying stack of previous positions that reads as heated air rather than a static rung — entries are never explicitly cleared on restrike, so a dying afterglow from the previous climb can legitimately still be fading near the top for its last ~100-200ms while a brand new arc is already striking at the base below it, matching the real device's overlap. RENDER: the live arc is stroked (round joins/caps) in --foreground at 0.95 alpha with a canvas shadowBlur of 6px*scale in the same --foreground colour for its halo — a pure luminance halo, --ns-accent never appears anywhere on the rails, arc or glow, and the arc's colour is always read live off --foreground (never a fixed value) so it keeps reading as the single brightest element in the frame in both themes. Colour is read once via getComputedStyle(document.documentElement) for --foreground and --ns-muted with zero literal fallback of any kind; if either is empty the mount loop retries on the next rAF and paints nothing until both resolve. A MutationObserver on document.documentElement's class attribute re-reads tokens on every theme flip and forces a redraw; a ResizeObserver on the root recomputes geometry and resizes the canvas backing store (devicePixelRatio capped at 2) without resetting the arc's in-flight phase; an IntersectionObserver stops driving new frames while off-screen and, on returning to view, re-reads tokens (picking up any theme flip that happened while hidden) and resumes the same continuous elapsed-time clock — no paint happens on any of these paths before a token read succeeds. Under prefers-reduced-motion the component runs zero rAF loops, zero timers: it renders exactly one deterministic frame — climb height frozen at 55% of maxClimb (clearly separated from both rails, elongated, neither the too-short t0 strike nor a near-top full climb) with two fixed-opacity static afterglow steps beneath it (0.32 and 0.16 alpha, deterministic jitter bucket 0, never re-rendered) — and never redraws. Cleanup on unmount cancels both the animation and token-wait rAF handles and disconnects all three observers. Optional children render over the field in a centered max-w-5xl column (a section label or short line, mirroring plasma-filament-wander's overlay slot), with pointer-events left to the canvas's aria-hidden sibling so nothing intercepts clicks meant for real content — there is no pointer tracking or hover state anywhere in this component, the climb-and-restrike cadence is the entire spectacle. Zero dependencies, DOM+canvas only, no colour literal anywhere including fallbacks."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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.",
        "rank": 8
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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": "aspheric-turn-spiral",
      "type": "registry:ui",
      "title": "Aspheric Turn Spiral",
      "description": "A full-bleed hero of a lens blank mid single-point diamond turning, a spiral groove cutting from center to rim with pitch that tightens toward the edge the way a real aspheric surface curves.",
      "files": [
        {
          "path": "registry/loud/aspheric-turn-spiral/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/aspheric-turn-spiral.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "canvas",
          "webgl",
          "shader",
          "lens",
          "metal",
          "cursor"
        ],
        "instruction": "Build <AsphericTurnSpiral speed? scale? relief? paused? children? className? style?> as a full-bleed WebGL hero rendering a lens surface being cut on a diamond-turning lathe. ONE HEIGHT FIELD evaluated per pixel and central-differenced (eps 1.2px, relief scaled by 10 * relief prop) for one surface normal, three summed contributions: (1) a shallow concave bowl, -BOWL_AMP*r^2 with BOWL_AMP 0.55 and r the polar radius normalized to a disc of radius 0.46*min(w,h)*scale, the lens's own aspheric sag; (2) the spiral groove itself, cos(2π*(θ/2π + turns(r))) where turns(r) = N_BASE*r + N_ASPH*r^2, N_BASE 14, N_ASPH 26 — the r^2 term is what makes the groove pitch tighten toward the rim, the whole reason this reads as an aspheric surface rather than a flat Archimedean spiral, at GROOVE_AMP 0.11; (3) a faint isotropic pre-cut texture, (vnoise(d*9+4.1)-0.5)*0.05, gated to only the annulus the tool has not yet reached, so cut and uncut read as two different surfaces rather than one disc with a groove drawn partway across it. THE FEED IS A TRIANGLE WAVE, never a reset: cutRadius(t) = mix(0.05, 1.04, tri(fract(t/90))) where tri is a 0→1→0 triangle over the 90s FEED_PERIOD (45s outward, 45s inward) — the tool is always mid-traverse, and a direction reversal at either end is a real process event (a verification or re-finishing pass), not fabricated motion. cutMask = smoothstep(rc+0.018, rc-0.018, r) gates the groove and blank texture; edgeGlow = exp(-((r-rc)/0.022)^2) is a separate bright term added straight into the lighting (0.35x) representing freshly-turned surface catching light hardest right at the tool's current position — this is the slow (45s-scale) structural cue for 'alive at rest.' THE FAST, FOLLOWABLE cue is decoupled from that: the reflection environment's azimuth is rotated by u_time * SPINDLE_RATE * 2π with SPINDLE_RATE 0.08 rev/s (slowed hard from a real SPDT spindle's ~600-3000rpm and documented here explicitly rather than driven 1:1, the round 9 decoupling rule), plus a second azimuthal panel at 4x that rate so a visible specular band crosses roughly every ~3s without the base rotation itself needing to be that fast. MONOCHROME STUDIO: reflection vector rf = reflect(-v, n) is looked up in an analytic achromatic room — sky/floor smoothsteps, two narrow elevation strips (exp(-((el-0.16)/0.075)^2) and exp(-((el+0.28)/0.10)^2)), the two azimuthal panels above, a fresnel rim (pow(1-dot(n,v),5) * 0.10) and one drifting specular lobe (exponent 70, weight 0.5) — several narrow sources so a nearly-flat patch of the bowl still crosses multiple reflection bands, the same 'give the room structure, not the material more noise' approach as weld-pool. Palette: five stops from --background, --foreground, --ns-muted and --border via getComputedStyle at mount, re-read on a documentElement class MutationObserver, spanning near-black to near-white in BOTH themes since this is a full-bleed sheet like weld-pool — only bias/contrast move between themes (dark: bias -0.08, contrast 1.15; light: bias 0.02, contrast 1.2), never direction, and no --ns-accent anywhere in the shader. POINTER is a gentle parallax only: pointer position eases into u_ptr over a 150ms time constant and nudges the view direction by ±0.18 in view-space xy, orbiting the specular highlight across the groove; it never restarts the cut, changes feed direction, or touches spiral phase, and it still redraws the frozen frame under prefers-reduced-motion or paused. HOST: DPR capped at 1.5 (full-bleed area cost dominates, matching weld-pool's convention), ResizeObserver on the wrapper, an adaptive render scale ladder (1/0.75/0.55, EMA frame time, steps down after ~900ms sustained over a 24ms budget, steps up only after a much longer clean stretch that doubles per failure), IntersectionObserver (threshold 0) and visibilitychange both pausing the single rAF loop, and webglcontextlost/restored rebuilding cleanly. prefers-reduced-motion (and paused) draw exactly one composed still frame at STATIC_TIME=27s, named 'spiral-60pct' (feed 60% along its outward leg, ph=0.3 so tri=0.6) — maximum visible groove density with the tool boundary and its edgeGlow clearly inside the frame, never a blank grey box. Children render over the surface for an eyebrow, subhead and CTA; because the surface spans the full value range in both themes they need a token scrim (bg-background/70 + backdrop-blur) rather than bare type."
      }
    },
    {
      "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-capillary-wick",
      "type": "registry:ui",
      "title": "Background Capillary Wick",
      "description": "A full-bleed ambient background modeling paper chromatography: ink fronts wick along a fixed fibre-vein lattice, decelerating per the Washburn capillary-rise law as they climb, stalling at every junction, and re-igniting a subset of the node's dry outgoing edges before evaporating back to dry to make room for the next front.",
      "files": [
        {
          "path": "registry/loud/background-capillary-wick/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-capillary-wick.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "chromatography",
          "capillary",
          "lattice",
          "ink",
          "decorative"
        ],
        "instruction": "Build <BackgroundCapillaryWick pitchRatio? paused? children? className? style?> as a full-bleed Canvas 2D background modeling REAL PAPER CHROMATOGRAPHY — capillary wicking along a FIXED fibre-vein lattice — not fluid turbulence and not a diffusion field. SUBSTRATE: a jittered triangular mesh of nodes and edges is generated once per container size (hex-staggered rows, pitch clamped to the container's smaller dimension so the mesh never reads as zoomed-in on a full-bleed hero or a small preview card alike) and never reshuffled while mounted — ink can only exist ON an edge, in the order the graph connects, which is the whole distinction from dye-whorl (registry/loud/dye-whorl), a real incompressible Navier-Stokes solver advecting a continuous dye field through open fluid with no lattice, no discrete fronts and no junctions at all. PHYSICS: an active front's position along its edge advances per the Washburn capillary-rise law, dPos/dt proportional to 1/sqrt(pos) — fast off the junction, decelerating as it climbs, because a real wicking front's flow resistance grows with the wetted length behind it. On reaching the far node the edge becomes fully wet and the node STALLS for a randomized dwell before re-igniting a probabilistic subset of its still-dry outgoing edges (always at least one if any remain dry, so a junction with open dry paper never simply stops) — this stall-then-reroute at every junction is the literal mechanic asked for, not a cosmetic delay. ALIVE AT REST BY CONSTRUCTION, NOT DECORATION: a wet edge holds full intensity only briefly, then evaporates on a slower exponential decay back to fully dry, which makes it eligible for a later independent nucleation event elsewhere on the mesh — so the field is a standing turnover with no saturated end state to ever settle into, forever, not a fill-to-completion animation that happens to loop. Nucleation itself is a periodic Poisson-ish process picking a random node and igniting one of its dry neighbor edges, independent of any prior front. READING ZONE: fresh nucleation is weighted away from the container's centre and every stroke's alpha is additionally scaled by a radial coverage mask that stays low near centre (~16%) and rises toward the edges (~100%), so centred headline/CTA content sitting over it keeps a legible field underneath even while the lattice is busy at the margins — the same discipline background-halftone-rosette used for its coverage gradient, applied here as a lighter centre rather than a denser one because chromatography's own physical layout (a reservoir at the edges of a strip, ink climbing inward) makes that the honest reading. TOKENS: --foreground is the only ink color, used for both wetted strokes and the brighter advancing meniscus drawn as a small filled circle at each active front's head; --ns-muted draws the always-visible dry lattice at low fixed alpha, kept well under --border's separator contrast so the fixed veins read as texture, never as a UI seam; --background clears the canvas every frame. No --ns-accent anywhere — this is a resting ambient surface with no interaction moment, so accent chrome has no job here. All three tokens are read via getComputedStyle(document.documentElement) only after document.fonts.ready resolves, before the first paint, and re-read on a MutationObserver watching documentElement's class; every path that could draw before that first read (ResizeObserver, IntersectionObserver, the reduced-motion branch) is gated behind a ready flag so nothing paints with an empty token string. WARM START: a fluid front graph has no meaningful t=0 either — a freshly built mesh is entirely dry — so the sim runs a fixed 260 steps at a 1/30s timestep before the first paint in every mode, seeding several fronts already mid-advance with visible branching by the time anything is shown. prefers-reduced-motion (and the paused prop) render exactly that warm-started frame once and never step again — chosen specifically because it is the first moment the mesh already shows multiple fronts at different stages and real junction branching, not a bare dry mesh (t=0 proper) and not a degenerate edge case. SCALE: lattice pitch is clamped to a fraction (default 0.055) of min(width, height), and stroke width derives from pitch, so spacing and front width stay proportional at both full-bleed hero scale and a small preview-card crop. HOST: DPR-capped backing store (max 2), IntersectionObserver (threshold 0) and visibilitychange both stop the single rAF loop, ResizeObserver rebuilds and re-warms the lattice on layout change. The canvas is aria-hidden and pointer-events-none; children render as real DOM above it in normal focus order and are never intercepted by the canvas."
      }
    },
    {
      "name": "background-engine-turn-guilloche",
      "type": "registry:ui",
      "title": "Background Engine Turn Guilloche",
      "description": "A full-bleed ambient background reproducing engine-turning (guilloché), the rose-engine lathe technique used to engrave the dense woven rosette patterns on banknotes and security documents: three superimposed hypotrochoid passes at close, unequal lathe ratios beat against each other while the ratios drift and each pass spins independently, so the weave never locks into a static pattern.",
      "files": [
        {
          "path": "registry/loud/background-engine-turn-guilloche/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-engine-turn-guilloche.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "canvas",
          "guilloche",
          "engraving",
          "moire",
          "hypotrochoid",
          "security-printing",
          "ambient"
        ],
        "instruction": "Build <GuillocheField ringCount? className? style?><...headline/CTA/></GuillocheField> as a full-bleed Canvas 2D background reproducing real engine-turning (guilloché): the rose-engine lathe technique — two eccentric 'rosette' cams rocking a workpiece through compound epicyclic motion against a fixed cutting tool — used to engrave the dense woven patterns on banknotes and security documents. GEOMETRY: three passes, each a hypotrochoid family with R fixed at 1, r = R/k, pen offset d = 0.85r, x(t) = (R-r)cos(t) + d*cos(((R-r)/r)t), y(t) = (R-r)sin(t) - d*sin(((R-r)/r)t), sampled once into a normalized (max radius 1) unit-curve table. Lathe ratios are close but not equal (k0 ~= 6.0, 6.4, 9.7) so the passes beat against each other exactly as two real guilloché screens do. Each pass draws RINGS (default 36) concentric, scaled copies of its own unit curve from the container center (s=0.05) out to the frame edge (s=1.0) — this is the literal rose-engine behavior of retracing the same curve at a slow crawl of radii as the cut deepens, not decoration. ALIVE AT REST: each pass's k(t) = k0 + driftAmp*sin(driftOmega*t + phase) drifts continuously (driftOmega periods 30-45s) while each pass also spins independently at a small, non-commensurate rate (0.5, -0.62, 0.83 deg/s) — neither the slow ratio drift nor the slow spin alone would read across a 2.5s gap, but together the near-equal k values (6.0 vs 6.4) produce a beat wavelength sweeping far faster than either individual motion, which is what a 2.5s screenshot pair actually shows moving. None of this requires the pointer. POINTER (optional): hovering nudges an eased 'tilt' scalar toward the pointer's normalized distance from center, which multiplies driftAmp up to 1.6x on the next curve-table rebuild — a local-ratio perturbation, like tilting a banknote to catch the security thread — and relaxes back to baseline once the pointer leaves. READABILITY: ring opacity follows an explicit coverage gradient via smoothstep(s), from near-clear at the center (~4-5%) up to full engraved density at the edge (~85% for the two --foreground passes, ~48% for the --ns-muted fine pass) — the mirror of a banknote's clear portrait window inside a dense engraved border — so `children` (rendered centered above the canvas) sit in the field's least-covered zone. SCALE: maxRadius = Math.min(width, height) * 0.46, so ring amplitude and density are always proportional to the container's smaller dimension and read correctly at both full-bleed hero scale and a small preview card. SUBSTRATE: the unit curve table (trig-heavy) is rebuilt at most every ~140ms, matched to how slowly the ratio actually drifts; every rAF frame only scales, rotates (one cos/sin per pass) and translates that cached table with plain arithmetic to rebuild the stroke path and redraw — never re-running the curve's trig per frame. Rings are grouped into 8 alpha buckets per pass (same discipline as background-ascii-plasma) so ctx.globalAlpha is set at most 24 times per frame rather than once per ring. TOKENS: stroke color is --foreground for the two primary passes and --ns-muted for the fine third pass, read via getComputedStyle(document.documentElement) at mount and re-read on a MutationObserver watching documentElement's class — no --ns-accent anywhere in the resting field, which is ambient, not interaction chrome. DPR-capped canvas backing store (1.5 full-bleed, 0.6 inside an autoplay preview card), ResizeObserver-driven resize, IntersectionObserver (threshold 0) plus visibilitychange pausing the rAF loop off-viewport or tab-hidden. prefers-reduced-motion freezes the clock at STATIC_T = 5.8s rather than t=0, specifically because by then the two close-ratio passes have visibly de-phased into a legible beat and each pass has turned enough to be structurally distinct, instead of sitting in the near-registration all three passes start in. Decorative canvas is aria-hidden and pointer-events-none; `children` render as real DOM in normal focus order above it and are never occluded from pointer input. Props: children, ringCount (default 36), className, style."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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), --ns-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.",
        "rank": 1
      }
    },
    {
      "name": "background-halftone-rosette",
      "type": "registry:ui",
      "title": "Background Halftone Rosette",
      "description": "A full-bleed ambient background of two same-ink halftone dot screens drifting at independent, non-commensurate angles: a real duotone/double-hit moiré rosette, with a coverage gradient keeping the frame edges readable and the visual center where the interference peaks.",
      "files": [
        {
          "path": "registry/loud/background-halftone-rosette/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-halftone-rosette.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "halftone",
          "moire",
          "css",
          "print",
          "ambient",
          "duotone"
        ],
        "instruction": "Build <BackgroundHalftoneRosette dotPitch? rateA? rateB? paused? className?> as a full-bleed ambient background of two overlaid halftone dot screens — the same single ink at two independent, continuously drifting angles, the way duotone/double-hit offset printing lays one ink down twice at different screen angles. TWO PASSES, ONE INK: this is not a color trick — both screens render in --foreground on --background, and the moiré rosette is genuine dot-overlap interference between same-ink coverage fields, the print phenomenon printers historically fought and this component makes the point of. COVERAGE GRADIENT SOLVES THE MID-GREY PROBLEM: same-ink moiré is strongest near 50% combined coverage and nearly invisible near the extremes, so a uniform-coverage field would pull the whole frame toward an unreadable mid-grey; instead coverage ramps from ~8% at the frame edges (a low-coverage reading zone for headline content, expressed as a --background-tinted scrim) toward full dot contrast at the visual center, where the rosette is the point. TWO NON-COMMENSURATE RATES: screen A rotates continuously at +0.6deg/s (default rateA), screen B at -0.9deg/s (default rateB), so their separation drifts forever rather than settling into a repeating period — moiré beat wavelength scales as pitch/(2*sin(deltaTheta/2)), so even this slow a drift produces a large, clearly visible shift in the beat pattern within a couple of seconds, keeping the field genuinely alive at rest. THEME-DEPENDENT BLEND MODE, NOT HARDCODED: overlap value must move monotonically away from --background and toward --foreground as coverage increases, in BOTH themes — mix-blend-mode:multiply is used when --foreground is darker than --background (light theme), screen when --foreground is lighter (dark theme), decided by comparing token luminance via getComputedStyle(document.documentElement) at mount and re-read on a MutationObserver watching documentElement's class; a hardcoded multiply would silently invert the coverage cue in dark theme. CSS-driven: two oversized (150%, negative-offset so a rotated square always fully covers the container) tiled radial-gradient dot layers, each under an independent infinite CSS rotation keyframe, zero rAF cost. prefers-reduced-motion and the paused prop both freeze the rotation at the screens' ~30deg resting separation (each screen held at +/-15deg) — the calm, print-safe minimal-rosette state, not an arbitrary mid-drift crest. Props: dotPitch (halftone screen pitch in px, default 8), rateA/rateB (deg/s, defaults 0.6/-0.9), paused, className."
      }
    },
    {
      "name": "background-text-branch-canopy",
      "type": "registry:ui",
      "title": "Background Text Branch Canopy",
      "description": "A full-bleed ambient background where an L-system tree grows entirely out of readable text: every limb is one run of words drawn with a single fillText, pivoting on its own foot as it angles from its parent, with the outer canopy senescing and regrowing forever so it never settles into a finished frame.",
      "files": [
        {
          "path": "registry/loud/background-text-branch-canopy/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/background-text-branch-canopy.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "text",
          "canvas",
          "growth",
          "typography",
          "ambient",
          "botanical"
        ],
        "instruction": "Build <BackgroundTextBranchCanopy text? className? style?> as a full-bleed ambient background: a Canvas 2D L-system tree whose every limb is drawn with exactly one ctx.fillText() call after translate(foot)+rotate(limbAngle) — a run of words from `text`, windowed to that limb's own length, pivoting on its own foot as it angles away from its parent. THE PRIMITIVE IS TEXT, NOT A TEXTURED STROKE: there is no path/stroke anywhere in the render, and no per-character grid — this is what separates it from the registry's ASCII family (glyph-per-cell luminance stand-ins on a fixed grid, meaningless characters) and from the registry's growth family, tendril-cast/auxin-canal/tree-root-trace/cambium-lay (all grow strokes that are colored or textured afterward). READABILITY DRIVES DEPTH, NOT THE REVERSE: trunk length is 0.28*min(container w,h), each generation is 0.72x the last, and a generation only recurses once its length can still hold ~4 monospace characters at the container-derived type size (9-15px, clamped, and NEVER shrunk by depth — recession is expressed as fewer words and lower alpha only). PIVOT ON THE FOOT: translate is always the limb's own foot (its parent's tip); when a limb's absolute angle points generally leftward (cos<0) it draws from angle+PI with textAlign 'right' instead of 'left' so the run stays upright and readable rather than mirrored, without moving the pivot. ALIVE FOREVER, TWO STRUCTURAL CHANNELS: (1) only the outer canopy (depth >= 2) ages — each churning limb has its own randomized 7-15s lifespan, senesces down to a stub over 1.5s, then regrows from the same foot with a new word offset, so the crown continually sheds and replaces its outer growth while the trunk/first branch level hold still; (2) every limb's angle carries its own sway term ON TOP of its parent's already-swayed angle (composed via a parent-before-child tree walk each frame), so the whole crown breathes rather than jittering per-limb. Both run unconditionally at rest, no pointer required. TOKENS: --foreground for the nearer ~40% of limbs by generation order, --ns-muted for the rest, with alpha also stepping down by depth on top of that — the near/far read holds by value alone in both themes, no luminance comparison needed. --ns-accent never appears; this is ambient, not interaction chrome. Canvas fillStyle can't take a raw var() string, so both tokens are read via getComputedStyle at mount and re-read on a MutationObserver watching documentElement's class. CONTENT PROTECTION: the trunk is origin-biased into the upper-left two-thirds of the frame, and a --background-tinted linear-gradient scrim (transparent through the top half, strongest at the bottom edge) sits above the canvas so a bottom-docked headline/CTA has a real reading surface — trunk bias alone still lets the crown reach the lower third, so both are required together. prefers-reduced-motion fast-forwards the simulation ~16s past first full maturation into steady turnover before freezing, so the frozen frame shows varied limb ages/depths, not a single freshly-grown tree; the live path is also fast-forwarded ~9s before first paint so t=0 already shows a full crown rather than a bare stub. DPR capped at 2 (1.5 blurs sub-15px monospace past legibility), 0.6 inside an autoplay card, render loop paused on IntersectionObserver-offscreen and visibilitychange. Props: text (source words, default a short passage about growth/branching), className, style."
      }
    },
    {
      "name": "bed-fluidize",
      "type": "registry:ui",
      "title": "Bed Fluidize",
      "description": "A full-bleed ambient background modeling a gas-fluidized particle bed: voids nucleate at the distributor plate, grow with height (the Darton bubble-growth relation), coalesce when they touch, and burst at the surface with a ballistic spray of ejected particles, while the surrounding bed circulates downward in each bubble's wake. A standing boil with no saturated end state.",
      "files": [
        {
          "path": "registry/loud/bed-fluidize/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/bed-fluidize.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "particles",
          "fluidized-bed",
          "bubbles",
          "granular",
          "decorative"
        ],
        "instruction": "Build <BedFluidize pitchRatio? paused? children? className? style?> as a full-bleed Canvas 2D background modeling a REAL GAS-FLUIDIZED PARTICLE BED, not generic noise churn or a fluid turbulence field. LAYOUT: the container height splits into a bed region (bottom ~90%, bedTopY..bedBottomY) and a freeboard region above it (top ~10%) where burst ejecta fly and fall; a thin --ns-muted baseline at bedBottomY reads as the gas distributor plate. PARTICLES: a jittered grid of dots fills the bed region, generated once per container size (base positions in two Float32Arrays, never reshuffled), sized so the total count never exceeds 6000 — pitch is a fraction of the container's smaller dimension (default 1/60), scaled up automatically if the raw grid would exceed the cap. Particles carry NO persistent velocity state: every frame, each particle's visibility and color are read live off the current bubble list, which is what keeps the whole sim cheap despite a dense grid. BUBBLES: a small population (array, hard-capped at 40) of circles. Nucleation is a genuine Poisson process — rate = (containerWidth/100) * 2.2 bubbles/s at rest, exponential inter-arrival via -log(1-rand()), a fresh bubble spawning at a uniform-random x along the distributor with the container-height-derived starting diameter (3% of height). GROWTH: diameter = d0 + k * heightClimbed^0.4 (the Darton relation — climbed = distance risen from the bubble's own spawn point, not from t=0), k derived once per resize so a bubble would reach the 22%-of-height burst cap over a full bed traverse; diameter is clamped at that cap regardless. RISE: rise speed = riseCoeff * sqrt(diameter), riseCoeff calibrated per resize so a diameter = cap*0.5 bubble crosses the full bed height in 3.2s — bigger bubbles measurably outrun smaller ones, matching the real sqrt(d) relation. COALESCENCE: every step, any two bubbles whose centers are closer than 0.42 * (sum of diameters) merge into one bubble of combined AREA (d_new = sqrt(d1^2+d2^2)), keeping the higher (smaller-y) center and the larger accumulated climb distance so growth continues correctly post-merge. BURST: when a bubble's top edge reaches bedTopY it is removed, a fading crater rim is added at the surface (~480ms fade), and 3-6 ejecta specks are spawned with a real ballistic trajectory under a constant 2600px/s^2 downward acceleration, each given a randomized 220-380ms flight time and an initial upward velocity solved so the parabola returns to its launch height exactly at that flight time (vy0 = g*life/2) — this is what makes the ejecta arc and fall back rather than just fading in place. RENDERING: a particle inside a bubble's radius is simply not drawn (the void interior is the cleared --background, not a separately colored fill) — a thin --ns-muted stroke traces every bubble's rim so the void still reads as delineated rather than a hole in the field. Particles within a wake band (radius = bubble radius * 1.55, outside the bubble itself) get a small downward positional offset scaled by local rise speed (the WAKE_DRAG constant, 0.6) AND a brightness lift toward --foreground — real fluidized-bed circulation, solids trailing bubbles' wakes rather than free-falling. ALIVE AT REST BY CONSTRUCTION: because nucleation never stops and every bubble present at any instant is guaranteed to burst and be replaced within a few seconds (bed traversal ~2.5-4s depending on size), there is no saturated end state to settle into — the t0/2.5s/5s checkpoints are structurally guaranteed to differ, not just phase-shifted, since no bubble present at t0 can still exist unburst by t5s. INTERACTION: pointer position (root pointermove/pointerleave, skipped for touch pointers) locally raises nucleation odds and particle brightness within a radius (18% of min(width,height)) via a pointerBoost scalar that eases toward its target and decays back over ~600ms after the pointer leaves — never a fixed accent-colored highlight, purely a rate/luminance change, and it never tints anything with --ns-accent. TOKENS: --background clears the canvas and is the void interior; --ns-muted is the resting particle/rim/distributor color; --foreground is the peak-agitation particle and ejecta color. All three are read via getComputedStyle(document.documentElement) only inside document.fonts.ready, before the first paint, and re-read on a MutationObserver watching documentElement's class, with every early-paint path (ResizeObserver, IntersectionObserver, reduced-motion) gated behind a `ready` flag. WARM START: 220 fixed-dt (1/30s) steps run before the very first paint so the field already shows several bubbles at different growth stages rather than a bare distributor plate. prefers-reduced-motion (and the paused prop) render one deliberately-authored static frame — FREEZE_PHASE = mid-rise-with-recent-burst: four bubbles at staggered heights including one at 60% of the bed's vertical span, plus a still-fading crater near the surface — chosen because it is the single frame that shows nucleation, growth and a recent burst simultaneously, never the bare t=0 floor. HOST: DPR-capped backing store (max 2), ResizeObserver on the root rebuilds the particle grid and bubble-growth constants on layout change, IntersectionObserver (threshold 0) and visibilitychange both stop the single rAF loop when offscreen/hidden. The canvas is aria-hidden and pointer-events-none; children render as real DOM above it in normal focus order."
      }
    },
    {
      "name": "bitplane-cascade",
      "type": "registry:ui",
      "title": "Bitplane Cascade",
      "description": "A full-bleed hero/background that composites a real Amiga-class planar framebuffer image one bitplane at a time: a coarse 2-tone silhouette lands first, then three more binary-weighted planes each add a finer layer of tonal detail on top of it, before the full 16-level image holds and the stack clears to rebuild from plane 0 again.",
      "files": [
        {
          "path": "registry/loud/bitplane-cascade/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/bitplane-cascade.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "canvas",
          "bitplane",
          "framebuffer",
          "print",
          "ambient",
          "monochrome"
        ],
        "instruction": "Build <BitplaneCascade cellRatio? paused? className? style? children?> as a full-bleed 2D canvas background modeling Amiga-class planar framebuffer compositing: an N-bit image is stored as N separate full-resolution 1-bit bitplanes, and the visible pixel value is the sum of each plane's binary contribution at its place value. Sample ONE static scalar field once per container size — two octaves of hashed value noise (a coarse octave at wavelength minGridDim/3.2 blended 0.7, a finer octave at minGridDim/9 blended 0.3, minGridDim = min(cols,rows) of the sample grid) — and quantize each cell to a 4-bit value (0-15, 16 levels). Sample grid: cell = clamp(round(containerMinDim/96), 4, 12) px, cols/rows = ceil(width|height / cell); render each cell as a flat unsmoothed cell x cell rect (no interpolation) so every plane's threshold edge stays a hard boundary. MSB-FIRST ARRIVAL IS THE LOAD-BEARING DECISION: planes arrive in place-value order 8, 4, 2, 1 (NOT LSB-first) — real progressive bit-plane transmission (fax/plotter preview passes) sends the most significant plane first because only it reconstructs a legible coarse silhouette on its own; sending the LSB first would just be spatial noise. Plane 0 (place value 8) arrives at t=0, plane 1 (value 4) at t=350ms, plane 2 (value 2) at t=700ms, plane 3 (value 1, the finest bit) at t=1050ms; each arrival ANDs one more high bit into the visible reconstruction (reconstructed = fieldValue & maskOfArrivedBits), so the already-revealed silhouette edge never moves, only finer bands fill in around it. Full stack holds 900ms once complete, then the accumulator clears and plane 0 lands again: cycle length 350*4 + 900 = 2300ms, unbounded loop, zero input. LANDING FLASH SOLVES A REAL ALIASING BUG: within a single plane's 350ms window cell states are static (a real compositor doesn't blend), so a bare step function makes two arbitrary real-time samples that land in the same window pixel-identical, which fails the alive-at-rest checkpoint rule; every cell touched by the most-recently-landed plane's bit gets an extra brightness pop (+alpha up to 0.35, decaying linearly over 220ms) that makes elapsed-time-since-landing part of every frame, not just which plane is current. Cell alpha = (reconstructed / 15), drawn as --foreground at that globalAlpha over a --background-cleared frame (never a literal color, never --ns-accent). Light theme: when only plane 0 has arrived (mask === 8) and the theme is light (per-mount luminance compare of --foreground vs --background via getComputedStyle, re-read on documentElement class MutationObserver), floor alpha at 0.25 so the first silhouette never washes out. prefers-reduced-motion and the paused prop both freeze on plane 2 of 4 just landed (3 bits revealed: 8+4+2, finest bit still pending) with the landing flash held near-max, not the full-stack hold and not plane 0 alone — the frame that most clearly shows a coarse silhouette with partially-resolved finer banding and one more step visibly pending. Tokens read via getComputedStyle(document.documentElement) with no paint before the first read; ResizeObserver rebuilds the field (debounced 150ms) and IntersectionObserver/visibilitychange pause the rAF loop off-screen/hidden; all observers and the rAF are torn down on unmount. Props: cellRatio (fraction of container's smaller dimension used before the 4-12px clamp, default 1/96), paused, className, style, children (rendered above the canvas for overlay content)."
      }
    },
    {
      "name": "blade-stop",
      "type": "registry:ui",
      "title": "Blade Stop",
      "description": "A full-viewport route curtain built as a real iris diaphragm: nine leaves cranked off one actuating ring stop the aperture down to zero to cover the outgoing page, swap the route at the covered frame, then run back open on the incoming one.",
      "files": [
        {
          "path": "registry/loud/blade-stop/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/blade-stop.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "transition",
          "route",
          "curtain",
          "canvas",
          "iris",
          "shutter",
          "overlay"
        ],
        "instruction": "Build <BladeStop active? leaves? duration? onCovered? onRevealed? paused? children? className?> as a full-viewport route curtain drawn on a 2D canvas over the page, implemented as an iris diaphragm rather than as an animated shape. ONE DEGREE OF FREEDOM: the only animated quantity is phi, the angle of the actuating ring. Leaf i has a pivot P at angle theta = 2*pi*i/n on a mount ring of radius 1.30R, a crank of length 0.72R, and an inner edge that is a circular arc of radius 0.80R centred at C = P + 0.72R*u(theta + phi); R is half the viewport diagonal times 0.88, so the frame corner sits at 1.136R — and that factor is load-bearing rather than cosmetic, because the open aperture is 1.22R and every bit of margin above the corner is travel the viewer cannot see (at 1.06 the leaves were still outside the frame for the first 42% of every close and the cycle read as blank more often than not). The aperture radius is |C| - 0.80R and nothing computes it directly. Because |C|^2 = Rp^2 + L^2 + 2*Rp*L*cos(phi), the whole travel is one cosine — 1.22R of clear aperture at phi=0 down to -0.10R (overlapped) at the stop — and every read of the motion follows from it: the aperture opens fast and closes slow (near phi=0 the crank moves radially and |C| barely changes; near phi=pi it moves tangentially), the leaves do not enter the frame at all until roughly a third of the travel has passed, the hole is a nine-lobed star and not a circle, and adjacent plates slide across each other by different amounts at different phases. NO EASING CURVE IS APPLIED ANYWHERE; that asymmetry is the linkage's. The crank is 0.72R and not a shorter one because at phi=pi the crank folds back along the pivot arm and d|C|/dphi is exactly zero; a crank that puts closure ON that dead point moves the blades almost not at all through the last third and leaves the stop nothing to strike. THE TRAVEL IS INTEGRATED: phi is a second-order system (crank inertia, viscous bearing damping, spring return) with omega = 3.4/duration and zeta 0.72 closing / 0.86 opening, stepped at a fixed 1/240s substep — a 50ms catch-up frame integrated in one go jumps the ring straight through its stop. Both ends are HARD STOPS with restitution 0.12, so the close arrives at phi=2.734 with the linkage still moving, strikes, rebounds and settles in two decaying bounces. That is where the weight comes from. Each leaf also carries a fixed per-leaf bearing clearance of a few tenths of a degree, faded in with phi, so the closed rosette is shimmed by hand rather than perfectly nine-fold. LEAF DRAWING — the part that is easy to get catastrophically wrong: do NOT sweep the leading arc about C and close it back along the mount ring, because the winding direction of that closing sweep has no reliable rule and getting it inverted makes every leaf contain the frame centre, which yields no aperture at any phase (the curtain reads as a hard cut and the whole mechanism is invisible). Instead sample the plate's inner boundary as a RADIUS PER FRAME-CENTRE ANGLE: r(psi) = d*cos(D) - sqrt(Rb^2 - d^2*sin(D)^2) with d = |C|, D = psi - angle(C), the near root of the ray/circle intersection, over psi within a half-width of min(asin(Rb/d), 1.9*pi/n) either side of angle(C), 26 samples, then straight out along the same rays to a mount ring of 2.7R (past the corner at any phi) and closed. The three regimes of that root are the three things the mechanism does: negative discriminant = the ray misses the circle, the blade has no material and pinches out against its mount (why nothing is in frame when open); negative root = the frame centre is inside the edge circle and the plates are pie slices meeting at the axis (closed); in between it is the aperture edge, nearest the axis mid-plate and furthest at the seams, which is what makes the hole a nine-lobed star scalloped by 5-19% rather than a circle. A 1.9x plate overlap is what keeps the covered state opaque at every phi and at every viewport aspect (verified 100% coverage at 16:9 and 4:3). One fixed source at -2.2rad lights the stack: each plate's value is 0.08 + 0.84*facing with facing = 0.5 + 0.5*cos(crank - light), applied as a linear gradient across the whole assembly rather than a flat per-plate tint, so leaves change tone AS THEY TRAVEL and a plate that spans the frame is brighter at the lit end. Each plate drops a contact shadow onto the one beneath — a SECOND, UNBLURRED FILL of the same outline offset up-left by 0.012R, not a canvas shadowBlur: at DPR 2 a 0.05R blur on nine full-canvas paths measured one ~400ms long task per frame and rAF fell to 9fps, while the offset fill holds a 16.7ms median with no long tasks at all. That is what makes the stack read as nine separate sheets instead of one filled polygon, and a bevel stroke along the sampled inner boundary — broken wherever the plate has reached the axis, or the closed shutter gets a star of lines drawn across it — gives the aperture its lit edge. COMPLETION IS REPORTED OFF THE APERTURE, NOT OFF SETTLING: onCovered fires the frame |C| - Rb first reaches zero — before the ring has finished bouncing — because that is the only moment a route may be swapped without the swap being visible through a still-open hole; onRevealed fires when the aperture clears the corner (1.136R, not a hardcoded 1), likewise before the ring has stopped ringing. The loop sleeps the instant the ring settles, so an open curtain costs nothing, and a change to `active` wakes it from wherever the ring currently is via a ref rather than an effect dependency, so a reversal mid-travel keeps its momentum. PALETTE: two plate stops, an edge highlight and a shadow tint mixed from --background, --foreground, --ns-muted and --border via getComputedStyle, re-read on a documentElement class MutationObserver. --ns-accent is not read at all: the shutter has no pointer interaction, and an accent tint on an occluder is a coloured blob. What is held constant across themes is the SPREAD across the nine plates, because that spread is the only thing showing the assembly is nine sheets and not one hole punched in a rectangle — the plates sit above a near-black page and well under a paper one; insisting the shutter be the low value in BOTH themes makes the dark covered state a black field with two faint seams in it. Light theme is the harder case because the plates must sit far enough under a bright page to read as metal in front of paper. prefers-reduced-motion and `paused` draw one still frame at 72% of the closing travel — leaves covering 73% of the frame with the nine-lobed aperture clearly formed — never a blank page or a flat plate. DPR-aware canvas sized by ResizeObserver; IntersectionObserver and visibilitychange both stop the loop. children render under the shutter; the canvas is pointer-events-none and aria-hidden. The demo must drive a real loop through the component's own onCovered/onRevealed callbacks (close, swap the route behind the closed leaves, open, dwell) rather than a timeline, and keep the fully-open dwell the shortest beat of the cycle so an arbitrary screenshot lands on the mechanism."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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, --ns-muted, --border and --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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 --ns-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 --ns-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 --ns-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, --ns-accent glow — as live var() references in the SVG so both themes render without any JS re-derivation. Zero dependencies, no canvas."
      }
    },
    {
      "name": "brinicle-descent",
      "type": "registry:ui",
      "title": "Brinicle Descent",
      "description": "A full-bleed underwater backdrop modelled on brinicles: dense, sub-zero brine sinking off newly formed sea ice freezes a hollow ice sheath around itself as it descends, flashes an expanding freeze halo where it touches bottom, then breaks free and drifts off while a fresh tip nucleates elsewhere. Several overlapping lifecycles running at once so the pane is never empty.",
      "files": [
        {
          "path": "registry/loud/brinicle-descent/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/brinicle-descent.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "simulation",
          "monochrome",
          "decorative",
          "ice"
        ],
        "instruction": "Build <BrinicleDescent density? speed? paused? children? className? style?> as a full-bleed 2D-canvas underwater pane growing brinicles — brine-rejection ice tubes, not a heightfield or shader trick. REAL MECHANIC: sea ice forming above a water column rejects brine; the plume is colder/denser than the water below and sinks, and because it sits at or below the freezing point a thin ice sheath nucleates and grows around it as it descends, extending a hollow tube downward (documented Antarctic/Arctic time-lapse footage; brine-drainage-channel literature, e.g. Dayton & Martin 1971). Field/time-lapse descent rates cluster 0.3-1.5 m/hr. LIFECYCLE PER TUBE, several running staggered and overlapping (2-4 concurrent, controlled by `density`, default 3) so the scene is never empty: descend (8000ms — tip travels linearly from top to bottom of a column representing ~2.5m of water, an explicit ~1900-2900x compression of the real 1.7-8.3hr crossing, documented not hidden) -> touchdown (4400ms total: a freeze halo at the seafloor expands 1800ms via easeOutCubic to radius = 0.15 * min(w,h), holds 600ms at full alpha 0.85, fades 2000ms to zero, while the sheath keeps thickening) -> detach (1900ms: first 400ms opens a small gap at the ceiling attachment by nudging the tube's top cutoff down 5% of the column height, then over 1500ms the whole tube drifts down 40px while its alpha eases 1 to 0) -> removed. A new tip nucleates via `makeTube` at a fresh random x whenever live tube count is below `density` and an engine-clock timer (jittered 3000-6000ms after the last spawn) elapses, or immediately if the tube list ever empties (a hard guarantee against a blank frame). The pane is PRE-SEEDED at mount with two tubes already mid-lifecycle (negative phase-start offsets relative to the engine clock, not simulated forward) so t0 is already alive rather than a blank column filling in. SHEATH RADIUS: for a sampled point at height y, depositElapsed = (y-topY)/columnHeight * 8000 is when the descending tip passed that y; that point's age is clamp(engineClock - tube.descendStart - depositElapsed, 0, 8000), and radius = minR + (maxR-minR)*sqrt(age/8000), with minR/maxR derived from the container's smaller dimension (/180 and /70) — the ceiling end (oldest) saturates toward maxR while the tip stays thin, and the whole tube keeps visibly thickening even once its tip has stopped moving. Each tube also carries a gentle sinusoidal x-wobble (amplitude ~1.8% of the smaller dimension, seeded per tube) so tubes read as organic descending plumes, not rigid lines. RENDERING: one 2D canvas, `w-full h-full`, geometry entirely derived from the host's own bounding box (never window size), redrawn every frame by filling a water wash then, per live tube, a filled quad-strip polygon (sampled at 22 points from cutoff to tip, offset left/right by radiusAt(y)) plus thin rim strokes down both edges, plus a radial-gradient touchdown halo when in that phase. TOKENS, NO MANUAL THEME BRANCH: colors read only from --background, --foreground, --ns-muted via getComputedStyle(document.documentElement), re-read on a MutationObserver watching its class, with no paint before that first read. Water wash = mix(background, muted, 0.3). Tube core = mix(muted, foreground, 0.6). Tube rim and the touchdown halo are pure foreground. Because --foreground itself already flips (near-white ink in dark theme, near-black ink in light theme), mixing toward it makes the tube automatically read bright against dark water in dark theme and dark against pale water in light theme — the correct per-theme value inversion with zero explicit theme detection, the same trick dye-whorl uses. --ns-accent never appears anywhere in the render. HOST: DPR-aware canvas capped at 2, sized off a ResizeObserver on the host div. A single rAF loop (engine-clock driven, dt capped at 48ms and scaled by `speed`) is stopped by an IntersectionObserver (threshold 0) when scrolled offscreen and by `visibilitychange` when the tab is hidden, and restarted cleanly on return. `paused` freezes the engine clock in place (loop keeps repainting the same instant) without unmounting anything. prefers-reduced-motion composes and draws exactly ONE still frame, built directly rather than simulated forward: one tube 60% down its descent (thickened sheath, active tip) plus a second, younger tip freshly nucleated near the top — the most structurally complete single frame, never the near-empty literal t0 — and the MutationObserver still repaints that frozen frame on a theme change. The canvas is aria-hidden/role=presentation; children render in an absolutely-positioned overlay above it and need a token scrim (bg-background/70 + backdrop-blur) since the water wash spans the full value range in both themes."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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-ns-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, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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/--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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/--ns-muted/--border, with --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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/--ns-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": "crease-fall",
      "type": "registry:ui",
      "title": "Crease Fall",
      "description": "A full-screen navigation overlay that is a sheet of paper folded flat into a concertina: opening releases the creases in order, each panel swinging down on its own weight with a nav link riding on it.",
      "files": [
        {
          "path": "registry/loud/crease-fall/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/crease-fall.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "navigation",
          "menu",
          "overlay",
          "dialog",
          "fold",
          "paper",
          "physics",
          "3d"
        ],
        "instruction": "Build <CreaseFall items? triggerLabel? label? eyebrow? footer? children? className?> as a full-screen navigation overlay whose open and close are a physical fold, not a fade. THE MECHANISM: the overlay is one sheet of paper folded flat into a concertina and pinned under the header bar. Panels are NESTED in the DOM — panel i is a child of panel i-1, absolutely positioned at top:100% of it, transform-origin top, transformed with rotateX inside a single 1400px perspective on the dialog, perspective-origin 50% 0%. That nesting is the whole point: every crease's rotation composes with every crease above it, so the far end of the sheet whips further and lands later than the near end exactly as a fold chain does, and a flat per-item stagger cannot fake it. It also forces the detail that makes or breaks the component: THE FOLDED ANGLE MUST ALTERNATE. Because each angle is parent-relative, a constant folded angle does not zig-zag, it ROLLS — the cumulative angle marches -96, -192, -288 until the deep panels point anywhere and leave the screen, which is exactly what a first pass ships and what makes the mid-fold frame two thirds empty. Local folded angle is therefore -96deg on the first crease and alternating +192 / -192 below it, which pins the WORLD angle of every panel to -96 / +96 / -96: a stack leaning alternately away from and toward the viewer, flat, one panel tall, on screen. Each crease is an independent damped spring integrated at a FIXED 1/240s substep (wall-clock dt changes a near-critical spring's character with the frame rate), released on a stagger: 132ms per crease top-first on the way down, 40ms per crease bottom-first on the way up, because a concertina closes from the far end. That opening stagger is tuned so the sheet is still landing its last two panels about 700ms in — the fold is meant to be caught mid-air, not glimpsed. Weight is a function of chain position: the lower panels carry more sheet, so stiffness falls from 260 to 164 and damping ratio rises only 0.5 to 0.6 across the chain, giving the bottom of the sheet a longer, looser flap and a visible overshoot before it slaps flat. A small angular coupling term feeds a fraction of each crease's rate into the crease it carries. Closing is a different spring, not the same one reversed: k=420, zeta=0.92, so the sheet snaps up crisply rather than lolling back. THE TYPE PARTICIPATES: each nav item is a real DOM <a> printed ON its panel, so it is foreshortened by the same rotateX that is unfolding it — a line of type arrives edge-on and flattens into legibility as its panel lands. Nothing is rasterized; there is no canvas. Do NOT put will-change:transform or backface-visibility on the panels: both promote the panel to a layer rasterized once at rest scale, and the foreshortened type then arrives as a smeared bitmap instead of re-rendered glyphs, which contradicts the entire premise. Paper is printed on one side, so instead of backface-visibility the ink layer is switched off by opacity whenever cos(world angle) goes negative — a panel turned past edge-on shows blank stock, never its own type in mirror. Shading is a function of WORLD orientation, not the angle across the crease (the light is in the room, not in the parent's frame): |sin(world)|^2.4 * 0.94 painted in --foreground, a steep falloff so only paper turned near edge-on is shaded and a landed panel is clean ink on clean stock. The crease hairline in --border fades in with the absolute angle across it, so a crease is visible only while it exists. AT REST the sheet is still there: five --border hairlines stacked with a progressive inset and falling opacity, pinned at the same 5.5rem the overlay unfolds from, so the closed page shows the concertina edge-on rather than hiding the mechanism until something is clicked. ACCESSIBILITY IS THE HARD PART AND IS NOT DECORATION: the trigger is a real button carrying aria-expanded and aria-haspopup=dialog; the overlay is role=dialog aria-modal with an aria-label; the items are real anchors with real hrefs reachable by Tab; focus moves to the first nav LINK on open (not the Close button that precedes it in the DOM), is trapped by a wrapping Tab/Shift-Tab handler bound on both the dialog and the document AND by a document focusin listener that pulls anything landing outside back to the first item — Tab wrapping alone is not a trap, because focus can arrive from outside onto the trigger, which is still in the DOM behind the sheet. Escape closes, and focus is restored to the trigger by the focus effect's cleanup so it is restored on unmount too, not only on a well-behaved close. That effect must depend on BOTH the open flag and the mounted flag: the dialog mounts one render after open flips, so keying it on open alone runs it against a null ref, never re-runs, and ships a menu with no focus move, no trap and no Escape. prefers-reduced-motion skips the solver entirely and settles every crease to its target in one paint, so the menu still opens, still traps focus and is fully usable with no motion at all. The rAF loop only exists while a crease is still moving; it stops itself when every spring is within 0.05deg and 0.4deg/s of target, and visibilitychange lands the sheet rather than resuming from a stale timestamp. PALETTE: --background --foreground --border --ns-muted only, through Tailwind token utilities; --ns-accent appears exclusively under hover and focus-visible on an item, so a resting frame carries no accent at all. Both themes work because the shading is painted in --foreground over a --background panel: in dark the standing panel catches light, in light it falls into shadow, and either way it reads as stock turning."
      }
    },
    {
      "name": "cursor-sixel-reveal",
      "type": "registry:ui",
      "title": "Cursor Sixel Reveal",
      "description": "A full-bleed ASCII plasma field rendered twice from one shared scalar field (quantized to glyphs everywhere, and as a true raw-pixel grayscale raster inside a soft circular window), literalizing the real sixel/kitty-graphics-protocol-vs-character-fallback split. The window sweeps an idle Lissajous path and eases to the pointer on hover.",
      "files": [
        {
          "path": "registry/loud/cursor-sixel-reveal/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/cursor-sixel-reveal.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "background",
          "canvas",
          "cursor",
          "raster",
          "terminal"
        ],
        "instruction": "Build a full-bleed Canvas 2D field running the same three-octave value-noise recipe as background-ascii-plasma, rendered twice from one shared field: everywhere as glyphs through the ' .:-=+*#%@' ramp, and inside a soft-edged circular window as true raw-pixel raster — literalizing the real sixel/kitty-graphics-protocol-vs-character-fallback boundary rather than a cosmetic magnifier. The window follows an idle Lissajous sweep at rest and eases to the pointer on hover, so the boundary is demonstrated continuously without requiring input. Feather the boundary by alpha, never a stroke. The canvas must fill its own backing rect with --background before either pass, every frame, so the glyph-pass alpha-composite and the raster-pass lerp share the exact same base color in both themes — do not rely on the DOM behind the canvas matching the token. Props: cellSize (default 13), windowRadius (default 90), className."
      }
    },
    {
      "name": "cursor-subpixel-fringe",
      "type": "registry:ui",
      "title": "Cursor Subpixel Fringe",
      "description": "A full-bleed field where every grid cell carries three thin vertical luminance slivers standing in for an LCD's physical red/green/blue subpixel stripes. Near the cursor, or the idle sweep target when nothing is pointing, the three slivers of a cell splay apart and diverge in value, as if a renderer were locally re-hinting glyphs to the pixel grid. The whole field shimmers at rest, not only under the pointer.",
      "files": [
        {
          "path": "registry/loud/cursor-subpixel-fringe/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/cursor-subpixel-fringe.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "cursor",
          "background",
          "canvas",
          "noise",
          "grid"
        ],
        "instruction": "Build a full-bleed Canvas 2D field sourced from real LCD subpixel rendering (ClearType and its relatives): an LCD pixel is physically three thin vertical stripes, and subpixel antialiasing exploits that by addressing each stripe independently for roughly 3x the effective horizontal resolution. Translate that literally: every grid cell is drawn as three vertical --foreground slivers standing in for the three stripes. Sample a shared three-octave value-noise field (same summed-sine-octave shape as background-ascii-plasma and cursor-sixel-reveal, own constants scaled ~2.5x for grid-cell rather than pixel sampling, sampled in GRID-CELL units so the field's character doesn't fold or change with container size) at the cell center AND its local horizontal slope (central difference a half-cell either side) — real subpixel AA fringes appear where coverage is CHANGING across a pixel, not uniformly, so each lane's luminance is the cell's base value, re-centered on THIS FRAME's real mean (the slow octave drifts that mean over time as the field's bands sweep through, so a fixed pivot would saturate roughly half the field to solid ink whenever the bands sit off-center), plus lane_index times the local slope times a gain. Flat stretches of the field read as three near-equal slivers and only the drifting field's edges fringe, everywhere, unconditional from mount, not only near the pointer. Near the cursor (or an idle Lissajous sweep target when no pointer is present, eased toward on hover and released back to on pointerleave) the fringe gain increases and the three slivers additionally splay apart in draw position, clamped so their footprint never exceeds the cell's own half-width — three stripes of one pixel, never smearing into the neighbour's — reading as local re-hinting. STATED ADAPTATION: real subpixel rendering produces COLOUR fringes, one stripe reading red, one green, one blue. This registry is strictly monochrome, so the fringe here is a VALUE fringe only — the three slivers differ in luminance, never hue; this is a deliberate substitution, not a fidelity claim. DISTINCT SUB-CELL GEOMETRY: this is the only 1x3 vertical-stripe addressing in the registry — hero-404-quadrant-occlusion addresses a cell as a 2x2 quadrant block, and empty-state-braille-orbit / loader-braille address it as a 2x4 braille dot grid; all three are grids of discrete points, this is parallel strips, a different tradition (display hardware, not typography or printmaking). Cell size is capped by the cellSize prop but scales down proportional to the container's smaller dimension (clamped to a floor), so the stripe grid stays fine at small preview-card size rather than collapsing into a few coarse bars. The canvas fills its own backing rect with --background every frame before any sliver is drawn, so the alpha composite agrees with the token regardless of what sits behind the canvas in the DOM. Canvas is aria-hidden and pointer-events:none; pointer tracking lives on the root element so real DOM children (headline/CTA) keep normal focus order and are never occluded from hit-testing. prefers-reduced-motion freezes on one static frame at the t where the idle Lissajous's x-term (sin(0.065t)) sits at its +1 amplitude extreme (t = (pi/2)/0.065, roughly 24.2s), a genuine characteristic excursion rather than an arbitrary or degenerate moment — no rAF, no pointer listeners bound. Props: cellSize (default 20), influenceRadius (default 130), className."
      }
    },
    {
      "name": "curtain-austrian-gather",
      "type": "registry:ui",
      "title": "Curtain Austrian Gather",
      "description": "A full-bleed route curtain modeled on a real Austrian (brail) stage drape: sewn-in-fullness fabric hoisted bottom-up on parallel vertical lift lines, so it doesn't slide flat but gathers into a row of scalloped swags whose bellies deepen and breathe on independent phase-offset sines, both at rest and as the drape hoists.",
      "files": [
        {
          "path": "registry/loud/curtain-austrian-gather/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/curtain-austrian-gather.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "preloader",
          "curtain",
          "route",
          "svg",
          "physics",
          "loading",
          "reveal"
        ],
        "instruction": "Build <CurtainAustrianGather onLiftComplete? paused? className?> as a full-bleed route curtain modeled on a real Austrian (brail) stage drape — the rigging that hoists BOTTOM-UP on parallel vertical lift lines, distinct from a traveler curtain (curtain-traveler-draw), which parts horizontally on a corded track, and a tab curtain (curtain-tab-diagonal), which peels diagonally toward one wing. Because an Austrian drape carries roughly 100% sewn-in fullness, the fabric does not slide flat as the lift lines rise: it SCALLOPS and BUNCHES upward into a row of swags, one swag per lift-line span, each swag belonging to its own pair of lift lines. CLOSED means the fabric hangs low and full-bleed with only a shallow scallop at each swag's hem; hoisting (via the component's own built-in 'Skip curtain' trigger button, marked data-curtain-trigger) raises every lift line together, and as the panel's height shrinks the swag bellies grow dramatically deeper and the fabric visibly bunches toward the header — the signature scalloped-swag silhouette, not a straight parting edge. A small per-lift-line stagger and a brief rope-give overshoot-and-settle at the end of the hoist (an easeRise timeline whose overshoot resolves back to the exact hoisted height rather than extending duration) read as real rope-and-pulley rigging, not a mechanical slide. SVG, NOT CANVAS: each swag is one closed path — a straight top edge pinned to the header track, two vertical sides riding its bounding lift lines, and a cubic-bezier belly for the scallop curve — filled with a per-swag horizontal gradient (a --foreground-tinted shadow band and a --background-tinted highlight band, both layered as partial opacity over the opaque --ns-muted fabric body, never --border, which is a separator token at ~1.1:1 contrast against --background in light theme and effectively invisible used as fabric ink). Lift-line ropes are thin --foreground strokes from the header track down to each swag boundary's current hem height. PALETTE: fabric body in --ns-muted, swag shadow/highlight gradients shade toward --foreground and --background respectively, blocked/revealed page area in --background — --ns-accent never touches the fabric, reserved only for the built-in skip/dismiss button's interaction chrome. Colors are read once via getComputedStyle(document.documentElement) and re-read on a MutationObserver watching documentElement's class — no hex or rgb() literals anywhere in the draw code. IDLE IS UNCONDITIONAL AND PER-SWAG: even fully lowered, every swag's belly depth breathes independently on its own slow ~4.3s sine, phase-offset from its neighbours by index, and each swag's internal gradient crease breathes on its own separate phase offset — individual scallops visibly deepen and relax, and shading shifts within them, frame to frame; this is not a whole-panel opacity or scale pulse, which is exactly what the no-autoplay verifier grades apart. Because the interesting state (hoisted) is non-resting, a gate descriptor (openBy the trigger button, expect a small dedicated marker rect, centred well below the fully-hoisted belly's maximum reach and well above the resting hem, positioned in paint order beneath the ropes and swags — covered by the fabric while lowered, exposed and hittable at its own centre once every lift line has risen clear of it) triggers it for its own screenshot. prefers-reduced-motion holds a single mid-breath frame at peak swag-belly depth, where scallops read as three-dimensional gathered fabric, and cuts hard (no tween, no rAF) between lowered and hoisted on trigger rather than tweening. role=status/aria-live=polite announces a loading state while lowered; the whole overlay goes aria-hidden once fully hoisted so the revealed route becomes the real focus target, and the trigger button unmounts at that point rather than staying focusable behind an aria-hidden ancestor."
      }
    },
    {
      "name": "curtain-leader-countdown",
      "type": "registry:ui",
      "title": "Curtain Leader Countdown",
      "description": "A full-bleed route curtain modeled on the SMPTE Universal Leader film countdown, compressed to preview-card scale and stated as a departure, not a fidelity claim.",
      "files": [
        {
          "path": "registry/loud/curtain-leader-countdown/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/curtain-leader-countdown.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "preloader",
          "route-curtain",
          "countdown",
          "canvas",
          "transition"
        ],
        "instruction": "Build <CurtainLeaderCountdown onComplete? loop? paused? className?> as a full-bleed route curtain modeled on the SMPTE Universal Leader film countdown — the real broadcast/projection standard, not an invented loading screen. A rotating clock-arm sweeps a circular target once per digit step while numerals tick 8->2 on a canvas-drawn countdown field; C/C/F/F control-frame glyphs flash in the ring's corners only during the '4' step, exactly where the real leader places them. COMPRESSED, NOT LITERAL: the historical leader runs one numeral per real second (~9s total); this component compresses to ~450ms per digit (~3.5s/cycle) for preview-card scale, a deliberate departure from the standard, not a fidelity claim. THE 2-POP HAS NO VISUAL EQUIVALENT IN THE SOURCE: the leader's '2-pop' is an audio cue; here it's translated as a one-frame flash of the target ring to full --foreground plus a brief scale-pulse, an invented stand-in, stated as such. PALETTE: ring and numerals in --foreground, black field in --background, ghost/inactive digit in --ns-muted, control-frame hairlines in --border — no --ns-accent anywhere, including the flash, which goes to full luminance instead. The countdown-and-flash cycle is the unconditional idle loop from mount, no autoplay descriptor needed; any phase must read as complete (numeral + arm angle + ring together) since the no-autoplay verifier can land anywhere in the cycle. prefers-reduced-motion freezes on the '4' step, where control-frame glyphs are visible and the arm sits at a clean quadrant angle — the most structured single frame, not t=0."
      }
    },
    {
      "name": "curtain-tab-diagonal",
      "type": "registry:ui",
      "title": "Curtain Tab Diagonal",
      "description": "A full-bleed route curtain modeled on a real theatrical tab (tableau/opera-drape) curtain: the rigging where a line of rings runs on the diagonal from a panel's center-meeting bottom corner up to a fixed tie-off roughly a third of the way up the wing side, so hauling it draws that corner UP AND OUT along a curved diagonal while the header stays pinned, gathering the fabric into a swag before the whole tied-back panel retracts offscreen; a different axis of motion from curtain-traveler-draw's horizontal parting on a corded track.",
      "files": [
        {
          "path": "registry/loud/curtain-tab-diagonal/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/curtain-tab-diagonal.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "preloader",
          "curtain",
          "route",
          "svg",
          "physics",
          "loading",
          "reveal"
        ],
        "instruction": "Build <CurtainTabDiagonal onOpenComplete? paused? className?> as a full-bleed route curtain modeled on a real theatrical TAB (tableau/opera-drape) curtain, distinct from a traveler curtain's horizontal corded parting. Two overlapping drape panels meet and overlap at center, full-bleed, when closed, each built from PLEATS+1 boundary points across its width with ~10 vertical sewn pleats between them. REAL TAB RIGGING, LOCALIZED TO ONE CORNER: a line of rings runs diagonally from each panel's center-meeting bottom corner up to a fixed tie-off point roughly a third of the way up the panel on its wing side. Hauling the component's own built-in 'Skip curtain' trigger (data-curtain-trigger) draws ONLY that bottom-inner corner up and out toward the tie-off; the bottom-outer (wing-side) corner and the entire top/header edge never move. Every intermediate boundary point interpolates by its own 'reach' (0 at the wing edge, 1 at the center-meeting edge) toward the tie-off, each along its own quadratic-Bezier arc through a control point bowed toward the viewer — never a straight diagonal, and never a uniform squash of the whole hem. The Bezier's own time parameter runs through a hoist-rope overshoot easing, so as the corner nears the tie-off it briefly overshoots past it and settles, the rope-going-taut bounce. Once the corner is fully gathered against its tie-off, the whole bunched swag then retracts fully offscreen via a plain translateX on the panel's SVG group, clearing the page — a lift-then-slide two-phase haul (the slide begins only once the gather is complete), not a pure slide, and a different choreography axis from curtain-traveler-draw's horizontal parting throughout. Pleats compress tighter toward the wing/tie-off end as the gather progresses, matching real fabric bunching toward the pull point. SVG, NOT CANVAS: pleats are shaded path quads (a --foreground-tinted shadow gradient and a --background-tinted highlight gradient per fold, each layered as partial opacity over an opaque --ns-muted envelope path sitting behind them in the same group, so the gradients' transparent bands composite over fabric, never over the page) and each panel moves as one SVG group on a plain translateX — no per-pixel field, so SVG is the cheap substrate. PALETTE: fabric body in --ns-muted, pleat shadow/highlight gradients shade toward --foreground and --background respectively (never --border, a separator token at ~1.1:1 contrast against --background in light theme and effectively invisible as fabric ink), blocked page area in --background — --ns-accent never touches the fabric, reserved only for the built-in skip/dismiss button's interaction chrome. Colors are read once via getComputedStyle(document.documentElement) and re-read on a MutationObserver watching documentElement's class — no hex or rgb() literals anywhere in the draw code. IDLE IS UNCONDITIONAL: even fully closed, every pleat's crease line breathes independently, its gradient stop offsets sliding back and forth across ±8% of the pleat's own width on a slow ~4s sine with a per-pleat phase offset — the closed state is never static, and fold lines genuinely shift position frame to frame rather than a whole-panel opacity or scale pulse, which is what the no-autoplay verifier grades. Because the interesting state (hauled open) is non-resting, a gate descriptor (openBy the trigger button, expect a track-rail element that sits behind the panels in paint order — covered while closed, exposed and hittable once the panels have gathered and retracted clear) triggers it for its own screenshot. prefers-reduced-motion holds a single mid-sway frame at peak fold-depth amplitude, where pleats read as three-dimensional fabric, and cuts hard (no tween, no rAF) between closed and open on trigger rather than tweening. role=status/aria-live=polite announces a loading state while closed; the whole overlay goes aria-hidden once fully open so the revealed route becomes the real focus target, and the trigger button unmounts at that point rather than staying focusable behind an aria-hidden ancestor."
      }
    },
    {
      "name": "curtain-traveler-draw",
      "type": "registry:ui",
      "title": "Curtain Traveler Draw",
      "description": "A full-bleed route curtain modeled on a real theatrical traveler/draw curtain: two overlapping SVG drape panels with sewn-in pleats shaded in value against a muted fabric body, breathing at rest and drawing open toward the wings on their own corded track, with catenary sag and a momentum overshoot on the pull.",
      "files": [
        {
          "path": "registry/loud/curtain-traveler-draw/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/curtain-traveler-draw.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "preloader",
          "curtain",
          "route",
          "svg",
          "physics",
          "loading",
          "reveal"
        ],
        "instruction": "Build <CurtainTravelerDraw onOpenComplete? paused? className?> as a full-bleed route curtain modeled on a real theatrical traveler/draw curtain — two overlapping drape panels on a corded track with a spreader bar and lead line, ~100% sewn-in fullness giving each panel roughly ten vertical pleats at rest. CLOSED means the panels meet and overlap at center, full-bleed; drawing OPEN (via the component's own built-in 'Skip curtain' trigger button, marked data-curtain-trigger) pulls both panels outward toward the wings on the corded track, revealing the page beneath. REAL RIGGING PHYSICS: the leading center-meeting edge shows a slight lag/catenary sag from rope-and-pulley slack during the draw (pinned at the top where it rides the track, sagging progressively toward the bottom, peaking mid-draw and vanishing at rest and at full open), the draw ends with a brief overshoot-and-settle bounce from real curtain momentum (an easeOutBack-style timeline whose overshoot resolves back to the exact open position rather than extending the total duration), and pleats visibly compress tighter toward the wing end of the panel and relax toward the center-meeting edge, an effect that sharpens as the draw progresses. SVG, NOT CANVAS: pleats are shaded path geometry (a --foreground-tinted shadow gradient and a --background-tinted highlight gradient per fold, each layered as partial opacity over the opaque --ns-muted fabric body and each with a hard stop at the crease so the fold reads as a fold, not a smooth wash) and panels move as whole SVG groups on translateX — no per-pixel field, so SVG is the cheaper substrate for this mechanic, unlike curtain-leader-countdown's canvas-drawn vector redraw. PALETTE: fabric body in --ns-muted, pleat shadow/highlight gradients shade toward --foreground and --background respectively (never --border, which is a separator token at ~1.1:1 contrast against --background in light theme and effectively invisible used as fabric ink), blocked page area in --background — --ns-accent never touches the fabric, reserved only for the built-in skip/dismiss button's interaction chrome. Colors are read once via getComputedStyle(document.documentElement) and re-read on a MutationObserver watching documentElement's class, matching the project's canvas/SVG token-reading idiom — no hex or rgb() literals anywhere in the draw code. IDLE IS UNCONDITIONAL: even fully closed, every pleat's crease line breathes independently, its gradient stop offsets sliding back and forth across ±8% of the pleat's own width on a slow ~4s sine with a per-pleat phase offset, like fabric responding to a faint draft — the closed state is never static, and fold lines genuinely shift position frame to frame rather than a whole-panel opacity or scale pulse, which is what the no-autoplay verifier grades. Because the interesting state (drawn open) is non-resting, a gate descriptor (openBy the trigger button, expect a track-rail element that sits behind the panels in paint order — covered while closed, exposed and hittable once the panels have translated clear) triggers it for its own screenshot. prefers-reduced-motion holds a single mid-sway frame at peak fold-depth amplitude, where pleats read as three-dimensional fabric, and cuts hard (no tween, no rAF) between closed and open on trigger rather than tweening. role=status/aria-live=polite announces a loading state while closed; the whole overlay goes aria-hidden once fully open so the revealed route becomes the real focus target, and the trigger button unmounts at that point rather than staying focusable behind an aria-hidden ancestor."
      }
    },
    {
      "name": "dye-whorl",
      "type": "registry:ui",
      "title": "Dye Whorl",
      "description": "A full-bleed tank of ink diffusing in water: a real GPU fluid solver whose coarse velocity field transports a fine dye field, so plumes billow with dense cores, sheets thin as they stretch, tendrils shear off the shoulders and everything dissipates into haze. Drifting injectors, falling drops and interface buoyancy keep it blooming with nobody touching it; the pointer stirs the fluid and presses drop a fresh bead.",
      "files": [
        {
          "path": "registry/loud/dye-whorl/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/dye-whorl.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "webgl",
          "shader",
          "fluid",
          "ink",
          "cursor",
          "decorative"
        ],
        "instruction": "Build <DyeWhorl speed? density? stir? paused? children? className? style?> as a full-bleed WebGL2 fluid tank rendering INK DIFFUSING IN WATER — volumetric plumes and threading filaments, never particles, streaks or a vector-field visualisation. REAL SOLVER, TWO GRIDS: an incompressible Navier-Stokes step runs on a coarse velocity grid cut to the container's aspect (208 cells on the long side) and its velocity field then transports a much finer dye grid (768 on the long side) which is what gets rendered at display resolution. That split is the whole performance story: the expensive physics is 208x130, the expensive fill is one texture fetch plus four gradient taps per screen pixel. Velocity is stored in GRID CELLS PER SECOND, never uv/s — the grid is aspect-cut so a cell is square in screen space, which makes curl, divergence and the pressure Laplacian isotropic with dx = 1; in uv units all three are silently anisotropic and the whorls come out as ellipses that stretch with the window. PASSES per step: (1) semi-Lagrangian velocity advection with exp(-0.16 dt) dissipation; (2) ONE combined body-force pass — vorticity confinement computed inline from twenty velocity taps rather than through a separate curl target, density buoyancy, curl-noise stirring and every splat, because at this grid size the extra fetches cost far less than two more full-screen passes and their FBO binds; (3) divergence; (4) 18 warm-started Jacobi pressure iterations (the pressure texture persists across frames, so the field being solved barely changed and a modest count converges); (5) gradient subtraction; (6) MacCormack dye advection; (7) render. ALIVE AT REST, three mechanisms: five injectors, each ANCHORED in its own region of the frame and orbiting locally (not five phases of one global lissajous — sharing a path shape puts every source near the middle at the same time and leaves whole thirds of the frame empty for minutes, which on a full-bleed field reads as a component that has not loaded), trailing ink behind a jet whose heading rotates on its own period; a one-shot drop lands every 1.5-3.2s at a pseudorandom point with a single frame of 1.35x amplitude and ~300 cells/s of momentum behind it, so it blooms instead of seeping; and a divergence-free curl-noise body force (analytic perpendicular gradient of an fbm potential, so it cannot fight the projection) stirs the whole tank at low frequency. BUOYANCY AGAINST A LOCAL MEAN — the single most load-bearing choice. Ink is denser than the fluid so the excess sinks, but taking the force against zero sediments the entire field into the bottom of the frame within half a minute and leaves a flat layer. Taking it against a wide 4-tap neighbourhood average (6 texels out) makes the force vanish inside a uniform patch and survive only across an interface, which is the Rayleigh-Taylor instability and is what grows fingers off the underside of a plume rather than translating the plume. A small lateral term modulated by noise is the symmetry break: a perfectly horizontal interface under a purely vertical force is a stable equilibrium and never fingers at all. MACCORMACK, CLAMPED: phi_new = advect(phi, +dt) + 0.5*(phi - advect(advect(phi,+dt), -dt)), clamped to the min/max of the four cells the backtrace landed between. The clamp is not optional — unclamped the correction is an antidiffusion term that manufactures new extrema at every sharp interface, so filament edges ring, go negative, advect and speckle the field. Clamped, it buys back most of the detail plain semi-Lagrangian advection throws away, which is the difference between a filament that threads for ten seconds and one that fogs in two. Soft walls (velocity scaled to 0.86 within 4.5% of the edge) stop the tank emptying itself into the margins, and density is clamped at 1.05 — a compositional constraint, not a physical one: a source sitting in slow water otherwise piles up an unbounded pool that renders as a flat hole with no internal structure, the opposite of ink, which is legible precisely because even its dense core stays modulated. Dye dissipates on a ~10s time constant, which is what makes the equilibrium a full frame rather than a few isolated plumes; measured unattended, ink is still distributed across the full height at t=60s. RENDER: coverage = pow(1 - exp(-density*k), gamma), ramped through five token stops; the dye gradient magnitude is added as a deliberately SUBORDINATE rim so a sheet seen edge-on separates from the one behind it — pushed harder it stops being ink and becomes a web of bright filaments, which is a different component. Plus a mild elliptical vignette and one hash of sub-LSB dither, because the ramp's low end is a very long flat gradient across a full-bleed field and that is exactly where 8-bit banding shows. PALETTE: five stops from --background, --foreground, --ns-muted and --border via getComputedStyle, re-read on a documentElement class MutationObserver. Unlike a reflective surface, which spans black-to-white in both themes and only shifts its distribution, ink departs from the page in ONE direction, so this genuinely INVERTS — light ink in dark fluid, dark ink in pale fluid — and the two ramps are written apart rather than derived from one with a bias term. Light is the harder half and took two passes: the first tuning pushed its low end down hard (gamma 1.55) on the theory that thin dark ink over a pale ground reads as dirt. It does not — it reads as a washed-out component, which is worse, because the same field that was dense in dark went nearly invisible over half the frame. The real asymmetry is small: light needs slightly MORE ink per unit density (2.90 versus 2.50) and only a little more toe (1.18 versus 1.08), and its haze stop must leave --background early or the low half of the range does nothing. --ns-accent is interaction-only: the dye texture is RG, R = density and G = a freshness channel nothing but the pointer ever writes, advected by the same velocity field in the same pass so the tint travels with the ink it marks rather than detaching from it, and dissipating ~20x faster than density, and tinted weakly on purpose — at a 0.55 mix it saturated whole plumes into flat blue, which stops reading as tinted ink and starts reading as coloured dye, the exact thing the palette rule exists to prevent, so it is capped at a 0.42 mix and the TARGET COLOUR is pre-adapted per theme on the CPU rather than the coverage being windowed. That distinction cost a pass: fresh ink is the densest ink, which under the light ramp is near-black, and mixing the raw token into near-black is a muddy navy that reads as a dirty patch; windowing the tint away from the dense core removes the mud but also deletes the cue in both themes. Lifting the token 20% toward white for dark and letting it down 50% toward --background for light keeps the same mark legible against either ground. POINTER: a lead-compensated follower (tau 12ms, velocity smoothed over 60ms, lead capped at 26px) — a plain exponential follower has a steady-state error of exactly v*tau, so stirring at the followed position makes the fluid respond LATE, and extrapolating the target one tau ahead cancels that term algebraically. Stir samples are deposited by distance (11px) with a one-frame time ceiling, so injection runs at the DISPLAY's 60Hz rather than at the pointer event rate; each sample pushes the fluid with the pointer's own velocity converted to cells/s and writes the freshness channel. pointerdown drops a full bead at the contact point. WARM-UP: a fluid has no meaningful t=0 — an empty tank is an empty frame — so the sim is spun 150 fixed 1/60s steps before the first paint in every mode, and prefers-reduced-motion (or paused) is simply a longer 260-step spin that is then never stepped again, with pointer input running one further step under the cursor. ADAPTIVE: a four-tier wall-clock ladder (never frame-counted: a frame-counted gate waits longer the slower the machine is) drops after ~1.8s over a 26ms EMA — 1.8s and not 0.9s because the frame time it watches is the PAGE's, and at ~6ms of GPU work per frame the tank is almost never what blew the budget, so a burst of unrelated main-thread work was enough to soften a surface that was in fact running at a flat 60Hz and climbs back only after a clean stretch that doubles on each failure. Jacobi iterations go first, then the MacCormack correction, then dye resolution, then display scale — sim resolution is last because it is what makes the tendrils fine. HOST: an internal Solver class owns the context, programs, ping-pong targets and the fullscreen triangle; one VAO with a_pos bound to location 0 for every program. Requires WebGL2 plus EXT_color_buffer_float (or half_float) for renderable R16F/RG16F; without them nothing renders and the caller's children sit on --background. DPR capped at 2, ResizeObserver is rAF-coalesced (it fires on every step of a window drag) and a resize CARRIES THE FIELD ACROSS: the replacement targets are built before the old ones are released, the live velocity and dye are resampled into them by the advection program with dt = 0, and only 8 settling steps run instead of a cold 260-step seed. Dropping and re-seeding instead measured a 87.8ms worst frame across a 30-step viewport drag, against 17.5ms with the carry, and it also made the ink the viewer was watching vanish and rebuild on every step. The clock is never reset on a resize, which would restart every injector phase. IntersectionObserver and visibilitychange both stop the single rAF loop, webglcontextlost/restored rebuild cleanly. The canvas is aria-hidden decoration with no controls of its own; children render over the tank and need a token scrim, since a plume can pass under any line of type."
      }
    },
    {
      "name": "ebb-flat",
      "type": "registry:ui",
      "title": "Ebb Flat",
      "description": "A pinned full-bleed tidal flat where scroll position is the water level: bars break surface, the channel drains and pools strand, and scrolling back floods it in exactly the order it emptied.",
      "files": [
        {
          "path": "registry/loud/ebb-flat/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ebb-flat.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "scroll",
          "story",
          "sticky",
          "canvas",
          "webgl",
          "shader",
          "water"
        ],
        "instruction": "Build <EbbFlat stages? pinLength? highWater? lowWater? speed? children? className? style?> as a pinned full-bleed WebGL scroll story of a tidal flat in which SCROLL IS THE WATER LEVEL and nothing else. ONE SCALAR, NO HISTORY: the frame is a single fragment shader over a fixed terrain field h(x,z), and every pixel asks the same question of one uniform — above = h - level. Positive is flat, negative is water, and the shoreline is wherever that difference crosses zero. Wetness is exp(-max(above,0)/0.17), i.e. a function of height above the CURRENT waterline, never of how long a patch has been out of the water; the moment that becomes a history the tide stops being reversible. Because flat_() carries no time term at all, the frame at a given level is bit-identical whether the tide fell into it or rose back through it, so scrolling up floods the flat in exactly the reverse order it drained and the claim is provable by diffing two screenshots taken at the same scroll depth from opposite directions. TERRAIN: a gentle seaward slope, four shore-parallel gaussian bars (crests set so each breaks at one stage level, each wobbling along its own length so it is not a ruled line), a meandering main channel widening seaward plus a feeder gully subtracted as gaussians, two octaves of broad relief whose hollows are what strand pools when the level drops past them, ~0.6m ripple marks and sand grain — the last two faded out by smoothstep on the pixel footprint rather than left to alias into moire. PROJECTION: not a plan view; distance up the frame is exponential, z = Z0*exp(y*ZK), so the flat recedes into a horizon at 11.5% of frame height and lateral scale grows with z. Exponential rather than true 1/y because its derivative is linear in z, which keeps the pixel footprint finite at the horizon instead of running away, and that footprint is what makes the far half sampleable at all. RELIEF IS REAL: three terrain taps one pixel apart give a surface normal for a low sun off to the right, and the SAME three taps give the antialias width of the waterline — aa = |hx-h| + |hy-h| + eps. A shoreline is a contour of a 2D field, so on the near-flat pans it would otherwise crawl and stair badly while the level moves; deriving the blend width from the local gradient is what keeps it a clean wet edge at every level. ON THE CLOCK, NOT THE SCROLL (so the resting un-scrolled frame is alive, never a still): swell running shoreward, chop, a glitter path under the sun, foam breaking wherever depth runs out — breakK = smoothstep(0.34, 0.0, depth), which puts the white line on the shoreline at any level AND over every bar the moment it starts to trip the swell — drift in the cloud band, and rills threading off the sand. A smoothed signed rate of level change scales foam and rill intensity, so a tide actually running looks different from slack water without any of it latching. SCROLL PLUMBING: the section is pinLength viewport-heights tall with an inner sticky top-0 h-screen stage, so layout releases the pin at both ends with no fixed-position boundary jump and no scroll ever hijacked or given back. Progress is read from getBoundingClientRect ONCE PER FRAME inside the rAF loop, never in a scroll handler — a handler samples at the browser's scroll cadence, which is bursty under trackpad momentum and on some engines delivered after paint, so the level would tear against the frame it is drawn in. The level then chases that target on a ~90ms exponential, short enough to stay attached to the finger and long enough to absorb a flick or a PageDown jump over about six frames. The loop seeds from wherever the page already is, so a reload part-way down paints the right level on the first frame. COPY: stage blocks are written straight to style (opacity, translate3d) at frame rate so scrolling never schedules a React render, each stage owning a crossfade band derived from its own neighbours rather than one window for the whole run (a single window sized for the close stages near high water leaves dead level with no copy on screen); those blocks are aria-hidden and the accessible copy of the whole sequence is one sr-only ordered list in tide order, always present in document order. A right-hand tide rail scales a fill and lights per-stage ticks as a readout, not a control. PALETTE: five luminance stops derived from --background, --foreground, --ns-muted and --border via getComputedStyle, re-read on a documentElement class MutationObserver. --ns-accent never reaches the canvas — water, wet sand and dry sand are three luminances, not three hues, so the flat is achromatic in both themes and the ramp direction never inverts; only bias and contrast move (light theme is the flat under an overcast noon, dark the same flat at the end of the day). A mild vignette toward the deepest stop gives the copy column somewhere to sit. HOST: an internal GLSurface owns the program, the fullscreen triangle pair and lazily resolved uniform locations; DPR capped at 2 with a three-rung adaptive render scale gated on wall-clock milliseconds rather than frame counts (a frame-counted gate waits longest on exactly the machines that need help soonest); ResizeObserver re-sizes; an IntersectionObserver and visibilitychange both stop the rAF loop entirely, since a pinned stage that has scrolled away is the most expensive idle thing a page can carry; webglcontextlost/restored rebuild cleanly. prefers-reduced-motion renders the ebb as DISCRETE STANDS — the level snaps to the nearest stage, the ambient clock is frozen at a composed t, and only the stage whose level is nearest is shown at full opacity — so the process is still legible and every word stays readable and reachable without any continuous motion."
      }
    },
    {
      "name": "edge-yield",
      "type": "registry:ui",
      "title": "Edge Yield",
      "description": "A full-bleed scanning electron micrograph: a packed micro-landscape of spore grains, rod crystals and draped filaments imaged by a detector simulation rather than a lighting model, so steep facets bloom into glowing rims, crevices fall into hard shadow, and the whole frame is crisp at once under a live scan with shot noise and a descending sweep line.",
      "files": [
        {
          "path": "registry/loud/edge-yield/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/edge-yield.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "webgl",
          "shader",
          "microscopy",
          "monochrome",
          "noise",
          "cursor",
          "decorative"
        ],
        "instruction": "Build <EdgeYield magnification? density? relief? noise? speed? paused? children? className? style?> as a full-bleed WebGL hero that renders a scanning electron micrograph — the world at 10,000x — and is a DETECTOR SIMULATION, not a lighting model. There is no light source anywhere in the shader; that constraint is what makes the image read as an electron micrograph instead of a bumpy heightfield with a lamp on it. One fragment shader evaluates a height field h(u) together with its ANALYTIC gradient — every primitive is a closed form whose slope is known, so the normal costs ONE field evaluation instead of three central-difference taps — then feeds that normal through the two terms that actually make an SEM image. (1) SECONDARY YIELD delta = sec(theta) = pow(1/max(n.z, 0.13), 0.95): yield rises as the surface tilts away from the beam because more of the interaction volume lies within escape depth of a free surface. This is the characteristic glowing rim, and it is not a fresnel stand-in for it — it is the actual 1/n.z dependence, which is why a silhouette edge blooms to near-white while the facet two pixels inside it stays mid-grey. The n.z floor exists so the silhouette saturates instead of going singular in whichever pixel happens to land on it. delta is then scaled by 0.86 + 0.30*matId, the per-grain atomic-number contrast: different grains are different stuff. (2) COLLECTION EFFICIENCY eta = 0.30 + 0.70*facing*shadow + 0.055*shadow, where the detector is a single Everhart-Thornley unit at DET = vec3(-0.593, -0.526, 0.610) — upper-left, ~34 degrees elevation, and the ONLY directional term in the shader. shadow comes from a five-step horizon march toward the detector, stepping t *= 2.20 from u_px*3.0 so the reach clears ~1.7 of the largest grain radius; the reach matters far more than the step count, because a short march degenerates into ambient occlusion on a heightfield, a tight contact halo around every bump, which is exactly the generic read this is trying not to be. The march compares against `coarse` — the specimen WITHOUT its additive debris and micro-relief — so shadows are not biased by detail the march's own lod cannot resolve. The isotropic 0.055 pedestal is electrons scattered off the chamber wall and returning, which is why a real crevice is dark but never black. Signal is then delta*eta*0.42, tilted 13% toward the detector corner (an SEM's broad collection gradient, which it has INSTEAD of a vignette) and compressed sig/(1+sig*0.50) so rims roll off rather than clip flat. THE SPECIMEN is five layers smooth-unioned with smaxG at k = u_px*2.6 — a crease a couple of CSS pixels wide at any magnification and any DPR, so the seam between two grains is a LINE, not a soft valley, and never a one-pixel normal discontinuity that sparkles under sec(theta): a warped fbm substrate, a SPARSE population of much larger bodies at 4.2 whose radius stays well under half a cell so most cells are empty and the big ones arrive at an irregular spacing the eye cannot pre-empt (without that layer every grain is the same size and the field reads as a manufactured lattice — packed roe rather than a specimen), two warped ridge trains of draped filaments at 5.6 and 8.3 cycles, round grain caps at 10.5, and rod/prism grains at 24.0 pushed through a squashed-and-turned matrix (ROD) so the medium layer reads as crystals wedged between the round grains rather than a second smaller copy of the same population. Grains are ellipsoid caps h = A*(1-|d|^2/r^2)^prof on a jittered 3x3 lattice; prof slightly above 0.5 rounds the last pixel of the silhouette without touching the broad slope ramp that carries the rim. Because q = u*S, the S in the height and the S in dq/du cancel and the gradient is scale-free — correct, since a sphere has the same slopes however small it is drawn — and the warp's TRANSPOSE is what the gradient must be multiplied by. Fine debris at 58.0 and two fbm micro-relief octaves are SUMMED on top rather than unioned: a fleck of dust on a grain sits above it, and a union would hide every small thing behind every large one. Everything is in focus at once because nothing here has a lens — the extreme depth of field is a property of the technique, not a choice. THE ACQUISITION is all in screen space and none of it drifts with the stage, because a detector's faults belong to the instrument and not to the sample: a scan comb at 2.7px, per-line DC error (one component reseeded every pass, one slow drift reading as the amplifier wandering), sub-pixel line-to-line misregistration applied to the SAMPLE POSITION so shadows move with their rims instead of sliding under them, charge streaking pulled along the fast horizontal axis and gated on smoothstep(0.35,0.9,sig) since it is bright features that charge, and Poisson shot noise whose amplitude tracks sqrt(signal) and which is 42% correlated with the pixel to its left because the beam has not finished moving. The shot lattice is floor(css px), phase-locked to the display, so the adaptive render scale cannot change the grain size. A sweep line descends the frame once per 1.15s: lines above it were acquired this pass and lines below on the last one, so the stage's drift SHEARS the picture very slightly across that line. The tear is the single detail that proves the image is being scanned rather than rendered; at 4s the same shear would be a visible skew of the whole specimen and read as a broken transform. MOTION WITH NO INPUT: the stage is an integrated position, not a function of time, so a long frame advances it by one clamped step instead of teleporting the specimen — a constant ~0.007 world-units/sec drift whose heading turns slowly plus two incommensurate wobbles, a 0.0075 rad/sec stage rotation, and a 4.5% magnification breathing on a 0.043 rad/sec sine that reads as the working distance creeping. POINTER: the pointer pans the stage (11% of the offset from centre, eased on a 0.16s constant because a stage is heavy) and parks the BEAM, which is a dwell, not a glow sprite — inside a gaussian of 0.155*min(W,H) the signal rises 30% and the shot noise drops to 28%, because more electrons per pixel is more signal and less Poisson. Pan velocity feeds the same tear term the drift does, so a fast sweep of the hand visibly shears the frame at the sweep line. Pointer smoothing extrapolates the target one tau ahead: a plain exponential follower has a steady-state error of exactly velocity*tau, which reads as the instrument not responding rather than as damping; a LEAD_MAX cap stops a teleporting pointer flinging the stage past the cursor. PALETTE: an SEM is natively greyscale because it maps electron yield to luminance and never to colour, so monochrome here is the honest reading rather than a restriction. Five stops derived from --background, --foreground, --ns-muted and --border via getComputedStyle, re-read on a documentElement class MutationObserver. The ramp's DIRECTION never inverts — yield always climbs toward light — but the exposure does: dark theme is the micrograph on the console, substrate low and the rims owning the top of the range (bias +0.035, contrast 1.24); light theme is the same acquisition PRINTED, specimen dark on pale, the top stop being the paper and the substrate lifted only to a pale grey (bias +0.17, contrast 1.26) while c0/c1 stay genuinely dark so the crevices anchor the bottom of the range. Putting the substrate ON the paper value, or clustering the top three stops near it, is the wash-out that kills it: the rims lose their headroom and the frame turns to flat mid-grey mush. NOTHING in the frame is chromatic, including the beam spot — an --ns-accent tint under the pointer is the one mark that reads as a rendered glow sprite rather than as a dwell, and it is the fastest way to lose the electron-image read, so the dwell is spent entirely on signal and shot noise. PERFORMANCE AND HOST: an internal GLSurface class owns the program, the fullscreen triangle pair and lazily-resolved uniform locations and knows nothing about the micrograph. DPR is capped at 2 rather than the 1.5 a cheaper full-bleed shader would take, because shot noise, the scan comb and the rim band are all high-frequency structure and a reduced backing store destroys exactly those first; the cost is paid back by the analytic gradient and bounded by an adaptive render-scale ladder [1, 0.78, 0.58] that steps down only after 900ms of sustained wall-clock overrun and climbs back on a doubling window. Every threshold is milliseconds, never frames — a frame-counted gate waits longer the slower the machine is, which is backwards — and a resize resets the ladder rather than carrying a verdict earned at a different fragment count. ResizeObserver re-sizes; an IntersectionObserver and visibilitychange both stop the single rAF loop; webglcontextlost/restored rebuild cleanly; `paused` is polled rather than made an effect dependency, since the dependency would tear down and recreate the whole GL context to change a boolean. prefers-reduced-motion (and paused) draw exactly one composed still frame at t=9.6 with the sweep parked at 0.63 and a lag velocity still applied — the tear, the rims and the noise are all present, never a blank grey box — and pointer input still redraws that frozen frame directly. The canvas is aria-hidden; children render over the micrograph for an eyebrow, headline and CTA, and because the image spans the full value range in both themes they need a token scrim (bg-background/70 + backdrop-blur) rather than bare type."
      }
    },
    {
      "name": "edm-crater-field",
      "type": "registry:ui",
      "title": "EDM Crater Field",
      "description": "A full-bleed metal plate eroded by electrical discharge machining: sparks continuously pit the surface with small raised-rim craters that fade back to base as later discharges erode the recast layer, holding the plate at a steady simmer of pockmarks rather than ever saturating.",
      "files": [
        {
          "path": "registry/loud/edm-crater-field/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/edm-crater-field.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "webgl",
          "shader",
          "metal",
          "erosion",
          "cursor",
          "decorative"
        ],
        "instruction": "Build <EdmCraterField paused? children? className? style?> as a full-bleed WebGL1 hero whose surface is a small CPU-side signed-height erosion buffer (a Float32Array sized off the container's SMALLER dimension at TEX_TEXELS_PER_MIN_DIM=220 texels across that dimension, so texels are uniform px in both axes regardless of aspect), decayed and stamped on the CPU every real frame, uploaded as a single-channel gl.LUMINANCE texture (0..255 packing signed height -1..1 via v*0.5+0.5), and read back in the fragment shader for a normal-from-heightfield + achromatic environment lighting pass — the same technique family as registry/loud/weld-pool/component.tsx's height field, simplified to a 3-source studio (env()) since a mostly-flat pitted plate needs less environment structure than a mirror pool to read as metal. REAL NUMBERS (from docs/specs/r9/edm-crater-field.md): discharge (birth) rate 14 craters/s uniform-random position; crater radius 0.9%-2.2% of the smaller container dimension per discharge; crater lifetime 5.5s birth-to-fully-faded, modeled as exponential decay of the WHOLE buffer each frame by exp(-dt/DECAY_TAU) with DECAY_TAU = 5.5/3 (~95% faded after three time constants); steady-state resident population target 14/s * 5.5s ~= 77 craters, which falls out of birth-rate=decay-rate rather than being enforced as a cap. STAMP SHAPE: each discharge additively stamps into the buffer — a recessed bowl (h = -DEPTH*(1-t^2), t = d/(1-RIM_FRACTION), for the inner 85% of the radius) plus a raised recast rim across the outer RIM_FRACTION=0.15 of the radius peaking at RIM_HEIGHT_MULT=1.15x the bowl depth via a half-sine profile — never a simple disc, the rim is what makes it read as EDM rather than a generic pit. STEADY STATE IS THE WHOLE POINT: this is the fix for the exact 'process that finishes and stops' Filter-2 failure the spec was written against — birth rate (14/s) and decay rate (buffer-wide exponential, constant regardless of occupancy) are BOTH constant and independent, so the field can never accumulate toward a saturated frame; it turns its population over continuously instead. WARM-UP: an erosion buffer has no honest t=0 (an empty plate reads as unloaded, not as freshly machined), so on every mount, every real resize, and every reduced-motion/paused transition, `warmup()` runs the exact same `simulate()` step function used by the live per-frame loop in fixed 0.2s chunks for WARMUP_SECONDS=10 (~5.5 decay time-constants, comfortably past steady state) BEFORE the first paint — this is the same pattern registry/loud/dye-whorl/component.tsx uses (its 150-260 fixed-step spin), scaled down because this sim is a decay+stamp buffer rather than a Navier-Stokes solver. The live (non-reduced) warm-up seeds from Date.now()-derived entropy (so different mounts show different compositions); the reduced-motion/paused warm-up seeds from a FIXED constant, STATIC_SEED=20260827, through the SAME mulberry32 PRNG and the SAME simulate() path, so the freeze frame is byte-reproducible rather than one-shot noise — named FREEZE_PHASE=steady-state-lock in the spec. POINTER: pointermove sets a target boost (boostTarget=1) that eases toward boostAmt every frame via a per-dt exponential (1 - exp(-dt/(POINTER_BOOST_DECAY_MS/1000)), POINTER_BOOST_DECAY_MS=600) rather than an elapsed-since-last-event timer, so continuous hovering (which fires pointermove far more often than once per rAF) still ramps the boost up instead of re-arming a 'just changed' clock every event; while boostAmt>0.01 an ADDITIONAL local stream of craters spawns at LOCAL_BOOST_RATE=28/s scaled by boostAmt, positioned by a uniform-disc sample (angle * sqrt(radial) so density is uniform, not centre-biased) within POINTER_RADIUS_CSS=150px of the pointer — reading as the electrode dwelling there, 3x the local density of the ambient 14/s field. pointerleave eases boostTarget back to 0 over the same curve. MUST NOT: tint craters or rims with --ns-accent (the exact standing defect registry/loud/edge-yield, granule-churn and shear-billow shipped, called out explicitly in docs/showpiece-recipe.md) — this component never reads --ns-accent at all, the pointer's only effect is WHERE sparks land, never their colour; there is no interactive control on this component, so accent has no legitimate use here either. TOKENS: five luminance stops (c0 darkest .. c4 lightest) derived from --background, --foreground, --ns-muted, --border via getComputedStyle(document.documentElement) at mount, re-read on a MutationObserver watching documentElement's class (redraw only when staticMode, since the live loop already redraws every frame) — NO PAINT BEFORE THE FIRST READ: readColors() runs synchronously before the first resize()/draw() call. Dark theme biases the ramp toward true black/white (bias=-0.08); light theme (checked first, per the recipe's 'light theme is the harder case') compresses toward --ns-muted/--background with a smaller positive bias so pit floors and rim highlights both stay inside the ramp without crossing into pure white/black or --border-adjacent values. RENDER HOST: an internal GLSurface class (mirroring weld-pool's) owns the WebGL1 context, one program, one fullscreen triangle pair, lazily-resolved uniform locations. DPR is capped at 1.5 (matches weld-pool's full-bleed-area-dominates rationale) with a simple two-tier adaptive scale (SCALES=[1,0.7]) that steps down only after ~900ms of a sustained over-budget (24ms) frame-time EMA and climbs back only after a much longer clean stretch that doubles per failure — lighter than weld-pool's three-tier ladder since this shader's per-pixel cost (one texture sample, a handful of exp()/pow() calls) is far cheaper than a warped fbm field. `ResizeObserver` reallocates the erosion buffer (a fresh Float32Array, not a resampled one — a resize is rare relative to the 5.5s crater lifetime) and re-runs warmup(); `IntersectionObserver` (threshold 0) and `visibilitychange` both pause the single rAF loop; `webglcontextlost`/`restored` rebuild the texture and context cleanly. The canvas is `aria-hidden` decoration with `w-full h-full`; children render over the field in a `relative z-[1]` layer and need a token scrim, since the field spans most of the value range and a crater can land under any line of type."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": "film-gate-weave",
      "type": "registry:ui",
      "title": "Film Gate Weave",
      "description": "A full-bleed ambient background modeling projector gate weave and bounce: a rigid, never-displaced aperture mask against an academy-leader test-pattern frame that drifts on two non-resonant sinusoids and snaps on a real critically-damped-ish spring every claw-pull interval, never sitting flush with the fixed edge.",
      "files": [
        {
          "path": "registry/loud/film-gate-weave/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/film-gate-weave.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "film",
          "projector",
          "gate-weave",
          "print",
          "ambient",
          "monochrome"
        ],
        "instruction": "Build a full-bleed ambient background (2D canvas, `w-full h-full`, absolutely positioned behind an optional `children` slot, same wrapper shape as `background-capillary-wick`: root `relative isolate h-full w-full overflow-hidden bg-background`, canvas `pointer-events-none absolute inset-0`, children in a `relative z-[1]` layer) modeling real projector gate weave and bounce.\n\nGate mask: a fixed rectangle inset from the canvas edges by `GATE_INSET = min(width,height) * 0.08`, drawn with `ctx.strokeRect`, `--foreground` at full opacity, 2px line width — this rectangle is computed once per resize and NEVER displaced by weave or bounce; it is the rigid reference the entire mechanic reads against. Deliberately never use `--border` for this stroke — at light theme's ~1.1:1 contrast it would vanish and remove the fixed edge the concept depends on. Also draw a small fixed '+' registration tick (arm length `max(6, minDim*0.018)` px) at the gate rectangle's own true, undisplaced center, same full-opacity `--foreground` stroke, as a second static reference.\n\nFrame content: an academy-leader-style test pattern — one horizontal + one vertical crosshair line spanning past the gate edges, plus three concentric circles (radii `0.32/0.6/0.88` of `min(gateW,gateH)/2`) — centered at the gate's center offset by the current weave+bounce displacement, stroked in `--foreground` at 0.6 opacity, 1.5px, and clipped to the gate rectangle (`ctx.save() -> ctx.rect(gate) -> ctx.clip()`) before drawing so the misalignment against the fixed mask is always directly visible rather than inferred from empty space.\n\nWeave is two independent low-frequency sinusoids computed directly from elapsed seconds `t`: `driftX(t) = 0.018 * gateWidth * sin(2*pi*t/3.1)`, `driftY(t) = 0.011 * gateHeight * sin(2*pi*t/4.7)` — periods 3.1s and 4.7s are deliberately non-round and mutually non-resonant so the combined path never visibly loops.\n\nBounce is a real 1D spring simulation (position `pos`, velocity `vel`), NOT a canned curve: stiffness `k = 5200`, damping `c = 2 * 0.95 * sqrt(k)` (damping ratio 0.95, slightly under 1 on purpose — the small resulting underdamp is what gives the settle its snap/overshoot character rather than a smooth glide). Integrate continuously with semi-implicit Euler at a fixed fine sub-step (~1/480s, sub-stepped inside whatever the caller's real frame dt is, so the integration stays stable regardless of actual frame rate). Every 41.7ms of SIMULATED time (`1/24`, the 24fps claw-pull interval) — tracked via an accumulator, not the render frame rate — add an impulse of `0.004 * gateHeight` to `pos`, with a deterministically alternating sign per tick (a cheap hash of the tick index) so the impulse schedule itself, not just its period, is reproducible. This guarantees local motion every 41.7ms regardless of where the slow drift terms currently sit, satisfying 'alive at rest' even at an instant both sinusoids cross zero. Final vertical offset is `driftY(t) + bounce.pos`; horizontal offset is `driftX(t)` alone (bounce is vertical-only per the sprocket-hole mechanic).\n\nColor is read via `getComputedStyle(document.documentElement).getPropertyValue('--foreground')` at mount, re-derived on a `MutationObserver` watching `documentElement`'s class, with NO paint before that first successful read. `--border` and `--ns-accent` never appear anywhere in the canvas paint. Backing store is DPR-capped at 1.5 (lower than the usual 2 — this is an always-on full-bleed background, not an interaction-triggered surface) and sized off the root's own `getBoundingClientRect`, rebuilt on `ResizeObserver`. Mount and every resize WARM-START the live spring against the new frame height — deterministically replay the same fixed-step spring simulation up to `STATIC_TIME_S` seconds (identical machinery to the reduced-motion frame below) — rather than resetting `pos` to 0: a bare zero spring paints the content exactly flush and perfectly concentric with the gate, which is the one picture this component exists to never show, at mount or after any resize.\n\nReduced motion: rather than freezing at a formula value, deterministically REPLAY the same fixed-step spring simulation from t=0 up to `STATIC_TIME = 2.15` seconds (using the same tick schedule and sub-step, so it reproduces exactly every mount) to get a genuine mid-settle spring position, then draw one frame at `t = 2.15` with drift computed from the same sinusoids — the freeze lands where the combined weave offset is clearly non-zero/off-center and a bounce is mid-settle, so the spring's snap character reads even as a single static frame. The animation loop itself is a plain `requestAnimationFrame`, paused via `visibilitychange` and `IntersectionObserver` when hidden/off-screen, and fully replaced by the reduced-motion single draw under `prefers-reduced-motion: reduce` or an explicit `paused` prop.\n\nOptional interaction: on `pointerenter`/`pointerleave` over the root element, linearly ramp a bounce-amplitude multiplier between 1 and 0 over ~600ms (a 'claw stall') — this multiplier scales ONLY the impulse magnitude fed into the spring on hover, never the slow weave drift, so the background stays visibly alive under hover; it is luminance-neutral, no color or opacity change, and must never introduce `--ns-accent`. No dependencies."
      }
    },
    {
      "name": "flyback-tear",
      "type": "registry:ui",
      "title": "Flyback Tear",
      "description": "A full-bleed dead-broadcast CRT: a live monoscope test signal scanned onto a phosphor face by a timebase that keeps losing its grip, with rolling vertical hold, tear bands, snow and real phosphor persistence.",
      "files": [
        {
          "path": "registry/loud/flyback-tear/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/flyback-tear.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "webgl",
          "shader",
          "crt",
          "glitch",
          "cursor"
        ],
        "instruction": "Build <FlybackTear caption? captionWeight? captionFit? captionY? tear? noise? persistence? curvature? speed? paused? children? className?> as a full-bleed WebGL CRT showing a dead broadcast. THE ORGANISING IDEA: the entire simulation runs in LUMINANCE and colour is applied exactly once, in the final composite, through a five-stop ramp read from the theme tokens. A CRT is normally sold by its phosphor hue, so with hue unavailable every cue is carried by value, structure and time instead — scanline pitch, aperture grille, persistence, bloom, amplifier ringing, the blanking bar, snow. Nothing upstream of the ramp knows what colour anything is. TWO PASSES over a ping-pong pair of RGBA8 framebuffers. PASS A (signal + phosphor) evaluates the picture in SIGNAL space — a rolling, per-line-displaced copy of tube space — but samples the accumulation buffer at the TUBE-space texel, which is the whole trick: the picture slides across a stationary phosphor, so a vertical-hold slip smears the way a real one does rather than translating a sprite. Three channels of persistence, each an exp(-dt/tau) per-frame multiplier so smear length is a property of the phosphor and not of the frame rate: R fast (75ms), G the slow burn behind it (340ms), B the pointer beam (520ms). Combined with max(), not mix() — a phosphor is excited to a level and decays from it. The subtracted floor (0.006/0.004/0.010) is load-bearing, not cosmetic: the buffer is 8-bit and pure multiplicative decay stalls at whatever value rounds to itself, leaving a permanent ghost of everything bright that ever crossed the tube. THE SOURCE is an analytic monoscope, and every element of it moves, because a still picture under the failures would read as a filter over a JPEG rather than as a transmission: a square graticule with every fifth line heavier and widths derived from the viewport so it holds one pixel of weight at any size, convergence rings drifting outward, twenty-four radial wedges turning slowly (angular width converted back through the radius or they fatten into a solid disc at the centre), a centre reticle, two resolution chirps whose frequency ramps past the raster's own pitch so the top of the sweep aliases against the scanline comb and crawls, a nine-step greyscale staircase, a low-frequency fbm swim standing in for an adjacent carrier, and the caption rasterized on a 2D canvas from the wrapper's own computed font-family and max()ed into the signal so it rolls and tears with everything else (re-rasterized after document.fonts.ready, since a texture baked pre-webfont freezes fallback letterforms permanently). THE FAILURES are event-driven on the CPU, not noise-shaped, so their timing is legible and the shader only ever sees a current amplitude. Vertical hold: an impulse into a velocity that decays over 900ms and then RE-LOCKS to the top of the frame — the re-lock is the beat, since a picture that drifts and stays put reads as a scrolling background while a picture that snaps back reads as a set fighting to hold sync. Tear: contiguous nine-line bands thrown sideways, the trigger quantized to 18Hz so they read as discrete failures instead of a shimmer, plus per-line timebase jitter and a smooth supply wobble; a displaced line wraps with fract() rather than clipping. Dropout: kept under a quarter second and first scheduled at t=11s, because a collapsed frame is the one state of this component that reads as broken rather than as an effect and must never be what a card or a screenshot catches. CHROMATIC ABERRATION WITHOUT CHROMA: where a colour build would split RGB, this weights three horizontal taps of the one monochrome signal — 1.18 at the sample, -0.30 at -d, +0.14 at +2.4d, and a fourth at +0.031 for a multipath ghost — which is amplifier overshoot and echo, the same fringing read, entirely inside the palette constraint. Also a vertical blanking bar with a bright edge line and noise in the interval, head-switching noise on the last lines, snow sampled per source line at half horizontal resolution and smeared into its neighbour (per-pixel white noise reads as digital sensor grain, not analogue), and interlace applied in TUBE space so the unwritten line is last frame's decaying one and the flicker reads as an interlaced source rather than a strobe. PASS B (glass) barrel-distorts with the picture overscanned by exactly the amount the distortion pulls in, so a full-bleed frame never shows an empty corner; adds a twelve-tap golden-angle bloom spiral whose radius rides the sample index so one loop covers both tight halation and the wide veil; multiplies in the scanline comb at 240 lines and a 3-css-px aperture grille phase-locked to the display rather than the picture so it survives the adaptive render scale unmoved; drifts a mains hum bar up the face; then maps the single remaining float through the ramp. PALETTE: five stops from --background, --foreground, --ns-muted and --border via getComputedStyle, re-read on a documentElement class MutationObserver. The ramp's DIRECTION carries the theme. Dark is a tube — beam energy climbing from an unlit face toward white-hot. Light is the same signal as a photographic negative of itself — energy climbing into ink on paper, so bloom becomes a smudge spreading out of the stroke instead of a halo, which is what that polarity ought to do. POINTER: a beam overdrive written as the distance to the SEGMENT the pointer swept this frame, not to a point, so the stroke is continuous at any speed with no ring buffer and no beading and the deposit rate is the display's rather than the pointer's; the follower is lead-compensated (target extrapolated one tau ahead) because a plain exponential follower has a steady-state error of exactly v*tau and the stroke would inherit it as lag. --ns-accent tints only where that beam channel is non-zero, and it decays to exactly zero, so a resting frame carries no accent. HOST: DPR is capped at 2 rather than 1.5 — scanline pitch and grille are exactly the high-frequency structure a reduced backing store destroys first — with a three-step adaptive ladder that engages only on sustained wall-clock overrun. Every path that invalidates the accumulation (mount, resize, a render-scale step, context restore) runs 22 warm-up accumulate frames before compositing, or the tube shows a thin cold picture that fills in a beat later; webglcontextlost/restored rebuilds both framebuffers, both textures and the ping-pong index, not just the programs. prefers-reduced-motion and paused compose one still at t=7.3 with the hold mid-slip and a tear burst live, warmed up so the phosphor carries a real smear, and a pointer over the frozen tube still writes and composites one stroke by hand. The caption's accessible copy is an sr-only <h1>; children render over the glass and need a token scrim (bg-background/70 + backdrop-blur) because the tube spans the full value range in both themes."
      }
    },
    {
      "name": "foam-drain-coarsen",
      "type": "registry:ui",
      "title": "Foam Drain Coarsen",
      "description": "A full-bleed weighted-Voronoi (Laguerre) foam mesh that coarsens under von Neumann's law (cells with more than six sides grow, fewer shrink and vanish) while its Plateau-border stroke width drains from hairline at the top to thick at the base, slowly re-wetting from below on a repeating 40s cycle.",
      "files": [
        {
          "path": "registry/loud/foam-drain-coarsen/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/foam-drain-coarsen.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "canvas",
          "foam",
          "voronoi",
          "power-diagram",
          "von-neumann",
          "physics",
          "ambient",
          "generative"
        ],
        "instruction": "A full-bleed 2D canvas rendering a weighted Voronoi (Laguerre/power) diagram of up to MAX_CELLS=90 sites, sized from the container's smaller dimension: targetCellSize = clamp(minDim/9, 40, 140), sites scattered on a jittered grid at that spacing with small initial weight jitter so growth is visible from t0. Each site i's cell is the exact polygon formed by clipping the canvas rectangle against every other alive site j's power-bisector half-plane (2*(jx-ix)*x + 2*(jy-iy)*y <= (jx^2+jy^2-wj) - (ix^2+iy^2-wi)) via Sutherland-Hodgman clipping, every rAF frame. On a throttled ~7Hz tick (CLASSIFY_INTERVAL_MS=150), each cell's true neighbour count n is measured directly from its own polygon: for every edge's midpoint, the site whose power distance there ties the cell's own power distance is that edge's real neighbour, and n is the number of distinct neighbours found this way (this is a real weighted-Voronoi tessellation query, not an authored adjacency graph). Von Neumann's law then updates that site's weight: w += KAPPA*(n-6)*dtSec, KAPPA=0.35 area-units/s per side-deficit, so cells with more than six real neighbours grow and cells with fewer shrink toward zero. Every frame, any alive cell whose measured polygon area drops under DEATH_AREA=6px^2 (and is past its 500ms birth grace period) is retired and immediately replaced by a fresh weight-0 site scattered elsewhere (70% biased into the lower half) — a genuine T2 vanish-and-renew event that is what keeps the field unbounded forever rather than coarsening to one cell and stopping; T1-style edge rewiring is never special-cased, it falls out for free because neighbour topology is re-measured from the live tessellation every classify tick rather than tracked as a fixed graph. Every rendered edge's stroke width is lerp(0.5, 4, clamp(edgeMidpointY/height,0,1)) plus an additive wetness bias (WETNESS_BOOST=1.5) scaled by (1-normY) that ramps 0->1 over the first 6000ms of a 40000ms cycle then decays back down over the remaining 34000ms (Math.exp(-3.2*u)) — a slow re-wet-then-drain sawtooth. For performance, every edge each frame is bucketed by its final stroke width into one of 8 Path2D objects and each bucket is stroked exactly once (ctx.strokeStyle = the --foreground token read via getComputedStyle at mount and re-read on a documentElement class MutationObserver), so the whole mesh costs a fixed ~8 canvas stroke() calls regardless of cell count. A pointer-proximity pass builds one extra small Path2D from only the edges within POINTER_RADIUS=140px of the cursor and strokes it once more at 30% extra alpha with the same --foreground colour — luminance/alpha only, never var(--ns-accent) and never triggering extra growth or death, so the resting loop stays fully unforced regardless of pointer presence. Canvas is DPR-aware (capped at 2), resized via ResizeObserver on the root element (150ms debounce), paused via IntersectionObserver (threshold 0) when scrolled offscreen and on document visibilitychange. Under prefers-reduced-motion the component seeds a fresh field and synchronously runs 40 classify+growth ticks (150ms of simulated time each, 6000ms total) before ever painting, freezing on that single measured, deterministic frame — t=6s of the 40s cycle, matching the live loop's own most-structured moment (clear wetness gradient established, at least one small cell caught near vanishing) rather than a hand-authored static layout. Zero colour literals: only var(--foreground) is ever read, and only through getComputedStyle for the canvas strokeStyle string. No React state changes during the animation; all per-frame numeric work lives in plain closures inside one rAF loop."
      }
    },
    {
      "name": "foil-block",
      "type": "registry:ui",
      "title": "Foil Block",
      "description": "A full-width closing CTA band built on hot foil blocking — a die strikes the headline's terminal word and a rule frame around the primary button, and only transfers foil where temperature AND pressure both clear a hard threshold at once. The spent web indexes forward after every strike, carrying a fading negative record of the last nine, so the band keeps moving with zero input.",
      "files": [
        {
          "path": "registry/loud/foil-block/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/foil-block.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "cta",
          "closing-cta",
          "canvas",
          "print",
          "foil",
          "letterpress",
          "threshold",
          "button"
        ],
        "instruction": "Build <FoilBlock eyebrow? headline? primaryLabel? primaryHref? secondaryLabel? secondaryHref? className?> as a full-width closing CTA band (eyebrow, headline, primary button, secondary link) whose visual identity is hot foil blocking (hot stamping): a heated die presses a metallised web onto the headline's LAST word and a thin rule frame offset 8px outside the primary button (never the button's own fill, which stays plain --ns-accent DOM chrome). Rasterize each die's own outline into a small offscreen 2D canvas sized to the DOM element's bounding rect times a capped dpr (word: fillText of the last word at the wrapper's real computed font; frame: strokeRect of a FRAME_STROKE_PX=3 rule inset 8px outside the button rect), read out the alpha channel as the 'sharp' mask, and box-blur a copy of it at radius 6px*dpr to build an edge-distance proxy used ONLY for the die's own 9°C cold-edge falloff (temp = 118 - 9*edgeFrac, inside the die outline only). Contact pressure is a continuous field of 3 gaussian lobes (sigma 0.16 in the region's own 0..1 local coords) summing sin(2*pi*0.014*tAbs + phase) terms of amplitude 0.11 about a mean of 0.42, evaluated at the die's own local (u,v) and an ABSOLUTE simulation-time argument so it never resets between strikes -- consecutive strikes fail in different places. A pixel receives foil iff temp>=96 AND pressure>=0.34: hard threshold, no soft ramp, which is what produces the ragged cold-edge void pattern (rendered as cDim blended 0.5 toward --background) instead of a clean fill. This mask is evaluated exactly ONCE per strike, at dwell end (720ms into the 4.60s cycle: 420ms approach ease-out-cubic die descent of max(10, 0.037*min(w,h))px + 300ms dwell), and held through 280ms peel (last 90ms: 3-7 hairline <=2px tail filaments stretching off the trailing edge as the carrier still clings) + 1550ms web index (spent web scrolls 71px at 46px/s) + 2050ms idle (web still, specular band still sweeping) -- it does NOT re-evaluate per frame. NOTE ON THE SPEC'S PHASE TABLE: the source spec's per-phase durations (420+300+280+1550+1960ms) sum to 4510ms against its own stated 4.60s cycle total; the 1960ms idle figure was the value that gave way, corrected here to 2050ms (CYCLE_MS - approach - dwell - peel - index) so the four other named numbers and the 4.60s identity both hold exactly. On every completed index phase, snapshot the word's sharp+mask arrays into a ghost history capped at 9 entries, each rendered in a dedicated 30px-tall strip along the band's bottom edge (behind a hairline --border rule), translating left with the live index progress and decaying alpha linearly from 1.0 (newest) to 0.18 (oldest, 9th) across the 639px visible run (9*71px) -- this ghost train, not the specular sweep, is the primary resting-loop signal, and is pre-seeded at mount (synthetic strikes at negative virtual times against the SAME continuous pressure field) so the band never looks freshly booted. Independently and unconditionally, a specular band (responsive width max(16, 0.064*min(w,h))px, nominally ~24px, at a 31° diagonal) sweeps across every currently-transferred pixel at 0.19Hz forever, applying a LUMINANCE-ONLY delta of local_foil_L +/- 0.16 (never a hue, never --ns-accent) -- this is what keeps the mature/already-struck region from ever being a static image sliding under a moving band, on top of every strike's own different void pattern. Composite each die region and the ghost strip with a single ImageData buffer + putImageData call per frame (not per-pixel fillRect), and rebuild the die rasters whenever the adaptive backing-store dpr changes so the buffer and the canvas device pixels never drift apart. Colors: cLive (transferred foil) is L 0.94 toward white in dark theme / L 0.78 toward black in light theme, both mixed from --background -- 'bright metal on dark ground' vs 'dark burnish on pale stock', not a compromise. cDim (void/cold-edge) is --ns-muted mixed 0.55 toward --background then blended another 0.5 toward --background at render. --border is used only as a 1px hairline (die registration bracket corners, ghost-strip rule). --ns-accent never appears in canvas code at all -- it is DOM-only, on the real <button>/<a>'s bg-ns-accent fill and focus-visible:outline-ns-accent ring. All five tokens are read via getComputedStyle(document.documentElement) before the first paint and re-read on a MutationObserver watching documentElement's class. Canvas is aria-hidden; the headline (h2) is always a real, fully readable DOM heading -- its last word's DOM span is set to color:transparent ONLY once a live 2D context is confirmed, so a context-creation failure leaves plain visible text instead of a gap. Activation (click or Enter/Space on the button, identically) restarts the strike cycle from t=0 as one out-of-cadence beat of feedback; navigation is never gated on the animation. prefers-reduced-motion renders one composed still frame at STATIC_TIME=1.14s with the mask force-struck, tail filaments at maximum extension (progress=1), the specular band centred on each region's own local middle (phase=0.5), and all 9 ghost slots populated -- deterministic and byte-stable since every input is a fixed time and fixed analytic formula, no Math.random anywhere. DPR is capped at 1.5 (full-bleed band) with a 3-step adaptive scale ladder (1, 0.75, 0.55) that only steps down after ~900ms of sustained >24ms frames and only climbs back after a doubling recovery window, never on frame count or device sniffing. ResizeObserver re-rasterizes both die outlines and re-syncs geometry; IntersectionObserver and visibilitychange both stop the single rAF loop."
      }
    },
    {
      "name": "forage-vein",
      "type": "registry:ui",
      "title": "Forage Vein",
      "description": "A hero background where a Physarum-style plasmodium forages the page's own content boxes: trail-following agents flood out from the headline, CTA and logo row, fat veins form where deposition keeps outpacing decay, and starved or moved food sources visibly retract and regrow with no scripted path underneath.",
      "files": [
        {
          "path": "registry/loud/forage-vein/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/forage-vein.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "canvas",
          "physarum",
          "simulation",
          "agents",
          "organic",
          "network"
        ],
        "instruction": "Build <ForageVein disabled? agentCount? children? className? style?> as a full-bleed Canvas 2D hero background running a real Jones-2010 physarum transport simulation, not a precomputed graph. LATTICE: a trail field sampled at 1/4 CSS-pixel resolution (one lattice cell per 4px), stepped on a fixed 30Hz accumulator independent of display refresh rate. AGENTS: ~4000 (agentCount, clamped 500..8000) live as parallel Float32Arrays (x, y, heading) with zero per-frame allocation. Each step every agent samples three sensors — front, front-left, front-right, 45deg apart, 9 lattice cells ahead, nearest-cell lookup, the two flanking sensors derived from one cos/sin of the heading via the angle-sum identity rather than four extra transcendental calls — turns 35deg toward whichever reads strongest (ties broken randomly, plus a small always-on jitter so paths aren't razor-straight), advances exactly one lattice cell, and deposits a fixed amount at its new cell. After every agent has moved, the WHOLE lattice gets one 3x3 box-blur pass (separable horizontal-then-vertical) blended 42% into itself and is then multiplied by decay constant mu = 0.90/step: a fat vein on screen is nothing but the set of cells where deposition is currently outrunning that decay, never a stored path. FOOD NODES: any descendant of children carrying a data-vein-node attribute has its live getBoundingClientRect() center read as a lattice coordinate; when none is present three non-collinear fallback anchors keep the layer from sitting empty and still able to show a route beating a redundant one (three points on a line never can). Each node holds a reservoir (0..1) that trickles up every step on its own and drains per agent-visit, capped at a fixed max drain per step regardless of swarm size — without that cap a crowd converging on one node empties a full reservoir in under half a second instead of the multi-second exhaustion the brief calls for. A just-respawned agent can't count as a visit for its first ~12 steps (it would otherwise instantly drain the node it was seeded at), and when every node is exhausted the rolling respawn stream (16% of the pool re-seeded per second at whichever node currently holds charge) skips reseeding entirely rather than dumping agents on a dead node, which is what lets REGEN_RATE actually claw a reservoir back up instead of being re-drained the moment it ticks above the exhausted threshold. While a node's reservoir is above that threshold it emits an attractant deposit, scaled by its own reservoir fraction, at its own cell right after the diffuse+decay pass — that emission is the entire mechanism agents steer toward via their normal sensor comparison, there is no separate force-toward-food term. A node exhausted by sustained visits simply stops emitting; nothing deletes the trunks that were feeding it, they lose to decay like any other cell over the next few seconds because traffic stopped choosing them, which is the falsifiable retraction the brief asks for. Node rects are re-measured every 15 sim steps (about once every 0.5s) folded straight into the step function during the live rAF loop — a node moving, resizing or being replaced is not a special code path, the sim just keeps foraging toward wherever the DOM says the food is now, and a full retract-then-regrow cycle plays out over roughly ten seconds at these constants because that's how long sustained drain takes to outpace passive regen and how long it takes the agent population to redistribute across a hero-sized viewport, not because any timer says so. RENDER: the lattice is rasterized once per stepped frame straight onto a small offscreen canvas sized cols x rows through a 3-stop ramp built from --background, --ns-muted and --foreground (each parsed from getComputedStyle(document.documentElement) at mount and re-parsed on a class MutationObserver), trail values gamma-compressed (^0.82) after normalizing against a render range set well below the hard accumulation ceiling — normalizing display against the same constant that exists purely to stop a swarm blowing the field out buried every real vein in the bottom fifth of the ramp, so the two are deliberately separate constants; that offscreen canvas is then drawImage-scaled onto the full-resolution, dpr-capped(2) visible canvas, which is where the actual bilinear sampling comes from — the browser's own image scaling, never a per-pixel sampling loop. A11Y: the canvas is aria-hidden and never receives a pointer or focus listener of any kind, matching the brief's 'no pointer or focus capture ever'; all real headline/CTA/logo content stays in the normal DOM and tab order, sitting above a flat --background/55 scrim wash between the canvas and the content layer (no gradient) so a bright trunk cell landing directly under a line of text still resolves to the theme's normal foreground/background contrast rather than washing the text out. prefers-reduced-motion runs ~500 fixed-30Hz-timestep steps synchronously right after the first DOM measurement (and again after a debounced resize, both with the periodic DOM re-poll disabled for the run since layout cannot change mid-synchronous-loop) and paints that one converged frame; no rAF loop runs under reduced motion at all. The step count is deliberately short of the brief's illustrative 600 — verified against a headless port of the exact step function, 500 steps of this lattice and agent count run in ~215ms of pure JS versus 600's ~500ms, and blocking the main thread for half a second at mount is worse for a reduced-motion visitor than one fewer converged spread of steps. disabled skips mounting the canvas entirely, leaving children on the plain scrim. The rAF loop pauses on document.hidden and resumes on visibilitychange."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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": "granule-churn",
      "type": "registry:ui",
      "title": "Granule Churn",
      "description": "A full-bleed stellar photosphere: bright convection cells packed edge to edge, born, swelling against their neighbours, splitting and dying, with the cooled material draining back down the dark intergranular lanes between them and bright flecks flaring in the strongest downdrafts.",
      "files": [
        {
          "path": "registry/loud/granule-churn/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/granule-churn.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "webgl",
          "shader",
          "convection",
          "cursor",
          "decorative"
        ],
        "instruction": "Build <GranuleChurn density? speed? faculae? paused? children? className? style?> as a full-bleed WebGL2 background rendering SOLAR GRANULATION — a stellar photosphere seen from above, boiling continuously with no input at all. The mechanism is a compressible surface flow, not a cell diagram: a potential field is built from a lattice of gaussian plumes, each born at a jittered position with its own period, swelling over its lifetime and fading; horizontal velocity is the negative gradient of that field, so material runs downhill off every summit and piles up where three or four plumes' skirts meet. Nothing computes a nearest-site distance — the dark intergranular lanes are not the edges of a partition, they are where an advected density has genuinely been squeezed together, so they vary in width, break into segments and lag behind the plumes that pushed them. Four channels ride one RGBA16F ping-pong target through a single semi-Lagrangian pass: temperature injected where the flow diverges and cooling radiatively as it travels out (so a granule is bright at the middle and dim by the time it reaches its own boundary), material density multiplied by the continuity term (which is what actually darkens a lane), faculae seeded stochastically in the strongest downdrafts and carried along the lane that made them, and pointer freshness. Birth and death are structural: when a plume's envelope reaches zero its successor is born at a new jittered position, neighbours immediately expand into the space and the lane network re-knits, and about a third of sites develop an exploding-granule dimple late in life that drives a lane through the middle of the cell and cleaves it in two. The pointer is an extra upwelling rather than a decal — it pushes the lanes apart and floats fresh hot material up under the cursor, on a lead-compensated follower so the response is not late, and --ns-accent tints only that freshly disturbed material. Colours come from --background, --foreground, --ns-muted, --border and --ns-accent via getComputedStyle, re-read on a documentElement class MutationObserver; the ramp inverts between themes rather than shifting its distribution, so dark theme is a luminous star and light theme its deliberate negative, graphite granules on a paper field. The sim is spun up for twenty seconds of convection before the first paint in every mode, so the surface is already boiling at mount; prefers-reduced-motion gets a longer spin-up and then a frozen frame. Pauses offscreen and on tab hidden, and steps down display then sim resolution only after a sustained stretch of missed frames."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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(--ns-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-404-quadrant-occlusion",
      "type": "registry:ui",
      "title": "Hero 404 Quadrant Occlusion",
      "description": "A full-bleed 404 hero where the numerals and an orbiting occluding disc are rendered from one shared silhouette at half-cell precision using Unicode quadrant/half-block glyphs (▘▝▖▗▚▞▛▜▙▟█▀▄▌▐), so the disc's curved edge stays crisp instead of stair-stepping at one glyph per cell.",
      "files": [
        {
          "path": "registry/loud/hero-404-quadrant-occlusion/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-404-quadrant-occlusion.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "404",
          "hero",
          "not-found",
          "canvas",
          "cursor",
          "occlusion",
          "typography"
        ],
        "instruction": "Build a full-bleed Canvas 2D 404 hero whose entire silhouette — both the '404' numeral and an orbiting occluding disc — is rendered through Unicode quadrant/half-block glyphs (▘▝▖▗▚▞▛▜▙▟█▀▄▌▐), each glyph cell addressing a 2x2 sub-grid (TL/TR/BL/BR) instead of the single full-cell density value every other ASCII component in this registry quantizes to. The '404' numeral is rasterized ONCE, after document.fonts.ready, into an offscreen alpha mask (bold weight, font-size derived from min(width,height) with real margin, per the registry's scale rule) and area-averaged (not point-sampled — averaging over each sub-cell's rectangle, threshold ~0.42, is what keeps thin numeral strokes from breaking up at small preview-card sizes) into a static per-sub-cell coverage table that is built once and reused every frame, since the numeral itself never moves. The occluder is a disc with a low-order radial harmonic on its edge — r(theta) = R * (1 + 0.22*sin(3*theta + 0.6t)) — tested analytically (a distance compare, no raster) at the same 4 sub-positions every frame. Per cell, per frame, TWO draws rather than one: numOnly = numeralBits & ~occluderBits is drawn in --ns-muted (the numeral, minus whatever the disc currently covers) and occluderBits is drawn in --foreground on top — so the boundary between them is a genuine sub-cell shape step wherever the disc crosses ink, not a full-cell color swap, and the disc still reads by shape alone over empty background where numeralBits is 0. The occluder orbits the numeral center continuously and unconditionally on an incommensurate Lissajous (never damping to a stop); the pointer BIASES that orbit with an eased offset rather than replacing it, so parking the pointer mid-canvas cannot freeze the demonstration. Cell metrics come from measureText('█'), not a letter — block glyphs tile by their own ink box, which can differ from a letter's advance in the loaded font — with textAlign='left'/textBaseline='alphabetic' draws at (col*cellW, row*cellH + ascent) so cells tile edge-to-edge by construction. Tokens (--foreground, --ns-muted) are read via getComputedStyle(document.documentElement) synchronously before any observer or rAF is armed, and re-read on a MutationObserver watching documentElement's class, so no path can ever paint a placeholder value. prefers-reduced-motion freezes the occluder parked off-center over the numeral — the phase that maximizes the count of cells whose 4-bit code is neither 0 (empty) nor 15 (solid), i.e. genuinely partial half-cell cells, rather than a position fully clear of or fully inside the numeral. Loop pauses on IntersectionObserver (offscreen) and document.hidden. Optional children render as real DOM below the field. Props: cellSize (grid cell px, default 16), occluderRadius (px, default derives proportionally from the container's smaller dimension), children, className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 --ns-muted and --ns-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.",
        "rank": 9
      }
    },
    {
      "name": "hero-ascii-lichtenberg",
      "type": "registry:ui",
      "title": "Hero ASCII Lichtenberg",
      "description": "A full-bleed ASCII hero of dielectric breakdown: a Lichtenberg figure grows site by site up the Laplace potential field, always branching to where the local field is strongest, holds its finished form, then fades as the next strike begins from a new seed.",
      "files": [
        {
          "path": "registry/loud/hero-ascii-lichtenberg/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-ascii-lichtenberg.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "ascii",
          "canvas",
          "cursor",
          "lichtenberg",
          "breakdown",
          "branching"
        ],
        "instruction": "Build <StrikeFigure cellSize? eta? quiet? children? className?> as a full-bleed Canvas 2D hero running the Dielectric Breakdown Model (Niemeyer-Pietronero-Wiesmann) directly on the glyph grid. STATE: a Float32Array phi (potential) over cols*rows shared by the simulation, plus per-strike Uint8Array occupied, Int32Array parent, Uint16Array depth, Uint8Array kids/glyph and an Int32Array order recording growth sequence. BOUNDARY CONDITIONS: an occupied cell is a perfect conductor pinned to phi = 0; the left and right plate columns are Dirichlet phi = 1; the top and bottom rows are insulating (Neumann, implemented as a ghost row mirroring its neighbour) — a Neumann top/bottom is what makes the figure spread ACROSS the frame instead of sprinting for whichever edge is nearest, which an all-round Dirichlet box does. FIELD SOLVE: each animated frame runs 12 Gauss-Seidel sweeps of the 5-point Laplace stencil phi[i] = 0.25*(up+down+left+right), skipping occupied cells, restricted to the aggregate's bounding box plus a 14-cell margin — the far field is already flat at 1, and the solution only changes locally as the tree grows, so 12 local sweeps per frame is enough and the cost never scales with the whole canvas. GROWTH: a candidate list holds every unoccupied 4-neighbour of the aggregate, minus any cell within PLATE_CLEARANCE = 10 columns of a plate — the relaxation never writes columns 0 and cols-1, so a cell at x = 1 permanently has a phi = 1 source as a neighbour and the channel, once it touched a plate, ran dead-straight down it and spent ~11% of its sites on a 1-cell rule glued to the frame edge (measured at both plates); holding growth 10 columns clear drops that to ~4% and costs no width. The clearance is x-only and clamped to min(10, cols/6): the top and bottom rows ARE written, by the Neumann mirror, so they are not sources and a y clearance would give up vertical span for nothing; each growth tick draws one candidate with probability proportional to phi_i^eta, eta = 3.0 by default, selected by a single linear pass accumulating a running cumulative sum against a mulberry32 draw (never Math.random, so a session replays identically). eta is the whole character of the figure: eta 1 gives a fat DLA blob, eta 3 makes the tip with the steepest local field win almost every draw, so the aggregate collapses into a few long sharply forked channels covering roughly 3% of the grid — the sparsity is structural, not a threshold applied afterwards, and every other cell in the frame draws nothing. Each newly occupied cell records its parent link and a branch depth of parent depth + 1. QUIET REGION: the optional `quiet` prop is a rectangle of the dielectric held at lower field strength, given in normalized frame coordinates {x, y, w, h}; it is undefined by default, so the bare component is exactly the undisturbed figure. On rebuild it bakes a Float32Array scale over the grid — 0 inside the rect, smoothstepped back to 1 across a falloff band of 0.05 of the frame width floored at 2 cells (not a prop: a hard edge would read as the panel this exists to avoid, and there is no reason to let a caller ask for one) — and that scale MULTIPLIES the phi^eta growth weight in the candidate draw. A region of lower field strength simply grows fewer channels, so the tree thins out and stops on its own; nothing is masked, overlaid or blurred after the fact, and because the falloff is continuous there is no contour at the rect edge (measured at 1440x900, grid 200x75, 640 sites: ink coverage inside the rect drops 6.10% -> 0.00% and 9.07% -> 0.00% for the two seed anchors, and the 4-cell band just outside drops too, 8.30% -> 0.71% and 3.30% -> 0.18%, so the suppression does not pile up into a rim). The fixed site budget is unchanged, so the denied sites are redistributed: coverage outside the rect rises from 3.73%/2.86% to 5.51%, and the figure concentrates into the part of the frame the copy does not use. Pass it the rectangle any overlaid copy occupies so the headline reads against bare background rather than through the field; do NOT cover a seed anchor (the upper-left and lower-right thirds) with it — with every candidate weighted to zero the draw falls back to a uniform pick and grows a blob inside the rect instead. It is destructured to primitives before the effect's dependency array, so an inline object literal from the parent does not rebuild the figure every render. STRIKE CYCLE, so the frame is never blank and never frozen: a figure grows over ~2.2s, a fresh strike is seeded every 3.6s, and a figure begins fading only at 4.9s of age over a 1.6s ramp (~0.6 alpha/s) — so the successor is already well grown before its predecessor starts to go, and a complete or near-complete figure is on screen at all times. Seeds alternate between two anchors (upper-left third and lower-right third) with a jittered offset, so successive strikes land on opposite sides of the frame. At mount (and on every resize) the first figure is built COMPLETE in one blocking pass and back-dated into its hold phase, so the hero never opens on an empty canvas. RENDER: only occupied cells ink, and the glyph is chosen by branch GEOMETRY rather than luminance — the two-link chord from a cell's grandparent gives '-' for a horizontal link, '|' for vertical, '/' or '\\' for the two diagonals (reading the chord, not the single link, is what makes a staircase run render as a diagonal instead of a stack of alternating dashes), and any cell that acquires a second child is redrawn as '+', the fork. The tree therefore reads as drawn line-work rather than a stipple. Luminance is pow(1 - depth/maxDepth, 0.7) multiplied by the strike's fade alpha: the trunk near the seed is hot and the fine tips are faint, which is what a discharge photograph actually looks like. POINTER: the pointer is an ELECTRODE. While it is over the field, unoccupied cells within 6 cells of it are pinned above plate potential on a gaussian profile, phi = 1 + 0.9*exp(-d^2/18), re-applied after every relaxation sweep, with the pin strength eased in over a 0.45s time constant. Because the growth weight goes as phi^3, the Laplace solution steepens toward the pointer and new branches visibly fork and race for it. On pointerleave the pin eases back out over the same time constant and subsequent branching returns to its undisturbed statistics; already-grown channel stays exactly where it is, because a discharge path does not un-happen. Direct-DOM rAF, zero React state on the hot path. Two-pass render: pass one walks each live strike's growth order and buckets each cell into one of 6 alpha buckets by luminance; pass two sets ctx.globalAlpha once per bucket and draws only that bucket, so there is never one globalAlpha write per cell. Ink is read via getComputedStyle(canvas).color with the --ns-accent custom property reserved for the single hottest bucket (the channel core), re-derived on a documentElement class MutationObserver for live theme flips. The mono cell is measured via an offscreen canvas's measureText after document.fonts.ready. prefers-reduced-motion builds exactly one complete figure synchronously at mount, draws it once, and skips the rAF loop and the pointer listeners entirely. The 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 12), eta (DBM growth exponent, default 3.0), quiet (normalized {x, y, w, h} rect of suppressed growth, undefined by default), children, className.",
        "rank": 10
      }
    },
    {
      "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.",
        "rank": 11
      }
    },
    {
      "name": "hero-ascii-reaction-front",
      "type": "registry:ui",
      "title": "Hero ASCII Reaction Front",
      "description": "A full-bleed ASCII hero driven by a live Gray-Scott reaction-diffusion sim, inking only the migrating chemical fronts (the gradient ridge of the V species), so self-organising worm-like fronts crawl as thin bright lines across an otherwise empty field, with the pointer injecting fresh reagent that nucleates new fronts.",
      "files": [
        {
          "path": "registry/loud/hero-ascii-reaction-front/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-ascii-reaction-front.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "ascii",
          "canvas",
          "cursor",
          "reaction-diffusion",
          "gray-scott",
          "simulation"
        ],
        "instruction": "Build <ReactionFront cellSize? className?> as a full-bleed <canvas> where THE SIM GRID IS THE GLYPH GRID — cols x rows derived from the measured mono cell, never supersampled, so every character is one cell of a real solver rather than a sampled texture. SOLVER: Gray-Scott on two Float32Array concentration fields U and V plus two scratch buffers, ping-ponged, dU = Du*lap(U) - U*V*V + F*(1-U) and dV = Dv*lap(V) + U*V*V - (F+k)*V, with Du=1.0, Dv=0.5, F=0.018, k=0.051, dt=1.0 and 4 substeps per rAF frame. lap() is the 9-point stencil (-1 centre, 0.05 on each diagonal) with TOROIDAL wrap so fronts never pile up on an edge, and with its 0.8 orthogonal budget SPLIT BY CELL ASPECT as 1/dx^2 : 1/dy^2 (about 0.294 horizontal against 0.106 vertical for a 7.2x12px cell) so the medium is isotropic on screen rather than rendering every front 1.67x taller than wide; the split preserves the stencil's eigenvalue, so Du*dt*|lambda|max stays at 1.6, inside the explicit-Euler bound of 2. F and k sit in the chaotic worm band, which measurement showed is the only band that is BOTH sparse and moving — the quieter bands from F=0.042 upward settle to exactly zero motion, dead static rings. SEEDING: U=1 and V=0 everywhere, then 7 patches of V=0.5/U=0.25 at mulberry32-seeded positions (radius 4 cells), then a MOUNT WARMUP of 600 substeps executed before the first paint — without the warmup the first second of the hero is a blank grid. SPARSITY, which is the whole trick: luminance is NOT V, which would ink the reacted interiors and read as a wash. Per cell compute g = |dV/dx| + |dV/dy| by central difference on the sim grid, then keep only the RIDGE of that gradient field by non-maximum suppression — a cell survives only if its g is a local maximum along EITHER axis, i.e. (g >= gRight && g >= gLeft) || (g >= gDown && g >= gUp). Testing only the locally dominant axis is the obvious variant and is wrong here: it breaks fronts into dashes wherever a front runs diagonally and the two axes tie, measured at 12.1 cells per connected run against 25-39 for the either-axis test, and short runs read as speckle rather than line. A plain gradient threshold with no suppression at all is worse still — it inks the entire four-to-six-cell shoulder of every front, measured at 80% of the frame by 3000 substeps, which is exactly the uniform-wash failure this pass exists to prevent. Either-axis ridge holds ink at a measured 10-15% of cells from the first paint out to 44000 substeps, with roughly half of all inked cells living in connected runs of 8 or more, and collapses each front to a one-cell line. Surviving cells take lum = pow(clamp(g / 0.14, 0, 1), 1.0), with 0.14 chosen as roughly the 95th percentile of the ridge gradient so the density ramp actually spans its range instead of saturating at the darkest glyph, and cells below an ink floor of 0.08 draw nothing. POINTER: while over the canvas an 'inject' scalar eases 0 -> 1 with a 0.4s time constant; each frame, cells within 3.5 cells of the pointer get V += 0.42 * inject * exp(-d^2/6) and U -= 0.20 * inject * exp(-d^2/6) — literally pipetting reagent into the dish and consuming the substrate, so a new front nucleates under the cursor and then propagates away under its own dynamics. On pointerleave the scalar eases back to 0 and the injected fronts keep travelling and dissipate through the reaction itself: the relaxation is physical, not a fade-out. Rendering is two-pass for cost control: pass one fills a Uint8Array ramp-index buffer from the ' .:-=+*#%@' ramp and pushes each inked index into one of 6 luminance buckets; pass two sets ctx.globalAlpha once per bucket (0.1 + b/5*0.9) and draws that bucket only, never one alpha write per cell. Direct-DOM rAF, zero React state on the hot path. Ink is read once via getComputedStyle(canvas).color and re-derived on a documentElement class MutationObserver so theme flips are live; the mono cell is measured via an offscreen canvas measureText('MMMMMMMMMM')/10. prefers-reduced-motion runs the 600-step warmup plus 400 more, draws exactly one frame, and skips the rAF loop and the pointer listeners entirely. The loop pauses on document.hidden and resumes on visibilitychange. Props: cellSize (grid cell px, default 12), className.",
        "rank": 12
      }
    },
    {
      "name": "hero-ascii-schlieren",
      "type": "registry:ui",
      "title": "Hero ASCII Schlieren",
      "description": "A full-bleed ASCII hero built as a real schlieren rig: a rising thermal plume's refractive-index gradient is cut by a hard knife edge, so only the steep density boundaries survive as thin bright and dark bands while the uniform bulk air stays completely blank.",
      "files": [
        {
          "path": "registry/loud/hero-ascii-schlieren/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-ascii-schlieren.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "ascii",
          "canvas",
          "cursor",
          "schlieren",
          "gradient",
          "optics"
        ],
        "instruction": "Build <SchlierenRig cellSize? gain? children? className?> as a full-bleed Canvas 2D hero that reproduces an optical schlieren bench, not a cosmetic ASCII wash. DENSITY FIELD: rho(x,y,t) is evaluated analytically per grid node — no advection sim — as rho = 1.0 - 0.55*plume(x,y,t) - 0.13*shear(x,y,t). plume() is a buoyant column whose axis sits at x0 = 0.42*W and meanders as x0 + 0.06*W*sin(y*0.011 - t*0.9), with a gaussian cross-section exp(-(x-x0)^2 / (2*sigma^2)) whose sigma = 0.024*W*(1 + 1.2*(1 - y/H)) so the column widens as it rises (canvas y grows downward, so the widening happens toward the top), multiplied by a puff train 0.5 + 0.5*max(0, sin(y*0.038 - t*2.2))^1.5 — a rectified train of shed buoyant parcels riding on a standing column. The 0.5 floor is load-bearing: with a bare rectified train the plume vanishes entirely between puffs and its flanks break into detached dashes instead of the two continuous filaments that make the frame read as a column at rest. shear() is a Kelvin-Helmholtz interface across the lower third: tanh((y - y1(x,t)) / (0.02*H)) where y1 = 0.68*H + 0.035*H*billow and billow = sin(x*0.03 - t*1.4) * (1 + 0.35*sin(x*0.017 + t*0.6)). The billow DISPLACES the interface rather than scaling the tanh — scaling it ripples the far field too, because tanh saturates to ±1 everywhere away from the interface, which wallpapers the entire frame with a gradient the knife then transmits (the exact uniform-wash failure this technique exists to avoid). Displacement leaves the bulk air perfectly flat and is also what gives the interface the horizontal gradient component that is the only reason a vertical knife can see a horizontal layer at all. KNIFE EDGE — this is the sparsity mechanism and it must be a HARD cut, never a soft tone map: take the central difference of rho on the glyph grid and divide each by that axis's own cell size in px, dRx = (rho[x+1] - rho[x-1]) / (2*cellW) and dRy = (rho[y+1] - rho[y-1]) / (2*cellH), so the result is a true per-px derivative — the glyph cell is roughly 7x12, and differencing in cell units alone makes the knife 1.7x more sensitive vertically than horizontally, a bias that shows up as the field lighting unevenly as the knife turns. Project onto the knife's axis as d = cos(a)*dRx + sin(a)*dRy, then s = clamp(0.5 + 118*gain*fit*d, 0, 1), where fit = hypot(W,H)/hypot(1440,900) sets the bench gain for the field size — the plume's own width scales with W, so without it a narrow viewport's much steeper gradients clip every surviving cell to solid '@'. s is literally the fraction of the deflected ray cone the knife transmits; 0.5 is undeflected. INK ONLY WHERE |s - 0.5| > 0.24 — every other cell draws nothing at all. Because the bulk field is flat, roughly 90% of cells fall inside that dead band and stay empty, and only the plume's two flanks and the shear interface survive. Luminance = pow((|s-0.5| - 0.24) / 0.26, 0.85). The dark flank (s < 0.5) is drawn in the SAME foreground ink at 0.45x its bucket alpha — never a second hue — so the bright/dark asymmetry of a real knife cut is legible in both themes without introducing colour. POINTER: the pointer IS the knife, and it rotates. Its normalized horizontal position across the container maps to a knife angle in [-0.55, +0.55] rad, eased with a 0.5s time constant, and the gradient is projected onto that axis before the cut — turning the knife re-selects which family of boundaries is transmitted, exactly as on a real bench where a vertical knife shows horizontal gradients and vice versa. Sweeping right therefore trades the plume's vertical flanks for its horizontal puff caps and lights up the shear interface; on pointerleave the angle eases back to 0 and the original vertical bands return. RENDER: rho is sampled once per node into a 1-cell-padded Float32Array so each central difference is four array reads rather than four field evaluations; a Uint8Array holds the ramp index; cells are bucketed into 6 alpha buckets x 2 polarities and drawn two-pass with exactly one ctx.globalAlpha write per bucket, from the shared ' .:-=+*#%@' ramp. Direct-DOM rAF, zero React state on the hot path. Ink is read via getComputedStyle(canvas).color and re-derived on a documentElement class MutationObserver for live theme flips; the mono cell is measured with an offscreen canvas measureText after document.fonts.ready. prefers-reduced-motion draws exactly one static frame at t=1.8 with the knife angle at 0 (the full plume-plus-interface read, not an empty state) and skips both the rAF loop and the pointer listeners. The loop pauses on document.hidden and resumes on visibilitychange. 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 12), gain (knife sensitivity, default 1.0), children, className.",
        "rank": 13
      }
    },
    {
      "name": "hero-ascii-shock-diamonds",
      "type": "registry:ui",
      "title": "Hero ASCII Shock Diamonds",
      "description": "A full-bleed ASCII hero of a supersonic jet plume: a repeating train of oblique shocks reflecting off the jet boundary draws the classic X-crossed Mach diamonds as thin bright lines in an otherwise empty exhaust, with the pointer acting as the throttle that stretches and compresses the shock-cell spacing.",
      "files": [
        {
          "path": "registry/loud/hero-ascii-shock-diamonds/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-ascii-shock-diamonds.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "ascii",
          "canvas",
          "cursor",
          "shock",
          "supersonic",
          "mach"
        ],
        "instruction": "Build <ShockTrain cellSize? mach? children? className?> as a full-bleed Canvas 2D hero that reproduces the shock-cell structure of an underexpanded supersonic jet, not a decorative diagonal hatch. THE WHOLE DESIGN IS SIZED SO THE DIAMONDS READ AS DIAMONDS AT DESKTOP SCALE — a tall band of chevrons, never a thin horizontal streak. GEOMETRY: a nozzle exit of diameter D at x0 = 0.10*W on the jet axis yc = 0.26*H. The axis sits HIGH, not at mid-height, because the optional children are anchored bottom-left; the plume band and the copy must occupy disjoint regions of the frame with no scrim, blur or fade between them. D is the one free geometric parameter and it sets how TALL the train is (the shock band is roughly 1.5*D high), so it is pushed as large as the frame allows: solved at resize as min(0.28*H, 0.98*(W-x0)/(3.2*k)) with k = 1.30*sqrt(Mdesign^2 - 1), and at least 8 grid rows. The cell COUNT is what gives way — only about 3.5 shock cells lay across a 16:10 frame (three crossings plus the lip), rising to about 5 on short wide frames where the 0.28*H cap binds first, which is the point: three or four legible diamonds beat six horizontal dashes. D is fixed hardware and is pinned to the design Mach, never the live one, which is precisely why a throttle change moves the cell length instead of the nozzle. The axis is snapped to a ROW CENTRE — the on-axis node sits exactly at y = yc, and if that lands between two rows the brightest cell in the picture is never sampled and the train degrades into a hatch. DESIGN MACH IS LOW BY DESIGN: default 1.5 (clamped 1.2..4.5). At M 1.5 the Mach angle mu = asin(1/M) is 41.8 degrees, so tan(mu) = 0.89 and the two shock families cross at close to a right angle — the arms read as chevrons. A high design Mach is the failure mode this component exists to avoid: at M 2.4 tan(mu) is 0.46, the arms lie down almost flat, they merge into horizontal streaks, and the short D that a high cell count then forces leaves the band only a handful of rows tall. PLUME BOUNDARY: r(x) = 0.5*D*(1 + 0.55*sin(2*pi*(x-x0)/L + pi/2)) * exp(-(x-x0)/(4.5*L)) — the periodic bulge/waist of a real free jet, decaying downstream; it is traced at the faintest ramp level only where a cell is within about half a row of it, so the envelope reads as a whisper, not an outline. SHOCK-CELL LENGTH is the Prandtl-Pack relation L = 1.30 * D * sqrt(M^2 - 1). SHOCK LINES: the oblique shock leaves the nozzle lip at the Mach angle and reflects specularly off the free boundary, so the whole train collapses to two travelling phase coordinates, p1 = (x-x0)/L + (y-yc)/(L*tan(mu)) and p2 = (x-x0)/L - (y-yc)/(L*tan(mu)); a cell is on a shock when frac(p1) or frac(p2) is within 0.035 of an integer. Ink strength is the max over the two families of pow(1 - dist/0.035, 1.6), multiplied by a downstream decay exp(-(x-x0)/(6.0*L)) — a slow decay, so the third and fourth crossings survive at readable opacity instead of ghosting out — and HARD-MASKED to zero outside |y - yc| > r(x): the shocks exist only inside the plume. WHERE BOTH FAMILIES ARE WITHIN TOLERANCE the strength is multiplied by 1.9 and clamped to 1: that crossing is the diamond node, and it is the entire reason the frame reads as a string of bright X's on the centreline rather than a hatch. RASTERIZATION: 0.035 cell-lengths of phase is thinner than one glyph row, so the hit test is conservatively dilated to at least 0.62 of a cell measured in phase units (hypot(1/L, 1/(L*tan(mu))) is the phase gradient magnitude); everything inside the dilation is full-strength line and the physical 0.035 profile fades over the last stretch — without this the lines break into dashes. SPARSITY: only two thin line families inside a tapering wedge are ever inked; the entire frame outside the plume, and the plume's own interior between shocks, stay blank — measured ink coverage around 3% of the frame. MEASURED at 1440x900 with cellSize 12 and the defaults: D = 252px, L = 366px, 3.56 cells across, the plume occupies 33 of 75 glyph rows and the arms above half opacity occupy 17 rows, all of it in the top 48% of the frame. AMBIENT MOTION: the train breathes with a calm resting pulse, M_eff = M * (1 + 0.018*sin(t*0.85) + 0.008*sin(t*2.3)) — a small jitter in cell spacing, never a scroll or a march. POINTER — the pointer is the THROTTLE: its normalized x maps to a nozzle pressure ratio spanning 0.66x to 2.13x the design NPR — for the default Mach 1.5 the design NPR is (1 + 0.2*M^2)^3.5 = 3.67, so the throttle sweeps NPR from 2.42 to 7.82 and M from 1.20 to 2.00, with the design plume sitting at the centre of pointer travel and L stretching from 0.59x to 1.55x its design value. The window must be anchored to the design NPR rather than to a fixed absolute span: a literal low end solves subsonic and there is no shock structure to draw at all. NPR is eased with a 0.6s time constant and M is re-solved from it isentropically as M = sqrt(5*(npr^0.2857 - 1)) at gamma 1.4, clamped to at least 1.05. Higher NPR means a higher M, a longer L and a shallower Mach angle, so the diamonds visibly stretch apart and the plume lengthens as the pointer moves right and compress as it moves left. Pointer y within +/-0.25*H tilts the jet axis by up to 0.04*H (thrust vectoring), quantized to whole rows so the node stays on a row centre wherever it is aimed, and eased with the same time constant; the travel is deliberately small so a downward vector never pushes the band into the copy. On pointerleave both ease back to the design plume (M 1.5, yc 0.26*H). RENDER: direct-DOM rAF, zero React state on the hot path; two-pass into a Uint8Array ramp-index buffer with 6 luminance buckets and exactly one ctx.globalAlpha write per bucket, from the shared ' .:-=+*#%@' ramp. Ink is read via getComputedStyle(canvas).color and re-derived on a documentElement class MutationObserver so theme flips are live; the mono cell is measured with an offscreen canvas measureText after document.fonts.ready. prefers-reduced-motion renders exactly one static frame at t=0 with M at the design value and no vectoring, and skips both the rAF loop and the pointer listeners. The loop pauses on document.hidden and resumes on visibilitychange. Optional children render bottom-left anchored, in the empty lower half of the frame that the raised jet axis leaves for them — the copy is never crossed by the plume, and no scrim, blur or gradient is used to separate them. Props: cellSize (grid cell px, default 12), mach (design Mach number at rest, default 1.5, clamped to 1.2..4.5), 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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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.",
        "rank": 7
      }
    },
    {
      "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.",
        "rank": 3
      }
    },
    {
      "name": "hero-beam-glyph",
      "type": "registry:ui",
      "title": "Hero Beam Glyph",
      "description": "A full-bleed hero wordmark drawn as a genuine stroke-based vector font, the way an XY vector CRT (Asteroids/Tempest/Vectrex-class arcade hardware, oscilloscope character generators) actually traced text: brightness at every point comes from simulated inverse beam speed, so corners and stroke endpoints glow while long straight runs stay dim, retraced 36 times a second with a short phosphor trail and a low-frequency deflection-coil jitter that never lets two frames land pixel-identical.",
      "files": [
        {
          "path": "registry/loud/hero-beam-glyph/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-beam-glyph.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "canvas",
          "vector",
          "stroke-font",
          "crt",
          "phosphor",
          "wordmark"
        ],
        "instruction": "Build a full-bleed Canvas 2D hero wordmark rendered from a self-authored single-stroke (Hershey-style) vector font — STROKE_FONT maps each supported character to one or more flat polyline arrays in a 4-wide x 6-tall unit cell, pen lifted between separate strokes within a glyph, unsupported characters rendering as a blank cell. On resize, lay glyphs left to right (unit step GLYPH_W + GLYPH_GAP = 5.4), derive a scale from the container's SMALLER dimension (target wordmark height = 42% of min(width,height)) then shrink further only if the resulting width would exceed 88% of the container width, and build one Vertex[] per stroke (world-px positions, each vertex carrying a corner/endpoint alpha and a random per-vertex jitter phase seed) — geometry is rebuilt once per resize/text change, never per frame. CORNER BRIGHTNESS: for each interior vertex, compute the turn angle between its incoming and outgoing segment directions (turn = (1 - dot(dirIn,dirOut))/2, 0 for straight-through, 1 for a full reversal), map that to a synthetic beam speed between SPEED_MAX=2200 (straight) and SPEED_MIN=400 (sharp corner), normalize brightness as SPEED_MAX/speed clamped to [1,3], then map that into a stroke alpha between ALPHA_MIN=0.32 and ALPHA_MAX=0.92 — stroke endpoints (no prev/no next vertex) always get ALPHA_MAX, since a real beam decelerates to land a stroke and decelerates again before lifting off it. RETRACE LOOP: a fixed-timestep accumulator (RETRACE_MS = 1000/36) fires a retrace pass whenever it crosses the threshold (capped at 8 catch-up passes per rAF frame so a resumed hidden tab doesn't burst-repaint); each retrace first fills the whole canvas with the `bg` token at alpha (1-DECAY)=0.15 under `source-over` (fading the previous pass toward background — the short multi-frame phosphor trail), then strokes every subdivided segment (one substep per SUBSTEP_PX=6px of segment length, capped 24/segment, each substep its own stroke() call so alpha can vary continuously along the path) under a THEME-AWARE composite mode: `\"lighter\"` (additive glow, correct when the foreground token is the brighter of the two — dark theme) or `\"multiply\"` (correct when the foreground token is the darker one — light theme, where additive blending would wash dark ink toward the light background and invert the whole mechanic). Which mode applies is recomputed on every token read by comparing relative luminance (0.2126r+0.7152g+0.0722b) of parsed --foreground/--background hex values, never inferred from a class name. BEAM JITTER: every vertex is perturbed at draw time by `amp * sin(simTime*JITTER_OMEGA + seed)` on both axes (JITTER_OMEGA=6.1 rad/s, JITTER_AMP=1.5px baseline), where simTime is a continuous clock that keeps advancing every rAF frame independent of the 36Hz retrace cadence (only visibly sampled when a retrace actually paints). POINTER DISTURBANCE: pointermove sets a `disturbWeight` scalar to 1 and records the local cursor position; every rAF frame `disturbWeight *= exp(-dt/0.35)` (decays to background within roughly a second of the pointer leaving); each vertex's jitter amplitude gains `disturbWeight * exp(-distanceToCursor^2 / (2*(0.28*minDim)^2)) * 4.5px`, so the disturbance is local (falls off with distance) and expressed purely in jitter amplitude — never a colour or accent change. prefers-reduced-motion renders exactly one static pass: the canvas is fully reset to an opaque `bg` fill (never a bare clearRect, so the theme-aware composite mode below never blends against a transparent pixel), jitter and pointer disturbance are both disabled, and the wordmark is stroked once with the same corner/endpoint alpha ramp — fully legible with the brightness contrast still visible, no accumulated trail, no rAF scheduled, no pointer listener bound. Tokens (--foreground, --background) are read via getComputedStyle(document.documentElement) at mount before any paint and re-read on a MutationObserver watching <html>'s class attribute (repainting immediately if reduced motion is active). ResizeObserver (debounced 120ms) rebuilds geometry on layout change; IntersectionObserver (threshold default) and `visibilitychange` both pause/resume the rAF loop, canvas.width/height are DPR-aware (capped at 2) and set from getBoundingClientRect, never left at intrinsic size. Props: text (default 'SIGNAL', uppercased, unsupported characters render blank), className, children (optional content — a subheading/CTA — rendered in a pointer-events-none absolute overlay so the canvas underneath stays receiving pointer events for the disturbance). Zero dependencies, Canvas 2D only, no WebGL, no colour literals anywhere."
      }
    },
    {
      "name": "hero-burin-hatch",
      "type": "registry:ui",
      "title": "Hero Burin Hatch",
      "description": "A copperplate engraving that relights itself: a sphere rendered only as meridian hatch lines whose stroke WIDTH swells with shadow and vanishes to blank paper in the light, with a second cross-hatch pass appearing solely in the dark, and the pointer moving the light source.",
      "files": [
        {
          "path": "registry/loud/hero-burin-hatch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-burin-hatch.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "canvas",
          "engraving",
          "hatching",
          "burin",
          "shading",
          "cursor",
          "generative",
          "monochrome"
        ],
        "instruction": "Build <BurinHatch eyebrow? headline? subcopy? primaryCta secondaryCta? plateLabel? className?> as a two-column hero whose right-hand stage is a Canvas 2D copperplate engraving — no WebGL, no dependencies, no fills and no gradients anywhere. SURFACE: an orthographically projected unit sphere of radius Rs = 0.38 * min(w, h). For longitude lam and latitude phi the object-space point is (cos(phi)*sin(lam + lam0), sin(phi), cos(phi)*cos(lam + lam0)); that same unit vector IS the surface normal N, screen position is (cx + Rs*Nx, cy - Rs*Ny), and any sample with Nz <= 0 is back-facing and skipped (the run is flushed and restarted, so a line never wraps around the limb). HATCHING FAMILY A, the primary burin pass, follows the surface parameterization so the lines themselves describe the curvature: half-meridians every 4.5 deg of longitude (80 around the full sphere, so the front hemisphere always shows exactly 40 lines however far it has turned), each sampled every 1.5 deg of latitude from -88 to +88. FAMILY B is the cross-hatch: parallels every 6 deg of latitude from -72 to +72, sampled every 1.5 deg of longitude — it stops short of the poles because past about 78 deg the screen spacing between consecutive parallels drops below their own width and they merge into a solid slab. Each parallel's longitude sweep STARTS at 180 deg (the back of the sphere) so its single visible arc is one contiguous run; starting at 0 splits the front face and leaves a seam down the middle of the plate. BURIN SWELL is the entire tonal mechanism and the reason the frame has real negative space: with a unit light L, width_A = 3.0 * pow(clamp(1 - dot(N, L), 0, 1), 3.0) px, clamped to [0, 3.0]. The exponent is deliberately high — the cut has to reach zero well before the surface is fully lit, or every meridian stays faintly visible and the plate reads as a wireframe globe instead of an engraving. Where the surface faces the light dot(N, L) goes to 1 and the width goes to ZERO — the cut disappears completely and the highlight is bare unworked paper, exactly the way an engraver leaves the lit side of a plate. Family B is drawn ONLY where dot(N, L) < 0.20, with width_B = 1.8 * min(1, (0.20 - dot(N, L)) / 0.20) — the min matters, because dot runs to -1 on surfaces turned away from the light and unclamped that swells the cross-hatch to ~7 px and fills the deep shadow in solid. So cross-hatching exists strictly inside the shadow, which is the real second-pass discipline of copperplate work. A variable-width cut CANNOT be a stroke: each hatch run is emitted as a closed polygon strip — walk the samples forward offsetting each by +width/2 along the screen-space normal of the local line direction, then walk back offsetting by -width/2, and close, so the strip pinches shut to nothing where the width reaches zero. All of family A accumulates into one Path2D and all of family B into another, each FILLED once per frame at globalAlpha 0.90 and 0.75 — two fill calls per frame total, zero per-sample canvas state changes, and per-frame trig reduced to a single angle-addition identity per line via precomputed sin/cos tables. The silhouette is a single 1.1 px circle at alpha 0.35; there is no other outline, because the form is made purely by hatch density. AMBIENT: lam0 += 0.06 rad/s so the meridians drift across the surface and it reads as a slowly turning engraved globe, and the ambient light direction wanders on azimuth = 0.9 + 0.35*sin(2*pi*t/26s) and elevation = 0.5 + 0.18*sin(2*pi*t/19s + 1.1) — two incommensurate periods, so the relight is a calm resting pulse with jitter that never repeats, never a busy loop. POINTER: L_target = normalize((px - cx)/Rs, -(py - cy)/Rs, 0.62), the fixed 0.62 z-component keeping the light from ever going fully edge-on; the live L slews toward L_target while the pointer is over the hero and toward the ambient wander once it leaves, integrated as L += (L_target - L) * min(1, dt/0.45) and re-normalized — a 0.45 s time constant. So the highlight physically travels with the cursor, the meridian cuts thicken behind it, and the cross-hatch pass appears and disappears as regions cross the 0.20 shading threshold; on pointerleave the light eases back onto its 26 s / 19 s ambient drift rather than snapping. TOKENS: it is a canvas, so the fill is getComputedStyle(canvas).color read at mount and re-derived on a documentElement class MutationObserver for live theme flips — never a color literal; --ns-accent appears only as the CTA focus ring, and the CTAs render as <a> or <button> depending on whether an href is given, both with a visible focus-visible outline. dpr clamped to 2, ResizeObserver with a <4 px guard, dt clamped to 1/30 s, rAF paused on document.hidden and resumed on visibilitychange. prefers-reduced-motion computes exactly one frame at t = 0 with the ambient L and mounts no rAF loop and no pointer listeners (resize and theme changes still redraw that single frame). The canvas carries role=\"img\" with plateLabel as its accessible name. Props: eyebrow, headline (string or string[] for explicit line breaks), subcopy, primaryCta / secondaryCta ({ label, href?, onClick? }), plateLabel, className.",
        "rank": 14
      }
    },
    {
      "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. 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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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, `--ns-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. `--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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 --ns-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/--ns-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-faraday-wave-cell",
      "type": "registry:ui",
      "title": "Hero Faraday Wave Cell",
      "description": "Full-bleed hero background reproducing Faraday's 1831 discovery: a fluid layer vibrated vertically above a critical amplitude breaks into standing surface waves oscillating at half the driving frequency, tessellating into stripe, square and hexagonal cells that keep reorganizing rather than settling.",
      "files": [
        {
          "path": "registry/loud/hero-faraday-wave-cell/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-faraday-wave-cell.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "canvas",
          "physics",
          "fluid",
          "wave",
          "cellular",
          "ambient"
        ],
        "instruction": "Build <HeroFaradayWaveCell cyclesAcrossMin? drivingFrequencyHz? amplitudeDriftPeriod? paused? children? className? style?> as a full-bleed Canvas 2D hero background reproducing Michael Faraday's 1831 observation that a fluid layer vibrated VERTICALLY above a critical driving amplitude develops standing surface waves oscillating at exactly HALF the driving frequency — a parametric, subharmonic response, not a driven resonance at the forcing frequency — and that above threshold the free surface tessellates into cellular patterns (stripes, squares or hexagons) whose symmetry comes from which resonant plane-wave triad the drive selects. MECHANISM: an analytic sum of standing-wave modes on a coarse grid, not a wave-equation solve. One frame-constant base wavevector (magnitude k derived from cyclesAcrossMin cycles across the container's smaller dimension, orientation rotating slowly and continuously) generates three plane-wave families: one wave alone reads as stripes, two orthogonal waves as a square lattice, three waves 60 degrees apart as a hexagonal lattice — the standard three-wave resonant-triad picture behind real parametric pattern selection. Height at a grid point is the weighted sum of all three families' spatial cosine patterns, multiplied by ONE shared subharmonic time factor cos(pi * drivingFrequencyHz * t) — pi*f, not 2*pi*f, because the visible oscillation is HALF the driving frequency by definition — so the whole field inverts in sync every drive cycle, the defining subharmonic signature. A slow driving-amplitude envelope (period amplitudeDriftPeriod, default 46s) sweeps through and periodically below a critical threshold: a smoothstep ramps pattern strength to zero below threshold (a genuinely flat surface, the real below-threshold behavior) and to full strength above it, never an invented fade. Three independent, incommensurate-period sinusoids (27s/33s/41s) set how much of the stripe/square/hex family is present at any moment, sharpened by a power curve so one tessellation usually dominates rather than three lattices blending evenly, matching how a real cellular pattern reorganizes between symmetries instead of interpolating. GRID + BUDGET: the field is evaluated on a small offscreen buffer (~35 samples across the smaller container dimension, aspect-scaled on the other axis, capped near 6500 total cells) and upscaled onto the full-bleed backing canvas via the canvas's own bilinear image smoothing — an interpolated coarse grid, not per-pixel evaluation. Per cell the inner loop does 4 dot products against wavevectors that are recomputed once per frame (not per cell) and 4 cos() calls, keeping the whole field under roughly 30k trig evaluations per frame. TOKENS: a three-stop ramp, trough at --background, mid at --ns-muted, crest at --foreground, read via getComputedStyle(document.documentElement) at mount and re-read on a MutationObserver watching documentElement's class — because the ramp direction is always background-to-foreground regardless of theme, crest and trough stay distinguishable under both polarities without inverting which token reads as 'crest'. No --ns-accent anywhere: this is a resting ambient background, not interaction chrome. LEGIBILITY: a full-frame tessellation fights centered headline/CTA type, the same problem background-halftone-rosette solved with an explicit coverage gradient — here inverted spatially to match a centered content block instead of a centered visual: an elliptical amplitude mask damps the field to ~15% of full amplitude behind the content region and grows to 100% toward the frame edges, so pattern density is bought at the edges rather than behind the type; the damped zone is still visibly live at 15%, never fully flat. SCALE: wavelength is derived from the container's smaller dimension, never a fixed pixel size, so the tessellation reads at the same relative cell size in a full viewport hero or a small preview card. HOST: DPR-capped backing store (1.5, matching other full-bleed showpieces since area cost dominates), ResizeObserver on the host element, IntersectionObserver (threshold 0) plus visibilitychange pausing the single rAF loop when offscreen or hidden. prefers-reduced-motion (and the paused prop) freeze on a pure-hexagon frame at full pattern strength and peak subharmonic phase (weights forced to [0,0,1], amplitude 1, cos term at +1) rather than sampling the live time formula at some t, because a numeric coincidence with the live formula could land on a transitional blend or a below-threshold flat dip — hexagons are chosen specifically because they read as the most legible tessellation at a glance. A11Y: the canvas is aria-hidden and pointer-events are never captured by it; children (headline, CTA) render as real DOM in normal focus order above the field, wrapped in a pointer-events-none layer so only elements that opt back in (a CTA anchor/button) are actually clickable."
      }
    },
    {
      "name": "hero-glyph-silhouette-pack",
      "type": "registry:ui",
      "title": "Hero Glyph Silhouette Pack",
      "description": "A full-bleed hero where a cloud of monospace glyph particles is packed to fill an arbitrary vector silhouette, breathing and repelling from the pointer at rest, and re-solving on its own into a fresh silhouette on a timer as particles migrate to their new slots.",
      "files": [
        {
          "path": "registry/loud/hero-glyph-silhouette-pack/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-glyph-silhouette-pack.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "ascii",
          "particles",
          "generative",
          "typography",
          "canvas"
        ],
        "instruction": "Build <HeroGlyphSilhouettePack shapes? wordmarkText? particleCount? glyphs? holdMs? migrateMs? className? children?> as a full-bleed Canvas 2D hero. SILHOUETTES ARE THE CONSTRAINT: each entry in `shapes` (default ['wordmark','star','orbit']) is authored as simple canvas path data drawn once into an offscreen canvas at mount/resize/shape-change -- 'wordmark' rasterizes `wordmarkText` in a bold monospace face shrunk to fit, 'star' is a 10-point star path, 'orbit' is two even-odd ring annuli -- no image or SVG assets. Only the alpha channel is read back and box-blurred twice for a cheap distance-ish edge falloff; that blurred field is both the inside/outside test and the importance weight used to IMPORTANCE-SAMPLE `particleCount` target points, so density stays high in the interior and tapers at the boundary rather than cutting hard. RE-SOLVE ON A TIMER, NO POINTER REQUIRED: every holdMs + the flight window, the cloud advances to the next shape in the list, samples a fresh target set from its mask, and assigns targets back to the CURRENT live particles by sorting both sets on x and pairing in order (cheap approximate nearest-matching, not real optimal assignment) so flights stay short and roughly non-crossing. Each particle's flight is staggered up to 420ms, eased in/out over migrateMs, and bulges perpendicular to its straight path (peaking mid-flight, zero at both ends) so migration reads as a flight, not a snap or a fade. At mount the cloud starts scattered at random positions and its first retarget condenses it into shapes[0], so the opening frame is already in motion. BREATHING AND REPULSION run continuously on top of the current interpolated position, both while holding a solved shape and while mid-flight: a small per-particle sinusoidal wobble (amplitude damped during flight so it does not fight the bulge), and pointer proximity repulsion within a fixed radius that eases back to zero once the pointer leaves or moves away -- this is what keeps the component alive with zero input, since holdMs alone would otherwise read as a static frame between re-solves. COLOR: particles cross-fade from --ns-muted (freshly retargeted / mid-flight) to --foreground (arrived and settled) as their own flight's eased progress goes 0 to 1 -- read via getComputedStyle on mount and re-derived on a documentElement class MutationObserver, precomputed into a small ramp of rgb() stops (never arithmetic on raw token strings at draw time). Draws are batched into ALPHA_BUCKETS (6) groups by that same eased progress via one counting-sort pass per frame, so fillStyle is set at most 6 times per frame regardless of particle count -- never per-particle. No --ns-accent anywhere; this is a resting hero, not interaction chrome. SCALE: particleCount defaults to an estimate derived from the container's smaller dimension (clamped 180-700) so glyph size and packing density hold steady from a full viewport down to a registry-card-sized frame; the wordmark shape is the first of the three to lose legibility as the frame shrinks (its stroke detail needs more resolved particles than a star or ring silhouette of the same area), so a caller targeting small card-sized placements should weight `shapes` toward star/orbit or drop wordmark from the cycle. PERFORMANCE: particle state lives in typed arrays sized to particleCount with zero per-frame allocation (draw position, bucket index and repulsion state are all reused scratch arrays), DPR is capped at 1.5 for the full-bleed backing store, and the rAF loop pauses on prefers-reduced-motion (not run at all), document.hidden, and an IntersectionObserver reporting the host offscreen. prefers-reduced-motion freezes on shapes[0] fully solved -- one deliberately chosen, fully packed, non-t0 frame with no wobble, no repulsion and no rAF bound at all, so the shape reads clearly and nothing animates. Accessibility: the canvas is aria-hidden and pointer-events-none so it can never intercept pointer events meant for content; `children` render as real DOM in normal focus order inside a bg-background/80 backdrop-blur scrim sized to the copy block itself (not the whole pane), which is what keeps the rest of the cloud visible around the headline instead of washing the whole hero out. Props: shapes (default ['wordmark','star','orbit']), wordmarkText (default 'NS'), particleCount (auto if omitted), glyphs (charset particles draw from, default '+x*.oO'), holdMs (default 2600), migrateMs (default 1600), children, className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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/--ns-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 --ns-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-ns-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.",
        "rank": 15
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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/--ns-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 --ns-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.",
        "rank": 16
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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-penrose-inflate",
      "type": "registry:ui",
      "title": "Hero Penrose Inflate",
      "description": "A hero backdrop of a true Penrose P3 rhomb quasicrystal built by Robinson-triangle substitution, drifting through a seamless golden-ratio infinite zoom, with the pointer acting as an inflation lens that resolves the tiling one substitution level finer under the cursor.",
      "files": [
        {
          "path": "registry/loud/hero-penrose-inflate/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-penrose-inflate.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "canvas",
          "penrose",
          "quasicrystal",
          "tiling",
          "generative",
          "cursor",
          "aperiodic"
        ],
        "instruction": "Build <PenroseInflate eyebrow? headline? subcopy? primaryCta? secondaryCta? depth? period? className?> as a full-bleed hero whose backdrop is a genuine Penrose P3 rhomb quasicrystal on a single Canvas 2D layer — Canvas, not SVG, because the tile budget is thousands of paths — driven by a direct-DOM rAF loop with zero React state on the hot path. GEOMETRY: phi = (1 + sqrt(5)) / 2 = 1.6180339887. A tile is a Robinson half-triangle (type, A, B, C); type 0 is the acute 36-72-72 triangle (legs AB = AC, base BC = leg / phi) and type 1 the obtuse 108-36-36 one (legs AB = AC, base BC = leg * phi). The substitution is stated exactly so nothing is invented: type 0 (A,B,C) takes P = A + (B - A) / phi and emits (0, C, P, B) and (1, P, C, A); type 1 takes Q = B + (A - B) / phi and R = B + (C - B) / phi and emits (1, R, C, A), (1, Q, R, B) and (0, R, Q, A). R sits ON the base BC and is measured from B, not offset from Q: solving |RA| = |RQ| = |RC| = leg / phi with |RB| = leg puts it exactly at B + (C - B) / phi, and that is the only placement keeping every child isoceles — offsetting from Q instead stretches one leg by phi and the tiling degenerates from the second generation onward (measured: 20 of 50 triangles still isoceles at generation 2, against 340 of 340 with the correct placement, base/leg holding at exactly 0.618034 for type 0 and 1.618034 for type 1 through generation 4). It is a strict refinement — child area sums to the parent's to twelve decimal places for both types — and at depth 6 the stroked legs fall into exactly five direction clusters 36 degrees apart (18, 54, 90, 126, 162) with identical population, which is the five-fold symmetry the resting frame is judged on. SEED: ten type-0 triangles in a wheel about the origin, triangle i having A = origin, B = e^{i(2i-1)pi/10} * S and C = e^{i(2i+1)pi/10} * S with every odd i mirrored (B and C swapped) so the wheel closes as a legal decagon; S is 0.62 * the viewport diagonal so the decagon covers the frame. Population grows ~phi^2 = 2.618 per level; depth is a prop defaulting to 6 (about 1,300 triangles after the viewport cull, ~59px rhomb edges at 1440x900) and clamped to 7, and the substitution loop hard-aborts at a 12,000-triangle budget across both held levels — a parent past the cut simply gets an empty child range and stays coarse. RENDERING AS RHOMBS, NOT TRIANGLES: every base edge BC is shared by exactly two triangles of the SAME type that are mirror images across it (verified numerically over four generations — of 180 BC edges at generation 4, 160 are same-type mirror pairs and the remaining 20 are the outer hull; no BC edge ever abuts the other type), so BC is the internal bisector seam of a rhomb and is NEVER stroked; only the two legs are, drawn as one subpath B -> A -> C. Two type-0 halves glued on BC give the thin 36/144 rhomb, two type-1 halves the fat 72/108 rhomb. That single omission is the whole legibility of the piece: stroke BC and the frame reads as a triangulation, omit it and it reads as fat and thin rhombi with unmistakable five-fold symmetry and no translational repeat — thin lines on empty ground, no fills, no gradient. Exactly two Path2D objects are accumulated per frame, one for fat-rhomb halves and one for thin, and each is stroked ONCE — 2 stroke calls per frame, lineWidth 0.9, globalAlpha 0.55 for the fat rhombi (the dominant tiles, phi times more numerous, which carry the structure) and 0.24 for the thin ones (recessive). Triangles whose screen bounding box falls entirely outside the viewport are culled before any stroke work. MOTION: one scale s(t) = pow(phi, f) with f = (t / period) mod 1 (period default 20000ms), applied about the canvas centre, so the field drifts through an endless calm zoom of one phi per ~20s and never reads as a slideshow. Two lists are held at all times: a coarse level N and its one-deeper substitution N+1, with a childStart index so a parent's children are a contiguous range. Across the last 40 percent of each cycle — smoothstep(0.60, 1.0, f) — each coarse triangle independently swaps to its children when a per-triangle score falls under that front, so the level change is a continuous resolving pass spread over seconds rather than an instantaneous global pop, and the frame is a uniform single-scale quasicrystal for the majority of every cycle. The score is 70 percent a smooth low-frequency closed-form field (a product and a sum of sines in centroid coordinates) so the front is coherent rather than salt-and-pepper, and 30 percent a deterministic sine white hash so its edge is dithered. At the wrap the fine list is culled and multiplied by phi and becomes the new coarse list — which is pixel-for-pixel the frame that was just drawn at s = phi, so the loop is exactly seamless and the model coordinates are renormalised every cycle instead of drifting toward float underflow. The cull box at the wrap is the full kept box DIVIDED by phi, so scaling by phi reproduces exactly the box the previous coarse list covered; that makes both coverage and population an exact fixed point — 2.618 children times the 1/phi^2 area retained = 1.0. Culling to (w/2)/phi + margin instead also converges, since the box is recomputed from the viewport every wrap and nothing compounds, but it converges on a WIDER box of w/2 + margin*phi carrying about 6 percent more tiles (1350 against 1276 at depth 6). Measured over 20 wraps at seven aspect ratios from 390x844 to 3440x1440, this form settles into a two-value cycle (1276/1250 at depth 6) and the two held levels together peak at 4,632 triangles at depth 6 and 11,414 at depth 7, under the 12,000 budget everywhere, while the wider form pushes depth 7 past the budget and truncates at a wrap. The count is resolution-independent because the seed radius scales with the viewport diagonal, so only aspect ratio moves it. Staying under budget matters because a truncated parent has no children to become and would leave a permanent hole at the next wrap rather than a stale tile. POINTER: an inflation lens. Its strength L eases 0 -> 1 with tau 0.6s on enter and back to 0 on leave, and it raises the same swap threshold locally to flip + (1 - flip) * L * exp(-d^2 / (2 * (0.32 * min(w,h))^2)) for d the screen distance from cursor to tile centroid. Because the threshold is compared against the per-triangle score rather than a hard radius, detail densifies under the cursor with a stochastic boundary rather than a visible circle, and thins back out over the same time constant on leave. Pointer listeners are bound to the hero section, not the canvas, and the canvas is aria-hidden and pointer-events-none so it never intercepts the real CTAs. The leave path listens for BOTH pointerleave and pointerout (the latter guarded by a relatedTarget containment check), because pointerleave does not bubble and a leave dispatched straight to a descendant would otherwise never reach the section and would strand the lens fully inflated. TOKENS: it is a canvas, so stroke colour is getComputedStyle(canvas).color — the canvas carries text-foreground — re-read on a documentElement class MutationObserver so a theme flip repaints live; --ns-accent appears exactly once in the whole component, on the CTA focus-visible rings (outline-2 / outline-offset-2 / outline-ns-accent), and never in the tiling. Backing store dpr is clamped to 2, a ResizeObserver rebuilds from the seed on a real size change with a zero-size guard, and the rAF loop pauses on document.hidden and resumes on visibilitychange. prefers-reduced-motion: substitute once to the requested depth, draw exactly one frame at s = 1 with L = 0, and never start the rAF loop or attach the pointer listeners at all; the resting rhomb field alone still does the full job. Props: eyebrow, headline (an array of h1 lines), subcopy, primaryCta and secondaryCta ({ label, href }), depth (default 6, clamped 1..7), period (ms, default 20000), className. Zero dependencies.",
        "rank": 17
      }
    },
    {
      "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.",
        "rank": 5
      }
    },
    {
      "name": "hero-slice-comb",
      "type": "registry:ui",
      "title": "Hero Slice Comb",
      "description": "A full-bleed hero background: a tri-axial superellipsoid sliced by camera-fixed depth planes and spun continuously beneath them, where uniform-spacing slices pile into a bright rim wherever the surface turns tangent to the camera and spread into a dark, sparse interior wherever it faces the camera head-on.",
      "files": [
        {
          "path": "registry/loud/hero-slice-comb/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-slice-comb.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "canvas",
          "3d",
          "silhouette",
          "generative",
          "monochrome",
          "ambient"
        ],
        "instruction": "Build a hero background whose mechanic is AXIAL SLICE COMB: a tri-axial superellipsoid (|x/aX|^n + |y/aY|^n + |z/aZ|^n = 1, no mesh data) is cut by a fixed stack of planes perpendicular to the camera's view axis (Z), and the solid rotates continuously beneath that fixed stack — never the reverse — so 'tangent to the camera' stays true to the CURRENT view on every frame, not just at rest. This is not a restyle of index-contour, hero-isobar-contours, ascii-engraving-contour or pecked-ring: those trace iso-lines of a 2D height field z(x,y) in the plane: this slices a 3D volume along the view axis and projects each cross-section, and its cue is VIEWING GEOMETRY (surface-normal-vs-view-angle), not magnitude. MATH: rotation by phi about the vertical axis leaves Y unchanged, so each slice's cross-section is closed-form — x' = X*cos(phi) - z0*sin(phi), z' = X*sin(phi) + z0*cos(phi), g(X) = |x'/aX|^n + |z'/aZ|^n, Y(X) = +/- aY * (1 - g(X))^(1/n) where g(X) <= 1 — no per-point iterative solve. g is convex in X, so its minimum comes from a 28-iteration ternary search and the two X roots of g(X)=1 from 22-iteration bisections either side of it, exact for any exponent n and any rotation angle. Samples across each slice's [Xlo, Xhi] use a cosine (Chebyshev) warp so point density stays even along the curve rather than bunching where it's flat. SUBJECT is swappable via the `form` prop: `bust` (n=2.4, aX 0.66 / aY 1.0 / aZ 0.6 of R) is an organic, rounded, taller-than-wide head-and-torso proxy; `slab` (n=5, aX 1.0 / aY 0.62 / aZ 0.155 of R) is a flat-faced, thin, rounded-bezel UI card — one closed-form surface family, two presets, same code path. DENSITY IS THE ONLY CUE: every dot in a frame is the SAME flat alpha within its color group (0.55 for the rim group, 0.5 for the interior group); brightness comes only from how many same-alpha dots land on the same pixels under plain source-over compositing, never from an opacity or lighting ramp, and there is no surface-normal or lighting term anywhere in the code. COLOR is exactly two tokens, chosen per HARD-WON RULE 2: each slice's own extent (Xhi - Xlo) is compared against the widest slice drawn that frame; slices at or above 55% of that width — the ones piled up near the true silhouette — draw --foreground, the narrower slices near the poles draw --ns-muted. No --ns-accent anywhere: this is a resting background, not interaction chrome. Because dots are always drawn --foreground over a --background fill, the rim/interior contrast is correct in both themes with zero branching: dark theme accumulates light dots into a bright rim over a darker, sparser middle, and light theme accumulates dark dots into a dark rim over a lighter, sparser middle — the same overlap math, read oppositely by the swapped token values. ALIVE AT REST: the solid spins continuously about its vertical axis, one full revolution every 17s (rotateMs, default 17000), with zero pointer input; the comb visibly migrates around the silhouette as it turns, and the three-slices-of-time check (t=0s / 2.5s / 5s with nothing touched) shows a genuinely different rotation angle and comb position each time. prefers-reduced-motion freezes at STATIC_PHI = 0.5 rad (~28.6deg) rather than phi=0 — a small nonzero angle chosen so the frozen frame still shows an asymmetric silhouette (proof it's a spinning 3D solid, not a static flat ring) while the rim stays unambiguously denser than the interior. CONTENT: eyebrow/headline/subcopy/CTA are real DOM, centered over the canvas and given a translucent bg-background/60 backdrop-blur-[2px] scrim for legibility — placed there deliberately, because the geometry's own near-pole slices (the sparsest ones) naturally collapse toward screen center at low rotation angles, so the dark, empty part of the mechanic is exactly where the type already needs to sit. SCALE: R (the solid's characteristic half-size) is derived from R = min(containerWidth, containerHeight) * 0.42, so the composition holds real margin and reads correctly from full hero width down to a small registry card — slice count (16 to 48) and per-slice sample count (12 to 40) also scale off the container's smaller dimension via Math.round(minDim / 14) and Math.round(minDim / 13), so a small card still shows a legible handful of distinct slices instead of gray mush. PERFORMANCE BUDGET: at the largest hero size (slices=48, samples=40) each frame does roughly 48 slice solves x 50 g-evaluations (28 ternary + 2x22 bisection) for the root-finds plus roughly 48 x 80 g-evaluations for the sampled ring points, on the order of 15,000 Math.pow-bearing evaluations and under 4,000 drawn dots total — canvas 2D fillRect only, no WebGL; backing store capped at devicePixelRatio 2; the render loop is paused via IntersectionObserver (threshold 0) when scrolled offscreen and via visibilitychange when the tab is hidden, and a paused prop freezes the spin without unmounting. TOKENS: --foreground/--ns-muted/--background are read via getComputedStyle(document.documentElement) inside a useLayoutEffect (so nothing paints before the read, no color literals or fallbacks anywhere in the draw path) and re-read on a MutationObserver watching documentElement's class attribute. A11Y: the canvas is aria-hidden and pointer-events-none; all real content (headline, subcopy, CTA link/button) renders as normal focusable DOM above it in natural tab order, unaffected by the canvas."
      }
    },
    {
      "name": "hero-string-envelope",
      "type": "registry:ui",
      "title": "Hero String Envelope",
      "description": "A hero whose backdrop is pure string art: a few hundred straight chords across a circle under the map i → k*i, whose envelope is an epicycloid caustic that the chords draw without any curve ever being plotted; the pointer's horizontal position continuously morphs k, so cusps split and merge under the cursor.",
      "files": [
        {
          "path": "registry/loud/hero-string-envelope/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-string-envelope.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "svg",
          "string-art",
          "envelope",
          "epicycloid",
          "caustic",
          "generative",
          "cursor",
          "geometry"
        ],
        "instruction": "A hero whose entire backdrop is a single string-art construction rendered as plain SVG: `points` (default 240) nodes are placed on a circle of radius R = 0.42 * min(w, h) centred on the hero box, node i at theta_i = 2*pi*i/N, and chord i is the straight <line> segment from theta_i to k*theta_i — the continuous generalisation of the classic modular-multiplication string art, with no mod and no rounding, so k is a real number and the whole family morphs smoothly instead of snapping between integers. The envelope of that chord family is an epicycloid with (k - 1) cusps: k = 2 gives the cardioid, k = 3 the nephroid, k = 5 four cusps. Nothing ever plots the epicycloid, and that is the entire honesty of the piece as well as its contrast mechanism — chord density near the envelope is an order of magnitude higher than in the interior, so 1px hairlines at strokeOpacity 0.26 accumulate into a bright caustic edge over a much fainter interior mesh, with no fills, no gradients and no glow anywhere. AMBIENT: k_base(t) = 3 + 0.35*sin(2*pi*t/18s) + 0.08*sin(2*pi*t/6.7s), a calm breathing around the nephroid plus a small incommensurate jitter term, so the cusps sharpen and soften instead of holding a dead pose; the generating circle additionally carries an aspect ratio a(t) = 1 + 0.06*sin(2*pi*t/25s) applied to y, skewing the caustic slightly. Geometry is recomputed on a throttled rAF accumulator (~24fps — 240 closed-form sin/cos pairs per tick is trivial) and written directly as x1/y1/x2/y2 attributes onto the existing <line> nodes via refs: never React state on the hot path, and never a re-mount of the node list. POINTER: normX = (clientX - rect.left)/rect.width drives k_target = k_base + 1.1*(2*normX - 1), integrated every animation frame (not just draw ticks) through a damped spring (k = 90, zeta = 0.85), so the cusp count glides between roughly two and four as the cursor crosses the hero and the cusps visibly split and travel around the circle; normY tilts the generating circle's aspect by a further ±0.18 through the same spring, so vertical position skews the caustic. On pointerleave both spring targets return to the ambient breathing values and the nephroid re-forms. Pointer tracking is bound to the hero <section> and restricted to mouse/pen (touch has no hover state and a move-during-scroll would read as a jump in k); the SVG layer itself is aria-hidden and pointer-events-none, so the real CTA underneath is untouched. TOKENS: every stroke is a presentation attribute reading a custom property directly — stroke=\"var(--foreground)\" at strokeOpacity 0.26 for the chord family, stroke=\"var(--border)\" at strokeOpacity 0.5 for the single node ellipse — so both light and dark themes repaint through the cascade with no getComputedStyle and no MutationObserver at all, which is the correct strategy for an SVG component (the canvas components in this registry need the observer; this one does not). --ns-accent appears exactly once in the whole component: the focus-visible outline on the CTAs (outline-2 / outline-offset-2 / outline-ns-accent, the registry-wide pattern), keeping the accent scarce; the primary CTA is a solid bg-foreground/text-background button and the secondary a bordered ghost in --border/--ns-muted. The rAF loop pauses on IntersectionObserver (hero offscreen) and on document visibilitychange (backgrounded tab), and the viewBox plus all geometry are re-derived from a ResizeObserver on the hero, so reflow never leaves the construction anchored to stale dimensions. Under prefers-reduced-motion the component renders exactly one frozen frame at t = 0 with k = 3 and aspect = 1 — no rAF loop and no pointer tracking is ever bound — and the static caustic alone still carries the hero, because the structure is geometric rather than animated. Zero dependencies.",
        "rank": 18
      }
    },
    {
      "name": "hero-text-ring-funnel",
      "type": "registry:ui",
      "title": "Hero Text Ring Funnel",
      "description": "A full-bleed hero where readable running text is the geometry itself: seven concentric rings of real words, laid out once by rotating and translating the 2D context per character rather than quantizing to a fixed grid, tapering in true single-point perspective and spinning at differential speeds so the growing phase lag between rings reads as depth.",
      "files": [
        {
          "path": "registry/loud/hero-text-ring-funnel/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/hero-text-ring-funnel.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "typography",
          "canvas",
          "3d",
          "funnel",
          "text"
        ],
        "instruction": "Build <TextRingFunnel ringText? children? className?> as a full-bleed Canvas 2D hero: seven rings sit at FIXED world depths evenly spaced across [Z_NEAR 0.62, Z_FAR 3.2] — a static funnel, not a treadmill, nothing translates in z. Each ring's screen radius and glyph size are BOTH K1*(1/z) of the same world constants (WORLD_R for radius, WORLD_GLYPH for font size), the standard single-point-perspective divide, so a ring twice as deep is both half the radius and half the type size together, never one without the other; glyph size is floored at MIN_FONT_PX=9 so far rings never fall below legibility, and radius is floored at 2.2x that ring's font size so the circle always physically fits its own type. TEXT ON A CIRCLE, NOT A GLYPH GRID: each ring's layout (which characters, at what angle) is computed ONCE per resize by walking the circle and accumulating each character's real proportional advance (offscreen measureText, cached per glyph+font-size pair, cleared and rebuilt after document.fonts.ready) until the circumference is filled, capped at 140 characters/ring; the character itself is then drawn by rotating the 2d context to that angle, translating out by the ring's radius, rotating again to orient the glyph tangent to the circle, and calling fillText at the origin — classic circular typesetting, never a fixed-cell raster lookup. DEPTH FROM PHASE LAG, NOT FROM Z MOTION: every ring accumulates its own persistent rotation angle every frame at a fixed angular speed that decreases with depth (0.46rad/s nearest down to 0.09rad/s farthest, a ~5x spread) — because the fixed per-character layout is simply replayed under one extra ctx.rotate() per ring per frame, the growing phase gap between neighboring rings is the entire per-frame cost and the entire depth cue; the far half of rings additionally renders in --ns-muted while the near half renders in --foreground, with a continuous alpha ramp inside each half, so the near/far read survives a hue-only theme flip in both polarities. Sparse motes (36, muted only, low alpha) drift radially between ring gaps and angularly around them for texture in the empty space. LEGIBLE CENTER: a caller's children render as real DOM, centered at the vanishing point; the ring nearest the content's measured bounding circle feathers its alpha to zero over a 56px band and any ring fully behind that radius is skipped, so the headline zone stays clean without the ring's characters visibly ending on a hard edge. Concentric rings never overlap in projected radius, so a single farthest-to-nearest draw order is a correct depth sort with no per-pixel depth buffer needed. Render loop pauses via IntersectionObserver (root offscreen) and visibilitychange (tab hidden), DPR capped at 1.5. prefers-reduced-motion freezes every ring's angle at exactly STATIC_TIME=2.2s of its own accumulated rotation rather than t=0, specifically because the differential angular speed only visibly separates ring phases once time has passed — a t=0 freeze risks rings that still look aligned by the luck of their random starting angles. The ring phrase is exposed once as a visually-hidden real DOM string for assistive tech (the canvas glyphs are decorative paint of the caller's own copy); the canvas itself is aria-hidden. Props: ringText (the phrase tiled around every ring, default a neutral line about the component itself), children, className. SIZE LIMIT: legibility depends on container size because both radius and font derive from the container's shorter side — below roughly a 420px-tall container the farthest rings sit at the MIN_FONT_PX floor and the near/far size contrast that sells the taper visibly flattens; this is a real limit of a text-as-geometry hero and the component does not attempt to hide it below that height."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 --ns-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 --ns-muted ring hints at the four centers. No SVG, no dependencies."
      }
    },
    {
      "name": "interlace-field-comb",
      "type": "registry:ui",
      "title": "Interlace Field Comb",
      "description": "A full-bleed background reproducing analog interlaced video's defining artifact: two temporally-offset fields drawn a beat apart, disagreeing on a moving diagonal edge and serrating it into a comb, then periodically re-weaving into one clean frame.",
      "files": [
        {
          "path": "registry/loud/interlace-field-comb/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/interlace-field-comb.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "crt",
          "interlace",
          "scanline",
          "monochrome"
        ],
        "instruction": "Build <InterlaceFieldComb children? className? style?> as a full-bleed 2D-canvas background reproducing analog interlaced video's combing artifact. THE MECHANIC: interlaced NTSC/PAL video draws a frame as two temporally-offset fields, odd scanlines then even scanlines, each a separate captured instant. A still frame weaves the two fields into one clean picture; anything that moved between the two field captures makes odd and even rows disagree on where an edge sits, so a diagonal edge serrates into a visible horizontal comb. This is reproduced literally, not simulated with a filter: content is rendered TWICE into two off-DOM canvases (one per field) with a small relative horizontal offset between the two renders, then the visible canvas is built by copying real device-pixel ROWS from each field's buffer in alternating groups of PITCH rows (pitch derived from the container's smaller dimension, clamp(round(minDim/260),1,3) css px, converted to device px) via ImageData row copies and a single putImageData — an actual two-buffer interleave, not a wobble on one buffer. THE OFFSET BETWEEN FIELDS is driven by divergence(t) = maxOffset * (1 + cos(2*PI*t/PERIOD)) / 2 with PERIOD = 4.0s and maxOffset = pitchCss * 2 px; the odd field gets +divergence/2 horizontal offset, the even field gets -divergence/2. This is a DELIBERATELY DECOUPLED, slow visualized cycle (round 9 lesson) rather than a literal 50/60Hz field rate, which would alias against the page's own paint rate and read as a glitch rather than as interlacing. At t=0 divergence is at its maximum (visible comb); it returns to exactly 0 at t = PERIOD/2 = 2.0s, the moment of full re-weave, and repeats every PERIOD after that. CONTENT is three long, soft-edged diagonal bands (angles 24deg, -16deg, 38deg; NOT horizontal — a horizontal edge sitting on a scanline boundary shows no comb at all, so the bands must run diagonally for a horizontal misalignment to visibly stagger their edges into steps), each a rotated ctx.filter-blurred fillRect drifting perpendicular to its own length at 6 css px/s * a per-band speed multiplier, wrapping modulo 1.3x the canvas diagonal so no seam is visible. Softness comes from ctx.filter = blur(thickness*0.55px) on a plain fillRect, the same technique already used by ring-graze/agar-starve in this registry — no gradients needed. THROTTLING: the full field-pair render + interleave (the expensive part: two off-DOM canvas draws, two getImageData calls, a row-copy interleave, one putImageData) is throttled to 30Hz (DRAW_INTERVAL_MS = 1000/30) inside the rAF loop, not run every frame — the simulation clock itself still advances every rAF tick so DRAW_HZ throttling never desyncs the divergence curve from wall time. PALETTE: --foreground and --ns-muted read via getComputedStyle(document.documentElement), re-read on a documentElement class MutationObserver; bands alternate between the two tones (alpha 0.3-0.5) with no accent anywhere — the mechanic has no pointer interaction to justify one. HOST: DPR capped at 1.5 (this component's per-frame cost is two full-canvas raster ops plus row copies, not a single shader pass, so it stays conservative like weld-pool rather than flyback-tear's uncapped 2). ResizeObserver (120ms debounce) reallocates all three canvases (visible + both off-DOM field buffers) and recomputes pitch/maxOffset for the new size; IntersectionObserver (threshold 0) and visibilitychange both pause the rAF loop, since a full-bleed per-frame raster op running offscreen is the most expensive idle thing the page can carry. prefers-reduced-motion freezes the clock at STATIC_TIME = PERIOD/2 = 2.0s — the exact re-weave instant, chosen because it is the most structured frame (the three bands fully visible with zero comb) rather than t=0's already-diverged state. No colour literals anywhere. No pointer interaction of any kind — the mechanic is a fixed timing artifact, not a hover effect, and the showpiece recipe's accent-highlight failure mode does not apply because nothing here is pointer-driven."
      }
    },
    {
      "name": "joint-iron",
      "type": "registry:ui",
      "title": "Joint Iron",
      "description": "A footer whose entire surface is a bookbinding building-in machine: three stations set a French groove under a heated brass iron, hold it through a real dwell, and release it with a measured 14% spring-back — the sitemap and legal line sit on the same case tone the groove is cut into.",
      "files": [
        {
          "path": "registry/loud/joint-iron/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/joint-iron.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "footer",
          "sitemap",
          "canvas",
          "groove",
          "bookbinding",
          "press",
          "newsletter"
        ],
        "instruction": "Build <JointIron brand? tagline? columns? legal? newsletterPlaceholder? onSubscribe? className?> where columns is JointIronColumn[] ({heading, links: {label, href}[]}), defaulting to a 4-column sitemap so the component needs zero props. STRUCTURE: a <footer data-joint-iron> whose canvas is the ENTIRE footer surface (absolute inset-0, not a strip above the content) — a scrim panel (bg-background/70 backdrop-blur-sm) holding the wordmark, a newsletter form, the sitemap columns and a copyright line sits in normal DOM flow starting below the forming zone, on top of the SAME case tone the groove is cut into. THE MECHANIC, real and load-bearing: the top of the footer is a book joint being formed in a building-in machine. One height field per station, g(x): a cosine bump centred on the station (bump(d) = depth * 0.5 * (1 + cos(pi*d/hw)), d = x - stationCenterX, hw = 1.44 * depth — the groove's WIDTH shrinks with its depth, not just its floor). Case surface is bg mixed with fg at alpha 0.16 (light) / 0.22 (dark), painted below a resting line at min(0.14 * canvasHeight, 56px) — clamped so the groove zone always clears the DOM scrim's top offset even on a very tall footer. The groove is carried ENTIRELY by a two-facet Lambert-style shade, never by swapping in raw --background (that reads as a stroke, not a formed material): each column's shaded rect runs from the resting line down by `bump` (so the shaded region's own height, not just a boundary line, grows with depth), and its colour is chosen from shade = clamp(-0.35 + wallFactor, -1, 1), wallFactor = -sin(pi*d/hw) — the -0.35 ambient term (builder-chosen, not in the spec) sinks the whole notch a little below the flat case so the floor, where wallFactor -> 0, still reads as a shadowed recess instead of vanishing back to case tone. NAMED REGRESSION, kept here because it is the exact failure mode that got a prior footer (footing-course) removed from the registry: the first build of this component rendered the notch by reverting the dipped region to raw --background and drawing a thin lit lip along the new boundary, which reads as 'an animated top border' — kill criterion territory, since a pressed groove is still case material, recessed and lit, not a hole cut through it. Caught in review before ship and reworked to the shade-fill approach above; if this component is ever rebuilt from this instruction alone, do NOT reintroduce a --background erase inside the notch. Positive shade composites --background (dark theme: --foreground) at up to alpha 0.34 * shade; negative shade composites the opposite token at up to alpha 0.34*(0.17/0.15) * -shade — the 0.15/0.17 asymmetry is the spec's real facet-contrast numbers, translated into canvas alpha rather than literal luminance math. THREE STATIONS, one per third of the canvas width, each independently running a 7.4s five-phase cycle offset by 2.6s from its neighbour (deliberately not an exact third, tuned so prefers-reduced-motion's frozen frame shows one station mid-release, one mid-dwell, one mid-index at once): index (0.85s, the previous cycle's finished groove — held depth = 0.86 of the dwell-end depth, i.e. the post-spring-back value — slides out of the station's clip rect while nothing new is drawn, standing in for the unformed arrival), descent (0.62s, depth follows 1 - exp(-t/0.28), reaching 0.89 of g_max = 0.055 * min(canvasWidth, canvasHeight)), dwell (3.6s, depth creeps the last 11% toward g_max on a 1.4s time constant — the longest phase, deliberately, since setting a joint is mostly waiting), release (0.45s, THE SIGNATURE MOMENT: over the first 170ms, depth drops by exactly 14% via an eased spring, and simultaneously an extra 'shoulder-rounding' factor (1 - 0.09*ease) multiplies the facet contrast on top of the depth-linear scaling, so all three channels — floor depth, facet width (hw = 1.44*depth, so it narrows 14% too), and facet contrast — move together over the same 170ms; depth alone is below the perceptual floor at card scale and must not be trusted on its own), lift (1.88s, the iron rises over its first 300ms and a 700ms heat-shimmer runs along its underside). THE IRON is a solid --foreground rectangle (never tinted, never given --ns-accent — this is the trap the spec calls out explicitly) whose fill alpha hunts between roughly 0.82 and 0.90 on an unconditional 2.1s sine (its thermostat cycling, running whether the iron is up or down) and whose base alpha (0.86, not 1.0) exists specifically to leave headroom for the shimmer: the shimmer is a shadowBlur glow using the SAME --foreground token as both fillStyle and shadowColor, which is the only token-only way to 'brighten' a silhouette that is already at the bright extreme of the palette in dark theme and the dark extreme in light theme. A static seeded board-grain texture (mulberry32, fixed seed, baked once per resize AND re-baked on a theme flip to an offscreen tile, blitted every frame, never regenerated per-frame) fills the case below the groove zone; below a 88px canvas height, station count drops from 3 to 2 and the grain is dropped, while the groove and iron are kept, because the groove is the component. Reduced motion draws exactly one frame at STATIC_TIME = 2.62s via the same pure draw(t) function used by the live loop — the iron's thermostat term is also a function of t, not performance.now(), specifically so re-renders of the frozen frame stay byte-stable rather than resampling wall-clock. Canvas: DPR capped at 2, ResizeObserver on the footer element (not window), and the rAF loop runs only when an IntersectionObserver (threshold 0) reports the footer intersecting AND document.visibilityState is 'visible' — both conditions tracked separately so a background tab that scrolls the footer into view, or a foreground tab where it's merely offscreen, cannot restart the loop on its own — plus an adaptive column-step (1px -> 2px) that only widens after 1.5s of sustained sub-30fps frames, gated on wall-clock time, never frame count. Tokens (--background, --foreground, --ns-muted, --border) read via getComputedStyle(document.documentElement) with no literal fallback and re-read on a MutationObserver watching documentElement's class; nothing paints until both resolve, guarded on the rAF-driven boot retry, the ResizeObserver callback and the IntersectionObserver resume path. --border appears only as the 1px hairline between the canvas band and the page above and as the sitemap's column rules — never as a fill, never as the groove. --ns-accent appears only on the newsletter submit button and on focus-visible rings. The DOM footer content (wordmark, newsletter form with a real onSubscribe(email) callback, sitemap columns, legal line) is real, keyboard-navigable, semantically ordered <footer> markup sitting in a bg-background/70 backdrop-blur-sm scrim over the case tone — it is never canvas text."
      }
    },
    {
      "name": "jominy-quench",
      "type": "registry:ui",
      "title": "Jominy Quench",
      "description": "A hardenability comparison card built on the real ASTM A255 / ISO 642 Jominy end-quench test: a water jet sprays one end of a heated steel bar continuously, a directional quench front creeps up the bar, and hardness falls off with distance from that face along a curve that is each alloy's signature, drawn beside a real numeric table so the comparison never lives only in the picture.",
      "files": [
        {
          "path": "registry/loud/jominy-quench/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/jominy-quench.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "comparison",
          "chart",
          "benchmark",
          "canvas",
          "metallurgy",
          "table",
          "spec-sheet"
        ],
        "instruction": "Build <JominyQuench bars? title? className?> where bars: JominyBar[] ({id, name, hardness: number[]}) each hold Rockwell-C hardness readings at nine standard Jominy distances from the quenched face (1/16\" to 2\", DISTANCE_LABELS). This is the real ASTM A255 / ISO 642 end-quench hardenability test: a bar is austenitised, then a water jet is sprayed against ONE end only; that face cools fastest and hardens most, and hardness falls off with distance along a curve that is the alloy's signature — comparing several bars means comparing several fall-off curves, which is the mechanic, not a metaphor. Default bars (1045, 5160, 8640, 4140) are shaped to the well-documented qualitative behavior — more alloy content flattens the fall-off — rather than lifted from one mill certificate, and are labelled as illustrative in the demo copy. LUMINANCE ADAPTATION (read this before touching the palette): the real test is intensely hue-coded, quenched steel running white -> yellow -> orange -> red -> black as it cools, but this registry has five value-only tokens and no orange. Temperature is deliberately mapped to LUMINANCE instead: the hottest point on a bar is always blended toward --foreground (maximum contrast against --background) and the coldest, just-quenched point always fades toward --background — a mapping that self-inverts correctly between light and dark theme because 'closest to --foreground' already means the salient value in either polarity, never a literal hue swap. ONE canvas runs all rows: a fixed 7.2s cycle per row (reheat 10%, quench-front creep 58%, hold 32%), phase-staggered by 16% per row so the cascade of bars never settles into one static pose — visibly different at t0, 2.5s and 5s with no input. Each row draws, left to right: a horizontal bar whose fill is a per-frame linear gradient sampled from the heat-at-x function (cold/quenched reads background-leaning, hot/unquenched reads foreground-leaning, with a soft residual gradient toward the far end, never a hard cliff); a vertical front-line marker at the current quench-front x, visible only mid-creep; and, above the bar, that alloy's hardness fall-off curve, revealed left-to-right in lockstep with the front position so the curve visibly draws itself as the quench proceeds, with a small lead dot at the point currently being traced. At the quenched face a continuous water-jet stroke plus four looping steam-plume puffs run on real elapsed time, independent of the quench-cycle phase — the jet never stops hitting the face, at rest or mid-cycle. Row geometry (bar thickness, curve band height, plume size) derives from rowH = clamp(48, 84, containerWidth * 0.15) so the mechanic stays legible at small preview-card widths rather than reading as a thin illegible sliver. Colour is read via getComputedStyle(document.documentElement) at mount and re-read on a MutationObserver watching documentElement's class (duplicated per-component per house convention, no shared lib/ helper); --ns-accent appears ONLY as the hover/focus highlight stroke on a compared bar's row (triggered by pointerenter/focus on that row's real <button> grade label) — never mixed into the resting heat ramp. Direct-DOM rAF, DPR-capped backing store (min(2, devicePixelRatio)), pauses on IntersectionObserver (root offscreen) and document visibilitychange. ACCESSIBILITY: the canvas is aria-hidden and purely decorative; every value the comparison depends on is real DOM text in a <table> beneath it — one row per bar with a <th scope=row> button (the same element that drives the hover highlight) and one <td> per distance holding the literal HRC number plus a sr-only ' HRC' unit, so a screen-reader user gets the full comparison independent of the canvas. prefers-reduced-motion freezes on a deliberately composed frame — front parked at 55% up the bar (so the directional-cooling mechanic is still visible) with the fall-off curve forced to FULLY drawn regardless of front position (bypassing the normal front-tied reveal specifically so the frozen frame shows both the mechanic and the complete signature curve at once) and the water jet/plume drawn once at a fixed pose, never animated. Props: bars (JominyBar[], default four illustrative grades), title (accessible caption for the table), className."
      }
    },
    {
      "name": "kamacite-etch",
      "type": "registry:ui",
      "title": "Kamacite Etch",
      "description": "A full-bleed ASCII Widmanstätten pattern: interlocking nickel-iron crystal lathes locked to the four octahedral lattice angles, surfacing out of blank metal as a slow diagonal acid-etch front sweeps the surface over about 90 seconds and loops by repolishing the oldest region rather than resetting.",
      "files": [
        {
          "path": "registry/loud/kamacite-etch/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/kamacite-etch.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "ascii",
          "canvas",
          "meteorite",
          "crystallography",
          "etch",
          "widmanstatten"
        ],
        "instruction": "Build <KamaciteEtch cellSize? className?> as a full-bleed <canvas>, aria-hidden and pointer-events-none — it never listens for input. LATTICE, built once per resize: four lath-family orientations are derived from the octahedral dihedral angle OCTA = acos(1/3) ~= 70.5288deg (the angle between the normals of any two adjacent {111} faces of the parent taenite octahedron), as angle_k = (k * OCTA) mod PI for k = 0..3, giving four fixed in-plane directions (~0deg, ~70.5deg, ~141.1deg, ~31.6deg) rather than an arbitrary or evenly-spaced set. Each family gets its own set of parallel boundary 'wall' lines built by projecting the four canvas corners onto that family's normal direction and walking outward from the min projection in irregular steps (base spacing distinct per family — 7.5, 6.2, 8.6, 7.1 cell-pitches — each step jittered +-22%, so lath thickness varies band to band while every wall stays dead straight). OWNERSHIP: for every glyph cell, per family, a binary search over that family's boundary array finds the enclosing band and a residual = 1 - (distance to nearest wall)/(half band width), 1 at the band's centre and 0 at its wall. The cell is OWNED by the family with the highest residual (deepest local interior, not nearest line) — since each family's residual is independently ~Uniform(0,1) regardless of its band width, this argmax splits the plane close to evenly across all four families and produces a genuine interlocking parallelogram tiling at the fixed angles, distinct from a point-based Voronoi partition and from an organic grown boundary. Per cell this build pass also stores: owning family (0-3), a per-band contrast in roughly [0.5, 1.0] from a cheap 32-bit hash of (family, bandId) boosted up to +28% the closer the cell sits to its own family's wall (interlocking seams read a little brighter, like a grain boundary catching the etch, clamped so brightness never exceeds 1 at render time), a shimmer phase = (projection of the cell onto its OWN family's axis direction) * 0.02 plus a per-band offset of 1.37 rad (decorrelates adjacent bands so they don't shimmer in lockstep), and a sparse ~14%-density 'grain' flag from an independent seeded stream. ETCH FRONT, evaluated fresh every frame, no simulation state carried between frames beyond elapsed time t: each cell has a fixed sweep coordinate s in [0,1], its position along a diagonal axis at 52deg — deliberately NOT one of the four lattice angles, since this is the physical acid-bath sweep direction, unrelated to the crystal. frontPos = t / 90 grows without bound (t accumulated in seconds), so a fixed s is first reached by the front at t = s * 90 seconds — the front visibly crosses the full diagonal once every 90 seconds. delta = frontPos - s, taken modulo a cycle width S_RANGE = 0.05 (rise) + 0.78 (idle) + 0.10 (fall) = 0.93 turns that unbounded growth into a periodic per-cell pulse: for the modded value in [0, 0.05) developed ramps 0->1 via a smoothstep (surfacing out of blank metal); in [0.05, 0.83) developed is held at EXACTLY 1 (fully etched, idle); in [0.83, 0.93) developed eases 1->0 via a smoothstep (repolished, fading back to blank) — and because that fall completes and reaches 0 before the front's own unbounded growth would need to jump back to a starting value, the loop point is always a region already faded to blank, never a visible reset. Cell brightness = developed * that cell's precomputed contrast; ONLY where developed === 1 exactly (the idle interior, never mid-ramp) add SHIMMER_AMP=0.09 * contrast * sin(t * 0.6 + that cell's shimmer phase), then clamp to [0,1]. GLYPH AND COLOUR: cells at brightness >= 0.32 draw the owning family's oriented character — one of '-', '|', '\\', '/', chosen by which of those four the family's angle rounds nearest to — in the --foreground colour, with alpha bucketed into 6 levels (0.22 to 1.0) by brightness so it reads as real oriented line-work, not stipple. Cells below that cutoff draw nothing UNLESS they were flagged 'grain' at build time, in which case they draw a faint '.' in the --ns-muted colour at a small constant alpha (0.15) — the raw polished metal's own micro-texture, visible wherever the crystal pattern hasn't grown over it yet, or has just repolished away. The canvas itself is filled with --background every frame before drawing (not left transparent), so the component is a self-contained backdrop that doesn't depend on a parent element already painting that token. All three colours are read via getComputedStyle at mount (--background and --ns-muted from the document root, --foreground from the canvas's own computed .color via a text-foreground class) and re-read on a documentElement class MutationObserver, so theme flips are live without a remount. Direct-DOM rAF, zero React state on the hot path; typed arrays (sArr, contrastArr, phaseArr as Float32Array, ownerArr and a grain flag as Uint8Array) are sized once per resize and the lattice/build pass reruns on every resize (debounced 150ms), never on every frame. prefers-reduced-motion draws exactly one static frame with developed forced to 1 everywhere and shimmer skipped entirely — the fully-etched pattern, no front, no fade, no motion — and never starts the rAF loop. The loop pauses on document.hidden and resumes cleanly on visibilitychange (cancelling any frame queued just before hiding, so the front can't advance at double rate). Inside an autoplay/showcase card (closest [data-autoplay-root]) devicePixelRatio is capped at 0.6 rather than 2, matching the sibling ASCII backgrounds' card-cost guard. Props: cellSize (grid cell px, also the scale for lath-family spacing, default 13), className."
      }
    },
    {
      "name": "kymograph-smoke-trace",
      "type": "registry:ui",
      "title": "Kymograph Smoke Trace",
      "description": "A full-bleed hero built on a subtractive recording mechanic: a soot-coated drum turns beneath a fixed stylus that scratches a bright, permanent trace through the dark coating, while a resmoking brush at the trailing edge repaints the outgoing soot fresh so the drum recirculates forever.",
      "files": [
        {
          "path": "registry/loud/kymograph-smoke-trace/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/kymograph-smoke-trace.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "canvas",
          "hero",
          "kymograph",
          "recording-instrument",
          "physiology",
          "drum",
          "ink",
          "mono",
          "ambient",
          "generative"
        ],
        "instruction": "`<KymographSmokeTrace children? className? />` renders a full-bleed hero canvas simulating a 19th-century kymograph: a drum wrapped in soot-blackened paper turns at a constant surface speed (40px/s, hardcoded, not a prop — this specific mechanical character is the component's identity) beneath a fixed stylus that scratches a bright permanent trace through the soot as the drum rotates. Internally the whole drum surface is drawn into ONE PERSISTENT OFFSCREEN CANVAS (`buf`) whose width is the drum's circumference — `max(containerWidth, 480) * 1.8` CSS px (device-scaled by a DPR capped at 1.5) — deliberately wider than the visible window, so a hidden arc of the drum exists off both edges of what's shown. Every rAF frame accumulates real elapsed time into `scrollOffset` (mod the circumference) and the visible `<canvas>` is just a window blitted out of `buf` at that offset (two `drawImage` calls when the window straddles the buffer's wrap seam) — nothing about the trace is redrawn from a sample array; ink drawn into `buf` stays there permanently until the resmoke brush repaints over it. The STYLUS is a fixed point on screen, `WRITE_INSET = 26px` from the right edge; each frame its world-space x-coordinate is `(scrollOffset + WRITE_INSET) mod circumference`, and a line segment is stroked into `buf` (in `--foreground`, 2px, round caps) from the previous frame's write position to the current one at a y driven by a twitch generator: low-amplitude sinusoidal baseline jitter (`±1.5px`, two summed sine terms) plus a discrete twitch event that fires on average every 3.2s (jittered ±0.5s) — a fast rise (0.12s) into an exponentially-decaying (0.6s time constant) ~2.4-cycle ring, amplitude randomized 18-30px — modeled as a muscle-twitch/pulse trace, not literal biodata. The RESMOKE BRUSH is a second fixed point, `BRUSH_INSET = 26px` from the LEFT edge; every frame it repaints the `buf` column(s) currently passing beneath that screen position back to fresh soot (a semi-transparent fill derived from `--ns-muted` mixed toward `--background`, plus a scatter of small randomized-alpha grain rects for texture) — since content only becomes visible again at the write point after crossing the hidden arc, by the time any given patch of drum re-enters view on the right it has already been resmoked clean at the left, which is what lets the loop run forever with no hard reset. All colors come from `getComputedStyle(document.documentElement)` on `--background`, `--foreground`, `--ns-muted`, re-read on a `MutationObserver` watching `documentElement`'s class/data-theme attributes and a `prefers-color-scheme` `matchMedia` listener (already-painted soot keeps its color until the next repaint touches it, by design — no hard re-tint sweep). `--ns-accent` is never used anywhere; this is a fully ambient, non-interactive hero (`pointer-events-none` on the canvas, `aria-hidden`) with `autoplay: mode: none` because it animates entirely on its own internal clock and there is nothing for a synthetic pointer driver to do. REDUCED MOTION: instead of the rAF loop, a deterministic replay runs synchronously at mount — a seeded PRNG (`mulberry32`, fixed seed) drives the exact same `step()` function through fixed 1/60s ticks from t=0 to `STATIC_TIME_S = 9.8` (chosen because it lands mid-revolution with a substantial scratch trace and at least two completed twitch spikes already inked, not the empty t=0 frame), then a single `blit()` is called and the loop never starts — the canvas is genuinely byte-stable afterward, not just slowed down. The rAF loop pauses on `document.hidden` (`visibilitychange`) and via an `IntersectionObserver` (threshold default) while scrolled offscreen, both resuming through the same `startLoop()` path used by resize and theme/reduced-motion changes; `ResizeObserver` on the root re-derives `w/h/dpr`, rebuilds `buf` at the new circumference and restarts (a visual restart on resize is an accepted tradeoff, not a correctness bug). `children` render as an ordinary absolutely-positioned DOM overlay on top of the canvas (untouched by any of this — a headline/CTA can sit on it with a token scrim, same pattern as `flyback-tear`). Zero dependencies, DOM+Canvas 2D only, no WebGL."
      }
    },
    {
      "name": "lamina-dome",
      "type": "registry:ui",
      "title": "Lamina Dome",
      "description": "Stromatolites accreting upward from the bottom edge of a full-bleed pane: a heightfield of columns competes for a light-occlusion clearance integral cast in a cone toward a slowly arcing light direction, so a few tall domes shade and outgrow their neighbours while a slow sea-level line periodically drowns and revives the whole front, and every ~2s the front is committed as a banded lamina stripe until the field reads like cut stromatolite rock.",
      "files": [
        {
          "path": "registry/loud/lamina-dome/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/lamina-dome.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "canvas",
          "simulation",
          "heightfield",
          "geological",
          "monochrome",
          "ambient",
          "decorative",
          "footer"
        ],
        "instruction": "Build <LaminaDome coneHalfAngleDeg? growthRate? speed? paused? children? className? style?> as a full-bleed canvas backdrop where a bottom-anchored stromatolite front accretes upward from a light-occlusion competition between neighbouring columns, never from summed sine bumps or a noise field with stripes drawn over it. Internally it holds a heightfield h[x], one column per roughly 2 css px (coarsened under an internal column budget on very wide panes). Every simulation tick, in order: CLEARANCE — for each column, cast K rays spanning a cone of `coneHalfAngleDeg` on either side of the current light direction; each ray marches upward in fixed height steps, and at each step its height is compared against whatever neighbouring column its horizontal offset now lands on — a neighbour tall enough to reach the ray's height blocks it, and the rest of that ray's steps stop counting. L[x] is the fraction of ray-steps, averaged over all K rays, that stayed unblocked: 1.0 for open sky across the whole cone, lower the deeper a column sits in another column's shadow. DEPOSIT — h[x] += growthRate * L[x] * dt, then a fixed 0.2 surface-tension blend with both neighbours (not exposed as a prop: below 0.1 the front grows hairline single-column spikes that read as glitch, above 0.4 neighbouring domes blur into one mound, 0.2 sits in the middle). This is the whole coarsening mechanism and it is falsifiable at the pixel level: a column whose growth stays low because a taller neighbour keeps shading its cone falls further behind every tick, so a few tall columns keep winning clearance and pull away while many short ones measurably stop accreting once they're buried in shadow — nothing here decides in advance which columns win. `coneHalfAngleDeg` (default 30, matching a 60-degree cone) is the single governing scalar and alone traverses the real morphospace: narrow (~10) tests only near-vertical sky, so a column is shaded solely by whatever sits almost directly upslope of it and the front stays columnar, many similar-height ridges; wide (~65) tests a broad hemisphere, so nearly any taller neighbour nearby blocks you and only true local maxima stay lit, coarsening hard into a few broad domes. The light direction arcs slowly (a steady lean plus a ~6s sine swing on top of it, clamped well short of grazing), so domes accrete more on the side of their crest that keeps clearance toward that lean and visibly lean the way real fossil stromatolite domes lean toward palaeo-north — but swinging the shadow ANGLE alone never relocates which column is tall in the first place, since light-occlusion coarsening is rich-get-richer (a column with any early lead keeps winning clearance at whatever angle the light currently holds); an earlier version tried compensating with a travelling favoured-growth-rate strip (a raised-cosine boost near a sweeping centre, throttled everywhere else) and that produced real sideways motion but the throttle suppressed deposit almost everywhere almost always, so the constant per-tick surface-tension diffusion had nothing to fight and flattened the ridge to near-zero amplitude — variance and migration were sharing one knob in opposite directions. CREST DRIFT is the mechanism that replaces it and holds both properties at once, because it is a translation rather than a growth-rate modulation: translating a heightfield preserves its peak-to-trough variance exactly, by construction, so migration and amplitude stop competing. Every tick a fractional-columns accumulator advances at a fixed columns/second rate; once it banks a whole column, the ENTIRE column arrays — the live heightfield AND every already-committed lamina snapshot — are rotated sideways by exactly that many whole columns, wrapping at the pane edge. This is a lossless integer relabelling, never a fractional/interpolated shift: no height value is itself resampled or blended, so the rotation cannot inject or remove silhouette variance the way even a small continuous shift with interpolation would. Because the whole committed stack advects together (not just the live front), no older band is ever left behind to poke through a newer one — the ridge and its rock read as one solid mass travelling continuously in one direction, wrapping and never rocking back and forth, like a reef belt migrating along a shoreline compressed into a card-scale glance. Measured on this component's own render, at a 480x300 css viewport: silhouette amplitude (peak-to-trough height variance across the pane at a single instant) holds at roughly 85-105px both with drift running and with it disabled, confirming the translation is amplitude-neutral; the tallest-column crest (tracked as the argmax of a box-blurred height profile, to avoid mistaking a near-tie flip between two similar peaks for motion) travels roughly 55-60px within a 3s window with a max single-step jump of a few px (smooth, not a teleport), and travels roughly 650-700px across a ~29s window spanning more than one full lap of the drift — against a near-flat ~0-25px baseline over the same 29s with drift disabled. A single sea-level scalar chases the field's mean height slowly (so it stays relevant as the front keeps growing) with a ~55s sine riding on top of that; a column currently below sea level has its deposit multiplied by a small drown factor, so the whole front nearly stalls together under a transgression and wakes back up together on the following regression — a faint dashed line at --border marks the current sea level whenever it's on screen. Every ~2000ms the live front polyline (h[x] at that instant) is committed as a lamina — a snapshot pushed onto an array capped at 200: once over the cap, the OLDEST TWO snapshots are averaged into one rather than the oldest simply dropped, real compaction that also bounds memory. Render never displays raw height directly: capPx = 40% of the pane's css height, and every render computes scale = capPx / max(runningMaxHeightEverSeen, capPx), so the tallest column across the whole simulation's history is always pinned at capPx and everything else, laminae included, compacts proportionally as the field keeps growing in raw units underneath — the front never has to be clamped or truncated, it just optically saturates, which is what keeps copy above it from ever having to fight it. That same render scale is then modulated +/-9% by a sinusoid in exact lockstep with the sea-level oscillation (same period, same phase) that already multiplies a drowned column's deposit by DROWN_FACTOR — so as the sea transgresses toward a peak the whole displayed ridge (every peak plus every already-committed lamina beneath it, uniformly) reads shorter, and as it regresses the ridge reads taller again, even though no column's underlying raw height ever decreases. This is a second, independent motion on top of the lateral migration above: not per-peak sideways movement, but the whole silhouette visibly breathing (rising and falling in place) on the same physically-grounded cadence that already governs growth suppression, reusing the model's existing shared scalar instead of inventing an unrelated oscillator. Each band between two consecutive laminae (and the live top band between the newest lamina and the current front) is filled as a closed canvas path, alternating two colors mixed from --ns-muted and --foreground over --background (read via getComputedStyle(document.documentElement) at mount and re-read on a documentElement class mutation, i.e. theme toggle) so the finished field reads as alternating banded rock. The canvas is aria-hidden and pointer-events:none; there is no interactive control anywhere in this component and `children` render as ordinary accessible DOM over the plate, positioned by the consumer (typically pinned to the top of the pane, since growth never exceeds 40% of pane height from the bottom). `growthRate` (default 10) is height-units/second for a fully unshaded column; `speed` is a live global tick-rate multiplier; `paused` freezes the current frame without unmounting (polled via a ref, not an effect dependency, so toggling it never tears down and reseeds the field). prefers-reduced-motion runs the tick function synchronously at mount until roughly 80 laminae exist (hard-capped so a pathological prop combination can't hang the mount) and paints that one static banded field, never scheduling a rAF loop; a live change to the media query freezes or resumes growth from wherever it currently is. ResizeObserver rebuilds the column array (debounced) on a real width change and reseeds with small per-column noise so the light competition has something to break symmetry from; a height-only change just re-renders at the new cap, no reseed. IntersectionObserver and visibilitychange stop the single rAF loop off-screen or in a hidden tab. A deterministic xorshift PRNG seeds the initial per-column noise, so the shipped resting frame and the reduced-motion static frame are reproducible mount to mount rather than drifting between screenshots."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 --ns-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": "magazine-drop",
      "type": "registry:ui",
      "title": "Magazine Drop",
      "description": "A full-bleed gallery driven by a gravity-fed slide magazine: a rotary drum indexes against a detent, releases a plate, and the plate falls into the gate and seats over two decaying bounces.",
      "files": [
        {
          "path": "registry/loud/magazine-drop/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/magazine-drop.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "gallery",
          "carousel",
          "canvas",
          "physics",
          "drag",
          "transport"
        ],
        "instruction": "Build <MagazineDrop items? defaultIndex? ambient? weight? paused? className?> as a full-bleed 2D-canvas gallery whose transport is a gravity-fed slide magazine rather than any kind of slide or fade. THE MACHINE: a rotary drum sits above a gate at the centre of the frame; a plate is released from the drum, FALLS under gravity, strikes the seat and settles. There is no tween anywhere in the transport — every visible motion is a fixed-timestep integrator over rigid bodies with y, vy, rot and vrot, gravity 2600*weight px/s^2, restitution 0.34 and exactly two bounces before the body is clamped, after which the gate's registration pins pull the residual tilt out with a critically damped return. Reverse is the same physics, not the animation reversed: the return ram tosses the previous plate up from the lower chute with sqrt(2*g*(drop+40)) of velocity, just enough to overshoot the seat, and gravity catches and settles it. The outgoing plate is kicked out of the gate with an impulse and a little spin and leaves the frame, and its impulse is SIGNED AND SIZED by direction because gravity is still acting on it: forward needs only a nudge, reverse must be thrown at sqrt(2*g*H*0.85) or it rises a few px, falls back and crosses the incoming plate at the gate mouth. Exactly ONE spent plate stays in flight, at 0.55 alpha — this component's entire per-frame cost is fill, and a train of three measured 33ms (two vsync intervals) during a drag at DPR 2. DRUM PHASE IS THE SOURCE OF TRUTH: a continuous scalar in notches, 96px of drag per notch. A drag maps to it DIRECTLY and unsmoothed — a plate must sit exactly where the finger is, and an exponential follower would introduce v*tau of steady-state error, which on a drag is not smoothing but lag. Release hands the phase to a detent spring (stiffness 190, damping 17) carrying an EMA of the flick velocity, so a slow drag lets you feel the detent and back out without committing while a flick crosses several notches; a transition fires whenever round(phase) changes, and a multi-notch crossing consumes the intermediate steps as drum rotation so only the last one gets a flight. Pointerdown on a button, link or role=button must NOT start a drag: the drag takes pointer capture on the wrapper, and a captured pointer retargets the subsequent click to the CAPTURE element, which silently killed both transport buttons while the keyboard path kept working. The fractional part of the phase also slides the edge-on stack of upcoming plates drawn above the gate and the spent stack below it, which is what makes a half-drag legible as a mechanism under load rather than a stuck animation. PLATES ARE PROCEDURAL: six engravings drawn once each into an offscreen canvas at gate resolution and blitted with a transform per frame — a turned concentric face with pitch opening toward the rim, a two-pass 60-degree hatch, a moiré of two rasters four degrees apart, a radial-falloff halftone lattice, a three-source interference field drawn as contour bands (bands rather than a shaded field so the plate survives being 5px tall in the magazine stack), and a braided warp weave. Each carries an engraved two-digit index, its title in mono, a registration bar and a bevelled edge, all cut into the same raster so they travel with the plate. A plate in flight is squashed vertically by 1/(1 + |vy|/9000) so it reads as being seen slightly edge-on at speed. PALETTE: --background, --foreground, --ns-muted, --border and --ns-accent only, via getComputedStyle, re-read on a documentElement class/data-theme MutationObserver which also re-engraves every plate. THREE values must separate, and their ORDER matters more than the numbers: the plate is the brightest thing in the frame because it is the thing being lit, the full-width lamp aperture band (as tall as the gate) sits under it, and the recessed bay is sunk below both. Light is the harder case twice over — a bay mixed toward a near-white --border lands within 3% of the plate and the machine reads flat, and the engraving alphas that work on the dark plate produce a ghost on the near-white one, so every ink alpha is written for dark and scaled by 1.7 for light. --ns-accent is interaction-only — the four gate registration ticks turn accent while dragging or focused and are --ns-muted at rest, so a screenshot at the default pointer position carries no accent at all. THE DRIVE FILLS THE MARGINS: the gutters either side of the gate carry the drum's drive sprockets, one on each end of the axle, each pushed most of the way off the frame so what shows is the near rim of a wheel far bigger than the gutter rather than a whole cog floating in it — a wheel centred in the margin reads as an illustration of a gear. Each is engraved ONCE into an offscreen canvas exactly like a plate and blitted rotated, which is what buys it a surface that survives being looked at: 44 teeth crowned proud of the rim and lit on the leading flank, ~140 concentric turned tool marks at the pitch of a lathe feed, six web ribs faded at both ends so they read as ribs standing off the face rather than light streaks laid across it, nine lightening bores with a shadowed interior and a lit far wall, a hub boss with a keyway, and seeded speckle wear that reproduces the same damage across a resize or theme flip. The bores are sunk but must NOT be the darkest value in the frame — at half way to --foreground in light, nine of them per wheel out-competed the plate and the margins won the composition. The wheels are geared straight off the drum phase (angle = phase * 2pi / 12, mirrored per side), so they turn under the finger during a drag, spin through a flick, and rock a sixth of a notch against the detent spring while the machine idles: the resting frame is never static and the transport is legible from the edge of the frame even when nothing is falling. Re-engraved only when the radius or the DPR changes, so a resize drag does not re-cut both wheels per ResizeObserver callback, and skipped entirely below a 56px gutter. ACCESSIBILITY: the wrapper is a focusable role=group with aria-roledescription='slide magazine' taking ArrowDown/ArrowRight/PageDown forward, ArrowUp/ArrowLeft/PageUp back and Home/End to the ends. Home/End are a JUMP, not N advances: the drum spins to the target and one plate flies, arriving from the side the jump came from; a full sr-only <ul> of every plate is the DOM equivalent of the canvas, with only the gated plate unhidden, and an aria-live=polite line announces 'Plate n of N'. Both transport buttons are real buttons with accessible names. prefers-reduced-motion and paused collapse the machine to discrete states: no flight, no spent stack, the plate is simply in the gate, and a drag resolves to a single step on release. IDLE: an ambient advance every `ambient` seconds (default 4.6) whenever the machine is settled and nothing is focused or dragging, suspended for one full period after any real input, and deliberately NOT gated on hover — the component is full bleed, so a cursor resting anywhere in the viewport is 'over' it and would stop the transport forever, including in the screenshot gate, which parks the mouse at 0,0 — so the transport demonstrates itself without ever taking the frame from someone driving it. HOST: DPR capped at 1.5 rather than 2 — the cost scales with the square of the ratio and the content is flat engraved line work already antialiased into the plate raster, so 1.5 is indistinguishable and is 44% of the pixels; bodies outside the frame are culled from rasterization while still being integrated, and the bay is painted as three bands rather than a full-frame fill under the aperture so the viewport is not paid for twice per frame. Measured on a production build at DPR 2, 1440x900: p50 16.7ms, p95 17.4ms, zero frames over 20ms through a continuous drag that indexed all six plates. ResizeObserver re-raster on gate size change, IntersectionObserver and visibilitychange both stop the single rAF loop, and paused/items are polled rather than effect deps so neither tears down the machine and drops a plate mid-flight."
      }
    },
    {
      "name": "magnetron-racetrack-sweep",
      "type": "registry:ui",
      "title": "Magnetron Racetrack Sweep",
      "description": "A full-bleed background built on sputter target erosion: a magnetron's plasma racetrack sweeps back and forth across a target face, slowly grooving it wherever the ring has lingered, rendered as a persistent radial erosion field under a live, non-accumulated glow ring.",
      "files": [
        {
          "path": "registry/loud/magnetron-racetrack-sweep/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/magnetron-racetrack-sweep.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "canvas",
          "webgl",
          "shader",
          "metal",
          "erosion",
          "lab-instrument",
          "cursor",
          "mono",
          "ambient",
          "generative"
        ],
        "instruction": "Build <MagnetronRacetrackSweep paused? children? className?> as a full-bleed WebGL background rendering sputter-target erosion under an oscillating magnetron racetrack. THE EROSION FIELD IS 1D: because the racetrack is a full annulus at every instant, its erosion history is a function of radius alone, never angle — the persistent, never-cleared accumulation buffer (same non-clearing technique as rime-creep/edm-crater-field, but MONOTONIC here, never decaying, matching real physical erosion) is therefore a 256-texel 1D LUMINANCE texture (EROSION_BUCKETS=256), not a full 2D grid. ringRAt(t) = 0.5 + 0.2*sin(2*pi*t/22) is the sweep's live normalized radius (oscillating between 0.3 and 0.7 of target radius, 22s period); every simulate(dt,t) call adds EROSION_RATE=0.002 depth-units/s to every bucket whose normalized radius falls within [ringRAt(t) - 0.04, ringRAt(t) + 0.04] (RING_WIDTH=0.08 total). Because the sinusoid dwells longest at its own turning points (0.3 and 0.7) and passes through the middle twice per cycle at full speed, the two turning-point bands erode measurably deeper than the middle band with ZERO extra logic — the dual-band asymmetry is the sweep's own kinematics, not a scripted bias. GROOVE SHADING: the composite fragment shader derives a surface normal from the 1D depth field's RADIAL gradient only (central difference at r +/- 1/256, no tangential term exists by construction) and projects it back into 2D along the local radial direction (rDir = p/length(p)), so the groove reads as a real machined cut catching a small achromatic multi-band studio (env(), same family as edm-crater-field's, fewer bands than a mirror pool needs) rather than a flat painted ring. Material removed subtracts directly from luminance (depthVis*0.34) — lower relative luminance than the fresh face, same direction in both themes, only bias/contrast differ per theme (dark: bias -0.06, contrast 1.18; light: bias 0.03, contrast 1.2). LIVE GLOW RING: a second, non-accumulated quantity — ringMask, a smoothstep band around the CURRENT ringRAt(t), added straight to luminance (+0.5) every frame, explicitly never colour (real sputtering plasma glows violet/pink; that hue is discarded on purpose per the monochrome constraint) and never composited into the persistent erosion texture. POINTER: pointermove computes the angle from the target centre to the pointer (never its radius) and eases pointerActive 0->1 over a 250ms exponential; the shader adds an angular gaussian boost (falloff 0.35 rad) on top of ringMask, brightening the glow ring locally by luminance only, never accent, and it must never alter ringRAt/simTime — the sweep is a fixed physical cycle, not pointer-driven. HOST: mirrors edm-crater-field's scaffolding — a GLSurface host class (fullscreen triangle pair, lazily-resolved uniform locations), a two-tier adaptive DPR ladder (SCALES=[1,0.7], steps down after a sustained >24ms EMA for 900ms, back up after a much longer clean stretch that doubles each time it trips), DPR capped at 1.5, a ResizeObserver on the wrapper (never window.resize), IntersectionObserver(threshold 0) + visibilitychange both gating the single rAF loop, webglcontextlost/restored rebuilding cleanly, and a paused prop polled every 140ms (props can't be read fresh inside a long-lived rAF closure without a ref). Tokens (--background, --foreground, --ns-muted, --border) are read via getComputedStyle(document.documentElement) before any paint and re-read on a MutationObserver watching documentElement's class. prefers-reduced-motion (or paused) sets a STATIC_WARMUP_S=480s deterministic warmup (the same simulate() function stepped in fixed 0.25s chunks from an all-zero buffer, no randomness anywhere in this component) then draws exactly one frame at STATIC_PHASE_S = 22*0.5 (sweep phase 0.5, ring at mid-radius, dual-band asymmetry already visible), named \"racetrack-mid\" — never a blank disc. The live (non-reduced) mode instead warms up only LIVE_WARMUP_S=40s before its first paint, so the groove starts faint rather than either pristine or already deep."
      }
    },
    {
      "name": "mailbag-hook-exchange",
      "type": "registry:ui",
      "title": "Mailbag Hook Exchange",
      "description": "A full-bleed ambient background reproducing the Railway Post Office catcher-crane mail exchange: a trackside crane holds an outgoing mailbag on a hook, swaying between passes, while a train sweeps through on a fixed cadence, its catcher arm snagging the bag and kicking a replacement onto the trackside net without ever stopping.",
      "files": [
        {
          "path": "registry/loud/mailbag-hook-exchange/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/mailbag-hook-exchange.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "rail",
          "postal",
          "exchange",
          "ambient"
        ],
        "instruction": "Build <MailbagHookExchange className? style?><...headline/CTA/></MailbagHookExchange> as a full-bleed Canvas 2D background reproducing the Railway Post Office (RPO) catcher-crane mail exchange used on US mail trains roughly 1864-1977: a trackside crane holds an outgoing mailbag on an iron hook at a fixed point along the line; a passing train's catcher arm swings out, snags the bag, and in the same motion kicks a replacement bag onto the trackside net, all without the train stopping. ONE GOVERNING CLOCK: every visual (train position, arm gesture phase, held-bag pattern, pendulum tremor, lamp blink, tie scroll) is a pure function of a single elapsed-seconds scalar `t`, no independent per-element timers. CYCLE = 7.0s between exchanges (a fixed cadence, not random jitter). CROSS_DURATION = 2.6s is how long the train is visible, sweeping its leading edge linearly from ENTER_FRAC=-0.2 to ENTER_FRAC+SPAN_FRAC=1.2 (fractions of container width) over that window, train body length TRAIN_LEN_FRAC=0.26 of width. CONTACT_T, the instant the train's engagement marker (ENGAGE_OFFSET_FRAC=0.4 of train length back from the leading edge) reaches the crane at CRANE_X_FRAC=0.62 of width, is DERIVED algebraically from those fractions and CROSS_DURATION (CONTACT_T = CROSS_DURATION*(CRANE_X_FRAC + TRAIN_LEN_FRAC*ENGAGE_OFFSET_FRAC - ENTER_FRAC)/SPAN_FRAC, works out to ~1.716s) rather than hardcoded, so gesture timing and visual alignment cannot drift apart if any fraction is tuned later. THE GESTURE (real engagement is 0.15-0.3s, undrawable at 60Hz — this is a documented, deliberate stretch to 900ms for legibility, not a 1:1 animation of the real rate): starting at GESTURE_START = CONTACT_T - 0.28s, the arm swings from REST_DEG=0 (hanging straight down) to EXTENDED_DEG=34 over 280ms (ease-in-out), holds at EXTENDED_DEG for 120ms (hook engaged — this is the CONTACT_FRAME reduced-motion freezes on, at the hold's midpoint), then swings back to REST_DEG over 500ms (ease-in-out). Arm pivots at the crane top; the hooked-bag position is hookX = craneX + sin(angleRad)*armLength, hookY = craneTopY + cos(angleRad)*armLength, angleRad including a pendulum tremor added on top (see below) even during rest frames. HELD-BAG ALTERNATION: a monotonic completed-exchange counter (floor((t-CONTACT_T)/CYCLE)+1 once t >= CONTACT_T, else 0) modulo 2 selects which of two bag renders is on the hook — variant 1 adds two thin --background-colored bands across the bag so a swap is visibly a DIFFERENT bag, never the same glyph re-drawn. KICKED-BAG STREAK: during the hold+swing-back window (620ms), a second small bag glyph eases (easeInOut) from the hook position to a net glyph (a small arc) near the crane's base, fading via globalAlpha from 1 to ~0.15 as it travels — the visible 'something left, something arrived' beat the round 9 legibility rule requires, not a blink-cut. IDLE PENDULUM (proves Filter 2 between exchanges): outside any gesture window, the arm angle gets an added tremor = (PENDULUM_FLOOR_DEG=2 + (PENDULUM_PEAK_DEG=18 - 2)*exp(-sincePass/0.9)) * sin(2*PI/1.6 * sincePass), where sincePass is seconds since the last gesture ended (wrapped through the 7s cycle) — amplitude decays from a hard kick down to, and never below, a 2deg resting tremor at a 1.6s period. SIGNAL LAMP: independent clock, `Math.floor(t / (LAMP_PERIOD/2)) % 2 === 0` with LAMP_PERIOD=2.0s toggles a small circle at the crane top between --foreground (lit) and --ns-muted (unlit) — luminance only, this is the standing accent-tint check: never mix --ns-accent into the lamp or the kicked-bag streak. Because CYCLE=7 and LAMP_PERIOD=2 share no common factor smaller than lcm(7,2)=14s, the lamp/pendulum phase pairing at any t0 essentially never repeats within a normal viewing window. RAIL + TIES: a horizontal --ns-muted line at trackY = 0.74*height across the full width, with perpendicular tie marks spaced tieSpacing = max(10, min(width,height)*0.05) apart, scrolling continuously at TIE_SPEED=40px/s via `(t*40) % tieSpacing`, drawn regardless of whether a train is currently visible — this is what keeps the frame alive even mid-idle-cycle. GEOMETRY: craneHeight = min(w,h)*0.22, armLength = min(w,h)*0.13, bagSize = min(w,h)*0.045, trainHeight = min(w,h)*0.09 — all derived from the container's smaller dimension so the scene reads correctly at both hero and card scale; trainLength stays a fraction of WIDTH (0.26) since it's a horizontal object whose crossing needs to scale with how wide the frame actually is. TOKENS: --foreground for the train silhouette, the hooked/kicked bag fills, and the lit lamp state; --ns-muted for the rail, ties, crane post, net, and unlit lamp state; --background used only as the bag's alternate-pattern stroke color (so the bands read as cutouts, not a hue). All three read via getComputedStyle(document.documentElement) at mount and re-read on a MutationObserver watching documentElement's class; nothing paints before that first read (guarded in draw()). DPR-capped canvas backing store (1.5 full-bleed, 0.6 inside an autoplay preview card via the same `canvas.closest('[data-autoplay-root]')` check other loud backgrounds use), ResizeObserver-driven resize, IntersectionObserver (threshold 0) plus visibilitychange pausing the rAF loop off-viewport or tab-hidden. prefers-reduced-motion freezes the clock at CONTACT_FRAME (GESTURE_START + 0.28 + 0.06s, i.e. mid-hold) rather than t=0, because that instant is the single most structured frame the loop produces: train mid-crossing, arm fully extended, hook visibly engaged, kicked bag mid-flight toward the net. Decorative canvas is aria-hidden and pointer-events-none; there is no pointer interaction anywhere in this component — `children` render as real DOM in normal focus order above it. Props: children, className, style."
      }
    },
    {
      "name": "murmur-shear",
      "type": "registry:ui",
      "title": "Murmur Shear",
      "description": "A dusk hero of ~1500 starlings running true boid separation/alignment/cohesion over their 7 nearest topological neighbours; on a slow clock an invisible falcon makes one pass, and the escape turn it triggers propagates bird-to-bird faster than the flock drifts, shearing a dark density band across the murmuration.",
      "files": [
        {
          "path": "registry/loud/murmur-shear/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/murmur-shear.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "boids",
          "murmuration",
          "flocking",
          "starlings",
          "canvas",
          "physics",
          "hero",
          "decorative",
          "wave"
        ],
        "instruction": "Full-bleed decorative Canvas 2D hero, aria-hidden, pointer-events-none, zero interactive controls, zero pointer handling of any kind — self-driving purely on an internal clock. Backing store is dpr-clamped to 2 with a ResizeObserver-driven resize and a zero-size guard. SIMULATION: birdCount (default 1500, clamped 50-4000) starlings live in flat Float32Arrays (position, velocity, per-frame acceleration scratch) mutated in place. Physics runs at a FIXED 30Hz tick decoupled from display refresh via a time accumulator (render happens once per rAF using the latest tick's state, up to 4 physics steps catch up per rAF to avoid a spiral of death on lag). NEIGHBOURS ARE TOPOLOGICAL: each bird finds its 7 nearest neighbours BY COUNT (Ballerini/STARFLAG), not by a metric radius, via a spatial hash sized so a bird's own cell already holds roughly 10 candidates on average (cell = sqrt(10*w*h/birdCount)); the search expands outward ring by ring (up to 5 rings) whenever the local cell is too sparse, guaranteeing k neighbours are found even where the flock has thinned to a fraction of its average density — this is exactly what lets the flock stretch into a thin ribbon and thin by an order of magnitude locally without tearing, which a fixed-metric-radius boid cannot survive (isolated stragglers there simply detach with zero neighbours). STEERING: separation weight 1.5 (inverse-square repulsion from each of the 7, scaled), alignment 1.0 (steer toward neighbour-average velocity), cohesion 1.0 (steer toward neighbour-average position) — separation outweighing cohesion is what prevents the classic boid clump-collapse into one static ball. Soft walls turn the flock back near the container edges; a small per-bird random jitter accel keeps the texture organic. Speed is clamped to [55, 150] px/s, raised up to 75 px/s further while a bird is agitated. GOVERNING SCALAR H (passesPerMinute prop, default 5): on average once every 60/H seconds (jittered +/-30%) an invisible falcon flies one straight chord through the container at 1050 px/s, entering and exiting off-canvas, at a random shallow angle and vertical offset each pass — it is never drawn. Every bird within 68px of the falcon's travelled segment this tick gets an immediate radial speed kick away from the strike point, its alarm scalar set to 1, and a signed turn direction recorded from the cross product of the falcon's heading and the away vector. WAVE PROPAGATION (the actual subject): every bird's alarm decays continuously (exp decay, tau 1.3s) but is re-sampled from its 7 neighbours' alarm from ONE FRAME AGO at most once every 90ms via a per-bird latency accumulator (never faster, so intra-frame cascades cannot shortcut the hop timing) — if neighbour-average alarm x gain 2.2 exceeds the bird's own current (decayed) value, the bird adopts it along with the dominant neighbour turn direction, clamped to a max of 1. That per-bird 90ms hop, not a whole-flock broadcast, is what makes the escape read as a band shearing across the murmuration rather than a synchronized flinch: a hop crosses one bird's ~14px neighbour spacing every 90ms (~150+ px/s of front-edge advance through a dense region), clearly outrunning the flock's own near-static bulk centroid (held in place by the soft walls) and on par with or faster than any individual bird's own cruise speed. H=0 disables the falcon's scheduler entirely so alarm can never be seeded anywhere — the propagation code path still runs every tick but has nothing to propagate, so the wave mechanism is structurally, not just visually, absent; only plain murmuration remains. Alarm also drives a lateral steering accel (perpendicular to heading, signed by the inherited turn direction, magnitude scaling with alarm) so agitated birds visibly bank as the wave passes through them, not just brighten. RENDER: each tick, solid --background fill, then a low-res leaky-integrator density grid (cell 22px, decay 0.86 per tick, accumulating both raw bird count AND a 3.2x-weighted sum of alarm so a genuine spatial knot forms where escaping birds bank together, not merely wherever birds happen to be) quantized into five discrete alpha stops of --foreground (0, 0.1, 0.2, 0.34, 0.52) written into a tiny offscreen canvas via ImageData and drawImage-scaled up with smoothing over the whole field, then every bird drawn as a 2-3px streak from its trailing point to its position oriented along its velocity, agitated birds (alarm >= 0.15) drawn in a second, bolder batched stroke pass. All ink is read via getComputedStyle(document.documentElement) at mount and re-read through a MutationObserver on documentElement's class attribute; --ns-accent is never used since nothing here is interactive. The rAF loop pauses on visibilitychange. REDUCED MOTION (matchMedia, tracked live): no rAF loop ever starts. Instead a silent physics-only warmup (180 ticks, with a falcon pass force-scheduled partway through when H > 0, never forced when H = 0) runs with zero canvas work, then a short 24-tick window is actually drawn without clearing between ticks at low alpha to accumulate one precomputed long-exposure still, sealed with a final density-wash pass — a genuinely different artifact computed once, not the live loop merely slowed down, and it is regenerated (not merely repainted) on both container resize and theme change while reduced motion is active. Distinct from avatar-stack-flock (a handful of DOM avatar chips flocking within a metric radius, no wave, no canvas) and from hero-vortex-street/hero-particles-webgl (ambient particle fields with no signal propagating between particles). No SVG, no dependencies."
      }
    },
    {
      "name": "not-found-attribute-clash",
      "type": "registry:ui",
      "title": "Not Found Attribute Clash",
      "description": "A 404 built from the ZX Spectrum's real colour-cell attribute clash: each 8x8 hardware cell could carry only one ink/paper colour pair, so image detail finer than the cell budget visibly bled and blocked. This translates that as a monochrome glyph+weight budget (one glyph and one font-weight per cell), so the numerals' own curved edges snap and bleed at the seams, with a rolling scanline that re-decides a handful of rows per frame like attribute RAM racing the beam.",
      "files": [
        {
          "path": "registry/loud/not-found-attribute-clash/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/not-found-attribute-clash.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "404",
          "ascii",
          "canvas",
          "hardware",
          "retro",
          "page"
        ],
        "instruction": "Build a full 404 background whose mechanic is sourced from real ZX Spectrum hardware, not invented: the machine's screen was split into 8x8-pixel cells that could each carry only ONE ink/paper colour pair, so any image content needing finer detail than that per-cell budget forced the whole cell to snap toward one colour, and the loser's detail bled/blocked wherever it lost — the well-documented 'colour clash' artifact. THIS IS A MONOCHROME ADAPTATION, NOT A FIDELITY CLAIM: state plainly that the original clash is a COLOUR artifact (one ink/paper pair per cell) and this translation is a glyph/weight-budget artifact (one glyph character + one font-weight per cell) — there is no hue anywhere in this component. Build it as: (1) once per resize, after document.fonts.ready, rasterize '404' at 900-weight onto a small offscreen canvas supersampled 3x per output cell, fit to the offscreen raster with real margin on each side (never edge-to-edge, never shrunk below legibility), and read its alpha channel into a per-cell ink-fraction float (0..1) — this fraction is the ONLY thing that decides ink vs paper per cell, and it never changes with time, only with resize/refit; (2) every frame, a rolling beam sweeps top to bottom over roughly sweepMs (default 1100ms) and only the handful of rows it newly crosses that frame get their ink/paper DECISION re-evaluated — most of the grid is deliberately left showing its previous decision, which is the literal translation of 'attribute RAM racing the beam' as a rolling seam rather than a full-field flash; (3) for cells whose ink-fraction sits within a clash band around 50/50 (the numerals' own edges), a small per-sweep jitter is added to the decision threshold from a cheap two-octave grid-index field, so specifically those contested cells flicker between full-ink and full-paper pass to pass — cells far from any edge never flicker, because they were never in a real budget dispute on the source hardware either. Each cell renders exactly one character (dense glyphs '#%@' for ink cells, sparse ' .:-' for paper cells, a third distinct ramp '*+?' for cells currently inside the clash band) at exactly one font-weight (700 for ink, 400 for paper) and one alpha derived from the same field value — the glyph+weight pair literalizes the one-attribute-per-cell budget. Colour is --foreground only, alpha-only, read via getComputedStyle(document.documentElement) at mount with a MutationObserver on documentElement's class for theme flips; a faint --ns-muted (never --ns-accent — this is ambient structure, not interaction chrome) horizontal line traces the beam's current row as a structural readout of the mechanism. Cell size derives from Math.min(width,height)/cellsAcrossMinDim (default 26), clamped 8-64px, so the component reads as a recognizable block grid at both small registry-card scale and full-bleed rather than either pixel-soup or one giant cell. prefers-reduced-motion freezes on one completed sweep (every row decided once) at a fixed non-zero field time chosen specifically because t=0 is a degenerate phase for the summed field terms — the frozen frame must show the clash-zone cells already disagreeing with each other, not a coincidentally-uniform snap; no rAF, no beam sweep runs after that. The canvas is aria-hidden decoration only: the demo composes a real h1 reading '404 — PAGE NOT FOUND', a short paragraph, and a real focusable 'Take me home' link in normal DOM with correct focus order, entirely independent of the glyph art. Props: cellsAcrossMinDim (default 26), sweepMs (default 1100), className."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-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, --ns-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'.",
        "rank": 20
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": "offset-fountain-split",
      "type": "registry:ui",
      "title": "Offset Fountain Split",
      "description": "A full-bleed ambient background modeling a web-offset press ink train: ink splits and smears across six rollers from fountain to form, driven by 24 independently drifting fountain-key zones so the field's ribbed banding never converges flat.",
      "files": [
        {
          "path": "registry/loud/offset-fountain-split/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/offset-fountain-split.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "print",
          "ink",
          "canvas",
          "ambient",
          "offset",
          "monochrome"
        ],
        "instruction": "Build <OffsetFountainSplit className? style?> as a full-bleed, aria-hidden, pointer-events-transparent 2D canvas background (no children wrapping — callers overlay their own content in a sibling element, matching the background-gradient-shader pattern). Model a web-offset ink train as 6 roller stages (index 0 fountain, 1-4 oscillating distributors, 5 form), each stage a Float32Array of N samples (N = clamp(ceil(width / colPitch), 256, 720); colPitch = clamp(min(width,height)/90, 3, 10)px, derived from the container's smaller dimension). Stage 0's per-sample TARGET comes from 24 discrete fountain-key zones spanning the width (zoneWidth = N/24, one stepped target per zone, not interpolated — a real fountain key is discrete), each zone drifting on its own sine: 0.5 + 0.18*sin(2*PI*t/40 + zonePhase[z]), zonePhase[z] = z*0.9137 for a non-repeating spread across zones. This 40s drift is what keeps the whole train from ever converging flat. For stages 1-5, each sample's TARGET is 0.5*prev[x] + 0.5*prev[x sampled at a laterally shifted, linearly-interpolated position] — an ink-splitting halve-and-recombine at every nip, with the shift only applied on stages 1-4 (distributors): offsetPx = 14*sin(2*PI*t/2.6 + stageIndex*0.7), converted to samples via /colPitch; stages 0 and 5 (fountain, form) never oscillate, offset = 0. Every stage's array RELAXES toward its target rather than snapping: value += (target - value) * (1 - exp(-dt/0.18)), a 180ms per-stage time constant, so a fountain-key shift visibly ripples down the train stage by stage over multiple frames rather than teleporting. Only the final (form-roller, index 5) array is painted, as full-height vertical strips: ctx.fillStyle = the --foreground token (read raw as a CSS color string via getComputedStyle(document.documentElement).getPropertyValue('--foreground'), re-read on a MutationObserver watching documentElement's class), globalAlpha = clamp(value,0,1) * maxAlpha per strip, strip width = exact colPitch (recomputed as width/N so columns tile with no gap), no background fill — the wrapper's own bg-background class supplies the ground, so nothing paints before the token read. maxAlpha is chosen by comparing the relative luminance (0.2126r+0.7152g+0.0722b from a parsed #hex) of --foreground against --background: 0.75 when background is the lighter token (light theme, where equal alpha reads heavier), 0.85 otherwise (dark theme) — this comparison, not a hardcoded theme guess, is what the token rule requires. Pointer interaction: a window-level pointermove listener (filtered against the canvas's own getBoundingClientRect so it only reacts inside the frame) finds the nearest fountain-key zone under the cursor's x and sets that zone's zoneBoost to 0.1, added directly into the zone's target for one relaxation pass; zoneBoost then linearly decays back to 0 over 1.2s ((0.1/1.2)*dt per frame) whether or not the pointer is still present. This is a luminance-only 'key tweak' nudge — never mix --ns-accent into it, and it must leave no permanent trace once decayed. Canvas backing store is DPR-capped at 1.5, resized via a 150ms-debounced ResizeObserver on the root (each resize reallocates and zeroes all 6 stage arrays — state loss on resize is acceptable). The rAF loop only runs while an IntersectionObserver (threshold 0) on the root reports intersecting AND document.visibilityState is visible; it is started/stopped from both the IntersectionObserver callback and a visibilitychange listener, and is never started at all under prefers-reduced-motion. prefers-reduced-motion instead renders exactly one static frame: the same 6-stage cascade computed once at a fixed sim-time of 8.4s with an idealized relax factor of 1 (each stage snaps straight to its instantaneous target rather than replaying the transient history) — no rAF loop, no pointer listener, no IntersectionObserver, just a direct recomputation on token-change too (the MutationObserver callback re-invokes this same static draw when reduced-motion is active). Clean up on unmount: cancel any pending rAF, clear the resize debounce timer, and disconnect the MutationObserver, ResizeObserver, and IntersectionObserver (the last only exists when motion is not reduced). Props: className, style — both merged onto the root; a `min-h-*` class in className suppresses the default `min-h-screen` the same way the sibling backgrounds do."
      }
    },
    {
      "name": "orb-web-construction",
      "type": "registry:ui",
      "title": "Orb Web Construction",
      "description": "A full-bleed hero background where a web builds in the real orb-weaver sequence: bridge, frame, radii laid hub-outward, a dashed scaffold spiral hub-outward, then a solid capture spiral laid outside-back-to-hub that visibly replaces the scaffold turn by turn. It then rests, tears a random sector, and rebuilds only that sector before resting again.",
      "files": [
        {
          "path": "registry/loud/orb-web-construction/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/orb-web-construction.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "canvas",
          "construction",
          "sequence",
          "monochrome",
          "ambient",
          "decorative",
          "organic",
          "repair"
        ],
        "instruction": "Build <OrbWebConstruction children? className? style?> as a full-bleed canvas hero background that lays down a web in the real orb-weaver construction order (Zschokke 1999), never a generic radial reveal. HUB at container center; outerRadius = min(w,h) * 0.46. PHASE 1 FRAME (1.2s total): three anchor threads at 120 degrees around outerRadius are drawn sequentially, 0.4s each, forming a triangle around the hub before anything else appears. PHASE 2 RADII: `n = clamp(round(min(w,h)/60), 16, 28)` spokes, evenly angled with +/-4deg per-spoke jitter, are drawn hub-to-outerRadius sequentially — one spoke starts every 90ms and takes 260ms to finish, so 2-3 are mid-draw at any moment; full radii phase takes roughly n*90ms+260ms. PHASE 3 AUXILIARY SPIRAL (2.1s): a dashed, low-alpha scaffold spiral grows from the hub outward along a shared curve `spiralPoint(u)` (u=0 hub, u=1 outer edge, angle = u*5.5 turns*2*PI, radius = u*outerRadius) — this is the SAME curve the capture spiral reuses in phase 4, so the 'replacement' reads as one spiral changing character rather than two different curves swapping. PHASE 4 CAPTURE SPIRAL (3.1s): a solid, brighter capture thread is laid from the OUTER edge (u=1) back toward the hub (u=0); a single scalar `uCap` (1 at capture-phase start, ramping to 0 over 3.1s) marks the boundary — samples along the shared curve with u>=uCap render solid (already replaced), samples with u<uCap render dashed (still original scaffold, not yet reached) — so the viewer watches the solid line eat the dashed one from the outside in, turn by turn. Once capture finishes the web rests fully built (uCap=0 everywhere) for a randomized 6-9s. TEAR: a random 40-70deg wedge is picked (widened in 15deg steps if it happens to catch zero spokes) and every spoke + spiral segment inside it erases over 180ms (spoke progress and a wedge-local uCap both ramp from built to erased in that window) while the rest of the web stays fully intact and static throughout. REPAIR: the SAME sequence (radii, then spiral) rebuilds ONLY the torn wedge — its spokes redraw staggered 90ms/260ms as in phase 2, then a wedge-local uCap ramps from 1 (erased) back to 0 (solid) over max(0.8s, (2.1+3.1)*wedgeWidthDeg/360) — while every spoke and spiral segment outside the wedge is untouched the entire time. After repair the web rests again, then tears a fresh random wedge, indefinitely (full cycle after the first build is roughly 9-13s: 6-9s rest + 0.18s tear + ~1-3s repair-radii + ~1-2s repair-spiral). All phase timing runs off a single continuous seconds-since-mount clock (`t`), never a per-frame delta accumulator, so phase transitions stay correct across variable frame rates and even across an off-screen sleep (IntersectionObserver/visibilitychange pause the rAF loop entirely; on wake, any backlog of phase transitions the wall clock accrued cascades through in the next few frames, which is inaudible/invisible since nobody was watching while paused). Every random choice (spoke jitter, wedge angle/width, rest duration) is driven by a deterministic xorshift32 PRNG seeded once per build, not Math.random, so the shipped resting frame is reproducible mount to mount. RENDER: capture-spiral solid segments use --foreground at ~0.35 alpha (dark) / ~0.45 alpha (light, where the token's contrast against --background compresses); frame threads, radii and dashed scaffold segments use --border at ~0.4 (dark) / ~0.55 (light) alpha; both colours are read via getComputedStyle(document.documentElement) at mount and re-read on a documentElement class MutationObserver, and light-vs-dark is decided from `document.documentElement.classList.contains('dark')`. ResizeObserver rebuilds the whole web (radii angles, hub position, outer radius, phase reset to the beginning) on a real size change, debounced 200ms except the very first real measurement which builds and paints immediately with no debounce; IntersectionObserver (threshold 0) and visibilitychange pause the single rAF loop off-screen or in a hidden tab. prefers-reduced-motion runs the identical phase-advance function synchronously in 50ms steps at mount (and after any resize) until the web lands solidly inside its first post-build rest window (3s into rest, well clear of the following tear), then paints exactly that one static, fully-built, undamaged frame and never schedules a rAF loop; a live change to the media query re-primes and re-freezes the same way. The canvas is aria-hidden with pointer-events implicitly none via absolute inset-0 with no listeners; children render as ordinary, independently focusable DOM above it. autoplay.mode is 'none' because the web animates entirely on its own internal clock with zero pointer/scroll/press dependency."
      }
    },
    {
      "name": "pancake-lap",
      "type": "registry:ui",
      "title": "Pancake Lap",
      "description": "A full-bleed ambient background modeling real pancake sea-ice formation: circular ice pans nucleate, grow, and drift on open water, and roughly every two seconds one pan's edge visibly rides up over a neighbour's raised rim, crosses, and welds into a permanently thicker doubled floe.",
      "files": [
        {
          "path": "registry/loud/pancake-lap/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/pancake-lap.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "ice",
          "sea-ice",
          "pancake-ice",
          "field",
          "decorative"
        ],
        "instruction": "Build <PancakeLap panRatio? paused? children? className? style?> as a full-bleed Canvas 2D background modeling REAL PANCAKE SEA-ICE formation (WMO sea-ice nomenclature; marginal-ice-zone wave/ice field studies, e.g. Doble & Wadhams on pancake formation timescales) — a discrete-event field sim, not a circle-packing relaxation (that territory belongs to background-lloyd-relax / floret-pack). SUBSTRATE AND GROWTH: pans nucleate as small discs and grow toward a per-pan stable radius via an exponential approach (dR/dt slows as R approaches max, matching real ice growth deceleration), each pan's growth spread across a randomized 6-9s window (compressed ~250x from the real ~20-40min under active wave forcing). Pan count is derived from container area divided by average pan footprint, clamped to 40-70 concurrent pans on a typical hero, and pan radius is derived from the container's SMALLER dimension (panRatio, default 0.09) so the field reads at both hero and card scale. THE RAFTING MECHANIC (the literal ask, not cosmetic): a field-wide timer fires every 1.8-2.5s and picks one touching, uninvolved pan pair via a spatial hash (cell size = min(width,height)/12); the picked 'rider' pan runs a three-part ~700ms sequence — RISE (250ms, smoothstep scale-up plus a soft shadow lens beneath it, reading as the edge lifting off the water), CROSS (450ms, an eased position interpolation carrying the rider's center to a point overlapping ~45% into the partner's footprint), then SETTLE (instant): the rider's position locks at the overlap point, `weldedWith` is set to the partner's id permanently, the rider inherits the partner's drift velocity so the pair travels as one composite piece from then on, and a brighter 'doubled ice' lens is drawn at the overlap centroid on every subsequent frame until either pan exits the frame. This lock-in is the whole point: it is what makes the field never converge to a static jam. AMBIENT DRIFT AND TURNOVER: pans translate ~4px/s toward the frame's trailing edge plus a small sinusoidal bob (period ~5s, near-1:1 with a real marginal-ice-zone swell period — the one sub-rate deliberately left uncompressed per the round-9 legibility rule, since it was already human-scale); a pan that drifts past the trailing edge is removed and immediately replaced by a fresh small pan nucleating at the leading edge, so the field's maturity mix continuously turns over and is never all-new or fully static. A lightweight soft-separation pass (spatial hash, single push-apart resolve per frame) keeps non-rafting, non-welded pans from stacking, explicitly skipped for a pan mid-cross (it is supposed to overlap its partner) and for any already-welded pair (their overlap is the mechanic). WARM START: the sim runs a fixed 14s at a 1/30s timestep before the first paint (and for the reduced-motion / paused frame), and if that warm start happens to land with no pan mid-raft, one eligible touching pair is forced into the mid-cross phase for that frame specifically so the very first paint always shows the diagnostic overlap — never a bare, jam-free field. TOKENS: --background is the water base; --ns-muted is mixed into it (12%) to lift open water off the raw page background in light theme, catching the flat pale-on-pale failure mode; pan-top color mixes --foreground into that water color (46%), and rim color is a further step DOWN in luminance, mixing the pan-top color back toward water (62%/38%) rather than ever using --border (a ~1.1:1 separator token, never a fill/stroke) — the same brighter-ice-than-water relationship holds in both themes via this mix ratio, not a color swap; the weld lens mixes --foreground more heavily (60%) as the one visibly brighter, thicker-reading tone. Every color is computed at runtime by parsing token hex values read via getComputedStyle(document.documentElement) and lerping RGB channels by hand into rgb() strings — never color-mix() (canvas fillStyle parses it unreliably across engines and fails silently) and never a literal color in source — read once document.fonts.ready resolves, before first paint, and re-read on a MutationObserver watching documentElement's class; every path that could paint first (ResizeObserver, IntersectionObserver, the reduced-motion branch) is gated behind a ready flag. --ns-accent never appears on the canvas: rafting is wave-driven, not pointer-driven, and there is no interaction moment for accent chrome to mark. HOST: DPR-capped backing store (max 2), IntersectionObserver (threshold 0) and visibilitychange both stop the single rAF loop, ResizeObserver reseeds and re-warms the field on layout change. The canvas is aria-hidden and pointer-events-none; children render as real DOM above it in normal focus order and are never intercepted by the canvas."
      }
    },
    {
      "name": "photostat-reverse",
      "type": "registry:ui",
      "title": "Photostat Reverse",
      "description": "A full-bleed hero headline reproduced like a photostat camera: each exposure flips the field between a negative and a positive generation, a little softer and thicker than the one before, until a fresh crisp exposure resets the drift.",
      "files": [
        {
          "path": "registry/loud/photostat-reverse/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/photostat-reverse.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "canvas",
          "webgl",
          "shader",
          "type",
          "print",
          "cursor"
        ],
        "instruction": "Build <PhotostatReverse headline? headlineWeight? headlineFit? headlineY? speed? paused? children? className?> as a full-bleed WebGL hero reproducing its own headline the way a photostat camera reproduced line art: shot directly onto photosensitive paper and developed in-camera, which comes out TONE-REVERSED, so a true-tone copy needs a second generation shot of the negative — and every generation pass also measurably softens and thickens the line work. MASK, NOT LIVE TEXT: the headline is rasterized once (on mount, resize, and headline change, after document.fonts.ready so fallback letterforms never freeze permanently) to an offscreen 2D canvas at up to 1024px wide, auto-fit so the longest line lands on headlineFit of the width. That sharp raster is baked as the R channel of a texture; a second canvas draws the SAME raster through `ctx.filter = 'blur(Npx)'` (N ~= 2% of the raster width) and its alpha becomes the G channel — a cheap, baked stand-in for a real signed-distance field, since thresholding a blurred alpha below 0.5 dilates it (thicker strokes) and above 0.5 erodes it (thinner), which is exactly the knob generation loss needs. GENERATION STATE MACHINE (CPU, not shader): a 1.3s cadence advances a generation index 0..4, where level L maps to threshold = 0.5 - 0.024*(1.04^L - 1)*4 (dilation growing 4% compounded per level) and edge = 0.014*1.06^L (softness growing 6% compounded per level); every step flips a tone flag EXCEPT the step that wraps level 4 back to level 0, which resets crisp but lands on whichever tone level 4 was already showing, never a flip and a reset at once. THE FLIP ITSELF is rendered as a 260ms crossfade: the shader evaluates the SAME mask sample against both the outgoing generation's (threshold, edge, tone) and the incoming generation's, and mixes the two resulting in-letter fractions by an eased transition progress — this is what makes the flip show genuine departure and arrival rather than a cut, per the round-9 legibility rule. TONE: mask value m produces color = mix(bg, fg, m) for a positive generation; a negative generation instead reads m as mix(bg, fg, 1-m) — the FIELD becomes ink and the letterform becomes the gap in it, a true full-frame inversion, not just a recolored glyph. PALETTE: only --background and --foreground are read (getComputedStyle(document.documentElement), re-read on a documentElement class MutationObserver) — the tone-flip mechanic already IS the monochrome rule's required value inversion, so there is no bias/contrast remap between themes the way a shaded material needs; both themes use the same two tokens directly. A very low-amplitude per-texel hash grain (photostat paper is never perfectly flat) and a soft corner vignette keep the field textured without ever approaching the flip's own amplitude. POINTER: a small radius around the cursor pulls the CURRENTLY-DISPLAYED generation's threshold/edge toward generation-1 crispness (a 'fresh exposure' halo, luminance-only, no --ns-accent anywhere) without touching the global cadence — the flip elsewhere in the frame never pauses or desyncs, and this pointer effect is NOT what makes the component alive (the tone-flip loop runs unconditionally), so autoplay stays 'none'. HOST: single WebGL pass (unlike weld-pool/flyback-tear's multi-pass accumulation, this shader is one texture sample plus two smoothstep evaluations, so DPR holds at the full cap of 2 rather than stepping down preemptively), DPR-aware backing store, ResizeObserver on the host element (not window.resize), IntersectionObserver (threshold 0) and visibilitychange both pausing the single rAF loop, webglcontextlost/restored rebuilding the program and mask cleanly. prefers-reduced-motion (and paused) freeze on generation level 2 (generation 3), positive tone, fully settled — soft enough to show the mechanic exists, still legible, never the near-mush of generation 5 — and pointer input over the frozen frame still recomposites once per event so the crisp halo works even at rest. The accessible copy is an sr-only <h1>, since the visible headline exists only as a mask driving the shader; children render over the field for an eyebrow, subhead and CTA, and because the field spans the full value range in both tone states they need a token scrim (bg-background/70 + backdrop-blur) rather than bare type."
      }
    },
    {
      "name": "ping-shadow",
      "type": "registry:ui",
      "title": "Ping Shadow",
      "description": "A full-bleed acoustic image of a seabed drawn by a sector beam that sweeps the frame: grazing-lit sand ripples, boulders with shadows stretching radially away from the transducer, range-growing speckle, and a persistence gradient trailing the wedge.",
      "files": [
        {
          "path": "registry/loud/ping-shadow/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ping-shadow.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "canvas",
          "webgl",
          "shader",
          "sonar",
          "cursor"
        ],
        "instruction": "Build <PingShadow speed? sweepPeriod? persistence? scale? paused? children? className?> as a full-bleed WebGL background that is an ACOUSTIC IMAGE of a seabed, built by a beam sweeping the frame, not a radar line over an empty field. POLAR FRAME: a transducer sits off-frame above the top edge at (0.5W, -1.18H) — on the edge would leave the top corners outside any sector and the display would carry two dead wedges — and every fragment computes its range r and bearing theta = atan(dx, dy) from it, with the sector half-angle derived from the viewport as atan(0.5W / 1.18H) * 1.05 so the rectangle always fits inside one sector. ANALYTIC PERSISTENCE, the load-bearing idea: the scanner is harmonic, theta_b(t) = A*sin(wt), so it is invertible, and the shader can ask 'how long ago did the beam last cross MY bearing' without a history buffer — sin(phase) = x has roots asin(x) and PI - asin(x), and the age is min(mod(phase - root, 2PI)) / omega. No feedback texture, no ping-pong FBO, and the consequence that matters is that frame zero is ALREADY a fully painted scene with a correct freshness gradient behind the beam rather than an empty display filling in. The clock also starts at 3.34s, so the first frame anyone sees has the beam a third of the way off boresight and travelling near its fastest. ECHO FIELD: return strength is assembled the way an insonified bottom assembles it. (1) Sand ripples lit at GRAZING INCIDENCE — phase = dot(wp, rd)*0.052 + fbm warp*5.2, and the return is -sin(phase) * dot(u, rd) where u is the unit outgoing ray, so facets tilted toward the transducer flare, facets tilted away go dark, ripples broadside to the beam band hard and ripples end-on almost vanish. That single dot product is the strongest cue that the frame is lit from one moving point. (2) Two fbm octaves of bottom sediment. (3) Eleven boulders, positions animated on the CPU and passed as a vec4[11] (x, y, radius, height): each gets a near-face specular gaussian at r = objectRange - radius*0.45 and an ACOUSTIC SHADOW covering everything at its bearing beyond it, length proportional to height and to range, with the lateral penumbra widening with distance behind because the source is not a point. Shadows always point directly away from the transducer and lengthen as the bottom drifts past — the tell that separates an acoustic image from a texture with a wipe over it. (4) Sparse point scatterers from a shell hash, one cell in a hundred. (5) Multiplicative speckle whose correlation cell grows with range, 1.7 + 5.5*r/refR px, SEEDED BY THE PASS THAT PAINTED THE PIXEL — floor((t - age)*2/period), not by now — so grain decorrelates one wedge at a time as the beam goes over it, exactly like ping-to-ping speckle decorrelation, instead of the whole frame boiling together. Then residual TVG (near range hotter than far) and log compression, log(1 + 7.5E)/log(8.5), the mapping every acoustic display uses to fit a decade of return into a screen's worth of luminance. BEAM AND RESOLUTION: the live wedge is defined in ANGLE, half-width 0.0055 + 0.0175*r/refR, so it fans out with range instead of staying a constant-width stripe, and it carries a wide faint skirt because no transducer has a hard-edged main lobe. Because azimuth resolution is an angle, the along-arc footprint grows with range, and the ripple term is low-passed by exp(-(beamWidth*r*rippleFreq*9)^2) — the far field genuinely loses fine structure the near field keeps, which is what stops the image reading as one flat texture. Gain is 0.085 + 0.915*exp(-age/tau) with tau = period*0.19, plus the wedge's own boost, so the frame carries a continuous gradient of freshness from the beam back into the dark and never fully empties. Receiver noise is redrawn every frame everywhere including inside the shadows, so the far side of the sweep is alive rather than dead. Range rings every fifth of full scale and bearing ticks are drawn at the threshold of legibility, brighter where the image is fresh. POINTER: the pointer aims and focuses. Horizontal position biases the sector centre by up to 0.34 of the half-angle and narrows the swing to match, so the beam is steered rather than teleported; vertical position sets the focal range, which narrows the wedge around that range (defocus = 0.55 + 1.5*|r - focal|/refR) and adds a gaussian gain band there. It is a lead-compensated follower — a plain exponential lags by exactly v*tau under constant velocity, so the aim is extrapolated one tau ahead — and the whole effect is gated by an eased hover term, so a resting pointer at the viewport origin only tilts the sector slightly rather than parking anything anywhere. PALETTE: four luminance stops from --background, --foreground, --ns-muted and --border via getComputedStyle, re-read on a documentElement class MutationObserver. --ns-accent is deliberately NEVER sampled: an echo has no hue, and the pointer's job here is aim and focus, not a coloured highlight. Dark theme is the instrument — strong returns bright on a near-black display. Light theme INVERTS to a wet-paper record, strong returns as dark ink on white, the way side-scan was printed before it was screened. The same L drives both and only the ramp reverses, so nothing in the shader knows about the theme. HOST: an internal GLSurface class owns the program, the fullscreen triangle pair and lazily-resolved uniform locations. DPR is capped at 1.5 because full bleed at 2 is four times the fragments and per-pixel hash speckle gains nothing past that, with a three-step adaptive ladder gated in milliseconds of wall clock (never in frames, since a frame-counted gate waits longest exactly on the machines that need help soonest). ResizeObserver re-lays out the apex and sector, an IntersectionObserver and visibilitychange both stop the single rAF loop, webglcontextlost/restored rebuild cleanly, and prefers-reduced-motion (or paused) draws one composed still frame at t = 3.34 that is a fully painted swath with the beam mid-pass, never a blank display — pointer input still re-aims that frozen frame. Children render over the display and need a token scrim, since the image spans the full value range in both themes."
      }
    },
    {
      "name": "plasma-filament-wander",
      "type": "registry:ui",
      "title": "Plasma Filament Wander",
      "description": "A full-bleed hero background modelled on a plasma globe: filaments reach from a center point to a faint outer ring, each retracting and regrowing toward a new point on its own staggered 1.2-2.6s cadence, with pointer proximity biasing where a filament next reroutes, never when.",
      "files": [
        {
          "path": "registry/loud/plasma-filament-wander/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/plasma-filament-wander.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "canvas",
          "plasma",
          "cursor",
          "filament",
          "ambient",
          "wander",
          "background"
        ],
        "instruction": "Build <PlasmaFilamentWander filamentCount? children? className?> as a full-bleed Canvas 2D hero background modelling a plasma globe. GEOMETRY: computeGeo(W, H, baselineCount) derives everything from the container's smaller dimension — ringRadius = minDim * 0.36 (the faint 'glass' reference circle, stroked once per frame in --ns-muted at 0.35 alpha, never a fill), a scale factor = clamp(minDim / 520, 0.55, 1.6) applied to filament width/jitter/halo so the same numbers read correctly from card scale up to a 100vw hero, and filamentCount = clamp(round(baselineCount * min(1, minDim/520)), 6, baselineCount) — the spec's 11-filament baseline holds at typical hero size and thins automatically toward 6 at card scale so the field never reads as noise (the kill criterion this exists to satisfy). STATE MACHINE: each filament is an independent object carrying phase ('attached' | 'rerouting'), its current angle, and a phaseEndMs/rerouteEndMs deadline; advanceFilament(f, nowMs, rand, pointerActive, pointerAngle) is called for every filament every frame and walks its phase machine forward through however many completed cycles fall between the last check and now (a for-loop with a 64-iteration guard, not a single if), which is what lets a background tab or the reduced-motion freeze jump straight to an arbitrary elapsed time without a filament ever appearing to teleport. TIMING: a filament attached at some angle holds it (with jitter, see below) until its own randomized phaseEndMs (1200-2600ms after its last reroute, drawn independently per filament — never a shared clock), staggered at init by giving each filament an initial phaseEndMs uniformly drawn from [0, 2600ms] so the 11 filaments never all move together; on reaching phaseEndMs it enters 'rerouting' for a fixed REROUTE_MS = 350ms, split into two eased halves via easeInOutCubic — the first half (0-175ms) shortens the filament from full ringRadius toward 0 at its OLD angle (retraction), the second half (175-350ms) regrows from 0 toward ringRadius at its NEW angle (re-extension) — so every reroute shows a real departure and a real arrival, never a blink or a snap. On completing the reroute the filament returns to 'attached' at the new angle and draws a fresh randomized 1200-2600ms lifetime. POINTER BIAS: pointerActive is true when the pointer's distance from the ring differs from ringRadius by less than 0.2 * minDim (tracked via a pointermove listener converting client coordinates into an angle+distance from the container's center); the bias is consulted ONLY inside advanceFilament, only at the exact instant a filament's own phaseEndMs is reached — with pointerActive true, each filament independently rolls a 40% chance (POINTER_BIAS_MAX_FRAC) to set its newAngle to pointerAngle plus a randomized +-0.35rad scatter instead of a uniform-random angle; the pointer never touches an already-attached or already-rerouting filament's current position, so it can only ever influence a filament that was already about to move on its own schedule — motion always originates from each filament's own clock, pointer proximity only ever redirects it. JITTER: while attached, a filament's interior polyline points (7 segments, JITTER_SEGMENTS, pinned exactly at the center and the ring so only the mid-span wanders, tapered by sin(pi*t)) are displaced perpendicular to the filament's direction by hashJitter(filamentIndex, floor(nowMs/100), segment) * (4-8px fixed per filament) * scale — a pure hash of the filament index, a 100ms time-bucket, and the segment index, so the ~10Hz plasma-noise resample falls out of flooring nowMs rather than needing a separate low-frequency timer or any persisted per-frame jitter state; jitter is zeroed during the 350ms reroute transition so the retract/regrow read stays clean. RENDER: each filament is stroked once (round joins/caps) in --foreground at 0.95 alpha with a canvas shadowBlur of 4px*scale in the same --foreground colour for its halo — luminance-only, --ns-accent never appears anywhere in the filament, halo or ring, per the standing accent-interaction-chrome-only rule; core width is 1.5-2.5px * scale, randomized per filament at build time and fixed for its life. Colour is read once via getComputedStyle(document.documentElement) for --foreground and --ns-muted with no literal fallback of any kind; if either is empty the mount loop retries on the next rAF and paints nothing until both resolve. A MutationObserver on document.documentElement's class attribute re-reads tokens on every theme flip; a ResizeObserver on the root recomputes geometry, resizes the canvas backing store (capped devicePixelRatio 2), and rebuilds the filament array from a fresh seed only when the computed count actually changes (a resize that doesn't cross a count threshold keeps every filament's in-flight phase intact); an IntersectionObserver stops driving new frames while off-screen and, on re-entering view, re-reads tokens and resumes the same continuous elapsed-time clock (filaments are never reset on re-entry, since each already self-corrects via advanceFilament's catch-up loop) — no paint happens on any of these three paths before a token read succeeds. Under prefers-reduced-motion the component runs zero rAF loops, zero timers, and attaches no pointer listeners at all: it renders exactly one deterministic frame by calling the same render/advanceFilament path once with nowMs = 1800 (a fixed seed's t=1.8s state — some filaments freshly regrown and short, most mid-jitter at full length, asymmetric and mid-cycle rather than the uniform t0 or fully-settled extremes) and never re-renders. Cleanup on unmount cancels both the animation and token-wait rAF handles, disconnects all three observers, and removes the pointer listeners. Optional children render over the field in a centered max-w-5xl column (headline/CTA slot, mirroring hero-ascii-lichtenberg's overlay pattern) with pointer-events left to the canvas's aria-hidden sibling so nothing intercepts clicks meant for real content. Zero dependencies, DOM+canvas only, no colour literal anywhere including fallbacks."
      }
    },
    {
      "name": "rapid-wire",
      "type": "registry:ui",
      "title": "Rapid Wire",
      "description": "Payment submission as an Edwardian cash railway: the amount folds into a wooden cup that catapults along a sagging catenary wire to the cashier post, rocks there while the server settles, then coasts back carrying the receipt and, when there is one, the change or instant refund as a separate line.",
      "files": [
        {
          "path": "registry/loud/rapid-wire/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/rapid-wire.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "payment",
          "checkout",
          "submit",
          "async",
          "receipt",
          "refund",
          "svg",
          "physics",
          "aria-live",
          "accessibility"
        ],
        "instruction": "Build a payment-submit control as an Edwardian cash railway: a till post and a raised cashier post joined by one sagging wire, and a cup that runs the wire in both directions carrying money. There is exactly one governing scalar, `s`, an arc-length position along a single SVG catenary path solved analytically from the two posts' real endpoints (the standard two-point a*cosh formula, not a bezier approximation) and sampled into a static polyline `d`. A second, invisible reference `<path>` carries that same geometry and is queried every animation frame via `getPointAtLength`/`getTotalLength` — never redrawn — so the physics reads real DOM geometry instead of duplicating the math. From the sampled point and its local tangent the frame derives three things, all per-frame, never a keyframe tween: velocity (energy conservation against the wire's actual height climb from till to cashier, so launching downhill off the till post genuinely speeds the cup up and climbing the last stretch to the cashier post genuinely slows it down), cup tilt (the tangent angle, clamped), and — on the separate VISIBLE wire path only — a small gaussian deflection bump centered under the cup's current x, a few px, cosmetic and never fed back into the physics query. On submit the outbound launch speed is solved, not guessed: given the wire's real height climb, v0 is picked so the cup arrives at the far post at a small target speed — it just crests rather than sailing past, with a short additional braking zone in the final ~28px so the crest reads as a landing, not an overshoot. State machine: idle -> outbound (launch, ~1s of gravity-coasting climb) -> settling (parked at the cashier post, rocking gently on a sine wobble while the real async onSubmit promise is in flight, held for a minimum window so the beat reads even if the promise resolves instantly) -> returning (coasts back, net downhill so it naturally accelerates, caught by a braking zone right at the till post so the landing is gentle rather than a physically-accurate slam) -> arrived, where the outcome unfolds as real DOM `<li>` rows inside an `<ol>`, staggered in, never painted SVG/canvas text. The built-in demo cycle (used whenever no `onSubmit` prop is supplied) rotates through the cases that matter for a payment window where value comes back: exact tender (single captured line), change due (captured plus a negative change line), a partial authorization with the remaining balance refunded instantly as its own line, and a decline where the entire hold is released back — each returning line carries a small down-arrow glyph and a leading minus sign distinct from the captured line's plain amount. The submit button is never given a `disabled` attribute; busyness is a guard on the click handler so it stays focusable and keyboard-reachable through the whole trip. Accessibility: one `role=status aria-live=polite aria-atomic=true` region speaks exactly three coarse events per cycle — payment sent, the settle result (e.g. 'Captured — $4.20 change returning.'), and receipt ready — never per-frame position, with a zero-width-space parity toggle so a repeated identical message still forces a re-announcement; the same text also renders as a small always-visible caption so nothing is carried by the graphic alone. The wire/cup SVG is `aria-hidden`; the receipt is a real `<ol>` in document order. `prefers-reduced-motion` swaps in a version where the cup stays parked at the till post — no rAF loop runs at all — while the same status guard, the same three announcements and the same receipt land in the same order, just without the trip. Colors are CSS custom properties only (`--background --foreground --ns-muted --border --ns-accent`) read as `var(...)` directly in SVG attributes; no canvas, no gradients, no hex. Zero dependencies."
      }
    },
    {
      "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/--ns-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": "rime-creep",
      "type": "registry:ui",
      "title": "Rime Creep",
      "description": "A full-bleed pane of window frost growing in real time: dendritic ice ferns advance from nucleation points on a fixed 60-degree lattice, sprout side branches, thicken behind their own tips and stop dead where two grains collide, leaving the clear glass between them slowly closing over before the whole pane thaws and seeds again.",
      "files": [
        {
          "path": "registry/loud/rime-creep/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/rime-creep.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "webgl",
          "shader",
          "simulation",
          "crystal",
          "monochrome",
          "cursor",
          "decorative"
        ],
        "instruction": "Build <RimeCreep density? growth? relief? sparkle? speed? paused? children? className? style?> as a full-bleed WebGL pane of dendritic window frost that is GROWN, not sampled from noise. The component is a CPU tip simulation feeding a persistent GPU accumulation buffer, plus one fragment shader that reads that buffer as ice. GROWTH: every crystal belongs to a grain with one random lattice orientation, and every tip in that grain travels on that lattice and no other — six primary arms at theta0 + k*60 from each nucleation point, side branches sprouting at exactly +/-60 from the parent's heading, which keeps them on the same six-fold lattice for every generation. That fixed angle set is the whole tell: a noise-driven or curvature-driven front gives mushy emergent branch angles, and the eye reads it as smoke rather than as ice. Tips advance at a per-generation speed (primaries fastest, each generation 0.66x) and deposit a segment every ~4px of travel; children inherit width*0.62 and a shorter branch spacing, so an arm tapers into a fern instead of into a bush. COLLISION AND GRAIN BOUNDARIES: a coarse occupancy grid (5 css px cells) is stamped with the grain id along every deposited segment. A tip stepping into a cell owned by a DIFFERENT grain dies on the spot; two fronts advancing at each other therefore both stop a cell short and leave a thin unfrosted seam between them, which is what a grain boundary on real glass looks like. A tip stepping into its OWN grain dies only if that cell is older than a few frames, so a tip cannot suicide on the cell it just wrote. ACCUMULATION: new segments are packed into one dynamic vertex buffer per frame and drawn as two batched passes into an RGBA8 framebuffer that is never cleared — R accumulates density additively, G and B are written source-over as the segment's birth phase and its grain orientation. Cost is O(new segments), not O(total frost), so the pane can carry a hundred thousand deposits at a flat frame cost. Each segment's fragment shader draws its own feather: a gaussian core, a broad low skirt, and BARBS on a lattice along the arm at +/-60 with hashed lengths, so the fern texture is resolved at pixel scale rather than at simulation scale. Core amplitude stays near 0.3 so overlapping hairs build structure instead of clipping R to 1 and flattening the dense regions into blob. THICKENING: nothing redraws old frost. The render pass lowers its coverage threshold with the AGE of each texel (G against the cycle phase), from 0.30 down to 0.055 over ~0.22 of a cycle, so a segment's soft skirt crosses the threshold some seconds after its core did and the arms visibly fatten behind the advancing tips while the clear glass between them closes over. CYCLE: growth, fill, then a thaw ramp over the last quarter of a ~56s cycle that multiplies density down while the next generation of seeds is already nucleating, so the resting frame is never empty and never static. The simulation is PREWARMED at mount by ~13s of accelerated steps, so the first painted frame is already a half-frosted pane and no autoplay descriptor is doing the work of proving it is alive. ICE, NOT A HEIGHTFIELD WITH A LAMP: density is central-differenced for a normal, but the dominant term is SCATTER, not diffuse shading — frost is bright because it is a mess of air-ice interfaces, so luminance climbs with accumulated thickness and climbs again at the gradient, giving every arm a bright edge and every crevice a dark contact line. On top of that sits micro-facet scintillation: a per-pixel hash beaten against two slow temporal frequencies and gated on coverage, so the whole frosted area twinkles continuously and every frame differs everywhere rather than only at the ~200 pixels of advancing tip. PALETTE: five luminance stops from --background, --foreground, --ns-muted and --border via getComputedStyle, re-read on a documentElement class MutationObserver. The glass level is an explicit uniform because the two themes are different pictures, not one picture with the contrast inverted: dark theme is a cold black pane with the ice owning the whole top of the range; light theme sets the clear glass at a mid pale grey so the frost still has headroom ABOVE it for the arm cores and headroom BELOW it for the contact lines, which is the failure mode that washes a pale-on-pale frost out to flat white. POINTER: the pointer is a warm fingertip, and it is destructive — the frost under it is multiplied down in the accumulation buffer through a colorMask that touches the density channel ONLY, since decaying the birth and orientation channels would make melting frost read as older, i.e. thicker, exactly as it should be vanishing. Melting also clears the occupancy grid it passes over, so tips creep back into the cleared glass and refreeze it a few seconds later. The warm halo is expressed purely in luminance — no --ns-accent anywhere in the shader, because a tinted blob parked at the viewport origin is the single fastest way to make a monochrome pane look like a rendered glow sprite. Melt is gated on a real pointerenter/pointermove, so a headless run with the mouse parked at the origin does not scrape a permanent bald corner into the pane. HOST: DPR capped at 2 for the visible canvas with an adaptive render-scale ladder [1, 0.8, 0.62] that steps only after 900ms of sustained wall-clock overrun; the accumulation buffer is sized independently of that ladder, so a scale step cannot wipe the frost. ResizeObserver re-sizes and reseeds; IntersectionObserver and visibilitychange stop the single rAF loop; webglcontextlost/restored rebuild cleanly; paused is polled rather than made an effect dependency. prefers-reduced-motion (and paused) prewarm to a composed still frame of a fully grown pane and draw it once, and pointer input still redraws that frozen frame. The canvas is aria-hidden; children render over the pane and need a token scrim (bg-background/70 + backdrop-blur) because the ice spans the full value range in both themes."
      }
    },
    {
      "name": "ring-graze",
      "type": "registry:ui",
      "title": "Ring Graze",
      "description": "Fairy-ring annuli grown from a single consumable, recovering substrate field: fronts expand hollow because they consume the ground they cross and leave it dead behind them, two fronts strand and die along a shared exhausted seam instead of passing through each other, and new rings only germinate once grazed ground has genuinely recovered. A slow ambient backdrop tuned for 404s, waitlist pages and article footers.",
      "files": [
        {
          "path": "registry/loud/ring-graze/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ring-graze.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "canvas",
          "simulation",
          "cellular-automaton",
          "reaction-diffusion",
          "monochrome",
          "ambient",
          "decorative",
          "organic"
        ],
        "instruction": "Build <RingGraze recovery? speed? paused? children? className? style?> as a full-bleed canvas field of fairy-ring annuli emergent from a single consumable, recovering substrate scalar, not from stroked circles with a collision mask on top. Two lattices run at roughly 1/6 viewport resolution (coarsened further under an internal cell budget so huge panes stay cheap): S, a substrate concentration in 0..1 that recovers toward 1 on time constant `recovery` (the governing scalar — the whole piece's tempo lives on this one clock), and a fixed pool of rings, each just an array of cell indices making up its currently-active front shell. Every simulation tick, stepped at a deliberately low 15Hz, runs in order: GROW — a shared budget accumulates at 6 cells/s; once it banks a whole cell, every living ring's front shell dilates one cell outward over its 8-connected neighbourhood, and each front cell claims any neighbour whose S exceeds 0.4, dropping that neighbour's S to 0.05 the instant it's claimed. The gap between 0.4 and 0.05 is what makes the annulus hollow instead of a filled disc: the OLD front cells are never carried into the next shell, they simply become dead interior, so only the newest ring of cells is ever drawn and the interior really is just substrate sitting at 0.05 with nothing currently consuming it — nothing here masks a filled circle after the fact. A front cell that claims zero neighbours (a canvas edge, or every neighbour already under 0.4 from its own ring's past growth or another ring's) is simply not carried into the next shell, which is a ring dying locally rather than a comparison against another ring's geometry: the mechanism never checks ring A against ring B, it only ever reads the one shared scalar field both rings happen to have grazed, so two fronts advancing at each other strand the seam between them — both sides face already-exhausted ground and neither can cross it — producing the scalloped interlocking arcs real fairy-ring lawns show from the air. Because growth only ever dilates outward into cells the threshold check just verified are still fresh, and a ring's own past sits at 0.05 far below that threshold, a ring structurally cannot re-cross its own past: at any sane `recovery` a ring finishes crossing the field or dies at a boundary/seam long before its own early interior could climb back over 0.4. RECOVER runs next — every cell's S relaxes toward 1 by (1-S) * dt/recovery each tick; `recovery` of 0 turns this off entirely, so nothing ever climbs back over the reseed threshold and, once every live ring has died out, the field is provably and permanently still within about two ring lifetimes, not asymptotically — this is the falsifiable core of the design. SEED runs last — a second, far slower budget accumulates at 0.8 spores/min; once it banks one, up to 40 random cells are sampled and the first with S above 0.92 (substantially healed, not merely un-eaten) germinates a brand-new one-cell ring that restarts the whole GROW cycle from a point. Sampling only ever tests S, never ring ownership, because already-eaten ground always reads below threshold on its own — the substrate field is the only bookkeeping the component needs, and a starved field (or `recovery` of 0) simply spawns nothing rather than bursting once ground eventually frees up. A single ring, unobstructed, crosses this whole lattice in roughly 8-10s at 6 cells/s — faster than the 0.8/min clock could ever place a second nucleus nearby — so mount also scatters several nuclei at once through the identical claim rule (scaled to grid area, roughly one per 1500 cells), which collide with EACH OTHER well short of any edge and hand back the scalloped interlocking-arc geometry immediately, while the one-at-a-time spore clock keeps reseeding for as long as the piece runs. RENDER: S is painted at grid resolution into an ImageData — a cell mixes --ns-muted (freshly eaten) toward --background (fully recovered) by its own value, so healed ground fades back toward invisible; only cells still meaningfully depleted (well under half recovered) layer an ordered 4x4 Bayer dither toward --border on top, so a fresh scar reads as broken-up texture while ground past that point is a smooth, dither-free ramp rather than dot-patterned everywhere it's merely still healing; every cell currently part of a live ring's front shell is then painted solid --foreground, a thin one-cell-wide arc. The grid is upsampled onto the visible canvas — sized at devicePixelRatio, capped at 2x — with the canvas blur radius scaled to the actual device-pixel cell size (a fixed small blur only softens the dither at the source grid's own tiny scale and still reads as a crisp dot grid once upsampled), so the result reads as soft aerial grain rather than visible grid cells. --background, --ns-muted, --border and --foreground are read via getComputedStyle(document.documentElement) at mount and re-read on a documentElement class mutation (theme toggle). `recovery` (seconds, default 11) is read live every tick, not only at mount. `speed` is a global tick-rate multiplier, also read live. `paused` freezes the current frame without unmounting (polled via a ref rather than an effect dependency, so toggling it does not tear down and reseed the field). There is no pointer or keyboard interaction anywhere: the canvas is aria-hidden, the wrapper renders `children` as ordinary accessible DOM over the field, and the whole point is a slow ambient process tuned to be glanced at across minutes rather than watched — the intended habitat is a 404, a waitlist page, or a long article's footer. prefers-reduced-motion runs the tick function enough times to cover 90 simulated seconds synchronously at mount and paints one static aerial frame instead of ever scheduling a rAF loop, and a live change to the media query freezes or resumes growth from wherever it currently is. ResizeObserver rebuilds the lattice and ring pool (debounced) on a real size change; IntersectionObserver and visibilitychange stop the single rAF loop off-screen or in a hidden tab. A deterministic xorshift PRNG (not Math.random) drives every spawn-cell sample, so the shipped resting frame is reproducible mount to mount rather than drifting between screenshots."
      }
    },
    {
      "name": "ripple-migrate-slip",
      "type": "registry:ui",
      "title": "Ripple Migrate Slip",
      "description": "A full-bleed dune-ripple field built on real aeolian bedform migration: each ripple's gentle windward face keeps piling higher until it crosses the angle of repose, its steep lee face avalanches, and the crest kicks one step downwind. Many ripples do this on independent, staggered clocks, so the whole train visibly creeps at rest with no two slips in sync.",
      "files": [
        {
          "path": "registry/loud/ripple-migrate-slip/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/ripple-migrate-slip.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "sand",
          "dune",
          "physics",
          "granular",
          "mono",
          "ambient"
        ],
        "instruction": "Build <RippleMigrateSlip children? className?> as a full-bleed 2D-canvas hero rendering AEOLIAN SAND-RIPPLE MIGRATION as an exact piecewise-linear ridge silhouette, not a particle field or a sampled heightfield. THE MECHANISM: each ripple is one object carrying a scalar stoss-pile height (amp) that grows continuously — modulated by a slow global gust sine, GUST_PERIOD_S=11, amplitude fraction 0.25 — until it crosses an angle-of-repose threshold (34deg over a fixed 40px lee run, THRESHOLD_AMP = 40*tan(34deg) ~= 27px), at which point a 260ms avalanche eases amp down to a stable residual (20deg over the same run, ~14.6px) and eases the crest x forward by 6-10px in the same interpolation — the avalanche interpolation IS the migration step, there is no separate transform. Growth rate is tuned (per-ripple, ~9-12s to threshold with a 0.85-1.15 per-ripple multiplier for staggering) so that across the several ripples visible at full-bleed width, avalanche events land roughly 1.1-1.6s apart somewhere in the field, never synchronized — this is what the round 9 legibility rule requires for a discrete-event mechanic. RENDERING: ripples are built left-to-right with randomized 150-210px (visual) gaps between crests, extending past both canvas edges by one wavelength so there is never a visible seam; the silhouette is drawn as ONE exact polygon through every ripple's crest point and its post-lee trough point (piecewise-linear lineTo calls, no bucketed heightfield sampling — this avoids any aliasing between the physics and the drawn shape), filled from a y0 baseline at 68% of container height down to the canvas bottom, with rendered ridge height = amp * 4 (a display-only scale-up from the sand-grain-scale physics amplitude to a legible full-bleed ridge height; the physics thresholds/rates themselves are NOT scaled). A handful of small fixed dots per ripple (4-6, precomputed offsets stored on the ripple object at creation) ride each ripple's stoss segment as a grain-texture stipple, in a slightly higher foreground-mix tone than the base ridge fill — cheap, never per-pixel, never a real grain simulation (that territory belongs to winnow-chaff-drift). ALIVE AT REST: t0 shows several ripples mid-accretion at different phases with no avalanche active; by 2.5s at least one avalanche has fired and completed somewhere in the field (a visible profile shift); by 5s cumulative crest drift is visible when compared to t0 and 2-3 avalanche events will have fired across the field, each individually legible at the moment it happens. Colors: ridge fill is a fixed --background/--foreground luminance mix (0.55) via getComputedStyle re-derived on a documentElement class/attribute MutationObserver and a prefers-color-scheme listener; stipple dots mix further toward --foreground (delta 0.12 dark theme, 0.22 light theme, since light theme is the harder case for a low-contrast stipple to hold) — zero color literals, zero hue, no --ns-accent anywhere (fully ambient, no pointer interaction to justify it). prefers-reduced-motion runs a deterministic seeded (mulberry32) 14s settle from a fixed seed, then forces the field's centre ripple into a manually-set t=0.5 avalanche pose (pre-slip overhang blended toward the post-slip stable line) before a single draw() and no further animation, so the frozen frame always shows the mechanic's signature mid-slip moment rather than an arbitrary resting pose. DPR capped at 1.5 (full-bleed cost dominates). Standard canvas host lifecycle: ResizeObserver on the root (rebuilds the ripple field on resize), IntersectionObserver (threshold default) and visibilitychange both pausing the rAF loop, full teardown on unmount. Zero dependencies, DOM + Canvas 2D only, no WebGL. Canvas is pointer-events-none and aria-hidden; children render as an ordinary absolutely-positioned DOM overlay on top, same pattern as winnow-chaff-drift and dye-whorl."
      }
    },
    {
      "name": "roast-first-crack",
      "type": "registry:ui",
      "title": "Roast First Crack",
      "description": "A full-bleed ambient background modeling first crack in a rotating coffee-roaster drum: beans tumble continuously (carried up the wall by rotation, then cascading back down once they pass their repose angle), and individual beans intermittently crack open at an irregular, clustered cadence, each releasing a fleck of chaff that curls off the fissure edge and drifts free while the bean settles back into the tumble.",
      "files": [
        {
          "path": "registry/loud/roast-first-crack/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/roast-first-crack.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "particles",
          "roasting",
          "coffee",
          "stochastic",
          "monochrome",
          "ambient",
          "decorative"
        ],
        "instruction": "Build <RoastFirstCrack paused? children? className? style?> as a full-bleed Canvas 2D background (w-full h-full, DPR capped at 1.5) modeling a REAL coffee-roasting first-crack drum, not generic tumbling particles. GEOMETRY: drum boundary is a circle centered in the container with radius = min(width,height) * 0.42; a thin low-alpha --ns-muted ring traces it and a single spoke line (drumRotation angle from center to rim) makes the drum's own rotation legible against the beans' faster individual tumble. BEAN COUNT: floor(drumArea / 900px^2), clamped 40-90, generated once per container size from a seeded PRNG (mulberry32) so the same size always regenerates the same population. TUMBLE (two-state per bean, not rigid-body physics): each bean holds theta (math-convention angle, DOWN = -PI/2 is a FIXED gravity reference the rotating wall sweeps past) and wallRadius (fraction of drum radius). In 'carry' state theta advances at the drum's angular rate (0.15 rev/s) times a per-bean omegaMul (0.85-1.15, jittered at spawn) — the wall dragging the bean up from the bottom. Once the bean's ascent from DOWN exceeds its own per-bean repose angle (100-140deg, jittered), it switches to 'fall' state for 400-800ms: theta eases (easeInOutQuad) from its current value to a new target near DOWN plus scatter (+-~25deg), and wallRadius eases to a freshly-sampled mid-pile depth (0.2-0.82 of drum radius) — simulating a slide down the cascading pile face — then resumes carry from there. A small per-bean sinusoidal radial wobble (freq 0.3-0.65Hz, amp 2-4% of drum radius) is added on top at render time so beans never look perfectly rigid even mid-carry. Bean fill ellipses use --ns-muted, rx = drum radius * (0.05-0.072 jittered), ry = rx * (0.72-0.94 aspect jitter), rotated so the ellipse's long axis follows the bean's tangential tumble direction (rotate by -theta + PI/2). CRACK SCHEDULER: a single Poisson-ish process (exponential inter-arrival via -log(1-rand())*1400ms mean, plus a hard 700ms floor added on top so consecutive cracks never blur together) picks one random CURRENTLY-UNCRACKED bean each time it fires and cracks it; if the pool is momentarily empty, the tick is skipped rather than forced. A cracked bean gets: a fissureAngle (random chord direction), a resetAt time 8000-12000ms in the future at which point cracked flips back to false (unbounded supply, never exhausted), and 1-2 chaff flecks (45% chance of the second, staggered by up to 40ms). FISSURE RENDERING: a --foreground stroke at FULL opacity (1.0, not reduced — a crack is a genuine full-contrast feature regardless of theme) drawn as a straight chord through the bean's local space at fissureAngle, visible for the bean's entire cracked duration (8-12s), so it travels with the bean's tumble via the same translate/rotate transform as the fill. CHAFF RENDERING (curling peel, not a free particle — this is the deliberate distinction from a lateral-crosswind chaff drift elsewhere in the registry): each fleck has a fixed hinge point (the bean's screen position at the moment of the crack) and a tangentAngle (roughly outward from drum center, biased toward screen-up). 0-400ms: the free end sweeps from the hinge through a 70deg arc (easeOutCubic on the sweep fraction) at length = bean's rx * 0.9, drawn as a quadratic curve (not a straight line) from the hinge through a midpoint at half the current arc angle to the tip — this reads as curling, not pivoting. 400-600ms: the whole stroke (hinge and tip together) detaches and drifts 15px/s further along tangentAngle while alpha fades linearly to 0; past 600ms the fleck is removed. Chaff alpha is 40% of --foreground, but read via getComputedStyle, composite fg-at-alpha over --background using the WCAG relative-luminance contrast formula, and step alpha up in 0.05 increments (capped at 0.85) until the composited-vs-background ratio clears 3:1 — the same discipline low-alpha ink needs in light theme elsewhere in this registry. ALIVE AT REST BY CONSTRUCTION: the crack scheduler never stops and every cracked bean is guaranteed to reset and become crackable again within 8-12s, so nucleation and turnover are permanent — t0/2.5s/5s are structurally guaranteed to differ (new fissures, new chaff, drum rotated ~22deg by 2.5s and ~45deg by 5s per the resting-loop spec). TOKENS: --background clears the canvas, --ns-muted is the bean fill and the drum ring/spoke, --foreground is the fissure and chaff color. All three read only via getComputedStyle(document.documentElement) inside document.fonts.ready, before the first paint, re-read on a MutationObserver watching documentElement's class, with every early-paint path (ResizeObserver, IntersectionObserver resume, the reduced-motion branch) gated behind a `ready` flag. --ns-accent never appears anywhere — there is no interaction surface on this component (interaction is None by spec). WARM START: 60 fixed-dt (1/30s) steps run before the first live paint so the drum already shows beans mid-tumble and mid-fall rather than a static ring. REDUCED MOTION / paused: freeze on the instant just after a crack fires, not t0 — rebuild the field deterministically, run 90 warm steps, force-crack the first currently-uncracked bean at 40% through the pool order, then run 15 more 1/30s steps (~500ms) so the frozen frame shows the fissure fully visible AND its chaff mid-drift, mid-fade (past the 400ms curl, partway through the 200ms detach-fade, never at spawn point and never fully gone) — the one frame that shows population, tumble state and the crack mechanic's full departure-and-arrival lifecycle simultaneously. HOST: DPR-capped (max 1.5) backing store, ResizeObserver on the root rebuilds the bean population and drum geometry on layout change, IntersectionObserver (threshold 0) and visibilitychange both stop the single rAF loop when offscreen/hidden and resume it (never during reduced-motion or paused). The canvas is aria-hidden and pointer-events-none; children render as real DOM above it in normal focus order. No interaction anywhere — no pointer listeners, no --ns-accent, no autoplay trigger beyond mount."
      }
    },
    {
      "name": "rolling-shutter-skew",
      "type": "registry:ui",
      "title": "Rolling Shutter Skew",
      "description": "A full-bleed ambient background modeling CMOS rolling shutter: a vertical rule grid bends into a shifting parallelogram because each virtual sensor row captures the simulated pan at a slightly later instant than the row above it, the readout period accumulating into more displacement toward the bottom of the frame.",
      "files": [
        {
          "path": "registry/loud/rolling-shutter-skew/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/rolling-shutter-skew.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "rolling-shutter",
          "camera",
          "sensor",
          "jello-effect",
          "decorative"
        ],
        "instruction": "Build <RollingShutterSkew gridSpacingRatio? paused? children? className? style?> as a full-bleed Canvas 2D background modeling REAL CMOS ROLLING SHUTTER — a sensor that reads photosite rows top-to-bottom over a readout period instead of capturing every row at once (the 'jello effect' seen on phone cameras panning past a fence). MECHANIC: draw a grid of vertical rules spaced at max(18, min(width,height) * gridSpacingRatio) (default gridSpacingRatio 1/24). Each rule is NOT a straight line: it is built from N_ROWS=48 points, one per virtual sensor row, where row `row` (0..48) is captured at captureTime(row) = t - (row/N_ROWS)*T_RO, T_RO = 0.0167s (a 1/60s progressive CMOS readout), and displaced horizontally by skew(row) = panVelocity(captureTime(row)) * (row/N_ROWS) * T_RO — later rows have accumulated more elapsed readout time and therefore more displacement, which is what bends a straight vertical line into a piecewise parallelogram instead of just sliding it sideways. panVelocity(t) = 820*sin(2*PI*t/5.8) + 140*sin(2*PI*t/0.9) px/s: a slow base pan (period 5.8s) plus a faster handshake wobble (period 0.9s), both periods deliberately non-round so the combined phase never visibly repeats on a short human-noticeable cycle. The pan amplitude is intentionally exaggerated roughly 6x above a literal handheld pan's contribution at 1/60s readout (which would only bend the grid a couple of px) so the effect reads clearly at showpiece scale — the readout period and the per-row capture-time formula are the real physical numbers, only the pan amplitude is a showpiece exaggeration, the same latitude weld-pool takes with its specular structure. EXPLICITLY NOT registry/loud/flyback-tear (cut on owner review, CRT family): flyback is a DISPLAY-side sync failure, a discontinuous tear/roll where the beam loses lock and the image jumps. This is CAPTURE-side and continuous by construction — adjacent rows are always adjacent in both space and capture time, so the grid bends smoothly and NEVER jumps or tears; if an implementation introduces any discontinuity, that is the exact CRT-family bug this component exists to avoid. EXPLICITLY NOT registry/core/grid-magnetic-lattice: that lattice bends toward the CURSOR via a gaussian pull field, an interactive tool grid with a positional attractor. This grid's bend comes entirely from the self-running panVelocity(t) function; skew is a pure, continuous function of elapsed time with no accumulated simulation state and no discrete steps, so ALIVE AT REST falls out of the formula itself — at any sampled instant the grid's shape is provably different because panVelocity's two non-resonant sine periods are never simultaneously at the same phase twice within any short window. POINTER (optional, do not gate the ambient loop on it): pointermove over the component measures instantaneous pointer velocity and adds a clamped kick term (gain 0.55, clamp +-420 px/s) to a shared `kickVel` value that is simply ADDED to panVelocity's output before computing skew — it perturbs the same shared velocity term the ambient pan already uses, it is never a separate positional attractor or a second highlight, and it exponentially decays (tau=0.12s, effectively fully decayed within ~400ms) back to zero whether or not the pointer keeps moving. TOKENS: --foreground is the only ink color, drawn at fixed alpha 0.35, lineWidth 1 (css px at dpr 1) — no gradient, no fill. --ns-accent never appears anywhere in the canvas; this is a resting ambient surface, and the pointer kick modifies the SAME shared physics term the idle animation already runs on rather than drawing a second accent-tinted highlight. --background clears the canvas every frame. Both tokens are read via getComputedStyle(document.documentElement) only after document.fonts.ready resolves, before the first paint, and re-read on a MutationObserver watching documentElement's class; every path that could draw before that first read (ResizeObserver, IntersectionObserver, the reduced-motion branch) is gated behind a `ready` flag so nothing paints with an empty token string. REDUCED MOTION: prefers-reduced-motion (and the paused prop) freeze t at STATIC_TIME=1.45s — a quarter into the base 5.8s pan period, near its peak velocity — and draw exactly that frame once, chosen because it shows clear asymmetric shear rather than the near-straight read a t where the pan sinusoid crosses zero would give. SCALE: grid spacing derives from min(width, height) so it reads proportionally at both full-bleed hero scale and a small preview-card crop; N_ROWS stays fixed at 48 regardless of container size since it represents virtual sensor rows, not a visual density knob. HOST: DPR-capped backing store (max 1.5, matching weld-pool's full-bleed cap), IntersectionObserver (threshold 0) and visibilitychange both stop the single rAF loop, ResizeObserver re-sizes the backing store on layout change (skew needs no rebuild on resize, it is a pure function of time and current width/height). The canvas is aria-hidden and pointer-events-none; the pointermove/pointerleave listeners live on the wrapping root element instead so the ambient kick works without the canvas itself needing to be hit-testable; children render as real DOM above the canvas and are never intercepted by it."
      }
    },
    {
      "name": "rosensweig-crest",
      "type": "registry:ui",
      "title": "Rosensweig Crest",
      "description": "A flat pool of ASCII glyphs that erupts into a hexagonal lattice of sharp spikes wherever the pointer's field crosses a critical threshold, tracking the cursor with a stiff-up, overdamped-down viscous lag. The Rosensweig instability rendered as a deformable surface, not particles.",
      "files": [
        {
          "path": "registry/loud/rosensweig-crest/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/rosensweig-crest.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "ascii",
          "hero",
          "canvas",
          "cursor",
          "physics",
          "ferrofluid",
          "field",
          "spring"
        ],
        "instruction": "Build a full-bleed Canvas 2D hero rendering a deformable ASCII SURFACE, not discrete particles. A hexagonal lattice of 'peak' points is authored directly in PIXEL space (dx = glyph cell size * 2.6, dy = dx * sqrt(3)/2, alternate rows offset by dx/2) so the hexagons stay visually equilateral regardless of the monospace glyph's cell aspect ratio — building the lattice in glyph-cell-index space instead would come out stretched, since a mono cell is taller than it is wide. Each lattice point carries its own scalar height and velocity (two Float32Array buffers, reallocated on resize). Every frame: field strength at a lattice point is (1 - distance/230px)^2 clamped to [0,1] against the eased pointer position (an inner cursor.x/y lerps toward the raw pointer at 0.22/frame, adding a first layer of lag on top of the spring), then passed through smoothstep(0.5, 0.64, field) so the response is a hard phase boundary, not a gradual bulge — nothing happens below threshold, height snaps toward its ceiling just above it. An exclusion rectangle measured from the real DOM children (the headline/CTA wrapper, expanded by 22px padding and a 78px soft feather via smoothstep) multiplies the target toward zero near and under that content regardless of pointer distance, so the surface can never rise under the wordmark — a physical constraint on the field target, not a z-index trick. Each point then springs toward that target with an ASYMMETRIC response chosen by the sign of the error every frame: k=260 stiffness / c=15 damping when rising (fast, slightly underdamped, a visible snap-and-settle 'eruption'), k=26 / c=42 when falling (heavily overdamped, a slow heavy liquid slump, no bounce) — same spring equation, two constant pairs. Rendering: for every glyph cell, a 3x3-candidate hex nearest-neighbor search (checking the lattice row above/at/below the cell's approximate row, each with the column above/at/below) finds the owning peak's height AND the tallest height among those same candidates; if the cell's own height is below 0.55 and the neighborhood max exceeds it by more than 0.22, the cell renders the hollow glyph '˘' (a moat beside a taller spike) instead of its height bucket. Otherwise height buckets at 0.02/0.24/0.5/0.74/1.01 map to the ramp ' · ~ ≈ ∧ ▲' (flat pool through crest), with the bottom two buckets drawn in --ns-muted at low alpha (resting/rising, subtle) and the top three in the canvas's resolved color() (crest, full ink, alpha scaling 0.55 to 1 with bucket). This is a faceted nearest-peak lookup (effectively Voronoi-per-lattice-point), not smoothed interpolation, which is what gives spikes their sharp faceted edges rather than a blurred bump. The physics loop tracks an aggregate energy term (sum of |velocity| + height across all lattice points) each frame and stops requesting frames once it drops below a small epsilon AND no pointer is active — a pointerenter/pointermove/pointerleave triple on the ROOT container (never the canvas, which is pointer-events:none and aria-hidden) toggles pointerActive and re-wakes the loop; the canvas listening on itself would risk stealing events meant for the real headline/CTA layered above it in the same box. Touch/coarse pointers (matchMedia('(pointer: coarse)'), checked once at mount) never receive pointer listeners at all: instead pointerActive is forced permanently true and the eased cursor target follows a slow 15s lissajous lap across the container — the exclusion rect still guards the headline even though the autonomous path pays it no attention, because the guard lives in the field-target math, not the path. prefers-reduced-motion renders exactly one static frame of the FORMED lattice (a virtual magnet placed off-center, heights set directly to their equilibrium target with zero velocity, no spring, no loop, no listeners) rather than the flat rest state — the drama the component exists to show is the erupted phase, and reduced motion should still show it once. Colors are read via getComputedStyle(canvas).color for foreground ink and the --ns-muted custom property for the resting/hollow tone, both re-derived on a documentElement class MutationObserver for live theme flips, and --ns-accent is never painted (interaction-only per the token rule). Props: cellSize (glyph cell px, default 13, also drives lattice spacing), children (rendered in a centered column, its measured bounding box becomes the exclusion rect), className."
      }
    },
    {
      "name": "rotor-window-bank",
      "type": "registry:ui",
      "title": "Rotor Window Bank",
      "description": "Full-bleed hero backdrop modeled on a rotor cipher machine's stepping mechanism: three letter-wheel windows where the right wheel ticks steadily, kicks the middle wheel once per revolution, and the middle wheel occasionally double-steps, moving twice in one beat and dragging the left wheel with it, the real Enigma-family stepping anomaly compressed to a 9-position ring so it recurs roughly every minute instead of every eleven.",
      "files": [
        {
          "path": "registry/loud/rotor-window-bank/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/rotor-window-bank.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "rotor",
          "cipher-machine",
          "dom",
          "ambient",
          "mechanical"
        ],
        "instruction": "Build a full-bleed DOM-only (no canvas/WebGL) hero backdrop showing three rotor-machine letter-wheel windows side by side (left, middle, right), each an overflow-hidden window 1.6 row-heights tall over a 3-row glyph strip, row-height = min(containerWidth, containerHeight) / 5 recomputed on a ResizeObserver so it reads at card scale as well as full-bleed. RING=9 positions per wheel cycling glyphs A-I (compressed from the historical 26-position Enigma-family ring, documented in a code comment as a stated departure, not a fidelity claim). MOTION: the right wheel advances one position every TICK_MS=1400ms via a 220ms ease-out-cubic step transition then holds; every 9th right-wheel tick (one full revolution) it kicks the middle wheel forward one position. The middle wheel carries TWO notches (positions 0 and 4 of its own 9-position ring, an uneven split so the interval between recurrences is irregular, not a fixed multiple): if the middle wheel sits on either notch at the instant it would be kicked, that beat plays as a double-step instead of a single step — two back-to-back 220ms transitions with a 90ms hold between them (the middle wheel visibly moves, pauses, moves again), and a left-wheel kick (its own 220ms step) fires starting immediately after the middle wheel's second step transition completes, never simultaneously with it. With these numbers the double-step recurs roughly every 50-65s (alternating 4-kick and 5-kick gaps at 12.6s/kick), which is the entire visible point of the mechanism, so DO NOT compress the ring further or the anomaly stops reading as an event. Drive all three wheels from a single rAF loop accumulating real elapsed ms into a `simMs` counter (dt clamped to 100ms so a tab-switch gap can't skip ticks); the right wheel's position is a pure function of simMs, while the middle/left wheels are driven by a small per-wheel queue of at most two pending step events, computed incrementally exactly once each time the right wheel's tick counter crosses a multiple of 9 (never recomputed from t=0, so this stays cheap over an arbitrarily long mount). RENDERING: each wheel's 3 rows are refs updated with direct style writes (textContent, opacity, transform: translateY in % of the row's own height, formula `(r - 0.7 - frac) * 100` for relative row index r in {0,1,2} and continuous fractional step position frac) — zero React state on the hot path. Row opacity = 0.35 + 0.55 * max(0, 1 - |1 + frac - r|), giving the centred glyph ~0.9 opacity and the peeking neighbours above/below ~0.35, crossfading continuously through a step rather than a hard cut. On mount, simMs starts at a random offset in [0, 1400) so a fresh load never lands on a clean idle frame, and draw(simMs) is called synchronously before the rAF loop starts so there is no blank first frame. COLOUR: every visible surface uses Tailwind classes bound to --background/--foreground/--border tokens (text-foreground for glyphs, border-border/divide-border for the wheel-window frame and dividers, bg-background for the field) — no JS colour reads needed since this is pure DOM/CSS, no accent anywhere (this is a non-interactive ambient background). LIFECYCLE: pause the rAF loop via IntersectionObserver (threshold 0) when scrolled offscreen and on document visibilitychange when hidden, resuming exactly where simMs left off; disconnect every observer and cancel the rAF on unmount. REDUCED MOTION: a `prefers-reduced-motion` matchMedia listener freezes on STATIC_RIGHT=4, STATIC_MIDDLE=1, STATIC_LEFT=1 (the frame right after a double-step resolves, showing all three wheels having just moved relative to one another) with no rAF running at all. The whole element is `aria-hidden` and non-interactive; no click/hover affordance of any kind."
      }
    },
    {
      "name": "rupert-snap",
      "type": "registry:ui",
      "title": "Rupert Snap",
      "description": "A hash-chained audit log drawn as a chain of Prince Rupert's drops, one per entry, each drop's tail fused into the next drop's head. Tampering with an entry re-derives every hash from that row down, and a single wavefront detonates the drops strictly tailward from the break: later entries shatter because they depend on the edited one, and nothing above it moves.",
      "files": [
        {
          "path": "registry/loud/rupert-snap/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/rupert-snap.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "table",
          "audit-log",
          "security",
          "canvas",
          "hash-chain",
          "photoelastic",
          "shatter"
        ],
        "instruction": "Build <RupertSnap entries? breakIndex? className?> as a real HTML <table> of audit entries (actor, action, timestamp, truncated hash, note) with a <canvas> laid BEHIND it in a 56px gutter to its left — the audit trail itself is never canvas, only the chain graphic is. Each entry gets one Prince Rupert's drop in that gutter, its bulb centred on the row and its tail drawn down into the next drop's head, so the drops form one physically continuous chain the length of the log. Every intact drop holds visible internal stress as photoelastic fringes: banded contour rings inside the bulb whose phase drifts at 0.02 cycles/second — deliberately too slow to read as 'playing', just alive, the stored-energy cue a real Rupert's drop carries right up until its tail is snipped. THE MECHANISM: a hash chain is tamper-evident because entry i's stored hash is a function of entry i's own fields AND entry (i-1)'s hash. The component keeps two independently-derived chains — storedHashes, computed once from the original entries, and recomputedHashes, computed every render from current field values — using an FNV-1a step over `${prevHash}|${actor}|${action}|${timestamp}` seeded from a fixed GENESIS string, run in two basins and concatenated to 16 hex characters (not cryptographic; order- and content-dependence is the only property the demo needs). A tamper is exactly the first index where the two chains disagree; the component never asserts a break, it finds one. 'Simulate tamper at #k' edits entry k's action in place (appends ` -> escalate:owner`), which re-derives every recomputed hash from k onward and marks entries k..n invalidated (struck through with decoration-border, aria-describedby a shared sr-only note explaining that entry k's recorded hash no longer matches the chain recomputed from its current content). Rows above k are untouched and must stay untouched — that asymmetry is the whole point. THE SHATTER: one wavefront, 6 drops per 100ms (STAGGER_MS = 100/6), travelling strictly tailward from k — never inward from both ends, never per-drop independent, because simultaneity would erase the one fact the component teaches (later hashes depend on earlier content, never the reverse). Each detonating drop throws seeded granules (mulberry32, 42 per drop, hard cap 120) on a 360ms ballistic flight; a drop that has finished detonating is frozen into a static debris-line paint and dropped out of the physics loop entirely, which is what keeps a 50-entry chain inside a 60fps budget rather than asserting it. 'Verify chain' clears the tamper and re-seats every drop. Both controls are idempotent: repeated clicks land in the same state, so a card driver or a verifier press-pass can click either one twice without toggling anything. COLOR: all four canvas inks (--background, --foreground, --ns-muted, --border) are read via getComputedStyle at mount and re-read from a documentElement MutationObserver, so a theme flip repaints without a remount — no color literal is ever baked into the draw code. A ResizeObserver re-seeds geometry; the rAF loop pauses on document.hidden. prefers-reduced-motion renders the finished states directly: no fringe drift, no granule flight, tampered rows simply arrive already shown as broken. The status line is a role=status aria-live=polite region naming the verified count or the first invalidated entry. Props: entries (array of {actor, action, timestamp}, oldest first — chain order is array order), breakIndex (0-based entry the tamper button edits, default length-5 clamped), className."
      }
    },
    {
      "name": "sand-lock",
      "type": "registry:ui",
      "title": "Sand Lock",
      "description": "A full-bleed Chladni plate: forty thousand grains of sand storm across a vibrating steel field and collapse onto the nodal lines of a real plate mode every time the sweeping drive frequency lands on a resonance, then break apart into a different figure as it leaves.",
      "files": [
        {
          "path": "registry/loud/sand-lock/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/sand-lock.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "canvas",
          "webgl",
          "particles",
          "physics",
          "cursor",
          "decorative"
        ],
        "instruction": "Build <SandLock grains? speed? plateScale? paused? children? className? style?> as a full-bleed WebGL Chladni plate — tens of thousands of sand grains on a vibrating steel field, resolving out of chaos onto nodal lines and breaking apart again as a driving frequency sweeps. THE FIELD IS AN ACTUAL MODAL SOLUTION, not a decorative squiggle. Each mode of a square plate is the degenerate pair A = sin(n*pi*x)*sin(m*pi*y) and B = sin(m*pi*x)*sin(n*pi*y) sharing the Kirchhoff eigenvalue w = n^2 + m^2; the physical shape at that frequency is a rotation of the pair, cos(th)*A - sin(th)*B, with th varied per mode across the sequence because a real plate settles into one rotation or another depending on where it is clamped, and the two look nothing alike. A single scalar drive u is swept through a sparse set of six modes whose eigenvalues are ~1.4x apart (n^2+m^2 is dense, and two modes four percent apart never separate their responses, so the sweep visits only the frequencies that give a clean figure) (n != m always: n == m makes the antisymmetric combination identically zero), and every mode answers with the steady-state amplitude of a damped oscillator, r_k = 1/sqrt((1-x^2)^2 + (2*zeta*x)^2), x = u/w_k, zeta 0.022. The displacement field is the three strongest responses summed with those weights, and that ONE line is what makes the piece behave: on a resonance a single r_k dominates and the zero set is a clean, sharp, symmetric figure; halfway between two resonances two comparable responses superpose into a field with no stable zero set, so the sand has nothing to land on and storms. Nothing cross-fades one pattern into another — the mess in between is the honest answer to being driven off resonance. COHERENCE, peak_r / sum_r, is the storm signal and drives both the grain mobility and the diffusion. COVERING A WIDE VIEWPORT: a Chladni figure is square and a hero is not, but every one of these modes vanishes on the plate boundary (sin(n*pi) = 0), so the ODD REFLECTION across that boundary is itself an exact mode of the doubled plate. The field is evaluated on a folded coordinate — triangle wave into the unit cell, sign flipped once per crossing — so the viewport tiles with mirrored cells that join continuously along a shared nodal edge instead of showing a seam, giving two-and-a-bit plate cells across a laptop and a full-bleed frame that is still an exact solution. TRANSPORT is physics, not placement: each grain reads |displacement| and its gradient and walks DOWN that gradient at a mobility scaled by coherence, while diffusing with a step proportional to the LOCAL amplitude — violent on an antinode, almost nothing on a nodal line — so sand accumulates at the nodes for the reason it does on a real plate. BOTH ARE GATED BY A FRICTION THRESHOLD on the local amplitude (~0.085 of peak, lowered by bowing and by loss of coherence): below it the grain is not being thrown at all and does not move. That threshold is what makes the figure LINES — an ungated drift still feels the second-order along-the-line component of the gradient and ratchets every grain into the line crossings, leaving a lattice of clumps and no lines within twenty seconds. COST SPLIT: the field is rebuilt every frame on a ~5px grid using separable per-harmonic sine tables (a few hundred thousand multiply-adds, independent of grain count) and each grain takes three bilinear samples, which is what makes 40k grains affordable; grain positions upload once per frame into a POINTS buffer. RENDER: one WebGL context, two passes. A fullscreen pass shades the steel from the same field texture the sim reads, so the plate is lit by its own flexure and the antinodes read as bands of moving sheen; then the grains, in a blend mode chosen by theme — additive on a dark page so piles blow out to a hot line, multiplicative (ZERO, ONE_MINUS_SRC_COLOR) on a light page so they print down into pale steel. Both accumulate, so density reads as value in either theme rather than the light theme washing out. POINTER: the sweep runs with or without input, so a resting frame catches either a locked figure or a storm; the pointer BOWS the plate rather than taking the sweep over — horizontal position detunes the drive by a fraction of the local mode spacing (breaking a locked figure and pulling a neighbour in), a press bows harder and raises the diffusion until the sand storms, and the contact point scatters the grains around it. Smoothing is an exponential follower with a lead term of velocity*tau, because a plain follower's steady-state error is exactly that and the bow would otherwise trail the cursor. TOKENS: --background, --foreground, --ns-muted and --border via getComputedStyle, re-read on a documentElement class MutationObserver; --ns-accent is interaction-only and appears solely on the keyboard focus ring, never on the sand or the plate. Arrow keys bend the drive for keyboard users. The sim is spun up before the first paint and before any still frame, so prefers-reduced-motion and `paused` show a resolved figure and never an undeveloped scatter. Pauses offscreen and on tab hidden; a display-resolution ladder steps down only after a sustained stretch of missed frames."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-surface": "var(--surface)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "surface": "#fafafa"
        },
        "dark": {
          "ns-muted": "#8f8f8f",
          "surface": "#171717"
        }
      },
      "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 --ns-muted, with slightly different quantization thresholds per band so bands read distinct in monochrome; exactly one accent vein — a 1.5px --ns-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 --ns-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.",
        "rank": 4
      }
    },
    {
      "name": "shakeout-crumble",
      "type": "registry:ui",
      "title": "Shakeout Crumble",
      "description": "Destructive-action confirm rendered as a foundry shakeout conveyor: a green-sand mould loses cohesion under vibration and crumbles away to reveal the casting, unforced and looping at rest, with a click-arm-then-confirm gesture layered on top to actually commit.",
      "files": [
        {
          "path": "registry/loud/shakeout-crumble/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/shakeout-crumble.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "button",
          "destructive",
          "confirmation",
          "canvas",
          "particles",
          "foundry",
          "physics",
          "accessibility"
        ],
        "instruction": "Build a destructive-action confirm button sourced from real green-sand casting foundry practice: a friable sand mould is vibrated on a shakeout conveyor until compaction fails and it crumbles away, revealing the casting inside, after which the sand is reclaimed and re-compacted into a fresh mould for the next pour. That is granular COMPACTION FAILURE UNDER VIBRATION — each of a budgeted ~2600-grain field detaches individually past its own random threshold and either settles into a loose pile at the bottom or drifts off entirely as fading dust — categorically different from a wipe (dew-coalesce's condensation runoff) or a chemical front sweeping a surface (kamacite-etch's acid etch): here cohesion fails locally and grain-by-grain, nothing sweeps across in a front. A single scalar drive (0 = mould intact over the casting, 1 = fully shaken out) is all that ever changes; every grain's position and alpha is a pure function of drive against its own static escape threshold, so the same render code handles crumbling forward and reclaiming backward with no separate undo animation. ALIVE AT REST: with no input at all, drive runs a continuous ~7s envelope (settle, vibrate/crumble, hold the reveal, reclaim, settle) forever, and even the terminal committed state keeps a low, constant grain-jitter floor so dust never stops sifting — there is no fully static frame at any point in the component's life, matching the real conveyor's unforced cycle. GESTURE: distinct from every other destructive confirm in the registry (confirm-hold-ink gates on time held, confirm-slide-shatter on distance dragged, confirm-dial-align on accuracy) — this is a plain <button> gated on click-arm-then-confirm. First activation (pointer click, or Space/Enter, free on a real button element) arms it: the mould visibly shakes harder as a countdown window runs, teasing the shakeout without completing it — reversible, nothing is destroyed yet. A second activation inside that window commits: the crumble finishes, the casting is fully revealed, and onConfirm fires once, exactly at the moment the reveal animation completes. Letting the window expire, pressing Escape while armed, or losing focus all de-arm: drive eases back down and the mould visibly reclaims itself over the same envelope the idle loop uses. A dedicated aria-live=polite status span (separate from the button's own text, which also changes per phase for a clear accessible name) announces 'Awaiting confirmation', 'Cancelled', or 'Cleared', with a zero-width-space parity toggle so repeated identical announcements still re-fire. The terminal committed state uses aria-disabled rather than a native disabled attribute so the button stays focusable and its final label stays announced. Grain positions/alpha, casting shape and colors are all direct canvas draws from --foreground (casting fill) and --ns-muted (grains) tokens read via getComputedStyle(document.documentElement) and re-derived on a MutationObserver watching its class attribute, assigned before first paint; --border and --background only frame the DOM label chip, and --ns-accent appears solely as the button's focus ring, never in the granular field. Per-frame draw batches grains into 6 alpha buckets and issues one canvas fill() per non-empty bucket, so draw-call count stays flat regardless of grain count; grain spacing (and therefore size) is derived from the button's own smaller dimension against a hard MAX_GRAINS=2600 budget rather than a fixed density, so it reads as fine grain at any card size instead of coarse blobs. DPR is capped at 2 (button-scale canvas, not a full-bleed shader). The rAF loop wakes on demand, sleeps whenever nothing is animating it back to idle, pauses on IntersectionObserver and visibilitychange, and never runs at all under prefers-reduced-motion: reduced motion instead snaps discretely between three static drive values (0.5 resting — a deliberately non-t0 frame with both intact mould and revealed casting visible at once, 0.85 armed, 1.0 committed) with zero vibration jitter and zero grain sifting, while every keyboard and click path stays fully operable. Props: label (default 'Clear all'), armedLabel (default 'Confirm clear all?'), clearedLabel (default 'Cleared'), armMs (default 4000), onConfirm, className."
      }
    },
    {
      "name": "shear-billow",
      "type": "registry:ui",
      "title": "Shear Billow",
      "description": "A full-bleed Kelvin-Helmholtz shear layer: two stratified layers slide past each other, the interface between them ripples, steepens, curls into a train of spiral billows and shreds into turbulence downstream, regenerating continuously with no input at all.",
      "files": [
        {
          "path": "registry/loud/shear-billow/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/shear-billow.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "background",
          "canvas",
          "webgl",
          "shader",
          "fluid",
          "physics",
          "cursor",
          "decorative"
        ],
        "instruction": "Build <ShearBillow billows? speed? turbulence? strata? stir? paused? children? className? style?> as a full-bleed WebGL background rendering a spatially developing Kelvin-Helmholtz shear layer — the breaking-wave cloud roll. THE MECHANISM is the whole component: the velocity field is Stuart's exact steady solution of the 2D Euler equations for a rolled-up shear layer, psi = (1/k) ln(cosh(k(y - yc)) + rho cos(kx - omega t)), where rho = 0 is a plain tanh shear profile and rho -> 1 is a row of closed cat's-eye vortices. Taking rho as a FUNCTION OF x — smoothstep(0.015, 0.60) growth, saturating at 0.735 and decaying 34% past x = 0.68 — turns the steady solution into a spatially developing layer, which is the picture: a nearly flat rippling line at the upstream edge, a closed cat's eye by mid-frame, fully wound and breaking downstream. u = dpsi/dy and v = -dpsi/dx are taken analytically INCLUDING the drho/dx term (a smoothstep-and-its-derivative helper returns both in one call), so the field stays divergence-free by construction — dropping that term is exactly what makes an x-modulated Stuart field look squeezed rather than advected. Clamp D = cosh + rho*cos to a 0.06 floor: rho + rho2 crossing 1 makes the log singular and punches a hole in the frame. A SUBHARMONIC, rho2 = 0.185 * smoothstep(0.26, 0.98, xn) at cos(kx/2 + 1.37), grows in only downstream — that is vortex PAIRING, alternate billows strengthening at their neighbours' expense, and it is what stops the train from reading as a rubber-stamped periodic row, the failure mode of every analytic version of this. THE IMAGE IS NOT THE FIELD, and this is the second load-bearing decision: nothing draws velocity. Every pixel backward-integrates its own parcel through the flow for TRACE seconds (RK2, a fixed 18 steps so the loop is coherent across the draw) and reads the STRATIFICATION at the position and time it came from. Inside a strong cat's eye the streamlines are closed, so the backward trace circles the core several times and drags the interface sheet around with it; at the upstream edge the same trace is nearly straight and the layers come back flat. The spiral is therefore EARNED by the flow instead of drawn as a primitive, and the braids — sheets of interface pulled taut between adjacent rolls — appear on their own, which is the detail that makes a KH photograph read as KH and not as generic swirl. Trace length is set in eddy turnover times, 1.65 * 2pi/(k*u0): under ~1.2 the cat's eye never closes and the frame is just a wavy line, over ~2.2 the upstream half winds up too and the developing structure is lost. STRATIFICATION, all of it read at the traced-back source point and drifted by -u_m*t so the texture belongs to the fluid rather than to the viewport: a tanh-like density profile across the interface, sinusoidal banding inside each slab (sin(Y*2.7) tilted by a very low-frequency fbm swim so the layers are not drafting-board parallel), laminar wind streaks from one noise sample stretched ~40:1 along the flow so the undisturbed layers read as moving air, the interface SHEET itself as exp(-Y^2 * 1.25) at 0.52 amplitude (the thing that gets wound), and two octaves of advected filament grain that strengthen near the interface. DOWNSTREAM BREAKDOWN: two curl-noise displacements (the curl of one scalar fbm, so the shredding transports the layers instead of inflating them) at 0.30 and 0.055 of min(W,H), gated both by how far downstream the pixel is (smoothstep 0.34 -> 1.02) and by how close its parcel came from to the interface. Applied to the sample point, NOT inside the trace loop — a curl field per step costs eight noise taps per step for detail the eye reads identically at one tap per pixel. PALETTE: stratification reads through value alone. Five stops derived from --background, --foreground, --ns-muted and --border via getComputedStyle, re-read on a documentElement class MutationObserver. The ramp's DIRECTION carries the theme rather than a bias term, because a density field departs from the page in one direction where a reflective surface spans black-to-white in both: dark is a lit interface in a dark column of air (the sheet is the brightest thing on screen), light is the same layer as ink on paper (the sheet is the darkest). Light is the harder case — pale strata over a pale ground wash out — so its stops are written separately, c0 = background with a deliberately small gap to c1 and the top three pushed hard toward the foreground. A mild elliptical vignette toward the deepest stop keeps the frame edges from competing with overlaid type. The pointer's own vortices lift the sheet toward the brightest stop in luminance only, weighted toward the sheet, and reach exactly zero when the last one decays. POINTER: the smoothed pointer is advanced in the frame, never in the event handler, from a target the handlers only assign to, so event cadence and coalescing cannot reach the surface; a one-tau velocity lead cancels the follower's steady-state v*tau lag (lateness reads as the field ignoring the cursor). Deposits are spaced by 46px of travel with a one-frame time ceiling, so cadence is the display's and not the pointer event rate, and they ALTERNATE IN SIGN — a single-sign trail merges into one fat rotating blob within a second, alternating signs shed a row of counter-rotating cores, which is what dragging something through a shear layer actually leaves behind. Four live gaussian vortices in a ring buffer, each a rotational velocity added inside flow() and therefore integrated by the same backward trace as everything else, so a stir winds real spirals rather than smearing pixels; a vortex not yet born at trace time t is skipped or the wake reaches backwards through itself. Amplitude tracks pointer speed so a resting cursor stops stirring, and the whole loop sits behind a coherent u_stir uniform branch, costing nothing at rest. HOST: an internal GLSurface class owns the program, the fullscreen triangle pair and lazily-resolved uniform locations and knows nothing about the shear layer. DPR is capped at 1.5 — the per-pixel cost is 36 streamfunction evaluations plus ~20 noise taps, so the area term dominates and spiral sheets a few px wide survive a 1.5x backing store intact. An adaptive render-scale ladder (1 / 0.75 / 0.55) engages only on measured wall-clock overrun, with asymmetric recovery so a sibling animation blowing the budget cannot soften the surface for the rest of the visit. The clock is integrated and per-frame clamped, so a long frame advances the flow one clamped step instead of teleporting the train sideways, and time stops entirely while the surface is asleep. ResizeObserver resizes and resets the ladder; IntersectionObserver and visibilitychange both stop the single rAF loop; webglcontextlost/restored rebuild cleanly; paused is polled rather than made an effect dependency, since that would recreate the GL context to change a boolean. prefers-reduced-motion (and paused) draw one still frame at t = 34, chosen because the train is fully developed there — flat upstream, two closed cat's eyes mid-frame, a paired shredding roll downstream — never an undeveloped flat line, and pointer input still redraws that frozen frame with one vortex aged into its strongest moment. The canvas is aria-hidden and the component renders no controls; children render over the layer for an eyebrow, subhead and CTA, and because the field spans the full value range they want a token scrim rather than bare type."
      }
    },
    {
      "name": "sieve-throw",
      "type": "registry:ui",
      "title": "Sieve Throw",
      "description": "A full-screen search overlay built as a gyratory sieve shaker: the corpus is a charge of grain lying on five stacked screens, the query is the grain SIZE, and every keystroke re-sizes the charge so weak matches drop through the mesh deck by deck into the pan while strong ones ride the throw back up onto the coarse top screen.",
      "files": [
        {
          "path": "registry/loud/sieve-throw/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/sieve-throw.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "search",
          "overlay",
          "dialog",
          "combobox",
          "command",
          "physics",
          "canvas",
          "filter"
        ],
        "instruction": "Build <SieveThrow items? triggerLabel? label? fieldLabel? placeholder? defaultOpen? onSelect? children? className?> as a full-screen search overlay whose filtering is a real separation process rather than a fade. THE MECHANISM: the overlay is a gyratory sieve shaker. Five screens of decreasing aperture (2.00 mm, 1.00 mm, 500 um, 250 um, pan) stack down the viewport as --border hairlines with mono aperture labels and a live per-deck grain count. Every corpus item is a GRAIN — a real DOM button carrying real result text — and the query is the grain SIZE: a subsequence match with contiguity, word-boundary and density bonuses returns 0..1, and that size decides which screen the grain can no longer pass (>=0.72 stays on deck 0, then 0.56, 0.42, anything above 0 on deck 3, no match falls to the pan). With NO query there is no sort to show, so each item falls back to a deterministic natural grain size hashed from its label: the resting deck is a spread charge across all five screens, not one heap on top. Each keystroke changes targets only; the sim is never reset, so grains migrate from wherever they currently are and typing reads as one continuous re-sort. Vertical motion is ballistic, not a transition: a grain that got smaller than its screen goes THROUGH the mesh and falls under gravity (2600 px/s^2) onto the next deck with a 0.24 restitution bounce, and a grain that got bigger can only leave the screen at the top of the shaker stroke — the upward kick is phase-gated on sin(throw) > 0.94 and its impulse is exactly sqrt(2*g*dh), so it arrives at the deck above with no velocity to spare, the way a real deck works grain upward. Horizontal motion is conveyed: a critically damped pull along the screen toward a flow position produced by a left-to-right, pile-upward layout that runs on re-size only, never per frame. TWO DETAILS CARRY THE WHOLE LOOK. First, the stack is SPRUNG: each screen's share of the viewport is half its standing share and half the share of the charge it is currently carrying (capped so a loaded pan cannot swallow the stack), eased at 5/s, so a cleared screen closes up and a loaded one opens and there is never a band of dead deck. Beds are therefore stored as a HEIGHT ABOVE THE MESH, not an absolute y, and resolved against the moving screens every substep — material rides its screen as the stack re-springs — which is also why the fall/kick thresholds are a full 20px rather than a pixel: only a genuine change of deck may put a grain in the air. Second, grain size is fit to the bed: each screen takes the largest type scale (from 1.85 down, x0.86 per try) whose flowed pile still fits between its mesh and the aperture label above it, so the top screen holding the whole corpus is a fine tightly packed bed and the same screen holding three survivors sets them as headlines. The canvas also draws the mesh itself to aperture — tick spacing 30/20/13/8 px down the stack, moving with the throw, and no ticks at all on the pan, which is a solid floor. The whole stack orbits (gyratory throw at 3.05 Hz, a cos/sin lissajous applied as one transform), each grain hops out of phase with its neighbours, and a keystroke kicks energy into the deck (0.34 idle, +0.85 per key, decaying) so the charge visibly jumps when you type. A canvas behind the type carries the FINES: sixteen dust specks per grain, each bound to a grain so the dust migrates with the sort, drawn in --ns-muted at a per-deck alpha. The canvas draws dust ONLY — every word on screen is live DOM type, scaled per deck by transform (1.85 down to 0.46) and inked by opacity, so a match on the top screen is set large and a rejected grain in the pan is a small dim speck of type. ACCESSIBILITY IS THE HARD PART: the field is a real <input> with a visible <label>, role=combobox, aria-expanded, aria-controls, aria-autocomplete=list and aria-activedescendant; the deck is a role=listbox whose grains are role=option buttons with aria-selected, tabIndex -1 (focus stays on the input, as the combobox pattern requires) and aria-disabled on anything that fell to the pan; ArrowUp/ArrowDown/Home/End walk the ranked results, Enter commits the active one, Escape closes, the active grain is marked by an --ns-accent border AND an arrow glyph so it is never colour alone, and a polite aria-live line announces how many passed and how many went to the pan. Focus moves into the field on open, is trapped by a Tab/Shift-Tab wrap bound on both the dialog and the document plus a document focusin listener that pulls stray focus back, and is restored to the trigger by the effect's cleanup so it survives an unmount too. That trap engages only for an overlay the USER opened: defaultOpen exists so a page can render the deck at rest as a display surface, and an overlay mounted open must not seize the document's focus. prefers-reduced-motion skips the rAF loop entirely and settles every grain onto its screen in one paint, so each keystroke shows the separation as a discrete before/after state and the overlay opens, filters, arrows and commits with no motion at all. PERFORMANCE: fixed 1/180 s substeps (a ballistic bounce integrated at wall-clock dt changes character with the frame rate), one transform write per grain per frame with no layout reads, the loop torn down on visibilitychange and by an IntersectionObserver on the stack, and the keystroke handler doing nothing heavier than bumping an energy scalar. PALETTE: --background --foreground --ns-muted --border only, with --ns-accent reserved for the focus ring and the active option — there is no pointer-reactive highlight anywhere, so a resting frame with the cursor parked at the origin carries no accent blob."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": "spark-test-id",
      "type": "registry:ui",
      "title": "Spark Test ID",
      "description": "A file dropzone that identifies a specimen the way a machinist reads unknown steel: an idle grinding-wheel spark shower runs continuously, and the detected file type sets a characteristic spark signature of burst count, fork depth, and carrier length.",
      "files": [
        {
          "path": "registry/loud/spark-test-id/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/spark-test-id.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "dropzone",
          "file-upload",
          "form",
          "canvas",
          "particles",
          "physics",
          "input"
        ],
        "instruction": "Build a file dropzone modeled on real shop-floor SPARK TESTING — the documented practice machinists use to identify unknown steel and alloy grades by holding a specimen against a grinding wheel and reading the SPARK STREAM: carrier-line length, how many times a burst forks, and shower density. A canvas layer over the zone runs a continuous ballistic-particle spark shower from a fixed 'wheel contact point' near the lower-left, arced by gravity — this must run unconditionally from mount, a low baseline idle shower, so the component is never a dead frame before any file is dropped. On drop or select, classify the file (image / text-or-document / audio-video / archive-or-binary / unclassified) and swap the shower's spawn rate, carrier length, fork probability and max fork depth to that family's signature: image files throw a high-carbon-steel pattern (dense, repeatedly forking bursts, short carrier), audio/video throws a wrought-iron pattern (long straight carrier lines, sparse forking), archives/binaries throw a cast-iron pattern (short, dense, low-lying streams), text/documents sit in between. Each spark is a discrete particle with its own fork point along its life; forking spawns 1-4 short terminal sprig particles at a randomized branch angle, and only the highest-fork-depth family allows a sprig to fork again — this is discrete ballistic particle physics with branching topology, categorically distinct from a continuous rising-wisp field or a vector spring outline. ADAPTATION, STATED PLAINLY: real spark streams are read partly by hue (color is part of how a machinist identifies the grade); this component has no hue to spend and maps spark intensity to LUMINANCE only, a stated adaptation, not a claim of color fidelity. Sparks render in two token passes: a soft --ns-muted halo stroke under a --foreground core stroke, both scaled to the container's smaller dimension so the pattern reads at small preview-card sizes, not just full-bleed. The dropzone itself is a real control: a native file input reachable by keyboard (Enter/Space opens the picker), a genuine accessible name, drag-and-drop as an enhancement over that same input rather than the only path, and the detected type is announced through a polite aria-live status line as plain text (family, MIME type, signature name) — never conveyed by the spark pattern alone, since that reading is purely visual. prefers-reduced-motion freezes on a deterministic long-exposure composite of the active signature's first ~1.35s (multiple carrier lines and at least one completed fork visible together), not a t0 frame, and re-renders that same freeze whenever the signature or theme changes since no loop is running to pick it up. Canvas: DPR-capped backing store, ResizeObserver on the zone, paused via IntersectionObserver and visibilitychange, particle count capped at 260, all ink read via getComputedStyle at mount and re-derived on a MutationObserver watching documentElement's class. Props: accept (string[]), maxSizeBytes, onFileChange, className, aria-label."
      }
    },
    {
      "name": "spiral-chute-accrete",
      "type": "registry:ui",
      "title": "Spiral Chute Accrete",
      "description": "A full-bleed ambient background reproducing a gravity spiral chute, the helical slide used to move bulk parcels/mail sacks between floors in sorting facilities and department-store stockrooms without powered conveyance. Parcels drop in at the top, descend the helix in continuous view, and pile up in a landing area at the base until the pile is periodically swept clear.",
      "files": [
        {
          "path": "registry/loud/spiral-chute-accrete/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/spiral-chute-accrete.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "canvas",
          "chute",
          "spiral",
          "helix",
          "logistics",
          "parcel",
          "ambient"
        ],
        "instruction": "Build <SpiralChuteAccrete className? style?><...headline/CTA/></SpiralChuteAccrete> as a full-bleed Canvas 2D background reproducing a real gravity spiral chute: the helical slide sorting facilities and department-store stockrooms use to move parcels/mail sacks between floors under gravity alone, no powered conveyance. GEOMETRY: the helix is a cached Path2D built once per resize from a parametric function evaluated at RAMP_SAMPLES (240) points, x(theta) = cx + radius*sin(theta), y(theta) = topY + (theta/THETA_MAX)*rampHeight, with radius = 0.32*min(width,height) and THETA_MAX = 3.5 full turns (TURNS*2*PI) — the container's SMALLER dimension drives radius so the chute reads correctly at both hero and card scale. Every parcel's on-screen position is this SAME cached function evaluated at the parcel's own progress, never a per-frame re-derivation of the curve. STATE IS A PURE FUNCTION OF ONE CLOCK: rather than mutating a live array of parcel/tile objects, every drawn quantity — which parcels are in flight and at what theta, which tiles are in the current pile, which are mid-sweep-out — is derived directly from the elapsed clock t (ms) and a fixed spawn index k, via spawn(k)=k*SPAWN_INTERVAL_MS and land(k)=spawn(k)+TRANSIT_MS. This is what makes prefers-reduced-motion reproducible: freezing the clock at a named STATIC_T reproduces the exact same layout deterministically, no history to serialize. REAL NUMBERS: TRANSIT_MS is 3600ms for one parcel's FULL 3.5-turn transit — this is one of the rare cases where the real-world rate (roughly 1-3 m/s on a shallow chute) and the legible rate are close, so descent renders near real proportion rather than being artificially decoupled. SPAWN_INTERVAL_MS is a fixed 1300ms cadence, which at a 3600ms transit keeps 2-3 parcels in flight simultaneously, each at a different theta so they never visually overlap on the same winding — per the spec's own kill criteria, THIS simultaneous-parcel count (not a faster descent speed) is the intended fix if the motion ever reads as too sparse to register as alive, because a faster descent would break the close-to-real-world proportion the concept was chosen to preserve. PILE / SWEEP: every landed parcel becomes one pile tile positioned near the helix's base point at a jittered offset drawn from a fixed 9-entry deterministic sequence keyed by k % 9 (never per-frame randomness), so the pile shape is exactly reproducible. Every SWEEP_PERIOD_MS (9000ms, fixed) the chute's current fill cycle ends; the tiles that belong to the cycle which just ended slide off-canvas to one side over SWEEP_DURATION_MS (500ms), staggered SWEEP_STAGGER_MS (30ms) apart, entirely derived from t % SWEEP_PERIOD_MS — this bounds the pile to at most one fill cycle's worth of tiles (about 5 at these rates) so it can never visually breach the canvas edge, which the spec calls out explicitly as a bug-read to avoid. Live mode's clock starts at WARM_START_MS = 5150 rather than 0 — a bare t=0 clock is a near-empty chute (one parcel just spawned, pile empty), which fails the spec's own t0 requirement of 2-3 visible parcels plus a partial pile; WARM_START_MS is a deliberately different phase than the reduced-motion STATIC_T so the two never read as the same frame. TOKENS: the ramp track renders at --ns-muted with globalAlpha 0.55 and a lineWidth floored at 1.5 CSS px (radius*0.02 alone thins under 2px at card-preview scale and risks disappearing on a light background) — it is the track, not the subject, and deliberately sits at a low, --border-like contrast step without literally using --border as a canvas stroke (--border stays a separator-only token per the binding rule). Parcels and pile tiles render at --foreground, both read via getComputedStyle(document.documentElement) at mount and re-read on a MutationObserver watching documentElement's class, closing the mutation-observer/resize/rAF-start paint-before-token-read gap. --ns-accent never appears anywhere in this component; the pile-sweep is this component's one climactic moment and is a plain --foreground fade-and-slide, never accent-tinted. prefers-reduced-motion (and initial mount before any observer fires) freezes the clock at STATIC_T = 6450ms, named MID_DESCENT — chosen 71.7% into a 9s fill cycle so the pile shows 3 of a possible ~5 tiles (roughly half full, neither freshly swept nor about to overflow) while two parcels remain visibly mid-spiral at clearly different theta, the single frame that shows track, motion-implying parcel spacing and an in-progress pile all at once. HOST: DPR capped at 1.5 full-bleed / 0.75 inside an autoplay preview card, ResizeObserver-driven resize (rebuilds the cached ramp Path2D and base landing point), IntersectionObserver (threshold 0) and visibilitychange both pausing the single rAF loop off-viewport or tab-hidden, dt clamped to 50ms per frame to avoid a pile/spawn-count jump after a long background tab stall. No interaction: this is a pure ambient background with no pointer state, per spec. Decorative canvas is aria-hidden and pointer-events-none; children render as real DOM in normal focus order above it, centered, with the same scrim convention as sibling full-bleed hero components. Props: children, className, style."
      }
    },
    {
      "name": "stack-step-carousel",
      "type": "registry:ui",
      "title": "Stack Step Carousel",
      "description": "A full-bleed background modelling vacuum thin-film deposition on a planetary carrier: 8 mirror/lens blanks each orbit a fixed 30-degree source arc every 3.5s, each pass stepping that blank's reflectance up a notch along a real quarter-wave-stack recursion, staggered swaps keeping the batch permanently mid-cycle while a slower 14s armature ring turns underneath.",
      "files": [
        {
          "path": "registry/loud/stack-step-carousel/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/stack-step-carousel.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "planetary",
          "deposition",
          "reflectance",
          "quarter-wave",
          "generative",
          "ambient",
          "monochrome",
          "carousel",
          "batch-process"
        ],
        "instruction": "A full-bleed single-canvas background modelling vacuum thin-film deposition on a planetary carrier. N_DISCS = 8 substrate discs sit on station points evenly spaced (45deg apart) around a single sun-ring circle of radius 0.32*min(w,h) centered on the container; disc radius = 0.09*min(w,h). PLANET_PERIOD_S = 3.5 is each disc's OWN orbital period around the center (the spec's 'planet spin', read as the substrate's own revolution rather than an axial self-spin) — disc i's live angle is stationAngle_i + 2*PI*t/3.5 with t real elapsed wall-clock seconds since mount and NO loop reset ever, so this orbit is exactly what carries the disc through the fixed 30deg source arc at the top of the frame once every 3.5s — a single disc's own crossing cadence matches the spec's legibility line literally. centerPhase(station,t) = t/3.5 + stationAngleFrac_i; layerAt = (seedOffset_i + floor(centerPhase)) mod 13: floor(centerPhase) is the disc's own completed-orbit count (ticks up once per 3.5s per disc, offset per station, so a new crossing lands somewhere on the 8-disc ring roughly every 3.5/8~=0.44s even though any single disc's own orbit stays 3.5s); mod 13 gives a 0..12 cycle where 12 (full stack) rolling to 0 on the next crossing IS the staggered fresh-blank swap the spec calls for, and because every disc shares the identical 3.5s cycle rate the per-disc seedOffset_i (a fixed integer 0..12 from a seeded mulberry32(0x5ea70c1e) PRNG, never Math.random) that staggers each disc's starting phase persists as a permanent offset forever — the batch is structurally incapable of resyncing into one synchronized reload. CARRIER_PERIOD_S = 14 (the spec's 'sun ring' number) gets its own separate, purely structural screen presence rather than driving crossings: a ring of N_ARMATURE_TICKS=16 short radial index ticks around the guide-ring circumference, all rotating together at 2*PI*t/14 — the classic epicyclic relationship (a slower carrier arm, a faster individual planet orbit) rendered as two visibly distinct rotations instead of one number inferred from the other. Reflectance is the real quarter-wave-stack recursion: Y(N) = n_sub*(n_L/n_H)^N, R(N) = ((n_air-Y)/(n_air+Y))^2 for a symmetric alternating H/L stack (n_air=1, n_H=2.35 TiO2-like, n_L=1.38 SiO2-like, n_sub=1.52 glass-like), giving R(0)~4% (bare glass) climbing with the real diminishing-returns shape to R(12)~99% (a realistic 12-layer HR mirror); normalized to reflectanceFrac=(R(N)-R(0))/(R(12)-R(0)) in 0..1, this is a plain linear RGB mix (parseHex'd once from the read tokens) between --ns-muted (0, bare) and --foreground (1, fully stacked) — since --foreground is by definition the maximum-contrast token against --background in every theme, 'fully stacked reads as the higher-contrast extreme, never a different hue' (the weld-pool convention's intent) holds structurally with no separate bias/contrast pair needed. glowAge(station,t) is referenced to the disc's ARC-ENTRY angle (-15deg), not the arc's center: it is 0 the instant the disc enters the 30deg window, and GLOW_PEAK_MS is computed exactly as the real entry-to-center transit time (half the arc's own 3.5*30/360=291.7ms transit, i.e. ~145.8ms) rather than an arbitrary constant, so the envelope's rise (linear 0 to 1 over GLOW_PEAK_MS) genuinely peaks as the disc passes the arc's center; it then fades back to 0 by GLOW_MS=800ms total, deliberately continuing to fade for a while after the disc has physically exited the arc so the crossing reads as an unmissable arrive-brighten-depart event rather than a 292ms blink. The halo itself is a --foreground/--ns-muted mix at full strength, alpha-only, never --ns-accent. The fixed source arc is stroked from a --ns-muted-to-foreground mix (never plain --border — a full-bleed background's fixed reference point, the one thing the entire mechanic is 'passing under', cannot be the ~1.1:1 near-invisible separator token in light theme) at alpha 0.75 baseline rising to 1.0 and colour shifting further toward --foreground while any disc's glow is active, tying the two together. Pointer: a wrap-level pointermove finds the nearest disc whose center is within 1.3x its radius and sets hoverIndex; a hoverAlpha value eases toward 1 (or 0) at a fixed per-frame rate approximating ~150ms, and while eased-in draws a muted-to-foreground-mix outline ring at 1.2x disc radius whose alpha is 0.3 + that disc's OWN current reflectanceFrac*0.4 (a legible 'how far along this one is' cue) — no accent tint anywhere, hover never touches orbit or carrier rate. `paused` genuinely pauses and resumes: the rAF loop stays armed and simply skips the time-advance/draw step while pausedRef.current is true, so clearing it resumes on the very next frame rather than requiring an external re-arm. Colour is read once via getComputedStyle(document.documentElement).getPropertyValue for --foreground, --ns-muted (both parsed from hex to RGB, no literal fallback) and --border (used as a CSS color string directly, only for the non-load-bearing guide ring and armature ticks) with no literal fallback of any kind; if any is empty the mount loop retries on the next rAF and paints nothing until all three resolve. A MutationObserver on document.documentElement's class attribute re-reads tokens on every theme flip and forces a resize+redraw; a ResizeObserver on the wrapper recomputes geometry (station positions and disc radius both derive from the container's own min(w,h)) and resizes the canvas backing store (capped devicePixelRatio 2); an IntersectionObserver stops driving new frames while off-screen and, on re-entering view, re-reads tokens and resumes — because position is derived from absolute elapsed wall time rather than accumulated per-frame deltas, the carrier is exactly where continuous real-time rotation would have put it, never a stale resume. autoplay is 'none' because the carrier turns on its own internal wall-clock regardless of pointer/scroll/press input — there is nothing for the site's synthetic-input driver to do, and the pointer halo is a hover-only affordance, not something a scripted input pass should trigger. Under prefers-reduced-motion the component runs zero rAF loops and zero timers: it solves analytically for a t at which station index 3 sits exactly at its glow peak with 2 completed orbits already banked (solveGlowPeakTime), draws exactly that one frame — one disc bright and mid-crossing under the source with real accumulated layer history, the other 7 stations at their own staggered screen positions and pre-crossing layer counts from seedOffset_i plus their own lap counts at that same instant — and never advances it; this is the frame named 'carrier-crossing'. A resize or pointer move under reduced motion redraws that same frozen instant rather than advancing it. Cleanup on unmount cancels both the animation and token-wait rAF handles, disconnects all three observers, and removes both pointer listeners. Zero dependencies, DOM+canvas only, no color literal anywhere including fallbacks, no hue anywhere (monochrome value-only ramp)."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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 --ns-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%, --ns-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, --ns-muted, --border, --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 --ns-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; --ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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 --ns-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 --ns-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(--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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|--ns-muted|--border|--ns-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 --ns-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|--ns-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": "termite-ventilation-shafts",
      "type": "registry:ui",
      "title": "Termite Ventilation Shafts",
      "description": "A full-bleed ambient background modeling Macrotermes mound thermosiphon ventilation: a fixed network of surface ridge conduits, generated once from a central chimney and never re-grown, visibly fills and drains with flow in alternating directions over a slow 42s diurnal cycle driven by a rising and falling temperature differential.",
      "files": [
        {
          "path": "registry/loud/termite-ventilation-shafts/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/termite-ventilation-shafts.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "canvas",
          "ventilation",
          "thermosiphon",
          "convection",
          "network",
          "ambient",
          "cyclical"
        ],
        "instruction": "Build <TermiteVentilationShafts disabled? children? className? style?> as a full-bleed Canvas 2D background modeling REAL Macrotermes mound ventilation (Turner 2001, 'On the mound of Macrotermes michaelseni', the exhalent/inhalent thermosiphon flow model) — the diurnal-cycle convective flow reversal, not fluid turbulence and not a growing network. NETWORK: generated exactly ONCE at mount via a plain recursive branch generator — 7 trunk branches radiate from the container's center (the chimney) at evenly spaced angles with mount-time jitter, each splitting into 2-3 children per node at a random ±25-40deg spread per split, child length = parent length * 0.72, down to 6 generations or until a branch's endpoint passes 46% of min(width,height) from center, whichever comes first. Coordinates are stored normalized to min(width,height) so the fixed topology re-scales cleanly on resize without ever being regenerated or re-simulated — this fixed-once network is the entire distinction from auxin-canal, forage-vein and agar-starve, which all regrow or rediscover their topology live; here only flow through an already-built structure animates. CLOCK: one governing 42s diurnal period drives dT(t) = sin(2*PI*t/42); dT>0 is exhale (flow trunk-to-tip, warm air rising out), dT<0 is inhale (flow reverses tip-to-trunk), magnitude |dT| scales both fill and particle count so the network goes visibly near-still at each of the two crossover points per cycle rather than snapping direction. FLOW RENDER: every segment eases its fill-fraction toward clamp(|dT| * segmentDepthFactor, 0, 1) on a 2.5s time constant, segmentDepthFactor = 1 - 0.05*generation so a direction change visibly propagates outward from (or inward toward) the trunk over about a second rather than updating everywhere at once. PARTICLES: any segment with fill-fraction above 0.15 carries 4-8 small dots (count scaled by the segment's actual pixel length) advancing at 12px/s along the segment in the current flow direction, wrapping at the segment end — this is the load-bearing legibility cue, since fill-fraction alone reads as magnitude but never direction. CROSSOVER STALL: for the roughly 1.5s window around each reversal (|dT| < 0.08) particle motion pauses completely — a real, observable full stop twice per 42s cycle, not a crawl through zero — while fill-fraction keeps easing normally (toward near-zero, since |dT| is small there anyway). MOUNT: the network mounts at a random phase within the 42s cycle (not t=0) so the resting loop always shows some fill and particle motion already present at first paint, with direction and magnitude depending on where the random phase landed; by t=2.5s and t=5s fill-fraction and particle density/direction have visibly shifted from that starting phase regardless of where it began. TOKENS: three passes per frame — a fixed --border stroke for the conduit outline (thinner per generation, never a fill), a --ns-muted stroke wash on top scaled 0 to ~0.4 alpha by that segment's current fill-fraction, and small --foreground dots at the particle positions, alpha 0.5 in dark theme and raised to 0.68 in light theme (detected via the documentElement 'dark' class) since light theme's fill/foreground contrast compresses and a lower fixed alpha reads as noise there. All three are read via getComputedStyle(document.documentElement) only after document.fonts.ready resolves, before the first paint, and re-read on a MutationObserver watching documentElement's class; nothing paints before that first read. --ns-accent never appears anywhere — direction is carried entirely by particle motion, magnitude entirely by fill-alpha, never by hue, so removing color leaves the piece exactly as legible. REDUCED MOTION: freezes on PEAK_EXHALE (dT=1, t=10.5 in-cycle) — chosen over the crossover-stall frame because it's the state that most clearly shows the network actively carrying flow rather than the one moment it's genuinely empty — reached via 40 deterministic step() calls at dt=2.5s (the fill time constant) so every segment's fill-fraction has fully converged to its target with no easing lag, then rendered once; the resize and MutationObserver paths under reduced motion re-run that same convergence pass rather than resuming a loop. HOST: DPR-capped(2) backing store, IntersectionObserver (threshold 0) pauses/resumes the single rAF loop alongside visibilitychange, ResizeObserver re-derives pixel geometry (never the topology) and re-buckets particle counts on layout change, everything torn down on unmount (cancelAnimationFrame, all three observers disconnected). The canvas is aria-hidden and pointer-events-none; children render as real DOM above it in normal focus order."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)"
        },
        "light": {
          "ns-muted": "#4d4d4d"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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(--ns-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.",
        "rank": 19
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-accent": "#006bff"
        }
      },
      "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(--ns-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(--ns-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": "thallus-siege",
      "type": "registry:ui",
      "title": "Thallus Siege",
      "description": "A full-bleed pane of crustose lichen: a dozen colonies grow lobed radial fronts across a shared occupancy lattice, stall on contact, occasionally creep over a markedly slower rival, and senesce from the centre outward to reopen ground the mosaic keeps renegotiating on a scale of minutes, not frames.",
      "files": [
        {
          "path": "registry/loud/thallus-siege/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/thallus-siege.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "simulation",
          "organic",
          "monochrome",
          "dither",
          "decorative",
          "ambient"
        ],
        "instruction": "Build <ThallusSiege count? variance? speed? paused? children? className? style?> as a full-bleed 2D canvas pane of crustose lichen fighting a slow, never-finished territorial war. TERRITORY IS GROWN, NOT PARTITIONED — the mechanism that must never regress into a Voronoi or distance-field lookup. A dozen (count, default 12) thalli occupy independent slots and nucleate at staggered simulated times on bare substrate. Each grows a front of ~180 radial spokes; every spoke's radius advances at that thallus's own inherited rate scaled by 1 + 0.3*marginNoise(theta, simTime), a slow multi-term sine perturbation in angle and time that gives the margin lobes instead of a computed circle. A spoke's target cell is looked up in a shared occupancy lattice (coarse: 6px cells, roughly a quarter the density anything on screen could resolve) storing an owner id and a claim time. Empty or self-owned cells are simply claimed. A cell owned by a DIFFERENT thallus stalls the spoke UNLESS this thallus's rate exceeds the rival's by more than 1.3x (CONTACT_RATIO), in which case the spoke keeps advancing at (ratio - 1.3) * baseRate — real, slow reconquest of the rival's ground, not a redraw of a boundary. The 1.3x dead-band exists so the mosaic can visually settle between reconquests instead of every border crawling forever. GOVERNING SCALAR: growth-rate variance, exposed as the `variance` prop (default 1) and sampled per thallus as baseRate * 2^((rng()*2-1)*variance). At variance = 0 every thallus grows at an identical pace, the 1.3x band is never crossed, no front ever reconquers another, and the mosaic settles into flat, static, roughly-equidistant cells — a fair near-Voronoi partition in every respect except how it was computed. That is the literal failure state this piece exists to avoid, which is why the default carries a real spread (rates commonly span roughly 0.5x-2x of each other). SENESCENCE falls out of the same lattice for free: a cell's claim time is written once and never refreshed, so the cells nearest a thallus's own nucleation point are always its oldest. Each thallus carries a senescence limit (~13 simulated minutes, +/-40% per individual); any of its cells whose age exceeds that limit clears back to substrate, which happens centre-first and spreads outward automatically as more time passes, with no separate shrink pass required. A cleared cell is bare rock again, and the neighbour whose front already stalled against it resumes growing into it the very next step — reclaiming ground rather than a new partition being recomputed. When a thallus's last cell clears, its slot goes silent for a staggered cooldown (40-180 simulated seconds) and then nucleates again elsewhere with a freshly sampled rate, so the war only ever pauses between shifts. RETIRED SEAMS: a cell that reverts to substrate while a differently-owned neighbour is still alive stamps a separate, slowly decaying (per-cell, ~220s time constant) seam field; wherever that field is still non-trivial the render adds a faint inset hairline stroke of --border, so the pane keeps a visible memory of where a border used to run after both sides that drew it are gone. PREWARM: at mount (and on every resize) the simulation is fast-forwarded 1200 simulated seconds (20 minutes) at 2s steps before the first paint, so the first frame is already a developed mosaic with at least one senescence-and-reclaim cycle baked in, not an empty pane proving its concept via autoplay. RENDER: the lattice's blocky per-cell ownership is smoothed into an organic contour with a genuine marching-squares pass — a binary occupied/not field per species, one segment for a normal 2-edge-crossing square, the diagonal-consistent pair of segments for the 4-crossing saddle case — stroked in --border at low alpha; no blur or upscale trick stands in for it. Fill is per-species ordered (8x8 Bayer) dithering between --background and --ns-muted, with 12 fixed dither densities assigned one per lattice slot (not per generation), so a slot keeps a recognizable texture across however many thalli nucleate into it over the piece's lifetime. Colors are read via getComputedStyle on mount and re-read on a documentElement class MutationObserver so both themes render correctly; --ns-accent never appears anywhere in the simulation or render, since nothing here is ever being interacted with. HOST: ResizeObserver rebuilds the lattice and reruns the full prewarm on a real size change; IntersectionObserver and visibilitychange stop the single rAF loop off-screen; redraws are throttled to roughly 2.6/s since the growth itself is glacial and a 60fps redraw of an unchanging frame would be pure waste; paused is polled rather than an effect dependency so toggling it does not discard 20 minutes of simulated history. prefers-reduced-motion (and paused) skip the loop entirely and paint the prewarmed mosaic once — the component's best single frame is the resting state, not a fallback. The canvas is aria-hidden and fully non-interactive (no autoplay descriptor: the stillness is the point, motion here rewards a second visit rather than demonstrating itself on the first); children render over the pane behind a bg-background/75 + backdrop-blur scrim."
      }
    },
    {
      "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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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/--ns-muted/--border/--ns-accent (--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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/--ns-muted/--ns-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": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "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/--ns-muted/--border/--ns-accent read via getComputedStyle on a MutationObserver watching <html>'s class attribute — --ns-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."
      }
    },
    {
      "name": "tray-weep",
      "type": "registry:ui",
      "title": "Tray Weep",
      "description": "A full-bleed section-divider band built on a bubble-cap distillation column tray: a stack of trays froths continuously as vapor bubbles up through submerged caps, liquid creeps toward a weir and spills to the tray below, and any cap running short on vapor visibly weeps liquid back down through itself instead of bubbling.",
      "files": [
        {
          "path": "registry/loud/tray-weep/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tray-weep.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "webgl",
          "shader",
          "divider",
          "background",
          "distillation",
          "process",
          "monochrome",
          "hover"
        ],
        "instruction": "Build <TrayWeep className? style?> as a full-bleed, aria-hidden, WebGL background band (no children wrapped — callers stack their own content around it, matching background-gradient-shader's hero-background pattern). A single fragment shader owns the whole viewport and lays out two periodic domains: N horizontal tray bands in Y, N = clamp(round(containerHeight/90px), 2, 6), and a 1D row of caps in X per band, cap spacing = max(1, min(width,height)/14) in pixels, converted to a normalized fraction of width — cap SIZE stays constant at card and full-bleed scale, cap COUNT is what re-derives on every ResizeObserver resize to fill the available width (never fewer than a handful of caps at minimum supported width). Every per-band mechanic (froth oscillation, liquid lateral flow, the weep schedule) is evaluated off a per-band LOCAL clock `tLocal(i) = u_time - i*0.18` (a fixed 180ms cascade delay) rather than the shared global clock, so every mechanic in a lower tray visibly lags the one above it and no two trays are ever in the same phase — this is what keeps the stack from reading as one band tiled vertically, which is an explicit reject criterion.\n\nPer band: froth height oscillates 30-55% of the tray gap on a 1.8s sine period (`frothFrac = 0.30 + 0.25*(0.5+0.5*sin(tLocal*2*PI/1.8))`), filling the region from the tray floor upward; a fine hashed-noise texture inside that region ramps a density value from 0 (froth top, sparse) to 1 (at the floor, dense), mixed between `frothLo` and `frothHi`. Liquid lateral flow creeps from the inlet (x=0) toward the weir (x=1) and spills over 250ms on arrival, restarting the inlet flow — the spec's literal real-world rate (4% of tray width/second, a ~25s traverse) does not fit inside the alive-at-rest gate's 2.5s checkpoint (which requires a full flow-to-spill cycle to already be visible on the topmost tray by then), so the implementation compresses the traverse to ~2.2s instead, keeping the mechanic (creep, weir spill, restart) but at a legible timescale — call this out explicitly rather than silently deviating from the spec's real numbers. The spill renders as a bright streak at the weir edge (x near 1) that bridges a short distance into the band below during its 250ms window.\n\nCaps: each cap runs a per-cap deterministic pseudo-Poisson weep schedule computed entirely from a hash of (capIndex, bandIndex, time) — no JS-side state. Local time is divided into fixed 3.5s cycles (mean interval); a hash keyed on (cap, cycle) draws one random event start and a 400-700ms duration inside that cycle, and the cap is weeping whenever local time falls in that window — statistically Poisson at these timescales, pure function of inputs, nothing to reset on resize. When NOT weeping, a cap renders a soft circular bubble blob rising UPWARD from the tray floor into the froth at 18Hz (boosted by local hover), fading and shrinking as it rises. When weeping, the cap instead renders an elongated droplet blob falling DOWNWARD past the tray floor into the band below, fading in/out over its event window — this upward-vs-downward reversal is the one non-negotiable visual distinction that makes the mechanic identifiably tray weeping and not generic bubbling; dropping it is an explicit reject criterion.\n\nColors: read `--background`, `--ns-muted`, `--foreground` via getComputedStyle(document.documentElement) at mount and re-derive on a MutationObserver watching documentElement's class. Dark theme ramps froth directly from `--ns-muted` (thin) to `--foreground` (dense); light theme is checked first per the recipe's harder-case rule and compresses the same ramp anchored off `--ns-muted` (`mix(background,muted,0.55)` to `mix(muted,foreground,0.55)`) rather than starting from raw `--background`, so the delta still reads once compressed. No color literals anywhere, including the shader source — every color the shader touches is a uniform derived from the same three tokens.\n\nInteraction: pointermove over the container tracks a normalized (x, yTop) position; the shader computes a true screen-space circular hover boost (radius = 15% of container width in pixels) that locally raises froth height (+30%) and bubbling frequency (2x at full strength) within that radius, decaying linearly to 0 over 500ms after pointerleave. This never recolors anything with `--ns-accent` and never touches the per-band `tLocal` clocks the flow/weep schedules are derived from — dwelling the pointer changes local density only, it cannot stall or rush the lateral-flow cycle or the weep schedule.\n\nHost: dpr-clamped to 2 backing store, resized via ResizeObserver with a zero-size guard, rAF loop paused via IntersectionObserver (out of view) and document visibilitychange (tab hidden), webglcontextlost (cancels the loop, preventDefault so the browser doesn't drop context permanently) / webglcontextrestored (recreates the program and resumes) handled, GL init failure leaves the container transparent rather than crashing, program/shaders/buffer deleted on unmount and on context loss. Under prefers-reduced-motion, a small JS-side search (mirroring the shader's exact hash/time math) scans forward from t=0 for the first moment where the topmost tray reads near-peak froth AND is mid-spill-transition AND at least one cap anywhere in the stack is visibly mid-weep (not just starting, not yet faded) — named `FREEZE_PHASE = \"mid-cascade-with-weep\"`, exposed as `data-reduced-motion-freeze` on the root — and freezes the clock there instead of an arbitrary or hand-picked t0, so the single static frame genuinely shows bubbling, spilling and weeping at once. No dependencies."
      }
    },
    {
      "name": "tricone-bit-teeth",
      "type": "registry:ui",
      "title": "Tricone Bit Teeth",
      "description": "A full-bleed rock face worked by a rotary tricone bit: three cone cutters orbit the bit centre in a fixed 120-degree pattern, each tooth row stamping a crater cluster into the face that heals back to flat before the next pass, so the surface reads as a ring being continuously worked rather than a diffuse pockmarked plate.",
      "files": [
        {
          "path": "registry/loud/tricone-bit-teeth/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/tricone-bit-teeth.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "rock",
          "drilling",
          "erosion",
          "decorative"
        ],
        "instruction": "Build <TriconeBitTeeth paused? children? className? style?> as a full-bleed 2D-canvas hero built on a rotary tricone rock bit: three cone cutters orbit the bit's centre together (bitAngle = t / LAP_SECONDS * 2*PI, LAP_SECONDS=7.5, an 8-RPM-equivalent sweep deliberately decoupled from the real 60-120 RPM rate per the round 9 aliasing rule) while a CPU-side depth buffer records where their teeth have struck. REAL NUMBERS: 3 cones, 120 degrees apart, orbit radius = 0.32 * min(fieldW, fieldH); TEETH_PER_CONE=11 teeth per cone, spread across a short arc of the cone's own track (not a single point) so one strike cluster reads as a row; a simultaneous 3-cone STRIKE_INTERVAL=0.68s cluster (33 real tooth-strikes/lap collapsed into one legible 3-cone event, per the round 9 'roughly a second between discrete events' rule); each tooth stamps a soft radial-falloff crater (exp(-d^2*2.2) within radius ref*0.028) at amplitude 0.75-1.0 into a Float32Array depth buffer sized FIELD_CELLS=96 cells across the container's SMALLER dimension (aspect-scaled on the other axis); every real frame the WHOLE buffer decays via depth *= exp(-HEAL_RATE*dt), HEAL_RATE=0.9/s (~90% refill in ~1.1s) — birth and decay are both constant and independent, so the ring is a resident population in equilibrium, never a monotonic fill (same steady-state discipline as registry/loud/edm-crater-field, but here births are STRUCTURED — always at the three cones' current orbit position — never uniform-random, which is what keeps this from reading as edm-crater-field's diffuse plate). RENDERING: the depth buffer is painted into an offscreen low-res canvas each frame (raised rock at depth<=0 mixed toward a struck-deep stop as depth approaches 1, both derived from --background/--foreground) and blitted scaled-up with imageSmoothingEnabled=true onto the display canvas — a soft, non-pixel-hard crater read appropriate to rock rather than a machined plate. The three cone BODIES are drawn as small triangular wedges directly on the display canvas each frame, orbiting at bitAngle with an additional decorative own-axis spin (t*6 rad/s, cosmetic only, not tied to any real number) — this vector overlay is what makes 'three cones rotating' legible on its own, independent of the crater ring, and is the deliberate differentiator from edm-crater-field's shader-only height field with no visible tool. A faint mud-jet sweep ring (0.08 peak luminance, radiating from bit centre at JET_SPEED=140px/s, resetting every JET_PERIOD=0.9s) stands in for nozzle discharge and must stay under the crater ring in read priority — a wash, not a highlight. MUST NOT: tint the crater ring, cone bodies or jet sweep with --ns-accent — there is no interactive control on this component (a pointer, if ever added, may only brighten the ring in luminance near the cursor), matching the showpiece recipe's standing 'accent-tinted pointer highlight' defect (edge-yield, granule-churn, shear-billow). RESTING LOOP: t0 shows an already-mid-cycle ring (seeded by two immediate strikeCluster() calls in resize(), at bit angles 0 and one-third of a lap, so mount never shows a blank flat frame); by 2.5s roughly 3-4 more clusters have landed and one or two early clusters have healed back toward flat; by 5s the ring shows continuous turnover — fresh strikes at the leading edge, healed craters fading at the trailing edge — which is the 'alive at rest' proof, not a filling-in or emptying-out pattern. TOKENS: three luminance stops (raised rock, struck-deep, muted) derived from --background, --foreground, --ns-muted via getComputedStyle(document.documentElement) at mount, re-read on a MutationObserver watching documentElement's class — NO PAINT BEFORE THE FIRST READ, readColors() runs before the first resize()/draw(). Light theme (checked first): the dark anchor for struck-deep is mixed from --foreground rather than pure black, since light theme has far less headroom below --background than dark theme does above it — verify the raised/struck-deep/muted three-step ramp stays legible there specifically. `ResizeObserver` reallocates the depth buffer and reseeds two clusters; `IntersectionObserver` (threshold 0) and `visibilitychange` both pause the single rAF loop; `prefers-reduced-motion`/`paused` freeze on STATIC_STRIKE_PHASE — the instant right after the most recent strike lands, at maximum unhealed depth, the single most structured frame in the loop. Canvas is `aria-hidden` decoration with `w-full h-full`; children render over the field in a `relative z-[1]` layer and need a token scrim, since the crater ring can pass under any line of overlaid type."
      }
    },
    {
      "name": "weir-crest",
      "type": "registry:ui",
      "title": "Weir Crest",
      "description": "A full-bleed pricing surface built as a spillway: three plans stand in one rising reservoir as dams of different crest heights, and the volume slider raises a single shared water level until a plan's crest is overtopped and pours, which is exactly the moment its overage starts costing money.",
      "files": [
        {
          "path": "registry/loud/weir-crest/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/weir-crest.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)",
          "color-ns-accent-hover": "var(--ns-accent-hover)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff",
          "ns-accent-hover": "#0059d1"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "pricing",
          "webgl",
          "canvas",
          "shader",
          "slider",
          "section",
          "hero",
          "interactive",
          "ambient",
          "fluid"
        ],
        "instruction": "Build <WeirCrest plans? minUsage? maxUsage? defaultUsage? eyebrow? headline? footnote? annualDiscount? onChange? className?> as a full-bleed usage-pricing section rendered as a spillway. THE MECHANISM: every plan is a dam standing in ONE shared reservoir, its crest height set by its included volume on a log scale (Starter 50k low, Growth 400k mid, Scale 2M high). A single vertical slider on the left rail IS the water level: dragging it up raises one continuous water surface across the whole viewport, and the instant that surface passes a plan's crest the dam is overtopped — a nappe pours down that pier's face into foam at the apron — which is exactly the moment that plan's overage term becomes non-zero. So the comparison is felt as a sequence of overtoppings, not read off three cards: at low volume the cheap short dam holds and is the cheapest; raise the level and it gushes while its price climbs past the taller dam standing dry. The cheapest plan at the current level is marked Recommended and its CTA is the only --ns-accent surface on screen. RENDERING: one full-bleed WebGL fragment shader (fullscreen triangle pair, lazily resolved uniform locations, DPR clamped to 1.5). Water surface is three sines plus an fbm term, amplitude scaled by a slosh value derived from the level spring's velocity, so dragging fast throws real chop and it settles back to a slow ambient swell; below the surface, depth-attenuated caustic sheets, five rising bubbles, and a translucent (0.86) composite so the masonry courses of a submerged pier read through the water. Piers, crest bars, nappe streaks and apron foam are all drawn in the same shader from a vec4[3] uniform (x, width, crestY, spill) measured from the REAL DOM column boxes via ResizeObserver, so the mechanism is registered to the layout at every width rather than approximating it. TYPE IS DOM, ALWAYS: plan names, prices, the base/overage breakdown, feature lists and CTAs are real DOM text in document order inside three <article> elements — nothing numeric is ever rasterized into the canvas, which is aria-hidden. Panels sit on a token scrim (bg-background/78 + backdrop-blur) because a full-bleed reservoir spans most of the value range in both themes. CONTROLS: the level slider is a role=slider tabIndex=0 vertical track (aria-orientation=vertical, aria-valuemin/max/now and an aria-valuetext naming the volume) driven by pointer capture with click-to-jump, plus ArrowUp/Down/Left/Right (one step), PageUp/PageDown (five), Home/End (rails) on the log domain; a monthly/annual segmented control is a real radiogroup. An sr-only aria-live=polite region announces the volume, the recommended plan and its price on COMMIT only (pointerup or keystroke), never per pointermove. PRICING: price = base * (annual ? 1 - annualDiscount : 1) + max(0, usage - included)/1000 * overagePer1k, recomputed in render so the printed number and the water line can never disagree. PALETTE: four stops derived from --background --foreground --ns-muted --border via getComputedStyle, re-read on a documentElement class MutationObserver; both themes deliberate (light reads as a mid-grey reservoir under a pale sky, dark as a lit pour in a dark room) and the shader contains no colour literal and no accent at all — --ns-accent exists only on the recommended CTA and focus rings. REDUCED MOTION: level snaps to target with zero spring, the shader clock is frozen at a composed still (chop settled, the short dam already pouring) and redraws only on state change; every control stays fully usable. PERF: one rAF loop, direct-uniform hot path with no React state per frame, IntersectionObserver and visibilitychange both stop it, webglcontextlost/restored rebuild cleanly, and all observers/listeners tear down on unmount."
      }
    },
    {
      "name": "weld-pool",
      "type": "registry:ui",
      "title": "Weld Pool",
      "description": "A full-bleed liquid-metal hero whose headline is summed into the shader's height field rather than layered over it, so the letterforms are lit, warped and dented by the same molten surface they sit in.",
      "files": [
        {
          "path": "registry/loud/weld-pool/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/weld-pool.tsx"
        }
      ],
      "dependencies": [],
      "cssVars": {
        "theme": {
          "color-ns-muted": "var(--ns-muted)",
          "color-ns-accent": "var(--ns-accent)"
        },
        "light": {
          "ns-muted": "#4d4d4d",
          "ns-accent": "#006bff"
        },
        "dark": {
          "ns-muted": "#8f8f8f"
        }
      },
      "meta": {
        "collection": "loud",
        "tags": [
          "hero",
          "canvas",
          "webgl",
          "shader",
          "metal",
          "cursor",
          "type"
        ],
        "instruction": "Build <WeldPool headline? headlineWeight? headlineFit? headlineY? relief? speed? scale? paused? children? className?> as a full-bleed WebGL hero rendering a molten metal pool with real headline type lying IN the surface. ONE HEIGHT FIELD: the whole component is a single fragment shader that evaluates h(x,y,t) and central-differences it (three taps, eps 1.35px, relief 15) for one surface normal. h is the sum of four contributions, and the point of summing them is that the type is then indistinguishable from the metal — it warps, catches light and dents with the flow instead of floating over it. (1) Convecting bulk: a domain-warped fbm, w = vec2(fbm2(q + t*0.055), fbm2(q + 3.71 - t*0.043 + 2.13)) and base = fbm3(q*1.18 + w*1.05 + drift), contributing (base - 0.5)*3.4, with q = p/(min(W,H)*0.62)*scale. (2) Four gaussian lobes at 2.2 amplitude, positions animated on the CPU on lissajous paths and passed as a vec3[4] uniform (x, y, radius) — buoyant volumes rolling under the skin, analytic so they cost four exp() calls and no noise. (3) An ANISOTROPIC advected grain: two octaves sampled on gq = (q + w*0.55)*vec2(4.5, 17.0), i.e. ~4x tighter across the flow than along it, at 0.20 and 0.07 amplitude. Isotropic grain at this amplitude reads as crumpled foil; stretching it turns the same energy into striations being drawn out by a pour. The magnitudes matter more than they look: an early build had the pool's slopes an order of magnitude gentler than the glyph bevel's, and the whole field collapsed into an out-of-focus grey cloud while the letters alone read as chrome. (4) The glyph bevel, sampled at uv = p/size + (w - 0.5)*0.014 — displacing the lookup by the same warp that drives the flow is what makes the letterforms shimmer like a reflection rather than a decal pinned to the pool. THE CALM GATE, which is the single most load-bearing line: a glyph is a solid plate floating in a churning liquid, so the liquid's turbulence is damped across it — the bulk scales by mix(1.0, 0.35, bevel), the lobes by mix(1.0, 0.4, bevel), and the grain by (1 - 0.88*bevel). Without it the pool's detail runs straight through the letters and the headline loses to its own background at exactly the density that makes the pool worth having. GLYPH TEXTURE: the headline is rasterized on a 2D canvas at up to 1024px wide using the wrapper's own computed font-family, auto-fit so the longest line lands on headlineFit of the width; the alpha channel is copied out, box-blurred separably three times at radius ~0.45% of the texture width, and packed as R = max(blurred, sharp) (a plateau with a soft skirt, whose gradient IS the letter's slope) and G = the hard mask. Rasterization repeats after document.fonts.ready — a texture is baked once, so a pre-webfont raster would freeze fallback letterforms permanently — and on any headline change, without recreating the GL context. MONOCHROME METAL: metal normally reads through hue; here the palette is achromatic, so every cue is carried by value, and density comes from the ENVIRONMENT having structure rather than the height field having more noise. r = reflect(-v, n) is looked up in an analytic achromatic studio of five sources at different elevations — a broad sky smoothstep(0.06, 0.68, r.y), a dark floor, and gaussian strips at el 0.13, 0.42, -0.28 and -0.62 — plus two slowly turning azimuthal panels (the only source of horizontal structure, without which the frame reads as pure horizon banding), a polish banding term, two drifting sharp specular lobes (exponents 80 and 34), anisotropic brushed streaks from one noise sample stretched ~50:1 along x, and a fresnel rim at pow(1 - dot(n,v), 5). Several narrow sources mean an almost-flat patch of surface still crosses three or four reflection bands, which is what a real chrome sheet does. G, the hard glyph mask, drives a roughness switch: inside a letter the main strip narrows from sigma 0.085 to 0.038, the polish banding roughly doubles in frequency and the specular exponents rise to 240/110, so inlaid type separates from the pool by being POLISHED TIGHTER, with no colour difference at all. A contact shadow of max(blur - sharp, 0)*0.24 darkens the annulus of surface pulled up around each letter. PALETTE: five stops from --background, --foreground, --ns-muted and --border read via getComputedStyle and re-read on a documentElement class MutationObserver, plus --ns-accent tinting only the hottest specular at 13%. Unlike a thin metal band on a page, a full-bleed sheet IS the page, so the ramp spans near-black to near-white in BOTH themes and the direction never inverts — only the distribution moves (light theme bias +0.02, contrast 1.20, reading as polished steel in a lit room; dark theme bias -0.09, contrast 1.15, the same pool sunk). A mild elliptical vignette toward the deepest stop stops the frame edges competing with the headline. POINTER: pointer positions are pushed into a 6-slot ring buffer at ~18Hz; each becomes a mexican-hat dent, (1 - 1.75r^2)*exp(-1.6r^2), whose radius grows 30 -> ~350px and whose amplitude decays over 1.9s, so a sweep drags a train of expanding rings through the pool and a press drops one full-amplitude ring at the contact point. Pointer-enter alone eases a hover term from 0.4 to 1.0 on the wake amplitude, so hovering differs from rest even with no motion. A u_wake uniform gates the trail loop entirely — a coherent uniform branch, so at rest, which is where the page spends most of its life, the wake costs nothing. HOST: an internal GLSurface class owns the program, the fullscreen triangle pair and lazily-resolved uniform locations and knows nothing about the pool, so a second preset can mount the same host with a different fragment source. DPR is capped at 1.5 rather than 2 because the shader is full-bleed and its cost is three field evaluations per pixel, so the area term dominates. ResizeObserver re-sizes and re-rasterizes; an IntersectionObserver and visibilitychange both stop the single rAF loop; webglcontextlost/restored rebuild cleanly. prefers-reduced-motion (and paused) draw exactly one composed still frame at t=6.4 — lobes spread across the field, striations drawn out and a specular hit on the headline, never a blank grey box — and pointer input still redraws that frozen frame. The accessible copy of the headline is an sr-only <h1>, since the visible one exists only as a height field; children render over the pool for an eyebrow, subhead and CTA, and because the surface spans the full value range they need a token scrim (bg-background/70 + backdrop-blur) rather than bare type."
      }
    },
    {
      "name": "winnow-chaff-drift",
      "type": "registry:ui",
      "title": "Winnow Chaff Drift",
      "description": "A full-bleed hero built on a real grain-cleaning mechanic: winnowing. A mixed charge of grain and chaff falls through a steady crosswind: heavy grain's momentum wins over drag and it drops in a tight, near-vertical column, while light chaff loses to the wind and fans out laterally before it lands. The separation reads entirely from trajectory and softness, never colour.",
      "files": [
        {
          "path": "registry/loud/winnow-chaff-drift/component.tsx",
          "type": "registry:ui",
          "target": "components/ui/winnow-chaff-drift.tsx"
        }
      ],
      "dependencies": [],
      "meta": {
        "collection": "loud",
        "tags": [
          "background",
          "hero",
          "canvas",
          "particles",
          "grain",
          "wind",
          "agriculture",
          "mono",
          "ambient",
          "generative"
        ],
        "instruction": "`<WinnowChaffDrift children? className? />` renders a full-bleed hero canvas simulating winnowing: a continuous stream of two particle populations falls from the top edge through a shared crosswind field, and separates purely by a mass/drag difference — no sorting logic, just different physics per population. GRAIN (65% of spawns, radius 2-3px, fall speed 180px/s, hard-edged solid disc at 70% foreground alpha) gets a tiny per-particle lateral velocity assigned ONCE at spawn, sized so its total lateral travel over the full fall never exceeds 8px (`GRAIN_DRIFT_MAX / (containerHeight / GRAIN_FALL_SPEED)`) — it is deliberately wind-INDEPENDENT, because mass dominates drag for real grain, and this is what keeps the grain column tight and centred through every gust phase, the visual invariant that sells the whole separation. CHAFF (35% of spawns, radius 4-7px, fall speed 45px/s, soft radial-gradient fleck fading to transparent at its own radius, alpha 0.4 dark theme / 0.5 light theme) reads its lateral velocity CONTINUOUSLY every frame from the local wind field at its current x, scaled by a 0.85 drag coefficient, so it visibly answers every gust change as it falls — this is the one population actually being blown. THE WIND FIELD is a 1D lattice of cells spanning the container, cell count = `floor(min(width,height) / 24)` (geometry derived from the smaller dimension per the standing rule), each cell holding a locally-varying noise value that is regenerated at a fixed 2Hz (every 0.5s) and linearly interpolated between updates — deliberately capped well under the paint rate so gust texture reads as coherent drift, not jitter (this is the exact strobe/aliasing failure mode named in the round-9 additions to the filters). Layered under the lattice is one slow global sine gust, 9s period, ±40px/s amplitude, so the whole field's mean lateral pull sweeps back and forth on an easily-followed cycle in addition to its local texture. SPAWN: 6 particles/s at a random x across the top edge (y=-10), accumulated as a fractional counter so the rate holds exactly regardless of frame time. DESPAWN: 40px past whichever edge a particle exits (bottom for grain, bottom or either side for chaff) — particles are removed from the array, never hidden, so the standing population is exactly what's rendered. PALETTE: both populations are `--foreground`-derived tone at fixed alpha, no hue, no `--ns-accent` anywhere (there is no pointer interaction to justify it) — the separation must read from trajectory and disc-hardness/opacity alone. `--background`'s relative luminance (`0.2126r+0.7152g+0.0722b`, computed once per token re-derive) decides whether the theme is light; chaff alpha bumps from 0.4 to 0.5 in that case since light is the harder case for a soft, low-alpha fleck to hold contrast. Tokens are read via `getComputedStyle(document.documentElement)` at mount and re-derived on a `MutationObserver` watching `documentElement`'s class/data-theme attributes plus a `prefers-color-scheme` `matchMedia` listener — colours of already-spawned particles update on the next paint since fill style is computed fresh every frame, not baked at spawn. REDUCED MOTION: a deterministic replay (seeded `mulberry32` PRNG, fixed 1/60s ticks) runs the exact same `step()` the live loop uses from t=0 to `STATIC_TIME_S = 20.25` — chosen as the THIRD gust-sine peak (`sin` peaks at `T/4 + n*T` for a 9s period: 2.25s, 11.25s, 20.25s), picked specifically over the first peak because the standing chaff population needs several multiples of its own fall lifetime (`containerHeight / 45px/s`, which can exceed 15s in a tall viewport) to fully saturate the frame — freezing at the first peak risks an under-populated, half-built frame. The frame is genuinely byte-stable afterward: a single `draw()` call, then the loop never starts. The rAF loop pauses on `document.hidden` (`visibilitychange`) and via an `IntersectionObserver` (default threshold) while scrolled offscreen, both resuming through the same `startLoop()` path used by resize and theme/reduced-motion changes, which also fully resets and rebuilds the particle population and wind lattice from scratch (`ResizeObserver` on the root re-derives `w/h/dpr`, capped at 1.5, and rebuilds the wind cell count for the new smaller-dimension). `children` render as an ordinary absolutely-positioned DOM overlay on top of the canvas, same pattern as `kymograph-smoke-trace` and `dye-whorl` — a headline/CTA needs a token scrim since particles can pass under any line of type. Zero dependencies, DOM + Canvas 2D only, no WebGL. Canvas is `pointer-events-none` and `aria-hidden`; this is a fully ambient, non-interactive hero, `autoplay: mode: none`, because it runs entirely on its own internal clock with nothing for a synthetic pointer driver to do."
      }
    }
  ]
}
