{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "marching-border",
  "title": "Marching Border",
  "description": "A marching-border component.",
  "files": [
    {
      "path": "registry/default/marching-border/marching-border.tsx",
      "content": "\"use client\";\r\n\r\nimport * as React from \"react\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\nimport \"./marching-border.css\";\r\n\r\nexport type MarchingBorderProps = React.ComponentProps<\"svg\"> & {\r\n  /** Stroke thickness in pixels. */\r\n  strokeWidth?: number;\r\n  /** Dash length as a percentage of the path's perimeter. */\r\n  dash?: number;\r\n  /** Gap length as a percentage of the path's perimeter. */\r\n  gap?: number;\r\n  /**\r\n   * Seconds per dash-cycle. One cycle = one dash + one gap traversal,\r\n   * which is the minimum seamless time-loop.\r\n   */\r\n  duration?: number;\r\n};\r\n\r\n/**\r\n * Builds the `d` for a rounded-rect path starting at the midpoint of the top\r\n * edge, so any residual subpixel seam falls on a straight side (invisible)\r\n * rather than at a corner. `inset` shifts coordinates inward by half the stroke\r\n * so the stroke sits inside the SVG box instead of clipping at the edges.\r\n */\r\nfunction buildRoundedRectPath(\r\n  width: number,\r\n  height: number,\r\n  radius: number,\r\n  inset: number,\r\n): string {\r\n  const innerW = width - 2 * inset;\r\n  const innerH = height - 2 * inset;\r\n  if (innerW <= 0 || innerH <= 0) return \"\";\r\n  const r = Math.min(radius, innerW / 2, innerH / 2);\r\n  const left = inset;\r\n  const top = inset;\r\n  const right = inset + innerW;\r\n  const bottom = inset + innerH;\r\n  // Round to 2 decimals to keep `d` compact; 0.01px is sub-visible so corner\r\n  // alignment with the parent's border-radius is unaffected.\r\n  const f = (n: number) => Math.round(n * 100) / 100;\r\n  return [\r\n    `M ${f(left + innerW / 2)} ${f(top)}`,\r\n    `L ${f(right - r)} ${f(top)}`,\r\n    `A ${f(r)} ${f(r)} 0 0 1 ${f(right)} ${f(top + r)}`,\r\n    `L ${f(right)} ${f(bottom - r)}`,\r\n    `A ${f(r)} ${f(r)} 0 0 1 ${f(right - r)} ${f(bottom)}`,\r\n    `L ${f(left + r)} ${f(bottom)}`,\r\n    `A ${f(r)} ${f(r)} 0 0 1 ${f(left)} ${f(bottom - r)}`,\r\n    `L ${f(left)} ${f(top + r)}`,\r\n    `A ${f(r)} ${f(r)} 0 0 1 ${f(left + r)} ${f(top)}`,\r\n    \"Z\",\r\n  ].join(\" \");\r\n}\r\n\r\n/**\r\n * Marching-ants dashed border, drawn as an absolutely-positioned SVG overlay\r\n * outside the wrapped element's box model so toggling it causes zero layout\r\n * shift. Parent must be positioned and should have a `rounded-*` class to\r\n * conform to. The SVG defaults to `rounded-[inherit]`, so it adopts the\r\n * parent's corner radius automatically; pass a `rounded-*` class to override.\r\n *\r\n * Seam-free loop via two techniques: (1) `pathLength` is rounded to a whole\r\n * multiple of `(dash + gap)` so the pattern tiles an integer number of times at\r\n * any size — making `dash`/`gap` perimeter percentages; (2) the path's `M` sits\r\n * mid-top-edge so residual subpixel error falls on a straight side. The shared\r\n * `@keyframes dash-march` (in `marching-border.css`) reads the one-cycle end\r\n * offset from a CSS var, so any `(dash, gap)` loops without per-cycle keyframes.\r\n *\r\n * Under `prefers-reduced-motion` only the animation is suppressed — the dashed\r\n * border still renders as the primary signal of pending/staged state.\r\n */\r\nfunction MarchingBorder({\r\n  strokeWidth = 2,\r\n  dash = 1,\r\n  gap = 0.75,\r\n  duration = 0.75,\r\n  ref,\r\n  className,\r\n  ...rest\r\n}: MarchingBorderProps) {\r\n  const svgRef = React.useRef<SVGSVGElement | null>(null);\r\n  const pathRef = React.useRef<SVGPathElement | null>(null);\r\n  const cycle = dash + gap;\r\n  const pathLength = Math.max(cycle, Math.round(100 / cycle) * cycle);\r\n\r\n  const setSvgRef = React.useCallback(\r\n    (node: SVGSVGElement | null) => {\r\n      svgRef.current = node;\r\n      if (typeof ref === \"function\") {\r\n        ref(node);\r\n      } else if (ref) {\r\n        (ref as React.RefObject<SVGSVGElement | null>).current = node;\r\n      }\r\n    },\r\n    [ref],\r\n  );\r\n\r\n  // useLayoutEffect so the first paint already has the path's `d`; otherwise\r\n  // the wait for ResizeObserver's initial callback flashes on staged-state entry.\r\n  React.useLayoutEffect(() => {\r\n    const svg = svgRef.current;\r\n    const path = pathRef.current;\r\n    if (!svg || !path) return;\r\n\r\n    const inset = strokeWidth / 2;\r\n\r\n    const apply = () => {\r\n      const { width, height } = svg.getBoundingClientRect();\r\n      if (width <= 0 || height <= 0) return;\r\n\r\n      // Read the SVG's own computed border-radius. With the default\r\n      // `rounded-[inherit]` this resolves to the parent's radius; a `rounded-*`\r\n      // class overrides it. Re-read each resize to track theme / dynamic updates.\r\n      const detected = parseFloat(getComputedStyle(svg).borderTopLeftRadius);\r\n      const outerRadius = Number.isFinite(detected) ? detected : 0;\r\n      // pathRadius = outerRadius − inset: the stroke is centered on the path,\r\n      // so its outer edge sits at (pathRadius + inset) = the parent's\r\n      // border-radius. Without the subtraction the corners wouldn't align.\r\n      const pathRadius = Math.max(0, outerRadius - inset);\r\n\r\n      path.setAttribute(\r\n        \"d\",\r\n        buildRoundedRectPath(width, height, pathRadius, inset),\r\n      );\r\n    };\r\n\r\n    apply();\r\n\r\n    const observer = new ResizeObserver(apply);\r\n    observer.observe(svg);\r\n    return () => observer.disconnect();\r\n  }, [strokeWidth]);\r\n\r\n  return (\r\n    <svg\r\n      ref={setSvgRef}\r\n      aria-hidden\r\n      data-slot=\"marching-border\"\r\n      {...rest}\r\n      className={cn(\r\n        \"pointer-events-none absolute inset-0 size-full rounded-[inherit]\",\r\n        className,\r\n      )}\r\n    >\r\n      <path\r\n        ref={pathRef}\r\n        fill=\"none\"\r\n        stroke=\"currentColor\"\r\n        strokeWidth={strokeWidth}\r\n        strokeDasharray={`${dash} ${gap}`}\r\n        strokeLinecap=\"round\"\r\n        pathLength={pathLength}\r\n        // `!` needed to defeat the inline animation style below.\r\n        className=\"motion-reduce:animate-none!\"\r\n        style={\r\n          {\r\n            \"--march-offset\": -cycle,\r\n            animation: `dash-march ${duration}s linear infinite`,\r\n          } as React.CSSProperties\r\n        }\r\n      />\r\n    </svg>\r\n  );\r\n}\r\n\r\nexport { MarchingBorder };\r\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/marching-border/marching-border.tsx"
    },
    {
      "path": "registry/default/marching-border/marching-border.css",
      "content": "/* -------------------------------------------------------------------------------------------------\r\n * MarchingBorder dash animation.\r\n *\r\n * One static keyframe shared across every instance. The end offset\r\n * (one full dash + gap cycle) comes from the `--march-offset` CSS\r\n * custom property set per-instance on the <path>, so any\r\n * (dash, gap) combination loops seamlessly without generating\r\n * per-instance keyframes.\r\n * -------------------------------------------------------------------------------------------------*/\r\n\r\n@keyframes dash-march {\r\n  to {\r\n    stroke-dashoffset: var(--march-offset);\r\n  }\r\n}\r\n",
      "type": "registry:file",
      "target": "components/ui/cubby-ui/marching-border/marching-border.css"
    }
  ],
  "type": "registry:ui"
}