{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flash-entrain",
  "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.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/core/flash-entrain/component.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState, type CSSProperties } from \"react\";\n\n// ---------------------------------------------------------------------------\n// FlashEntrain — an ambient field of pulse-coupled firefly oscillators\n// (Mirollo-Strogatz coupling), not a scripted animation. Every firefly holds\n// one phase in a shared Float32Array, climbing linearly from 0 toward a\n// firing threshold of 1 at its own natural rate. The instant a firefly\n// reaches 1 it fires: its phase resets to 0 and it nudges the phase of each\n// of its k=12 STATIC nearest neighbours forward by a fixed coupling strength\n// epsilon. A nudged neighbour that itself crosses 1 fires immediately in the\n// same tick, cascading — this is the whole mechanism. There is no target\n// synchrony state written anywhere; unison, travelling bands and partial-sync\n// clusters are all the same cascade rule evaluated on a random initial phase\n// field, and they are what makes the field's very long-run arc (twinkle,\n// then order, then a local relapse near a deaf firefly, then re-entrainment,\n// forever) genuinely emergent rather than authored.\n//\n// COUPLING STRENGTH is the one governing scalar. epsilon = 0.03 is the\n// widest value that still shows travelling bands: above ~0.08 the field\n// snaps to full unison in a few seconds and the \"order\" act eats the whole\n// clip; below ~0.02 it never locks within a normal page dwell and only ever\n// twinkles. The neighbour graph is k=12 nearest by planar distance, computed\n// ONCE at mount and never rebuilt — coupling only ever happens along that\n// fixed graph, which is what makes synchrony a spatially local, propagating\n// event (a travelling wave, a cluster negotiating a merge) instead of a\n// global average snapping over instantly.\n//\n// DEAF FIREFLIES (2% of the population, a fixed random subset) are the\n// honest source of relapse: they never RECEIVE a neighbour's nudge (their\n// own phase only ever advances at their own natural rate), they run a\n// slightly faster natural period than the rest of the field, and they still\n// SEND a pulse to their own neighbours every time they fire. A deaf firefly\n// therefore drifts out of whatever local consensus has formed around it and,\n// on its own schedule, reaches back in and perturbs its neighbourhood — that\n// perturbation is what pulls a locally synced patch apart and restarts local\n// entrainment, differently every time, with no scripted reset anywhere.\n//\n// RENDER SPLIT is the whole performance story: 350 fixed-position DOM dots\n// are pre-mounted once at --ns-muted and never move. JS owns exactly one\n// number per firefly (phase) and, on a fire event, toggles which of two\n// identical-effect CSS classes is present on that one dot — the 200ms\n// opacity/scale/color flash envelope itself is a `@keyframes` animation that\n// then runs entirely on the compositor. JS never touches opacity, transform\n// or color directly; it only ever adds/removes a class name.\n//\n// ACCESSIBILITY / PHOTOSENSITIVITY: two hard visual caps live in the render\n// path, independent of prefers-reduced-motion. (1) A per-firefly refractory\n// period of ~910ms after each flash means no single dot can visually flash\n// faster than ~1.1Hz, even mid-cascade. (2) A global burst gate: when a\n// cascade's eligible-flash count exceeds a small fraction of the field (a\n// \"unison\" event), the WHOLE burst is suppressed unless at least 900ms has\n// passed since the last one — phase state still updates normally underneath,\n// only the visual flash of that particular burst is skipped, so unison never\n// reads as flicker faster than roughly once per 900ms. That is well under\n// the three-per-second photosensitivity guideline even without reduced\n// motion engaged. With prefers-reduced-motion: reduce, no rAF loop ever\n// starts: the field renders once, statically, at its seeded resting phases,\n// and one spatial cluster (a BFS walk over the same neighbour graph used for\n// coupling) is marked at +10% opacity as the frozen stand-in for a\n// synchrony band — the arc is described, not demonstrated, with nothing\n// live.\n//\n// Distinct from loader-pendulum-sync: that component is a small mechanical\n// loader whose handful of pendulums converge on an AUTHORED schedule (their\n// CSS animation-durations are picked so the row is guaranteed to land back\n// in phase every periodMs, by integer-oscillation arithmetic with zero\n// per-frame JS) and stay converged once finished. FlashEntrain has no\n// schedule and no finish: 350 independent phases pulse-couple over a static\n// neighbour graph, synchrony is earned frame by frame, and a handful of deaf\n// individuals mean it never stays settled — the story is chaos-to-order-to\n// relapse, forever, not converge-and-stop.\n// ---------------------------------------------------------------------------\n\nconst TICK_MS = 1000 / 30; // fixed 30Hz physics step\nconst REFRACTORY_MS = 910; // per-firefly visual cooldown -> caps flash rate just under 1.1Hz\nconst BURST_GAP_MS = 900; // global unison-burst gate\nconst DOT_MIN_PX = 3;\nconst DOT_MAX_PX = 5;\nconst BAND_OPACITY_BOOST = 0.1; // reduced-motion frozen band differential\n// Every firefly's own uncoupled cadence stays comfortably below the 1.1Hz\n// visual cap: refractory should only ever bind during a coupling-accelerated\n// cascade, never as routine background muting of normal ticking.\nconst MIN_PERIOD_MS = REFRACTORY_MS + 60;\n// Concavity of the Mirollo-Strogatz charge/rise function f(phase). This is\n// what a naive \"phase += epsilon on every nudge\" coupling is missing: with a\n// LINEAR phase, equal nudges never compress the gap between two out-of-phase\n// fireflies, and a headless sweep of that naive version never rose above an\n// order parameter of ~0.1 in 90s at any epsilon from 0.02-0.25 — it just\n// never locks. With f concave (f(phase)=ln(1+(e^b-1)*phase)/b, b=3 here,\n// verified by the same sweep), an equal charge-nudge buys a LARGER phase\n// jump for a firefly that is further from firing than for one already close\n// to it, which is exactly the absorption mechanism that drives real\n// convergence — chaos visibly resolving into partial and then near-full\n// synchrony over tens of seconds, continuously reopened by the deaf\n// fireflies, at coupling=0.03 exactly as specified.\nconst CONCAVITY_B = 3;\n\nfunction mulberry32(seed: number): () => number {\n  let a = seed >>> 0 || 1;\n  return () => {\n    a = (a + 0x6d2b79f5) | 0;\n    let t = Math.imul(a ^ (a >>> 15), 1 | a);\n    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\nconst CSS = `\n.ns-fe-field{ position:relative; overflow:hidden; }\n.ns-fe-dot{\n  position:absolute;\n  left:0; top:0;\n  border-radius:9999px;\n  background:var(--ns-muted);\n  will-change:opacity,transform;\n}\n.ns-fe-flash-a, .ns-fe-flash-b{\n  animation-duration:200ms;\n  animation-timing-function:cubic-bezier(.22,.7,.32,1);\n  animation-fill-mode:none;\n}\n.ns-fe-flash-a{ animation-name:ns-fe-flash-a; }\n.ns-fe-flash-b{ animation-name:ns-fe-flash-b; }\n@keyframes ns-fe-flash-a{\n  0%{ opacity:var(--ns-fe-rest-o); transform:scale(1); background-color:var(--ns-muted); }\n  18%{ opacity:1; transform:scale(1.9); background-color:var(--foreground); }\n  100%{ opacity:var(--ns-fe-rest-o); transform:scale(1); background-color:var(--ns-muted); }\n}\n@keyframes ns-fe-flash-b{\n  0%{ opacity:var(--ns-fe-rest-o); transform:scale(1); background-color:var(--ns-muted); }\n  18%{ opacity:1; transform:scale(1.9); background-color:var(--foreground); }\n  100%{ opacity:var(--ns-fe-rest-o); transform:scale(1); background-color:var(--ns-muted); }\n}\n@media (prefers-reduced-motion: reduce){\n  .ns-fe-flash-a, .ns-fe-flash-b{ animation:none; }\n}\n`;\n\ninterface Firefly {\n  xFrac: number;\n  yFrac: number;\n  sizePx: number;\n  deaf: boolean;\n}\n\nexport interface FlashEntrainProps {\n  /** Population size. */\n  count?: number;\n  /** Mirollo-Strogatz coupling strength per firing (governing scalar). Above ~0.08 the field snaps to unison in seconds; below ~0.02 it never locks. */\n  coupling?: number;\n  /** Static nearest-neighbour graph size per firefly. */\n  neighbors?: number;\n  /** Fraction of the population that is deaf (never receives coupling), the source of relapse. */\n  deafFraction?: number;\n  /** Mean natural period, ms, before per-firefly jitter. */\n  periodMs?: number;\n  /** extra classes merged onto the rendered root element */\n  className?: string;\n}\n\nexport function FlashEntrain({\n  count = 350,\n  coupling = 0.03,\n  neighbors = 12,\n  deafFraction = 0.02,\n  periodMs = 1250,\n  className = \"\",\n}: FlashEntrainProps) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const elRefs = useRef<(HTMLDivElement | null)[]>([]);\n  const [reduced, setReduced] = useState(false);\n\n  useEffect(() => {\n    const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    setReduced(mq.matches);\n    const onChange = (e: MediaQueryListEvent) => setReduced(e.matches);\n    mq.addEventListener(\"change\", onChange);\n    return () => mq.removeEventListener(\"change\", onChange);\n  }, []);\n\n  const n = Math.max(24, Math.round(count));\n  const k = Math.max(3, Math.min(n - 1, Math.round(neighbors)));\n\n  // -- one seeded field: positions, sizes, deaf flags, neighbour graph -----\n  // Deterministic per (n, k, deafFraction) so re-mounts (and the SSR pass)\n  // agree with the client without needing to defer render.\n  const fieldRef = useRef<{\n    fireflies: Firefly[];\n    neighborIdx: Int32Array;\n    restOpacity: number;\n    bandMembers: Uint8Array;\n    n: number;\n    k: number;\n    deafFraction: number;\n  } | null>(null);\n  if (\n    !fieldRef.current ||\n    fieldRef.current.n !== n ||\n    fieldRef.current.k !== k ||\n    fieldRef.current.deafFraction !== deafFraction\n  ) {\n    const rand = mulberry32(0x5eed0001 ^ n ^ (k << 8));\n    const fireflies: Firefly[] = Array.from({ length: n }, () => ({\n      xFrac: rand(),\n      yFrac: rand(),\n      sizePx: DOT_MIN_PX + rand() * (DOT_MAX_PX - DOT_MIN_PX),\n      deaf: false,\n    }));\n    // Floored at 1: the deaf minority is the only source of relapse in this\n    // model (see header comment), so deafFraction=0 would silently produce a\n    // field that locks into unison and then just sits there forever — a\n    // floor of one keeps that story honest even at a tiny population.\n    const deafCount = Math.max(1, Math.round(n * deafFraction));\n    // pick a random subset deaf, without replacement\n    const order = Array.from({ length: n }, (_, i) => i);\n    for (let i = order.length - 1; i > 0; i--) {\n      const j = Math.floor(rand() * (i + 1));\n      const tmp = order[i]!;\n      order[i] = order[j]!;\n      order[j] = tmp;\n    }\n    for (let i = 0; i < deafCount; i++) fireflies[order[i]!]!.deaf = true;\n\n    // k-NN by planar (fractional) distance, brute force — n is small (<=~700\n    // in practice), computed once and never rebuilt.\n    const neighborIdx = new Int32Array(n * k);\n    const candIdx = new Int32Array(k);\n    const candD2 = new Float32Array(k);\n    for (let i = 0; i < n; i++) {\n      let filled = 0;\n      let worst = -1;\n      let worstD2 = -Infinity;\n      const xi = fireflies[i]!.xFrac;\n      const yi = fireflies[i]!.yFrac;\n      for (let j = 0; j < n; j++) {\n        if (j === i) continue;\n        const dx = fireflies[j]!.xFrac - xi;\n        const dy = fireflies[j]!.yFrac - yi;\n        const d2 = dx * dx + dy * dy;\n        if (filled < k) {\n          candIdx[filled] = j;\n          candD2[filled] = d2;\n          if (d2 > worstD2) {\n            worstD2 = d2;\n            worst = filled;\n          }\n          filled++;\n        } else if (d2 < worstD2) {\n          candIdx[worst] = j;\n          candD2[worst] = d2;\n          worstD2 = -Infinity;\n          for (let m = 0; m < k; m++) {\n            const dm = candD2[m]!;\n            if (dm > worstD2) {\n              worstD2 = dm;\n              worst = m;\n            }\n          }\n        }\n      }\n      for (let m = 0; m < k; m++) neighborIdx[i * k + m] = candIdx[m] ?? i;\n    }\n\n    // Reduced-motion frozen band: BFS over the same neighbour graph from one\n    // seeded anchor, collecting ~15% of the population as \"already synced\".\n    const bandMembers = new Uint8Array(n);\n    const bandTarget = Math.max(1, Math.round(n * 0.15));\n    const anchor = Math.floor(rand() * n);\n    const queue: number[] = [anchor];\n    bandMembers[anchor] = 1;\n    let bandCount = 1;\n    let qi = 0;\n    while (qi < queue.length && bandCount < bandTarget) {\n      const cur = queue[qi++]!;\n      for (let m = 0; m < k; m++) {\n        const nb = neighborIdx[cur * k + m]!;\n        if (bandMembers[nb]) continue;\n        bandMembers[nb] = 1;\n        bandCount++;\n        queue.push(nb);\n        if (bandCount >= bandTarget) break;\n      }\n    }\n\n    fieldRef.current = {\n      fireflies,\n      neighborIdx,\n      restOpacity: 0.55,\n      bandMembers,\n      n,\n      k,\n      deafFraction,\n    };\n  }\n  const field = fieldRef.current;\n\n  useEffect(() => {\n    if (reduced) return; // static frame only, no loop, no listeners\n    const root = rootRef.current;\n    if (!root) return;\n\n    const { fireflies, neighborIdx } = field;\n\n    const phase = new Float32Array(n);\n    const rate = new Float32Array(n); // phase units per ms\n    const isDeaf = new Uint8Array(n);\n    // Float64, not Float32: holds performance.now()-scale timestamps, which\n    // lose the millisecond precision the refractory comparison needs once a\n    // long-lived tab's clock runs past ~4.6 hours in a Float32.\n    const nextFlashOkAt = new Float64Array(n);\n    const flip = new Uint8Array(n); // alternates which flash class to use\n    const firedThisTick = new Uint8Array(n);\n\n    const rand = mulberry32(0x5eed0002 ^ n);\n    for (let i = 0; i < n; i++) {\n      const ff = fireflies[i]!;\n      isDeaf[i] = ff.deaf ? 1 : 0;\n      phase[i] = rand(); // start scattered, not synced\n      const jitter = 0.85 + rand() * 0.3; // +-15% natural-period spread\n      // deaf fireflies run ~14% faster; MIN_PERIOD_MS floors every period\n      // (deaf or not) above the refractory so ordinary ticking never trips\n      // the visual rate cap on its own — only a coupling cascade does.\n      const period = Math.max(\n        MIN_PERIOD_MS,\n        ff.deaf ? periodMs * jitter * 0.86 : periodMs * jitter\n      );\n      rate[i] = TICK_MS / period;\n    }\n\n    const eps = coupling;\n    const eb = Math.exp(CONCAVITY_B);\n    // phase -> charge (concave) and charge -> phase (its inverse). A firing\n    // pulse nudges a neighbour's CHARGE by epsilon, then the neighbour's\n    // phase is recovered from that new charge — see CONCAVITY_B above.\n    const toCharge = (p: number) => Math.log(1 + (eb - 1) * p) / CONCAVITY_B;\n    const toPhase = (x: number) => (Math.exp(CONCAVITY_B * x) - 1) / (eb - 1);\n    const fireQueue: number[] = [];\n    const eligible: number[] = [];\n    let lastBurstAt = -Infinity;\n    const burstThreshold = Math.max(6, Math.round(n * 0.06));\n\n    const applyFlash = (i: number, nowMs: number) => {\n      nextFlashOkAt[i] = nowMs + REFRACTORY_MS;\n      const el = elRefs.current[i];\n      if (!el) return;\n      el.classList.remove(\"ns-fe-flash-a\", \"ns-fe-flash-b\");\n      // Alternating between two classes with distinct animation-name values\n      // (not the same class re-added) is what forces the keyframe to\n      // restart: re-adding an identical animation-name is a no-op per spec\n      // even after the previous run finished, no reflow trick needed.\n      el.classList.add(flip[i] ? \"ns-fe-flash-b\" : \"ns-fe-flash-a\");\n      flip[i] = flip[i] ? 0 : 1;\n    };\n\n    const tick = (nowMs: number) => {\n      for (let i = 0; i < n; i++) phase[i] += rate[i]!;\n\n      firedThisTick.fill(0);\n      fireQueue.length = 0;\n      eligible.length = 0;\n      for (let i = 0; i < n; i++) {\n        if (phase[i]! >= 1 && !firedThisTick[i]) {\n          firedThisTick[i] = 1;\n          fireQueue.push(i);\n        }\n      }\n\n      let qi = 0;\n      while (qi < fireQueue.length) {\n        const i = fireQueue[qi++]!;\n        phase[i] = 0;\n        if (nowMs >= nextFlashOkAt[i]!) eligible.push(i);\n\n        const base = i * k;\n        for (let m = 0; m < k; m++) {\n          const j = neighborIdx[base + m]!;\n          if (isDeaf[j]) continue; // deaf: never receives coupling\n          // Mirollo-Strogatz coupling proper: nudge the neighbour's CHARGE\n          // (the concave transform of its phase) by epsilon, not its phase\n          // directly — see CONCAVITY_B above for why that distinction is\n          // what makes convergence actually happen.\n          const chargeJ = toCharge(Math.min(1, phase[j]!)) + eps;\n          if (chargeJ >= 1) {\n            if (!firedThisTick[j]) {\n              firedThisTick[j] = 1;\n              fireQueue.push(j); // popped next: reset to 0, coupled onward\n            }\n          } else {\n            phase[j] = toPhase(chargeJ);\n          }\n        }\n      }\n\n      if (eligible.length === 0) return;\n      if (eligible.length > burstThreshold) {\n        if (nowMs - lastBurstAt < BURST_GAP_MS) return; // gated: physics ran, visuals held back\n        lastBurstAt = nowMs;\n      }\n      for (let e = 0; e < eligible.length; e++) applyFlash(eligible[e]!, nowMs);\n    };\n\n    let raf = 0;\n    let lastFrame = 0;\n    let acc = 0;\n\n    const loop = (now: number) => {\n      const dt = lastFrame ? Math.min(200, now - lastFrame) : TICK_MS;\n      lastFrame = now;\n      acc += dt;\n      while (acc >= TICK_MS) {\n        tick(now);\n        acc -= TICK_MS;\n      }\n      raf = requestAnimationFrame(loop);\n    };\n\n    const onVisibility = () => {\n      if (document.hidden) {\n        if (raf) cancelAnimationFrame(raf);\n        raf = 0;\n      } else if (!raf) {\n        lastFrame = 0;\n        acc = 0;\n        raf = requestAnimationFrame(loop);\n      }\n    };\n\n    raf = requestAnimationFrame(loop);\n    document.addEventListener(\"visibilitychange\", onVisibility);\n\n    return () => {\n      if (raf) cancelAnimationFrame(raf);\n      document.removeEventListener(\"visibilitychange\", onVisibility);\n    };\n  }, [reduced, n, k, coupling, periodMs, field]);\n\n  return (\n    <div ref={rootRef} className={`ns-fe-field block h-full w-full ${className}`}>\n      <style>{CSS}</style>\n      <div aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0\">\n        {field.fireflies.map((ff, i) => {\n          const bandOpacity = reduced && field.bandMembers[i]\n            ? field.restOpacity + BAND_OPACITY_BOOST\n            : field.restOpacity;\n          return (\n            <div\n              key={i}\n              ref={(el) => {\n                elRefs.current[i] = el;\n              }}\n              className=\"ns-fe-dot\"\n              style={{\n                left: `${ff.xFrac * 100}%`,\n                top: `${ff.yFrac * 100}%`,\n                width: ff.sizePx,\n                height: ff.sizePx,\n                marginLeft: -ff.sizePx / 2,\n                marginTop: -ff.sizePx / 2,\n                opacity: bandOpacity,\n                \"--ns-fe-rest-o\": field.restOpacity,\n              } as CSSProperties}\n            />\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/flash-entrain.tsx"
    }
  ],
  "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."
  },
  "type": "registry:ui"
}