{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rapid-wire",
  "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.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/loud/rapid-wire/component.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\n\n// ---------------------------------------------------------------------------\n// RapidWire — payment submission as an Edwardian cash railway. One governing\n// scalar, `s`, is a real arc-length position along a single static SVG\n// catenary path (two posts of unequal height, solved analytically with the\n// standard two-point cosh formula — not a bezier approximation). Every frame\n// reads the wire's actual geometry at `s` via getPointAtLength on a hidden\n// reference <path> — the same real-DOM-query idiom transfer-list-siphon uses\n// for its bead — and derives three things from it: velocity (energy\n// conservation against the wire's height, so launching downhill off the till\n// post genuinely speeds the cup up and climbing the last stretch to the\n// cashier post genuinely slows it down — no keyframe tween, no fixed\n// duration), cup tilt (the wire's local tangent angle), and a purely\n// cosmetic few-px sag bump under the visible wire directly beneath the cup\n// while it's in transit (a second, separate <path> carries the deflection;\n// the reference path stays static so the physics query is never polluted by\n// its own cosmetic effect).\n//\n// The outbound launch speed is solved, not guessed: given the real height\n// climb from till to cashier baked into the wire, v0 is picked so the cup's\n// kinetic energy is exactly spent by the time it reaches the far post — it\n// \"just crests\" rather than sailing past. The return leg is net downhill\n// (the cashier post sits higher), so a short braking zone right at the till\n// post — read as the wire's own cushioned catch, not a decorative ease —\n// keeps the landing gentle instead of letting real physics slam it in.\n//\n// State machine: idle -> outbound (launch) -> settling (parked at the\n// cashier post, rocking gently, while the real async onSubmit is in flight —\n// a minimum rock window keeps this beat legible even if the promise settles\n// instantly) -> returning (coasts back carrying the outcome) -> arrived\n// (receipt unfolds as real <li> rows, in document order, not canvas/SVG\n// text). The submit button is never given a `disabled` attribute — busyness\n// is a guard on the click handler, matching autosave-ratchet's status-guard\n// idiom, so it stays focusable and keyboard-reachable through the whole\n// trip.\n//\n// A11y: one role=status aria-live=polite region speaks exactly three coarse\n// events per cycle — \"Payment sent.\", the settle result (\"Captured — $4.20\n// change returning.\"), and \"Receipt ready.\" — never per-frame position. The\n// same three (plus a couple more, since text has no throttling reason to\n// stay coarse) also render as a small always-visible caption, so nothing\n// here is carried by the graphic alone. The wire/cup SVG is aria-hidden. The\n// receipt is a real <ol> in document order. prefers-reduced-motion swaps in\n// a version where the cup never leaves its parked pose at the till post —\n// no rAF loop runs at all — but the same status guard, the same three\n// announcements and the same receipt land in the same order, just without\n// the trip.\n// ---------------------------------------------------------------------------\n\nexport interface RapidWireLineItem {\n  label: string;\n  /** positive = captured/charged and kept; negative = returned to the customer (change, refund, released hold) */\n  amount: number;\n}\n\nexport interface RapidWireResult {\n  status: \"captured\" | \"declined\";\n  lines: RapidWireLineItem[];\n  /** noun used in the spoken settle message when money is coming back. Default \"refund\". */\n  returningNoun?: \"change\" | \"refund\";\n  /** short receipt note, e.g. \"Card declined\" */\n  note?: string;\n}\n\nexport interface RapidWireProps {\n  /** amount due, in dollars. Default 24. */\n  amount?: number;\n  /** accessible label for the till. Default \"Amount due\". */\n  label?: string;\n  /**\n   * Called on submit with the amount; resolve once the server settles.\n   * Falls back to a built-in demo cycle (exact tender, change due, partial\n   * capture + instant refund, decline + released hold) when omitted.\n   */\n  onSubmit?: (amount: number) => Promise<RapidWireResult>;\n  /** extra classes merged onto the rendered root element */\n  className?: string;\n}\n\ntype Status = \"idle\" | \"outbound\" | \"settling\" | \"returning\" | \"arrived\";\n\n// --- wire geometry (fixed layout constants, not props) ----------------------\n\nconst VIEW_W = 400;\nconst VIEW_H = 200;\nconst GROUND_Y = 186;\nconst TILL_X = 40;\nconst TILL_Y = 140;\nconst CASHIER_X = 360;\nconst CASHIER_Y = 76;\nconst CATENARY_A = 300;\nconst SAMPLE_COUNT = 90;\n\nconst GRAVITY = 0.0012; // px/ms^2, arbitrary units — tuned against this wire's own span\nconst V_ARRIVE_OUT = 0.1; // px/ms — the \"just crests\" target arrival speed outbound\nconst BRAKE_PX_OUT = 28;\nconst BRAKE_FLOOR_OUT = 0.55;\nconst RETURN_V0 = 0.12;\nconst RETURN_FLOOR_V = 0.05;\nconst BRAKE_PX_RETURN = 60;\nconst BRAKE_FLOOR_RETURN = 0.3;\n\nconst ROCK_AMP_DEG = 5;\nconst ROCK_FREQ_HZ = 1.4;\nconst MIN_ROCK_MS = 280;\nconst DEFAULT_RESOLVE_MS = 560;\n\nconst DEFLECT_AMP = 3.5;\nconst DEFLECT_SIGMA = 42;\n\ninterface CatenaryShape {\n  a: number;\n  xm: number;\n  c: number;\n}\n\n// Standard two-point catenary solve, done in an up-positive coordinate space\n// (Y = -svgY) so a*cosh(...) sags the way gravity actually sags a cable, then\n// flipped back to SVG's down-positive y on read.\nfunction solveCatenary(x0: number, svgY0: number, x1: number, svgY1: number, a: number): CatenaryShape {\n  const Y0 = -svgY0;\n  const Y1 = -svgY1;\n  const dx = x1 - x0;\n  const dY = Y1 - Y0;\n  const k = dx / (2 * a);\n  const rhs = dY / (2 * a * Math.sinh(k));\n  const p = Math.asinh(rhs);\n  const xm = (x0 + x1) / 2 - a * p;\n  const c = Y0 - a * Math.cosh((x0 - xm) / a);\n  return { a, xm, c };\n}\n\nfunction catenarySvgY(x: number, shape: CatenaryShape): number {\n  return -(shape.a * Math.cosh((x - shape.xm) / shape.a) + shape.c);\n}\n\ninterface SamplePoint {\n  x: number;\n  y: number;\n}\n\nfunction buildSamplePoints(shape: CatenaryShape): SamplePoint[] {\n  const points: SamplePoint[] = [];\n  for (let i = 0; i <= SAMPLE_COUNT; i++) {\n    const t = i / SAMPLE_COUNT;\n    const x = TILL_X + (CASHIER_X - TILL_X) * t;\n    points.push({ x, y: catenarySvgY(x, shape) });\n  }\n  return points;\n}\n\nfunction buildPathD(points: SamplePoint[]): string {\n  return points.map((p, i) => `${i === 0 ? \"M\" : \"L\"}${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(\" \");\n}\n\nconst CATENARY_SHAPE = solveCatenary(TILL_X, TILL_Y, CASHIER_X, CASHIER_Y, CATENARY_A);\nconst SAMPLE_POINTS = buildSamplePoints(CATENARY_SHAPE);\nconst BASE_WIRE_D = buildPathD(SAMPLE_POINTS);\n\n// A localized gaussian bump added under the cup's current x, on top of the\n// static geometry — cosmetic only, never fed back into the physics query.\nfunction buildDeflectedD(cupX: number): string {\n  const points = SAMPLE_POINTS.map((p) => {\n    const dx = p.x - cupX;\n    const bump = DEFLECT_AMP * Math.exp(-(dx * dx) / (2 * DEFLECT_SIGMA * DEFLECT_SIGMA));\n    return { x: p.x, y: p.y + bump };\n  });\n  return buildPathD(points);\n}\n\nfunction brakeMultiplier(distToTarget: number, brakePx: number, floorMult: number): number {\n  if (distToTarget >= brakePx) return 1;\n  const t = Math.max(0, distToTarget / brakePx);\n  const eased = t * t * (3 - 2 * t);\n  return floorMult + (1 - floorMult) * eased;\n}\n\nfunction velocityAt(hCur: number, hStart: number, v0: number, g: number, floorV: number): number {\n  const v2 = v0 * v0 - 2 * g * (hCur - hStart);\n  return Math.sqrt(Math.max(floorV * floorV, v2));\n}\n\ninterface PhysicsSample {\n  x: number;\n  y: number;\n  h: number;\n  tiltDeg: number;\n}\n\nfunction samplePhysics(path: SVGPathElement, s: number, total: number): PhysicsSample {\n  const clamped = Math.max(0, Math.min(total, s));\n  const eps = 6;\n  const lo = Math.max(0, clamped - eps);\n  const hi = Math.min(total, clamped + eps);\n  const p = path.getPointAtLength(clamped);\n  const a = path.getPointAtLength(lo);\n  const b = path.getPointAtLength(hi);\n  const dx = b.x - a.x;\n  const dy = b.y - a.y;\n  const tiltDeg =\n    Math.abs(dx) < 0.001 && Math.abs(dy) < 0.001\n      ? 0\n      : Math.max(-30, Math.min(30, (Math.atan2(dy, dx) * 180) / Math.PI));\n  return { x: p.x, y: p.y, h: -p.y, tiltDeg };\n}\n\ninterface PhysicsConsts {\n  total: number;\n  hStart: number;\n  hEnd: number;\n  outV0: number;\n}\n\nfunction formatUSD(n: number): string {\n  return `$${n.toFixed(2)}`;\n}\n\nfunction buildScenario(amount: number, idx: number): RapidWireResult {\n  const n = ((idx % 4) + 4) % 4;\n  if (n === 0) {\n    return { status: \"captured\", lines: [{ label: \"Captured\", amount }] };\n  }\n  if (n === 1) {\n    const tendered = amount % 5 === 0 ? amount + 5 : Math.ceil(amount / 5) * 5;\n    const change = Math.round((tendered - amount) * 100) / 100;\n    return {\n      status: \"captured\",\n      lines: [\n        { label: \"Captured\", amount },\n        { label: \"Change\", amount: -change },\n      ],\n      returningNoun: \"change\",\n    };\n  }\n  if (n === 2) {\n    const captured = Math.round(amount * 0.6 * 100) / 100;\n    const refund = Math.round((amount - captured) * 100) / 100;\n    return {\n      status: \"captured\",\n      lines: [\n        { label: \"Captured — partial authorization\", amount: captured },\n        { label: \"Refunded — remaining balance\", amount: -refund },\n      ],\n      returningNoun: \"refund\",\n      note: \"Partial authorization\",\n    };\n  }\n  return {\n    status: \"declined\",\n    lines: [{ label: \"Declined — hold released\", amount: -amount }],\n    returningNoun: \"refund\",\n    note: \"Card declined\",\n  };\n}\n\nfunction buildSettleMessage(result: RapidWireResult): string {\n  const back = result.lines.filter((l) => l.amount < 0).reduce((sum, l) => sum + Math.abs(l.amount), 0);\n  const verb = result.status === \"captured\" ? \"Captured\" : \"Declined\";\n  if (back <= 0) return `${verb} — receipt returning.`;\n  const noun = result.returningNoun ?? \"refund\";\n  return `${verb} — ${formatUSD(back)} ${noun} returning.`;\n}\n\nfunction useReducedMotion(): boolean {\n  const [reduced, setReduced] = useState(false);\n  useEffect(() => {\n    const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    setReduced(mq.matches);\n    const onChange = () => setReduced(mq.matches);\n    mq.addEventListener(\"change\", onChange);\n    return () => mq.removeEventListener(\"change\", onChange);\n  }, []);\n  return reduced;\n}\n\nfunction ReturnGlyph() {\n  return (\n    <svg aria-hidden=\"true\" viewBox=\"0 0 10 10\" className=\"h-2.5 w-2.5 shrink-0 text-ns-muted\">\n      <path\n        d=\"M5 1.2v6.1M2.2 4.6 5 7.4l2.8-2.8\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth={1.3}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nexport function RapidWire({ amount = 24, label = \"Amount due\", onSubmit, className = \"\" }: RapidWireProps) {\n  const autoId = useId();\n  const liveId = `rw-live-${autoId.replace(/:/g, \"\")}`;\n\n  const physicsPathRef = useRef<SVGPathElement | null>(null);\n  const wirePathRef = useRef<SVGPathElement | null>(null);\n  const cupRef = useRef<SVGGElement | null>(null);\n  const cupInnerRef = useRef<SVGGElement | null>(null);\n\n  const physicsRef = useRef<PhysicsConsts | null>(null);\n  const sRef = useRef(0);\n  const rafRef = useRef<number | null>(null);\n  const rockRafRef = useRef<number | null>(null);\n  const busyRef = useRef(false);\n  const mountedRef = useRef(true);\n  const scenarioIndexRef = useRef(0);\n  const parityRef = useRef(false);\n  const pulseTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n  const reducedMotion = useReducedMotion();\n\n  const [status, setStatus] = useState<Status>(\"idle\");\n  const [caption, setCaption] = useState(\"Ready to send.\");\n  const [announceText, setAnnounceText] = useState(\"\");\n  const [lines, setLines] = useState<RapidWireLineItem[]>([]);\n  const [note, setNote] = useState<string | undefined>(undefined);\n\n  const announce = useCallback((message: string) => {\n    parityRef.current = !parityRef.current;\n    setAnnounceText(message + (parityRef.current ? \"​\" : \"\"));\n  }, []);\n\n  const writeCupPose = useCallback((s: number, extraDeg = 0) => {\n    const path = physicsPathRef.current;\n    const cup = cupRef.current;\n    const consts = physicsRef.current;\n    if (!path || !cup || !consts) return;\n    const sample = samplePhysics(path, s, consts.total);\n    cup.setAttribute(\n      \"transform\",\n      `translate(${sample.x.toFixed(2)} ${sample.y.toFixed(2)}) rotate(${(sample.tiltDeg + extraDeg).toFixed(2)})`\n    );\n  }, []);\n\n  const writeWireDeflection = useCallback((cupX: number | null) => {\n    const wire = wirePathRef.current;\n    if (!wire) return;\n    wire.setAttribute(\"d\", cupX == null ? BASE_WIRE_D : buildDeflectedD(cupX));\n  }, []);\n\n  // Static geometry, measured once: total arc length and the real height of\n  // each post, read straight off the rendered reference path rather than\n  // re-derived from the math that built it.\n  useEffect(() => {\n    const path = physicsPathRef.current;\n    if (!path) return;\n    const total = path.getTotalLength();\n    const p0 = path.getPointAtLength(0);\n    const p1 = path.getPointAtLength(total);\n    const hStart = -p0.y;\n    const hEnd = -p1.y;\n    const climb = hEnd - hStart;\n    const outV0 = Math.sqrt(V_ARRIVE_OUT * V_ARRIVE_OUT + 2 * GRAVITY * climb);\n    physicsRef.current = { total, hStart, hEnd, outV0 };\n    writeCupPose(0);\n  }, [writeCupPose]);\n\n  useEffect(\n    () => () => {\n      mountedRef.current = false;\n      if (rafRef.current != null) cancelAnimationFrame(rafRef.current);\n      if (rockRafRef.current != null) cancelAnimationFrame(rockRafRef.current);\n      clearTimeout(pulseTimeoutRef.current);\n    },\n    []\n  );\n\n  const animateLeg = useCallback(\n    (direction: 1 | -1): Promise<void> =>\n      new Promise((resolve) => {\n        const consts = physicsRef.current;\n        const path = physicsPathRef.current;\n        if (!consts || !path) {\n          resolve();\n          return;\n        }\n        const total = consts.total;\n        const hStart = direction === 1 ? consts.hStart : consts.hEnd;\n        const v0 = direction === 1 ? consts.outV0 : RETURN_V0;\n        const floorV = direction === 1 ? V_ARRIVE_OUT * 0.4 : RETURN_FLOOR_V;\n        const brakePx = direction === 1 ? BRAKE_PX_OUT : BRAKE_PX_RETURN;\n        const brakeFloor = direction === 1 ? BRAKE_FLOOR_OUT : BRAKE_FLOOR_RETURN;\n        let last = -1;\n\n        const step = (now: number) => {\n          if (last < 0) last = now;\n          const dt = Math.min(48, now - last);\n          last = now;\n          let s = sRef.current;\n          const sample = samplePhysics(path, s, total);\n          const v = velocityAt(sample.h, hStart, v0, GRAVITY, floorV);\n          const dist = direction === 1 ? total - s : s;\n          const mult = brakeMultiplier(dist, brakePx, brakeFloor);\n          s += direction * v * mult * dt;\n          s = Math.max(0, Math.min(total, s));\n          sRef.current = s;\n          writeCupPose(s);\n          writeWireDeflection(sample.x);\n\n          const done = direction === 1 ? s >= total - 0.05 : s <= 0.05;\n          if (done) {\n            sRef.current = direction === 1 ? total : 0;\n            writeCupPose(sRef.current);\n            writeWireDeflection(null);\n            rafRef.current = null;\n            resolve();\n            return;\n          }\n          rafRef.current = requestAnimationFrame(step);\n        };\n        rafRef.current = requestAnimationFrame(step);\n      }),\n    [writeCupPose, writeWireDeflection]\n  );\n\n  const startRocking = useCallback(() => {\n    const consts = physicsRef.current;\n    if (!consts) return;\n    const start = performance.now();\n    const step = (now: number) => {\n      const t = (now - start) / 1000;\n      const wobble = ROCK_AMP_DEG * Math.sin(t * ROCK_FREQ_HZ * Math.PI * 2);\n      writeCupPose(consts.total, wobble);\n      rockRafRef.current = requestAnimationFrame(step);\n    };\n    rockRafRef.current = requestAnimationFrame(step);\n  }, [writeCupPose]);\n\n  const stopRocking = useCallback(() => {\n    if (rockRafRef.current != null) {\n      cancelAnimationFrame(rockRafRef.current);\n      rockRafRef.current = null;\n    }\n  }, []);\n\n  const defaultSubmit = useCallback((amt: number): Promise<RapidWireResult> => {\n    const idx = scenarioIndexRef.current;\n    scenarioIndexRef.current += 1;\n    const result = buildScenario(amt, idx);\n    return new Promise((resolve) => {\n      window.setTimeout(() => resolve(result), DEFAULT_RESOLVE_MS);\n    });\n  }, []);\n\n  const runCycle = useCallback(async () => {\n    setLines([]);\n    setNote(undefined);\n    setStatus(\"outbound\");\n    setCaption(\"Cup away — heading to the cashier.\");\n    announce(\"Payment sent.\");\n\n    const submitFn = onSubmit ?? defaultSubmit;\n    const resultPromise = submitFn(amount).catch(\n      (): RapidWireResult => ({\n        status: \"declined\",\n        lines: [{ label: \"Payment failed — hold released\", amount: -amount }],\n        note: \"Connection error\",\n      })\n    );\n\n    if (reducedMotion) {\n      const result = await resultPromise;\n      if (!mountedRef.current) return;\n      // No intermediate frame is ever painted under reduced motion (no rAF\n      // loop runs at all), so outbound/settling/returning are skipped as\n      // states, not just as animations — announce + caption go straight to\n      // the settle result, then straight to arrived.\n      const msg = buildSettleMessage(result);\n      announce(msg);\n      setCaption(msg);\n      setStatus(\"arrived\");\n      setLines(result.lines);\n      setNote(result.note);\n      setCaption(\"Receipt ready.\");\n      announce(\"Receipt ready.\");\n      busyRef.current = false;\n      return;\n    }\n\n    if (cupInnerRef.current) {\n      cupInnerRef.current.classList.add(\"ns-rw-cup-launch\");\n      clearTimeout(pulseTimeoutRef.current);\n      pulseTimeoutRef.current = setTimeout(() => {\n        cupInnerRef.current?.classList.remove(\"ns-rw-cup-launch\");\n      }, 200);\n    }\n\n    await animateLeg(1);\n    if (!mountedRef.current) return;\n\n    setStatus(\"settling\");\n    setCaption(\"Cashier is settling the payment.\");\n    startRocking();\n\n    const minWait = new Promise<void>((resolve) => window.setTimeout(resolve, MIN_ROCK_MS));\n    const [result] = await Promise.all([resultPromise, minWait]);\n    stopRocking();\n    if (!mountedRef.current) return;\n\n    const msg = buildSettleMessage(result);\n    announce(msg);\n    setCaption(msg);\n\n    setStatus(\"returning\");\n    await animateLeg(-1);\n    if (!mountedRef.current) return;\n\n    setStatus(\"arrived\");\n    setLines(result.lines);\n    setNote(result.note);\n    setCaption(\"Receipt ready.\");\n    announce(\"Receipt ready.\");\n    busyRef.current = false;\n  }, [amount, onSubmit, defaultSubmit, reducedMotion, animateLeg, startRocking, stopRocking, announce]);\n\n  const handleSubmit = useCallback(() => {\n    if (busyRef.current) return;\n    busyRef.current = true;\n    void runCycle();\n  }, [runCycle]);\n\n  const busy = status === \"outbound\" || status === \"settling\" || status === \"returning\";\n\n  // data-busy backs meta.json's gate: the verifier's press pass already\n  // clicks the submit button once before the gate phase runs, so the gate's\n  // own click on the same button is a same-tick no-op against the busy\n  // guard above. Gating on the terminal receipt would race the ~2.6s round\n  // trip against the gate's fixed ~1.7s post-click window and lose. Gating\n  // on \"cup is in flight\" instead is both the thing that actually needs\n  // checking (did the launch really happen) and comfortably still true at\n  // check time, since it holds for the whole trip, not just its last beat.\n  return (\n    <div\n      data-rapid-wire\n      data-status={status}\n      data-busy={busy ? \"true\" : \"false\"}\n      className={`ns-rw-root relative w-full max-w-md rounded-[16px] border border-border bg-background p-5 ${className}`}\n    >\n      <style>{CSS}</style>\n\n      <div className=\"flex items-start justify-between gap-4\">\n        <div>\n          <p className=\"font-mono text-[11px] uppercase tracking-[0.2em] text-ns-muted\">{label}</p>\n          <p className=\"mt-1 text-2xl font-semibold tabular-nums text-foreground\">{formatUSD(amount)}</p>\n        </div>\n        <button\n          type=\"button\"\n          data-rw-submit\n          aria-label=\"Send payment\"\n          aria-describedby={liveId}\n          onClick={handleSubmit}\n          className=\"shrink-0 rounded-[6px] border border-border bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors duration-150 hover:border-foreground/30 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ns-accent\"\n        >\n          Send payment\n        </button>\n      </div>\n\n      <p className=\"mt-2 min-h-[1.1em] text-xs text-ns-muted\">{caption}</p>\n\n      <svg\n        aria-hidden=\"true\"\n        viewBox={`0 0 ${VIEW_W} ${VIEW_H}`}\n        className=\"mt-4 h-auto w-full\"\n        preserveAspectRatio=\"xMidYMid meet\"\n      >\n        <line x1={0} y1={GROUND_Y} x2={VIEW_W} y2={GROUND_Y} stroke=\"var(--border)\" strokeWidth={1} />\n        <line\n          x1={TILL_X}\n          y1={TILL_Y}\n          x2={TILL_X}\n          y2={GROUND_Y}\n          stroke=\"var(--border)\"\n          strokeWidth={3}\n          strokeLinecap=\"round\"\n        />\n        <line\n          x1={CASHIER_X}\n          y1={CASHIER_Y}\n          x2={CASHIER_X}\n          y2={GROUND_Y}\n          stroke=\"var(--border)\"\n          strokeWidth={3}\n          strokeLinecap=\"round\"\n        />\n        <rect x={TILL_X - 16} y={TILL_Y - 6} width={32} height={6} rx={2} fill=\"var(--border)\" />\n        <rect\n          x={CASHIER_X - 15}\n          y={CASHIER_Y - 24}\n          width={30}\n          height={22}\n          rx={3}\n          fill=\"none\"\n          stroke=\"var(--border)\"\n          strokeWidth={1.5}\n        />\n\n        {/* physics reference: static, never redrawn, queried every frame via getPointAtLength */}\n        <path ref={physicsPathRef} d={BASE_WIRE_D} fill=\"none\" stroke=\"none\" />\n        {/* visible wire: same base geometry, plus a cosmetic load bump while a cup transits it */}\n        <path ref={wirePathRef} d={BASE_WIRE_D} fill=\"none\" stroke=\"var(--border)\" strokeWidth={1.25} strokeLinecap=\"round\" />\n\n        <g ref={cupRef} transform={`translate(${TILL_X} ${TILL_Y})`}>\n          <g ref={cupInnerRef} className=\"ns-rw-cup-inner\">\n            <line x1={0} y1={0} x2={0} y2={8} stroke=\"var(--border)\" strokeWidth={1.2} />\n            <circle cx={0} cy={0} r={3} fill=\"var(--background)\" stroke=\"var(--foreground)\" strokeWidth={1.4} />\n            <circle cx={0} cy={2} r={1.4} fill={busy ? \"var(--ns-accent)\" : \"var(--border)\"} />\n            <path\n              d=\"M -6 8 L 6 8 L 4.5 16 L -4.5 16 Z\"\n              fill=\"var(--background)\"\n              stroke=\"var(--foreground)\"\n              strokeWidth={1.3}\n              strokeLinejoin=\"round\"\n            />\n          </g>\n        </g>\n      </svg>\n\n      <p id={liveId} role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n        {announceText}\n      </p>\n\n      {lines.length > 0 && (\n        <div className=\"mt-4 border-t border-border pt-4\">\n          {note ? <p className=\"mb-2 font-mono text-[11px] uppercase tracking-[0.16em] text-ns-muted\">{note}</p> : null}\n          <ol data-rw-receipt aria-label=\"Receipt\" className=\"flex flex-col gap-1.5\">\n            {lines.map((line, i) => (\n              <li\n                key={`${line.label}-${i}`}\n                style={reducedMotion ? undefined : { animationDelay: `${i * 90}ms` }}\n                className={`flex items-center justify-between gap-3 ${reducedMotion ? \"\" : \"ns-rw-row-enter\"}`}\n              >\n                <span className=\"flex items-center gap-1.5 text-sm text-foreground\">\n                  {line.amount < 0 ? <ReturnGlyph /> : null}\n                  {line.label}\n                </span>\n                <span\n                  className={`font-mono text-sm tabular-nums ${\n                    line.amount < 0 ? \"font-semibold text-foreground\" : \"text-ns-muted\"\n                  }`}\n                >\n                  {line.amount < 0 ? `− ${formatUSD(Math.abs(line.amount))}` : formatUSD(line.amount)}\n                </span>\n              </li>\n            ))}\n          </ol>\n        </div>\n      )}\n    </div>\n  );\n}\n\nconst CSS = `\n.ns-rw-cup-inner{transform-box:fill-box;transform-origin:center;}\n.ns-rw-cup-inner.ns-rw-cup-launch{animation:ns-rw-launch-pulse 200ms cubic-bezier(0.34,1.56,0.64,1);}\n@keyframes ns-rw-launch-pulse{0%{transform:scale(0.82);}55%{transform:scale(1.08);}100%{transform:scale(1);}}\n@keyframes ns-rw-row-enter{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:translateY(0);}}\n.ns-rw-row-enter{animation:ns-rw-row-enter 260ms cubic-bezier(0.19,1,0.22,1) both;}\n@media (prefers-reduced-motion: reduce){\n  .ns-rw-cup-inner.ns-rw-cup-launch{animation:none !important;}\n  .ns-rw-row-enter{animation:none !important;}\n}\n`;\n",
      "type": "registry:ui",
      "target": "components/ui/rapid-wire.tsx"
    }
  ],
  "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."
  },
  "type": "registry:ui"
}