{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "catenary-contact-stagger",
  "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.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/core/catenary-contact-stagger/component.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\n// ---------------------------------------------------------------------------\n// CatenaryContactStagger — a live connection/sync-quality indicator built\n// from overhead contact wire stagger (OLE engineering). Contact wire is\n// never laid dead straight above the track: it's deliberately zigzagged\n// side to side, span by span, so a pantograph's carbon collector strip wears\n// evenly across its width instead of grooving one spot. The strip\n// continuously tracks that lateral zigzag as spans pass beneath it; on the\n// rare span where contact genuinely breaks, a brief arc punctuates an\n// otherwise smooth ride.\n//\n// Geometry is exact, not sampled: the stagger is a true engineering zigzag —\n// dead straight spans between alternating extremes at every support — so the\n// wire is a polyline with vertices only at span boundaries, where it flips\n// from +stagger to -stagger. A single continuous phase u(t) = elapsed /\n// SPAN_MS drives both the wire's horizontal scroll AND the fixed-position\n// contact marker's lateral value, sampled from the exact same triangle\n// function, so the marker never drifts out of sync with the wire it's\n// supposedly riding. One sweep leg (extreme to extreme) takes SPAN_MS; the\n// marker's lateral position is therefore always continuous and always\n// legible at a glance, at a speed (tens of px/s) nowhere near 60Hz paint.\n//\n// The dewirement arc is a separate, decoupled Poisson-ish process (mean\n// interval independent of the sweep) so it reads as a genuine rare event,\n// not a beat in the main rhythm: a brief luminance flash plus a small\n// vertical wire \"kick\" that decays back to the smooth zigzag it interrupted.\n// A `connectionQuality` prop (0-1, default 1) shortens the arc's mean\n// interval as quality degrades — severity reads via event rate, never hue.\n//\n// Pure DOM/SVG, no canvas. Wire, strip band and marker are all\n// var(--foreground) strokes/fills at full weight in both themes (the CSS var\n// itself repaints on a theme swap, no JS token re-read needed since nothing\n// here touches a canvas context). The arc flash uses an SVG drop-shadow\n// filter seeded from var(--foreground) — glows light in dark mode, glows\n// dark in light mode automatically, never introduces a colour of its own.\n// ---------------------------------------------------------------------------\n\nconst SPAN_MS = 2000; // one sweep leg (extreme -> extreme) per REAL NUMBERS\nconst ARC_MS = 180; // dewirement arc duration\nconst ARC_KICK_PX = 3; // vertical wire \"kick\" during an arc\nconst DEFAULT_MIN_ARC_MS = 18000;\nconst DEFAULT_MAX_ARC_MS = 28000;\n// degraded connections never arc faster than 4 sweep legs apart — any\n// tighter and the arc joins the sweep's own rhythm instead of punctuating it.\nconst MIN_ARC_FLOOR_MS = SPAN_MS * 4;\nconst VISIBLE_SPANS = 4; // spans visible across the container at rest\nconst BAND_FRACTION = 0.68; // strip band height, as a fraction of min(w,h)\nconst AMPLITUDE_FRACTION = 0.2; // stagger half-throw, as a fraction of min(w,h) — stays inside the band with margin even mid-arc\nconst MARKER_R = 3.5; // px, contact marker radius at rest\nconst MARKER_R_ARC = 5.5; // px, contact marker radius during an arc\nconst WIRE_WIDTH = 1.4; // px, wire stroke at rest\nconst WIRE_WIDTH_ARC = 3; // px, wire stroke during an arc — weight carries the flash in both themes\n\n/** Exact engineering zigzag: straight spans between alternating extremes.\n * Period 2 (one full extreme -> extreme -> extreme cycle), range -1..1,\n * piecewise-linear so vertices at every integer u ARE the true shape —\n * no sampling, no aliasing. */\nfunction triWave(u: number): number {\n  const m = ((u % 2) + 2) % 2; // 0..2\n  return m < 1 ? m * 2 - 1 : 3 - m * 2;\n}\n\nfunction useReducedMotion() {\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\nexport interface CatenaryContactStaggerProps {\n  /** 0-1, default 1 (perfect). Degraded connectivity shortens the arc's mean\n   * interval — severity reads via event rate, never colour. */\n  connectionQuality?: number;\n  /** floor of the randomised arc interval range at connectionQuality = 1 */\n  minArcIntervalMs?: number;\n  /** ceiling of the randomised arc interval range at connectionQuality = 1 */\n  maxArcIntervalMs?: number;\n  /** accessible label for the root status region */\n  label?: string;\n  /** extra classes merged onto the root element */\n  className?: string;\n}\n\nexport function CatenaryContactStagger({\n  connectionQuality = 1,\n  minArcIntervalMs = DEFAULT_MIN_ARC_MS,\n  maxArcIntervalMs = DEFAULT_MAX_ARC_MS,\n  label = \"Connection sync status\",\n  className = \"\",\n}: CatenaryContactStaggerProps) {\n  const reduced = useReducedMotion();\n  const rootRef = useRef<HTMLDivElement>(null);\n  const svgRef = useRef<SVGSVGElement>(null);\n  const wireRef = useRef<SVGPolylineElement>(null);\n  const bandRef = useRef<SVGRectElement>(null);\n  const markerRef = useRef<SVGCircleElement>(null);\n  const [arcing, setArcing] = useState(false);\n  const [announce, setAnnounce] = useState(\"Contact wire tracking nominal.\");\n  const quality = Math.max(0, Math.min(1, connectionQuality));\n\n  useEffect(() => {\n    const root = rootRef.current;\n    const svg = svgRef.current;\n    const wire = wireRef.current;\n    const band = bandRef.current;\n    const marker = markerRef.current;\n    if (!root || !svg || !wire || !band || !marker) return;\n\n    let disposed = false;\n    let visible = true;\n    let w = 0;\n    let h = 0;\n    let sized = false;\n    let spanWidth = 1;\n    let amplitude = 1;\n    let bandCenterY = 0;\n    let bandHeight = 0;\n    // origin of the phase clock — set once, on first successful measure, and\n    // never reset afterward. A visibility pause/resume (tab hidden, card\n    // scrolled off) must NOT jump the phase back to a t0 extreme: the wire\n    // is a continuous physical process, so elapsed time keeps accumulating\n    // against wall-clock time whether or not a frame was painted for it.\n    let originTime = 0;\n    let raf = 0;\n    let arcStart: number | null = null;\n    let arcTimer = 0;\n    let arcEndTimer = 0;\n    let arcPending = false;\n\n    const arcInterval = () => {\n      const min = Math.max(MIN_ARC_FLOOR_MS, minArcIntervalMs * quality || MIN_ARC_FLOOR_MS);\n      const max = Math.max(min + 500, maxArcIntervalMs * quality || min + 500);\n      return min + Math.random() * (max - min);\n    };\n\n    const cancelArcSchedule = () => {\n      window.clearTimeout(arcTimer);\n      arcTimer = 0;\n      arcPending = false;\n    };\n\n    const scheduleArc = () => {\n      if (disposed || reduced || arcPending) return;\n      arcPending = true;\n      arcTimer = window.setTimeout(() => {\n        if (disposed) return;\n        arcPending = false;\n        arcStart = performance.now();\n        setArcing(true);\n        setAnnounce(\"Momentary contact loss.\");\n        arcEndTimer = window.setTimeout(() => {\n          if (disposed) return;\n          arcStart = null;\n          setArcing(false);\n          setAnnounce(\"Contact wire tracking nominal.\");\n          if (visible) scheduleArc();\n        }, ARC_MS);\n      }, arcInterval());\n    };\n\n    const measure = () => {\n      const rect = root.getBoundingClientRect();\n      w = rect.width;\n      h = rect.height;\n      if (w < 2 || h < 2) {\n        sized = false;\n        return;\n      }\n      sized = true;\n      const minDim = Math.min(w, h);\n      spanWidth = w / VISIBLE_SPANS;\n      amplitude = minDim * AMPLITUDE_FRACTION;\n      bandHeight = minDim * BAND_FRACTION;\n      bandCenterY = h / 2;\n      svg.setAttribute(\"viewBox\", `0 0 ${w} ${h}`);\n      band.setAttribute(\"x\", \"0\");\n      band.setAttribute(\"y\", `${bandCenterY - bandHeight / 2}`);\n      band.setAttribute(\"width\", `${w}`);\n      band.setAttribute(\"height\", `${bandHeight}`);\n    };\n\n    // exact zigzag vertices: straight spans between alternating extremes,\n    // vertex n sits at screenX = centerX + spanWidth * (n - u), y alternates\n    // -amplitude / +amplitude by n's parity — the true engineering shape,\n    // not a sampled approximation.\n    const render = (u: number, kick: number) => {\n      const centerX = w / 2;\n      const half = VISIBLE_SPANS / 2;\n      const nMin = Math.floor(u - half) - 2;\n      const nMax = Math.ceil(u + half) + 2;\n      const pts: string[] = [];\n      for (let n = nMin; n <= nMax; n++) {\n        const x = centerX + spanWidth * (n - u);\n        const y = bandCenterY + amplitude * (n % 2 === 0 ? -1 : 1) + kick;\n        pts.push(`${x.toFixed(1)},${y.toFixed(1)}`);\n      }\n      wire.setAttribute(\"points\", pts.join(\" \"));\n      const contactY = bandCenterY + amplitude * triWave(u) + kick;\n      marker.setAttribute(\"cy\", `${contactY.toFixed(2)}`);\n      marker.setAttribute(\"cx\", `${centerX.toFixed(2)}`);\n    };\n\n    const loop = (now: number) => {\n      raf = 0;\n      if (!visible || !sized) return;\n      const elapsed = now - originTime;\n      const u = elapsed / SPAN_MS;\n      let kick = 0;\n      if (arcStart != null) {\n        const p = Math.min(1, (now - arcStart) / ARC_MS);\n        kick = ARC_KICK_PX * Math.sin(Math.PI * p);\n      }\n      render(u, kick);\n      raf = requestAnimationFrame(loop);\n    };\n\n    // measures the container and, once sized, (re)activates whatever isn't\n    // already running — the rAF loop and the arc scheduler. Safe to call\n    // repeatedly (from resize, from the font-ready settle, from IO resume):\n    // it's idempotent whenever the loop/scheduler are already live.\n    const activate = () => {\n      measure();\n      if (!sized) return; // will retry on the next resize/IO callback\n      if (originTime === 0) originTime = performance.now();\n      if (reduced) {\n        // dead-centre, maximum contact margin — the \"everything is fine\"\n        // frame, never a stagger extreme and never mid-arc.\n        render(0.5, 0);\n        return;\n      }\n      if (visible && !raf) raf = requestAnimationFrame(loop);\n      if (visible) scheduleArc();\n    };\n\n    activate();\n\n    let resizeTimer = 0;\n    const onResize = () => {\n      window.clearTimeout(resizeTimer);\n      resizeTimer = window.setTimeout(() => {\n        if (disposed) return;\n        activate();\n      }, 80);\n    };\n    const ro = new ResizeObserver(onResize);\n    ro.observe(root);\n\n    const io = new IntersectionObserver((entries) => {\n      visible = entries[0]?.isIntersecting ?? true;\n      if (visible) {\n        activate();\n      } else {\n        cancelArcSchedule(); // don't keep announcing arcs nobody can see\n      }\n    });\n    io.observe(root);\n\n    document.fonts.ready.then(() => {\n      if (!disposed) onResize();\n    });\n\n    return () => {\n      disposed = true;\n      cancelAnimationFrame(raf);\n      raf = 0;\n      cancelArcSchedule();\n      window.clearTimeout(arcEndTimer);\n      window.clearTimeout(resizeTimer);\n      ro.disconnect();\n      io.disconnect();\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [reduced, quality, minArcIntervalMs, maxArcIntervalMs]);\n\n  return (\n    <div\n      ref={rootRef}\n      role=\"group\"\n      aria-label={label}\n      className={`relative h-full w-full ${className}`}\n    >\n      <svg\n        ref={svgRef}\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        className=\"pointer-events-none block h-full w-full\"\n      >\n        <defs>\n          <filter id=\"ccs-arc-glow\" x=\"-200%\" y=\"-200%\" width=\"500%\" height=\"500%\">\n            <feDropShadow dx=\"0\" dy=\"0\" stdDeviation=\"2.4\" floodColor=\"var(--foreground)\" floodOpacity=\"0.9\" />\n          </filter>\n        </defs>\n\n        {/* strip band — fixed reference, the zigzag must stay inside it */}\n        <rect\n          ref={bandRef}\n          fill=\"none\"\n          stroke=\"var(--foreground)\"\n          strokeOpacity={0.28}\n          strokeWidth={1}\n        />\n\n        {/* contact wire — exact zigzag polyline, span-by-span alternation.\n            An arc flash reads through WEIGHT (stroke bumps up), not colour —\n            the glow filter is secondary polish, never the sole signal, so\n            it still reads against a light card. */}\n        <polyline\n          ref={wireRef}\n          fill=\"none\"\n          stroke=\"var(--foreground)\"\n          strokeWidth={arcing ? WIRE_WIDTH_ARC : WIRE_WIDTH}\n          strokeLinejoin=\"round\"\n          strokeLinecap=\"round\"\n          style={{\n            filter: arcing ? \"url(#ccs-arc-glow)\" : \"none\",\n            opacity: arcing ? 1 : 0.85,\n            transition: \"opacity 60ms linear, stroke-width 40ms ease-out\",\n          }}\n        />\n\n        {/* contact marker — the one followable thing, riding the wire's\n            lateral value at the pantograph's fixed position. Starts off the\n            visible canvas until the first measured frame writes real\n            coordinates, so there's never a stray dot at the SVG origin. */}\n        <circle\n          ref={markerRef}\n          cx={-999}\n          cy={-999}\n          r={arcing ? MARKER_R_ARC : MARKER_R}\n          fill=\"var(--foreground)\"\n          style={{\n            filter: arcing ? \"url(#ccs-arc-glow)\" : \"none\",\n            transition: \"r 80ms ease-out\",\n          }}\n        />\n      </svg>\n\n      <p role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n        {announce}\n      </p>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/catenary-contact-stagger.tsx"
    }
  ],
  "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."
  },
  "type": "registry:ui"
}