{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tracing-beam",
  "type": "registry:component",
  "title": "Tracing beam",
  "description": "A vertical beam beside long content that grows as you scroll, with a glowing tip and a node that lights for each entry.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/premium/tracing-beam.tsx",
      "type": "registry:ui",
      "target": "components/ui/tracing-beam.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { motion, useReducedMotion, useScroll, useSpring, useTransform, type MotionValue } from \"motion/react\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * A vertical beam beside long content that grows as you scroll, with a glowing tip and a node that lights for each entry.\n * The beam is an SVG path driven by scroll progress through the section and\n * smoothed with a spring, so it glides rather than jumps. Each direct child of\n * the content gets a node on the track that fills the moment the beam passes\n * it. Reduced motion drops the spring and follows the scroll exactly.\n */\ntype TracingBeamItem = {\n  /** Release tag shown in mono. */\n  version?: string\n  /** When it shipped. */\n  date?: string\n  title: string\n  body: string\n}\n\ntype TracingBeamProps = {\n  /** Entries to trace. Each one gets a node on the beam. */\n  items?: TracingBeamItem[]\n  /** Your own content in place of the entries. Each direct child gets a node. */\n  children?: React.ReactNode\n  /** Beam colour. Any CSS colour, including var(--token). Defaults to the accent. */\n  color?: string\n  /** Width of the rail the beam runs in, in pixels. */\n  rail?: number\n  className?: string\n}\n\nconst DEFAULT_ITEMS: TracingBeamItem[] = [\n  {\n    version: \"v3.2\",\n    date: \"14 Oct 2026\",\n    title: \"Invoices that close themselves\",\n    body: \"Lumen now matches incoming payments to open invoices by amount, reference and payer, then marks them paid without a click. Partial payments stay open with the balance shown, and anything it is not sure about lands in a review queue for Maya and the finance team. Northwind cleared 212 invoices on launch day with four left to check.\",\n  },\n  {\n    version: \"v3.1\",\n    date: \"22 Sep 2026\",\n    title: \"Runway forecast\",\n    body: \"The cash page gained a forecast line that blends recurring bills, payroll and the invoices most likely to be paid this month. Drag the hiring slider to see how three new engineers move the runway date. Theo Okafor from the Growth team asked for scenarios to be saved and shared; they now live beside the plan.\",\n  },\n  {\n    version: \"v3.0\",\n    date: \"02 Aug 2026\",\n    title: \"Workspaces and roles\",\n    body: \"Every company gets its own workspace with owners, approvers and viewers. Approvals route to the right person by amount, so Priya can sign off on anything under 5,000 while larger spends wait for Sam. Audit history records who changed what and when, exportable for your accountant in one file.\",\n  },\n]\n\nfunction Node({ top, x, y, color }: { top: number; x: number; y: MotionValue<number>; color: string }) {\n  const lit = useTransform(y, (v) => (v >= top ? 1 : 0))\n  return (\n    <g transform={`translate(${x} ${top})`}>\n      <circle r={5} fill=\"var(--bg)\" stroke=\"var(--border-strong)\" strokeWidth={1.5} />\n      <motion.circle r={5} fill={color} style={{ opacity: lit }} />\n      <motion.circle r={1.8} fill=\"var(--on-accent)\" style={{ opacity: lit }} />\n    </g>\n  )\n}\n\nfunction TracingBeam({ items = DEFAULT_ITEMS, children, color = \"var(--accent)\", rail = 40, className }: TracingBeamProps) {\n  const reduce = useReducedMotion()\n  const ref = React.useRef<HTMLElement>(null)\n  const contentRef = React.useRef<HTMLDivElement>(null)\n  const id = React.useId().replace(/[^a-zA-Z0-9]/g, \"\")\n  const [h, setH] = React.useState(0)\n  const [tops, setTops] = React.useState<number[]>([])\n  const x = Math.max(16, rail) / 2\n  const inset = 6\n\n  // Measure on the client only, so the server renders an empty rail and hydration matches.\n  React.useEffect(() => {\n    const el = ref.current\n    const content = contentRef.current\n    if (!el || !content) return\n    const measure = () => {\n      const box = el.getBoundingClientRect()\n      const next = Array.from(content.children).map((c) => Math.round(c.getBoundingClientRect().top - box.top) + 10)\n      setH(Math.round(box.height))\n      setTops((prev) => (prev.length === next.length && prev.every((v, i) => v === next[i]) ? prev : next))\n    }\n    measure()\n    const ro = new ResizeObserver(measure)\n    ro.observe(el)\n    for (const c of Array.from(content.children)) ro.observe(c)\n    return () => ro.disconnect()\n  }, [items, children])\n\n  const { scrollYProgress } = useScroll({ target: ref, offset: [\"start 0.72\", \"end 0.55\"] })\n  const smooth = useSpring(scrollYProgress, { stiffness: 400, damping: 40, restDelta: 0.0005 })\n  const progress = reduce ? scrollYProgress : smooth\n  const y = useTransform(progress, [0, 1], [inset, Math.max(inset, h - inset)])\n  const hotLength = useTransform(progress, (p) => Math.min(0.12, p))\n  const hotOffset = useTransform(progress, (p) => Math.max(0, p - 0.12))\n  const d = `M ${x} ${inset} V ${Math.max(inset, h - inset)}`\n\n  return (\n    <section ref={ref} data-slot=\"tracing-beam\" className={cn(\"relative w-full max-w-2xl\", className)}>\n      <svg\n        aria-hidden\n        data-slot=\"tracing-beam-rail\"\n        className=\"pointer-events-none absolute top-0 left-0 z-0 overflow-visible\"\n        width={Math.max(16, rail)}\n        height={h}\n        viewBox={`0 0 ${Math.max(16, rail)} ${h}`}\n      >\n        <defs>\n          <linearGradient id={`${id}-beam`} gradientUnits=\"userSpaceOnUse\" x1=\"0\" y1=\"0\" x2=\"0\" y2={h || 1}>\n            <stop offset=\"0\" stopColor={color} stopOpacity=\"0.4\" />\n            <stop offset=\"1\" stopColor={color} />\n          </linearGradient>\n          <radialGradient id={`${id}-glow`}>\n            <stop offset=\"0\" stopColor={color} stopOpacity=\"0.55\" />\n            <stop offset=\"0.5\" stopColor={color} stopOpacity=\"0.18\" />\n            <stop offset=\"1\" stopColor={color} stopOpacity=\"0\" />\n          </radialGradient>\n        </defs>\n        {h > 0 && (\n          <>\n            <path d={d} fill=\"none\" stroke=\"var(--border-strong)\" strokeWidth={1.5} strokeLinecap=\"round\" />\n            <motion.path d={d} fill=\"none\" stroke={`url(#${id}-beam)`} strokeWidth={2.5} style={{ pathLength: progress }} />\n            <motion.path d={d} fill=\"none\" stroke=\"var(--accent-2)\" strokeWidth={3} style={{ pathLength: hotLength, pathOffset: hotOffset }} />\n            {tops.map((top, i) => (\n              <Node key={i} top={top} x={x} y={y} color={color} />\n            ))}\n            <motion.g style={{ y, x }}>\n              <circle r={18} fill={`url(#${id}-glow)`} />\n              <circle r={4} fill={color} />\n              <circle r={1.6} fill=\"var(--on-accent)\" />\n            </motion.g>\n          </>\n        )}\n      </svg>\n      <div\n        ref={contentRef}\n        className=\"relative z-10 flex flex-col gap-10\"\n        style={{ paddingLeft: Math.max(16, rail) + 8 }}\n      >\n        {children ??\n          items.map((it, i) => (\n            <article key={i} data-slot=\"tracing-beam-entry\" className=\"flex flex-col gap-2\">\n              <div className=\"flex flex-wrap items-center gap-2 text-xs text-fg-subtle\">\n                {it.version && (\n                  <span className=\"rounded-md border border-border bg-surface px-1.5 py-0.5 font-mono text-2xs text-fg-muted shadow-xs\">\n                    {it.version}\n                  </span>\n                )}\n                {it.date && <time>{it.date}</time>}\n              </div>\n              <h3 className=\"text-xl font-medium tracking-[-0.014em] text-balance text-fg\">{it.title}</h3>\n              <p className=\"max-w-[60ch] text-sm leading-[1.6] text-pretty text-fg-muted\">{it.body}</p>\n            </article>\n          ))}\n      </div>\n    </section>\n  )\n}\n\nexport { TracingBeam }\nexport type { TracingBeamProps, TracingBeamItem }\n"
    }
  ]
}
