{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "border-electric-arc",
  "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.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/loud/border-electric-arc/component.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useRef } from \"react\";\n\n// ---------------------------------------------------------------------------\n// SparkGap — an electric border for a CTA. A rounded-rect outline crackles\n// like a live wire: feTurbulence -> feDisplacementMap jitters a 1.5px\n// --foreground stroke over a blurred --ns-accent glow copy, with random\n// micro-arcs sparking off the corners every 1-3s. Near the cursor the border\n// OPENS a small gap at the pointer's nearest-edge projection and a short\n// bright arc jumps across it, tracking the pointer along the perimeter.\n// Pressing the button is a full discharge: a bright flash floods the stroke,\n// then the border runs \"spent\" (calmer jitter, dim glow) for 2s before\n// normal idle crackle resumes.\n//\n// Dash-gap math is done in RAW SVG user units against the path's real\n// measured perimeter (getTotalLength on an explicit rounded-rect <path>).\n// Deliberately NO pathLength attribute and NO vector-effect anywhere:\n// combining pathLength with non-scaling-stroke makes Chromium compute the\n// dash in screen space and the gap lands wrong while every attribute reads\n// correct. This SVG is never CSS-scaled (width/height attrs = CSS pixels,\n// no viewBox scaling), so raw-unit dasharray math is exact.\n//\n// Hot path is all direct DOM writes via refs — feTurbulence seed/frequency\n// rewrites on a throttled 150ms interval (not per frame), dasharray/offset\n// and the arc path on pointermove, opacity/stroke-width via short CSS\n// transitions for flash/settle. No React state anywhere in the loop.\n//\n// A11y: the CTA is a real <button> with its accessible name intact; every\n// effect layer is an aria-hidden, pointer-events-none SVG sibling so nothing\n// ever intercepts clicks, hover or focus meant for the button. Idle work\n// (jitter + micro-arcs) pauses entirely off-viewport via IntersectionObserver\n// and while the document is hidden. prefers-reduced-motion: steady solid\n// border (no displacement filter, no gap, no arcs), glow only as a plain\n// CSS box-shadow on the button's :hover / :focus-visible.\n// ---------------------------------------------------------------------------\n\nexport interface SparkGapProps {\n  /** Accessible label / visible text of the CTA button. */\n  label?: string;\n  /** called when the button is clicked */\n  onClick?: () => void;\n  /** extra classes merged onto the rendered root element */\n  className?: string;\n}\n\nconst OUT = 5; // border sits this far outside the button box\nconst MARGIN = 14; // extra svg room for glow + corner arcs\nconst EXT = OUT + MARGIN;\nconst RADIUS = 10;\nconst STROKE_W = 1.5;\nconst GAP_PX = 16; // gap opened under the cursor\nconst NEAR_PX = 30; // pointer-to-border distance that opens the gap\nconst JITTER_MS = 150; // throttled turbulence rewrite cadence\nconst SPARK_MIN_MS = 1000;\nconst SPARK_MAX_MS = 3000;\nconst FLASH_MS = 110; // discharge flash duration\nconst SPENT_MS = 2000; // calm \"spent\" window after a discharge\nconst SCALE_IDLE = 2.8; // displacement scale, live wire\nconst SCALE_SPENT = 1.1; // displacement scale, spent wire\n\ninterface Layout {\n  x: number;\n  y: number;\n  w: number;\n  h: number;\n  r: number;\n  P: number; // measured perimeter (getTotalLength), raw user units\n  k: number; // measured / analytic correction factor\n}\n\nfunction roundedRectPath(x: number, y: number, w: number, h: number, r: number): string {\n  return (\n    `M ${x + r} ${y} L ${x + w - r} ${y} A ${r} ${r} 0 0 1 ${x + w} ${y + r}` +\n    ` L ${x + w} ${y + h - r} A ${r} ${r} 0 0 1 ${x + w - r} ${y + h}` +\n    ` L ${x + r} ${y + h} A ${r} ${r} 0 0 1 ${x} ${y + h - r}` +\n    ` L ${x} ${y + r} A ${r} ${r} 0 0 1 ${x + r} ${y} Z`\n  );\n}\n\n// Nearest point on the perimeter, expressed as analytic arc-length from the\n// path start (top-left, clockwise) plus the pointer's distance to it. The\n// four straight edges are checked and the projection clamped to each; corner\n// arcs are approximated as belonging to the nearer adjacent edge (clamping\n// to a segment end IS that approximation — no exact arc math needed).\nfunction projectToPerimeter(\n  lx: number,\n  ly: number,\n  L: Layout\n): { s: number; dist: number } {\n  const { x, y, w, h, r } = L;\n  const q = (Math.PI * r) / 2; // one corner arc, analytic\n  const edges = [\n    { ax: x + r, ay: y, bx: x + w - r, by: y, start: 0 },\n    { ax: x + w, ay: y + r, bx: x + w, by: y + h - r, start: w - 2 * r + q },\n    {\n      ax: x + w - r,\n      ay: y + h,\n      bx: x + r,\n      by: y + h,\n      start: w - 2 * r + q + (h - 2 * r) + q,\n    },\n    {\n      ax: x,\n      ay: y + h - r,\n      bx: x,\n      by: y + r,\n      start: 2 * (w - 2 * r) + (h - 2 * r) + 3 * q,\n    },\n  ];\n  let bestS = 0;\n  let bestDist = Infinity;\n  for (const e of edges) {\n    const abx = e.bx - e.ax;\n    const aby = e.by - e.ay;\n    const len2 = abx * abx + aby * aby;\n    const t =\n      len2 > 0\n        ? Math.max(0, Math.min(1, ((lx - e.ax) * abx + (ly - e.ay) * aby) / len2))\n        : 0;\n    const px = e.ax + abx * t;\n    const py = e.ay + aby * t;\n    const d = Math.hypot(lx - px, ly - py);\n    if (d < bestDist) {\n      bestDist = d;\n      bestS = e.start + t * Math.sqrt(len2);\n    }\n  }\n  return { s: bestS, dist: bestDist };\n}\n\nexport function SparkGap({\n  label = \"Get Started\",\n  onClick,\n  className = \"\",\n}: SparkGapProps) {\n  const uid = useId().replace(/:/g, \"\");\n  const filterId = `ns-sg-jitter-${uid}`;\n\n  const wrapRef = useRef<HTMLDivElement>(null);\n  const svgRef = useRef<SVGSVGElement>(null);\n  const glowRef = useRef<SVGPathElement>(null);\n  const coreRef = useRef<SVGPathElement>(null);\n  const arcRef = useRef<SVGPathElement>(null);\n  const sparkARef = useRef<SVGPathElement>(null);\n  const sparkBRef = useRef<SVGPathElement>(null);\n  const turbRef = useRef<SVGFETurbulenceElement>(null);\n  const dispRef = useRef<SVGFEDisplacementMapElement>(null);\n  const engineRef = useRef<{ discharge: () => void } | null>(null);\n\n  useEffect(() => {\n    const wrap = wrapRef.current;\n    const svg = svgRef.current;\n    const glow = glowRef.current;\n    const core = coreRef.current;\n    const arc = arcRef.current;\n    const sparkA = sparkARef.current;\n    const sparkB = sparkBRef.current;\n    const turb = turbRef.current;\n    const disp = dispRef.current;\n    if (!wrap || !svg || !glow || !core || !arc || !sparkA || !sparkB || !turb || !disp)\n      return;\n\n    const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    let reduced = mq.matches;\n\n    // -- hot-path state: locals + refs only, never React state --------------\n    let layout: Layout | null = null;\n    let visible = true;\n    let mode: \"idle\" | \"spent\" = \"idle\";\n    let near = false; // pointer within NEAR_PX of the border\n    let gapCur = 0;\n    let gapTarget = 0;\n    let sPath = 0; // gap center, measured units along the path\n    let seed = 7;\n    let skipTick = false;\n    let sparkFork = false;\n    let tweenRaf = 0;\n    let tweenLast = 0;\n    let jitterTimer: number | undefined;\n    let sparkTimer: number | undefined;\n    let flashT1: number | undefined;\n    let flashT2: number | undefined;\n\n    const applyGlow = () => {\n      if (mode === \"spent\") {\n        glow.style.opacity = \"0.12\";\n        return;\n      }\n      glow.style.opacity = near ? \"0.55\" : \"0.3\";\n    };\n\n    // Dash window: pattern is [dash P-g, gap g]; offsetting by P - s - g/2\n    // centers the gap at path distance s. All raw measured units — no\n    // pathLength, no vector-effect (see header comment for why).\n    const applyGap = () => {\n      if (!layout) return;\n      const { P } = layout;\n      if (gapCur < 0.6) {\n        core.removeAttribute(\"stroke-dasharray\");\n        core.removeAttribute(\"stroke-dashoffset\");\n        glow.removeAttribute(\"stroke-dasharray\");\n        glow.removeAttribute(\"stroke-dashoffset\");\n        arc.style.opacity = \"0\";\n        return;\n      }\n      const g = gapCur;\n      const dash = `${(P - g).toFixed(2)} ${g.toFixed(2)}`;\n      const off = (P - sPath - g / 2).toFixed(2);\n      core.setAttribute(\"stroke-dasharray\", dash);\n      core.setAttribute(\"stroke-dashoffset\", off);\n      glow.setAttribute(\"stroke-dasharray\", dash);\n      glow.setAttribute(\"stroke-dashoffset\", off);\n      // the arc that jumps the gap: jagged 4-point path between the gap lips\n      const a = core.getPointAtLength((((sPath - g / 2) % P) + P) % P);\n      const b = core.getPointAtLength((sPath + g / 2) % P);\n      const dx = b.x - a.x;\n      const dy = b.y - a.y;\n      const len = Math.hypot(dx, dy) || 1;\n      const nx = -dy / len; // unit normal for the zigzag\n      const ny = dx / len;\n      const j1 = (Math.random() - 0.5) * 5;\n      const j2 = (Math.random() - 0.5) * 5;\n      arc.setAttribute(\n        \"d\",\n        `M ${a.x.toFixed(1)} ${a.y.toFixed(1)}` +\n          ` L ${(a.x + dx * 0.33 + nx * j1).toFixed(1)} ${(a.y + dy * 0.33 + ny * j1).toFixed(1)}` +\n          ` L ${(a.x + dx * 0.66 + nx * j2).toFixed(1)} ${(a.y + dy * 0.66 + ny * j2).toFixed(1)}` +\n          ` L ${b.x.toFixed(1)} ${b.y.toFixed(1)}`\n      );\n      arc.style.opacity = Math.min(1, g / GAP_PX).toFixed(2);\n    };\n\n    // short rAF tween only while the gap is opening/closing — not a\n    // permanent loop; pointer position writes happen in the move handler\n    const tweenStep = (t: number) => {\n      tweenRaf = 0;\n      const dt = tweenLast ? Math.min(0.05, (t - tweenLast) / 1000) : 1 / 60;\n      tweenLast = t;\n      gapCur += (gapTarget - gapCur) * Math.min(1, dt * 16);\n      if (Math.abs(gapTarget - gapCur) < 0.4) gapCur = gapTarget;\n      applyGap();\n      if (gapCur !== gapTarget) tweenRaf = requestAnimationFrame(tweenStep);\n    };\n    const startTween = () => {\n      if (!tweenRaf && gapCur !== gapTarget) {\n        tweenLast = 0;\n        tweenRaf = requestAnimationFrame(tweenStep);\n      }\n    };\n\n    const onMove = (e: MouseEvent) => {\n      if (reduced || !layout) return;\n      const rect = wrap.getBoundingClientRect();\n      if (rect.width < 4) return;\n      // svg's top-left sits at (rect.left - EXT, rect.top - EXT)\n      const lx = e.clientX - rect.left + EXT;\n      const ly = e.clientY - rect.top + EXT;\n      const proj = projectToPerimeter(lx, ly, layout);\n      const nowNear = proj.dist < NEAR_PX;\n      if (nowNear !== near) {\n        near = nowNear;\n        applyGlow();\n      }\n      sPath = proj.s * layout.k;\n      gapTarget = nowNear ? GAP_PX : 0;\n      if (gapCur > 0.6) applyGap(); // gap follows the cursor instantly\n      startTween();\n    };\n\n    // -- throttled crackle: rewrite turbulence attrs every 150ms, not/frame --\n    const jitterTick = () => {\n      if (!visible || document.hidden) return;\n      skipTick = !skipTick;\n      if (mode === \"spent\" && skipTick) return; // spent wire ticks half rate\n      seed = (seed + 1) % 997;\n      turb.setAttribute(\"seed\", String(seed));\n      turb.setAttribute(\n        \"baseFrequency\",\n        `0.02 ${(0.05 + Math.random() * 0.04).toFixed(3)}`\n      );\n      disp.setAttribute(\"scale\", String(mode === \"spent\" ? SCALE_SPENT : SCALE_IDLE));\n    };\n    const startJitter = () => {\n      if (jitterTimer === undefined && !reduced)\n        jitterTimer = window.setInterval(jitterTick, JITTER_MS);\n    };\n    const stopJitter = () => {\n      if (jitterTimer !== undefined) {\n        window.clearInterval(jitterTimer);\n        jitterTimer = undefined;\n      }\n    };\n\n    // -- micro-arcs: recursive setTimeout re-rolling 1-3s each round ---------\n    const flashPath = (el: SVGPathElement, d: string) => {\n      el.setAttribute(\"d\", d);\n      el.style.transition = \"none\";\n      el.style.opacity = \"0.95\";\n      requestAnimationFrame(() => {\n        el.style.transition = \"opacity 220ms ease-out\";\n        el.style.opacity = \"0\";\n      });\n    };\n    const jaggedBranch = (cx: number, cy: number, nx: number, ny: number): string => {\n      let px = cx;\n      let py = cy;\n      let d = `M ${px.toFixed(1)} ${py.toFixed(1)}`;\n      for (let i = 0; i < 3; i++) {\n        const step = 3 + Math.random() * 4;\n        const jit = (Math.random() - 0.5) * 5;\n        px += nx * step - ny * jit;\n        py += ny * step + nx * jit;\n        d += ` L ${px.toFixed(1)} ${py.toFixed(1)}`;\n      }\n      return d;\n    };\n    const fireSpark = () => {\n      if (!layout || !visible || document.hidden || reduced || mode === \"spent\") return;\n      const { x, y, w, h, r } = layout;\n      const inset = r * 0.3;\n      const corners = [\n        { cx: x + inset, cy: y + inset, dx: -1, dy: -1 },\n        { cx: x + w - inset, cy: y + inset, dx: 1, dy: -1 },\n        { cx: x + w - inset, cy: y + h - inset, dx: 1, dy: 1 },\n        { cx: x + inset, cy: y + h - inset, dx: -1, dy: 1 },\n      ];\n      const c = corners[Math.floor(Math.random() * corners.length)];\n      if (!c) return;\n      const nx = c.dx / Math.SQRT2;\n      const ny = c.dy / Math.SQRT2;\n      flashPath(sparkA, jaggedBranch(c.cx, c.cy, nx, ny));\n      sparkFork = Math.random() < 0.4;\n      if (sparkFork) {\n        // second branch forks off at a steeper angle from the same corner\n        const rot = (Math.random() < 0.5 ? 1 : -1) * 0.7;\n        const fx = nx * Math.cos(rot) - ny * Math.sin(rot);\n        const fy = nx * Math.sin(rot) + ny * Math.cos(rot);\n        flashPath(sparkB, jaggedBranch(c.cx, c.cy, fx, fy));\n      }\n    };\n    const scheduleSpark = () => {\n      sparkTimer = window.setTimeout(() => {\n        fireSpark();\n        scheduleSpark();\n      }, SPARK_MIN_MS + Math.random() * (SPARK_MAX_MS - SPARK_MIN_MS));\n    };\n    const startSparks = () => {\n      if (sparkTimer === undefined && !reduced) scheduleSpark();\n    };\n    const stopSparks = () => {\n      if (sparkTimer !== undefined) {\n        window.clearTimeout(sparkTimer);\n        sparkTimer = undefined;\n      }\n    };\n\n    // -- discharge: flash flood, then 2s spent, then idle resumes ------------\n    const discharge = () => {\n      if (reduced) return;\n      window.clearTimeout(flashT1);\n      window.clearTimeout(flashT2);\n      mode = \"idle\";\n      const fast =\n        \"opacity 80ms ease-out, stroke-width 80ms ease-out, filter 80ms ease-out\";\n      core.style.transition = fast;\n      core.style.opacity = \"1\";\n      core.style.strokeWidth = \"2.6\";\n      glow.style.transition = fast;\n      glow.style.opacity = \"1\";\n      glow.style.filter = \"blur(6px)\";\n      flashT1 = window.setTimeout(() => {\n        mode = \"spent\";\n        disp.setAttribute(\"scale\", String(SCALE_SPENT));\n        const settle =\n          \"opacity 260ms ease-in, stroke-width 260ms ease-in, filter 260ms ease-in\";\n        core.style.transition = settle;\n        core.style.opacity = \"0.65\";\n        core.style.strokeWidth = String(STROKE_W);\n        glow.style.transition = settle;\n        glow.style.filter = \"blur(3.5px)\";\n        applyGlow();\n        flashT2 = window.setTimeout(() => {\n          mode = \"idle\";\n          disp.setAttribute(\"scale\", String(SCALE_IDLE));\n          core.style.transition = \"opacity 300ms ease-out\";\n          core.style.opacity = \"1\";\n          glow.style.transition = \"opacity 300ms ease-out\";\n          applyGlow();\n        }, SPENT_MS);\n      }, FLASH_MS);\n    };\n    engineRef.current = { discharge };\n\n    // -- sizing: real pixel geometry, measured perimeter ---------------------\n    const resize = () => {\n      const rect = wrap.getBoundingClientRect();\n      if (rect.width < 4 || rect.height < 4) return;\n      const bw = rect.width + OUT * 2;\n      const bh = rect.height + OUT * 2;\n      const sw = bw + MARGIN * 2;\n      const sh = bh + MARGIN * 2;\n      svg.setAttribute(\"width\", String(sw));\n      svg.setAttribute(\"height\", String(sh));\n      const r = Math.min(RADIUS, bw / 2, bh / 2);\n      const d = roundedRectPath(MARGIN, MARGIN, bw, bh, r);\n      glow.setAttribute(\"d\", d);\n      core.setAttribute(\"d\", d);\n      const P = core.getTotalLength();\n      const analytic = 2 * (bw - 2 * r) + 2 * (bh - 2 * r) + 2 * Math.PI * r;\n      layout = { x: MARGIN, y: MARGIN, w: bw, h: bh, r, P, k: P / analytic };\n      applyGap();\n    };\n\n    const applyReduced = () => {\n      if (reduced) {\n        stopJitter();\n        stopSparks();\n        window.clearTimeout(flashT1);\n        window.clearTimeout(flashT2);\n        if (tweenRaf) cancelAnimationFrame(tweenRaf);\n        tweenRaf = 0;\n        gapCur = gapTarget = 0;\n        mode = \"idle\";\n        near = false;\n        applyGap();\n        // steady solid border: crisp stroke, no displacement filter at all\n        core.removeAttribute(\"filter\");\n        core.style.transition = \"none\";\n        core.style.opacity = \"1\";\n        core.style.strokeWidth = String(STROKE_W);\n        arc.style.opacity = \"0\";\n        sparkA.style.opacity = \"0\";\n        sparkB.style.opacity = \"0\";\n        // glow layer off; hover/focus glow comes from CSS box-shadow instead\n      } else {\n        core.setAttribute(\"filter\", `url(#${filterId})`);\n        applyGlow();\n        if (visible) {\n          startJitter();\n          startSparks();\n        }\n      }\n    };\n    const onMq = () => {\n      reduced = mq.matches;\n      applyReduced();\n    };\n    mq.addEventListener(\"change\", onMq);\n\n    // -- observers -----------------------------------------------------------\n    const ro = new ResizeObserver(resize);\n    ro.observe(wrap);\n    resize();\n\n    const io = new IntersectionObserver((entries) => {\n      visible = entries[0]?.isIntersecting ?? true;\n      if (visible && !reduced) {\n        startJitter();\n        startSparks();\n      } else {\n        stopJitter();\n        stopSparks();\n      }\n    });\n    io.observe(wrap);\n\n    const onVis = () => {\n      // interval ticks already no-op while hidden; kick a tick on return\n      if (!document.hidden && !reduced && visible) jitterTick();\n    };\n    document.addEventListener(\"visibilitychange\", onVis);\n\n    window.addEventListener(\"pointermove\", onMove);\n    window.addEventListener(\"mousemove\", onMove); // synthetic MouseEvents too\n\n    applyReduced();\n    if (!reduced && visible) {\n      startJitter();\n      startSparks();\n    }\n\n    return () => {\n      stopJitter();\n      stopSparks();\n      window.clearTimeout(flashT1);\n      window.clearTimeout(flashT2);\n      if (tweenRaf) cancelAnimationFrame(tweenRaf);\n      mq.removeEventListener(\"change\", onMq);\n      ro.disconnect();\n      io.disconnect();\n      document.removeEventListener(\"visibilitychange\", onVis);\n      window.removeEventListener(\"pointermove\", onMove);\n      window.removeEventListener(\"mousemove\", onMove);\n      engineRef.current = null;\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  return (\n    <div ref={wrapRef} className={`ns-sg relative inline-flex ${className}`}>\n      <style>{CSS}</style>\n      {/* Effect overlay: aria-hidden + pointer-events-none sibling, extends\n          EXT px past the wrapper so glow and corner arcs never clip. */}\n      <svg\n        ref={svgRef}\n        width={0}\n        height={0}\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        className=\"pointer-events-none absolute\"\n        style={{ left: -EXT, top: -EXT, overflow: \"visible\" }}\n      >\n        <defs>\n          <filter id={filterId} x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\">\n            <feTurbulence\n              ref={turbRef}\n              type=\"fractalNoise\"\n              baseFrequency=\"0.02 0.06\"\n              numOctaves={2}\n              seed={7}\n              result=\"n\"\n            />\n            <feDisplacementMap\n              ref={dispRef}\n              in=\"SourceGraphic\"\n              in2=\"n\"\n              scale={SCALE_IDLE}\n              xChannelSelector=\"R\"\n              yChannelSelector=\"G\"\n            />\n          </filter>\n        </defs>\n        <path\n          ref={glowRef}\n          className=\"ns-sg-glow\"\n          fill=\"none\"\n          stroke=\"var(--ns-accent)\"\n          strokeWidth={5}\n          strokeLinecap=\"round\"\n          style={{\n            opacity: 0.3,\n            filter: \"blur(3.5px)\",\n            transition: \"opacity 200ms ease-out\",\n          }}\n        />\n        <path\n          ref={coreRef}\n          fill=\"none\"\n          stroke=\"var(--foreground)\"\n          strokeWidth={STROKE_W}\n          strokeLinecap=\"round\"\n          filter={`url(#${filterId})`}\n        />\n        <path\n          ref={arcRef}\n          className=\"ns-sg-arc\"\n          fill=\"none\"\n          stroke=\"var(--foreground)\"\n          strokeWidth={1.6}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          style={{\n            opacity: 0,\n            filter:\n              \"drop-shadow(0 0 4px var(--ns-accent)) drop-shadow(0 0 1.5px var(--ns-accent))\",\n          }}\n        />\n        <path\n          ref={sparkARef}\n          className=\"ns-sg-spark\"\n          fill=\"none\"\n          stroke=\"var(--ns-accent)\"\n          strokeWidth={1.3}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          style={{ opacity: 0, filter: \"drop-shadow(0 0 3px var(--ns-accent))\" }}\n        />\n        <path\n          ref={sparkBRef}\n          className=\"ns-sg-spark\"\n          fill=\"none\"\n          stroke=\"var(--ns-accent)\"\n          strokeWidth={1.1}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          style={{ opacity: 0, filter: \"drop-shadow(0 0 3px var(--ns-accent))\" }}\n        />\n      </svg>\n\n      <button\n        type=\"button\"\n        onPointerDown={() => engineRef.current?.discharge()}\n        onClick={(e) => {\n          // keyboard activation (Enter/Space) arrives as click with detail 0\n          if (e.detail === 0) engineRef.current?.discharge();\n          onClick?.();\n        }}\n        className=\"ns-sg-btn relative z-[1] rounded-[6px] bg-background px-6 py-2.5 text-sm font-medium text-foreground transition-colors hover:bg-foreground/[0.08] active:bg-foreground/[0.14] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ns-accent\"\n      >\n        {label}\n      </button>\n    </div>\n  );\n}\n\nconst CSS = `\n@media (prefers-reduced-motion: reduce){\n  .ns-sg-glow,.ns-sg-arc,.ns-sg-spark{opacity:0 !important;transition:none !important;}\n  .ns-sg-btn:hover,.ns-sg-btn:focus-visible{\n    box-shadow:0 0 14px 0 color-mix(in srgb, var(--ns-accent) 45%, transparent);\n  }\n}\n`;\n",
      "type": "registry:ui",
      "target": "components/ui/border-electric-arc.tsx"
    }
  ],
  "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."
  },
  "type": "registry:ui"
}