{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"animated-beam","type":"registry:ui","title":"AnimatedBeam","description":"Fibre-optic connector between two elements: a light packet with a dispersing chromatic tail runs along a glass fibre and blooms behind the destination on arrival; theme-aware, seeded and still under reduced motion.","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["effects"],"registryDependencies":["https://fancy-ui.rama.app/r/internals.json","https://fancy-ui.rama.app/r/internals-dom.json","https://fancy-ui.rama.app/r/internals-motion.json","https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/animated-beam/AnimatedBeam.tsx","content":"\"use client\";\n\nimport { useEffect, useState, type CSSProperties } from \"react\";\nimport { cn } from \"../../utils\";\nimport { useFancyId } from \"../../internals/use-id\";\nimport { useIsomorphicLayoutEffect } from \"../../internals/dom/ssr\";\nimport { useEventCallback } from \"../../internals/dom/use-event-callback\";\nimport { useReducedMotion } from \"../../internals/motion/media-query\";\nimport \"./animated-beam.css\";\n\n/**\n * A target element for the beam. The Svelte side receives the live elements\n * (`bind:this` values); in React the same prop also accepts a ref object,\n * since `ref.current` is how a React consumer holds an element.\n */\nexport type AnimatedBeamTarget = HTMLElement | { readonly current: HTMLElement | null };\n\nexport interface AnimatedBeamProps {\n\tclassName?: string;\n\tcontainerRef: AnimatedBeamTarget | null;\n\tfromRef: AnimatedBeamTarget | null;\n\ttoRef: AnimatedBeamTarget | null;\n\t/** Vertical bend of the fibre in px (the quadratic control point sits this far above the start). */\n\tcurvature?: number;\n\t/** Send the light from `toRef` back to `fromRef` (the bloom then lands on `fromRef`). */\n\treverse?: boolean;\n\t/** Fibre colour. Unset = theme-aware glass (dark on light, pale on dark). */\n\tpathColor?: string;\n\t/** Width of the fibre core and of the light packet, in px. */\n\tpathWidth?: number;\n\t/** Opacity of the fibre. Unset = 1 for the theme-aware glass, 0.2 when `pathColor` is set. */\n\tpathOpacity?: number;\n\t/** Colour of the packet's leading edge (its white-hot head is this colour mixed with white). */\n\tgradientStartColor?: string;\n\t/** Colour of the far end of the dispersing tail, and of the arrival bloom. */\n\tgradientStopColor?: string;\n\t/** Seconds before the first packet leaves. */\n\tdelay?: number;\n\t/** Seconds per packet cycle. Unset = derived from `seed` (between 4 and 7). */\n\tduration?: number;\n\tstartXOffset?: number;\n\tstartYOffset?: number;\n\tendXOffset?: number;\n\tendYOffset?: number;\n\t/** Packets in flight at once, spaced evenly over one cycle. */\n\tpulses?: number;\n\t/** Length of the packet's tail as a fraction of the fibre (0.05–0.9). */\n\ttail?: number;\n\t/** Strength of the soft glow under the packet and the arrival bloom (0–1). */\n\tglow?: number;\n\t/** Seed for the default `duration`, so server and browser agree. */\n\tseed?: number;\n}\n\n/** Small seedable PRNG. Same seed → same sequence, on the server and in the browser. */\nfunction mulberry32(seed: number): () => number {\n\tlet a = seed >>> 0;\n\treturn () => {\n\t\ta = (a + 0x6d2b79f5) >>> 0;\n\t\tlet t = a;\n\t\tt = Math.imul(t ^ (t >>> 15), t | 1);\n\t\tt ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\n/** The default cycle length for a seed: somewhere between 4 and 7 seconds. */\nexport function beamDuration(seed = 1): number {\n\treturn mulberry32(seed)() * 3 + 4;\n}\n\n/** Every flow path is normalised to this length, so dash maths is in fractions of the fibre. */\nconst PATH_LENGTH = 1000;\n/** Share of the cycle the head spends travelling; the rest drains the tail into the node. */\nexport const ARRIVAL = 0.78;\n\nexport interface PacketLayer {\n\tkey: \"stop\" | \"mid\" | \"start\" | \"head\";\n\tcolor: string;\n\t/** Dash length, in PATH_LENGTH units. */\n\tlen: number;\n\t/** How far this layer has fallen behind the head by the moment of arrival. */\n\tlag: number;\n\topacity: number;\n\t/** Stroke width as a multiple of `pathWidth`. */\n\twidth: number;\n\t/** The white-hot core: on light surfaces it keeps more of its colour so it never vanishes. */\n\tcore?: boolean;\n}\n\n/**\n * The packet: a white-hot head followed by three chromatic sub-pulses,\n * each longer, dimmer and further behind than the one before, so the tail\n * spreads out as it travels — light dispersing through glass. Listed bottom\n * to top (the order they are painted).\n *\n * Each chromatic band is drawn as three stacked dashes sharing one leading\n * edge (full, two thirds, one third of its length), so its brightness ramps\n * up toward the head instead of ending in a hard block.\n */\nexport function packetLayers(tail: number, start: string, stop: string): PacketLayer[] {\n\tconst t = Math.min(0.9, Math.max(0.05, Number.isFinite(tail) ? tail : 0.35)) * PATH_LENGTH;\n\tconst spread = t * 0.07;\n\tconst mid = `color-mix(in oklab, ${start} 50%, ${stop})`;\n\tconst hot = `color-mix(in oklab, ${start} 45%, white)`;\n\tconst head = Math.max(12, t * 0.1);\n\tconst bands: Array<[PacketLayer[\"key\"], string, number, number, number]> = [\n\t\t[\"stop\", stop, t, spread * 3, 0.26],\n\t\t[\"mid\", mid, t * 0.66, spread * 2, 0.34],\n\t\t[\"start\", start, t * 0.38, spread, 0.46],\n\t];\n\tconst layers: PacketLayer[] = [];\n\tfor (const [key, color, len, lag, opacity] of bands) {\n\t\tfor (const share of [1, 2 / 3, 1 / 3]) {\n\t\t\tlayers.push({ key, color, len: len * share, lag, opacity, width: 1 });\n\t\t}\n\t}\n\tlayers.push({ key: \"head\", color: start, len: head * 1.7, lag: 0, opacity: 1, width: 1.6 });\n\tlayers.push({ key: \"head\", color: hot, len: head, lag: 0, opacity: 1, width: 0.7, core: true });\n\treturn layers;\n}\n\n/** The blurred copies under the packet: one per band plus a wide head. */\nexport function glowLayers(tail: number, start: string, stop: string): PacketLayer[] {\n\tconst all = packetLayers(tail, start, stop);\n\tconst bands = all.filter((l, i) => l.key !== \"head\" && i % 3 === 0);\n\t// Always present: packetLayers ends with the head sheath and the head core.\n\tconst head = all[all.length - 2] as PacketLayer;\n\treturn [\n\t\t...bands.map((l, i) => ({\n\t\t\t...l,\n\t\t\topacity: Math.min(1, l.opacity * (1.5 + i * 0.4)),\n\t\t\twidth: 2.5,\n\t\t})),\n\t\t{ ...head, len: head.len * 1.6, opacity: 1, width: 3.5 },\n\t];\n}\n\n/** Dash offsets for the three keyframes: hidden before the start, arrival, fully drained. */\nfunction dashVars(layer: PacketLayer): Record<string, string> {\n\tconst o0 = layer.len + 4;\n\tconst o1 = layer.len - (PATH_LENGTH - layer.lag);\n\tconst o2 = -(PATH_LENGTH + 4);\n\treturn {\n\t\t\"--_len\": layer.len.toFixed(1),\n\t\t\"--_o0\": o0.toFixed(1),\n\t\t\"--_o1\": o1.toFixed(1),\n\t\t\"--_o2\": o2.toFixed(1),\n\t};\n}\n\nfunction resolveTarget(target: AnimatedBeamTarget | null | undefined): HTMLElement | null {\n\tif (!target) return null;\n\treturn target instanceof HTMLElement ? target : target.current;\n}\n\ninterface BeamGeometry {\n\tpathD: string;\n\tflowD: string;\n\tends: { x1: number; y1: number; x2: number; y2: number };\n\tlandingRadius: number;\n\twidth: number;\n\theight: number;\n}\n\nconst EMPTY_GEOMETRY: BeamGeometry = {\n\tpathD: \"\",\n\tflowD: \"\",\n\tends: { x1: 0, y1: 0, x2: 0, y2: 0 },\n\tlandingRadius: 24,\n\twidth: 0,\n\theight: 0,\n};\n\nexport function AnimatedBeam({\n\tclassName = \"\",\n\tcontainerRef,\n\tfromRef,\n\ttoRef,\n\tcurvature = 0,\n\treverse = false,\n\tpathColor,\n\tpathWidth = 2,\n\tpathOpacity,\n\tgradientStartColor = \"#FFAA40\",\n\tgradientStopColor = \"#9C40FF\",\n\tdelay = 0,\n\tduration,\n\tstartXOffset = 0,\n\tstartYOffset = 0,\n\tendXOffset = 0,\n\tendYOffset = 0,\n\tpulses = 1,\n\ttail = 0.35,\n\tglow = 0.6,\n\tseed = 1,\n}: AnimatedBeamProps) {\n\t// Stable across server render and hydration — the counterpart of `$props.id()`.\n\tconst uid = useFancyId(\"beam\");\n\tconst id = uid; // the gradient keeps the bare id\n\tconst glowId = `${uid}-glow`;\n\tconst bloomId = `${uid}-bloom`;\n\n\tconst reduced = useReducedMotion();\n\n\tconst cycle =\n\t\tNumber.isFinite(duration) && (duration as number) > 0\n\t\t\t? (duration as number)\n\t\t\t: beamDuration(seed);\n\tconst packetCount = Math.max(1, Math.min(12, Math.floor(Number.isFinite(pulses) ? pulses : 1)));\n\tconst packets = Array.from(\n\t\t{ length: packetCount },\n\t\t(_, i) => +(delay + (i * cycle) / packetCount).toFixed(3)\n\t);\n\tconst layers = packetLayers(tail, gradientStartColor, gradientStopColor);\n\tconst glows = glowLayers(tail, gradientStartColor, gradientStopColor);\n\tconst glowAmount = Math.min(1, Math.max(0, Number.isFinite(glow) ? glow : 0.6));\n\tconst midColor = `color-mix(in oklab, ${gradientStartColor} 50%, ${gradientStopColor})`;\n\tconst hotColor = `color-mix(in oklab, ${gradientStartColor} 45%, white)`;\n\n\t// Fibre: theme-aware glass by default; an explicit pathColor keeps the old faint-line behaviour.\n\tconst fibreOpacity = pathOpacity ?? (pathColor ? 0.2 : 1);\n\tconst fibreStyle: CSSProperties | undefined = pathColor ? { stroke: pathColor } : undefined;\n\n\tconst [geo, setGeo] = useState<BeamGeometry>(EMPTY_GEOMETRY);\n\tconst { pathD, flowD, ends, landingRadius } = geo;\n\n\t/** Where the light lands — the far end of the flow. */\n\tconst landing = reverse ? { x: ends.x1, y: ends.y1 } : { x: ends.x2, y: ends.y2 };\n\tconst launch = reverse ? { x: ends.x2, y: ends.y2 } : { x: ends.x1, y: ends.y1 };\n\n\t// The path calculation reads the current render's props, exactly like the\n\t// Svelte closure. `useEventCallback` keeps the identity stable for the life\n\t// of the component while publishing the closure from an insertion effect, so\n\t// the long-lived ResizeObserver callback never goes stale and a render React\n\t// throws away never installs itself.\n\tconst updatePath = useEventCallback(() => {\n\t\tconst container = resolveTarget(containerRef);\n\t\tconst from = resolveTarget(fromRef);\n\t\tconst to = resolveTarget(toRef);\n\t\tif (!(container && from && to)) return;\n\t\tconst containerRect = container.getBoundingClientRect();\n\t\tconst rectA = from.getBoundingClientRect();\n\t\tconst rectB = to.getBoundingClientRect();\n\n\t\tconst startX = rectA.left - containerRect.left + rectA.width / 2 + startXOffset;\n\t\tconst startY = rectA.top - containerRect.top + rectA.height / 2 + startYOffset;\n\t\tconst endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset;\n\t\tconst endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset;\n\n\t\t// The dash-driven packet follows the path itself, so horizontal,\n\t\t// vertical and diagonal fibres all travel the same way.\n\t\tconst cx = (startX + endX) / 2;\n\t\tconst cy = startY - curvature;\n\t\tconst d = `M ${startX},${startY} Q ${cx},${cy} ${endX},${endY}`;\n\t\tconst target = reverse ? rectA : rectB;\n\n\t\tconst next: BeamGeometry = {\n\t\t\tpathD: d,\n\t\t\tflowD: reverse ? `M ${endX},${endY} Q ${cx},${cy} ${startX},${startY}` : d,\n\t\t\tends: { x1: startX, y1: startY, x2: endX, y2: endY },\n\t\t\tlandingRadius: Math.max(8, Math.max(target.width, target.height) / 2),\n\t\t\twidth: containerRect.width,\n\t\t\theight: containerRect.height,\n\t\t};\n\t\t// Both effects below may measure in the same commit; an unchanged\n\t\t// geometry keeps the previous object, so it does not re-render.\n\t\tsetGeo((prev) =>\n\t\t\tprev.pathD === next.pathD &&\n\t\t\tprev.flowD === next.flowD &&\n\t\t\tprev.landingRadius === next.landingRadius &&\n\t\t\tprev.width === next.width &&\n\t\t\tprev.height === next.height\n\t\t\t\t? prev\n\t\t\t\t: next\n\t\t);\n\t});\n\n\t// Setup ResizeObserver — the counterpart of the Svelte container effect,\n\t// re-run only when the container target itself changes. Passive, not\n\t// layout: when the beam sits inside the container and receives ref\n\t// objects, React attaches the container's ref AFTER this child's layout\n\t// effects, so `containerRef.current` is still null in the layout phase and\n\t// a layout-only arm would never draw. Every ref is attached by the time\n\t// passive effects run. `updatePath` is identity-stable, so it never re-runs\n\t// this on its own.\n\tuseEffect(() => {\n\t\tconst container = resolveTarget(containerRef);\n\t\tif (!container) return;\n\n\t\tconst resizeObserver = new ResizeObserver(() => {\n\t\t\tupdatePath();\n\t\t});\n\t\tresizeObserver.observe(container);\n\n\t\t// Catch-up measure for the ref-object case above (otherwise a no-op:\n\t\t// an unchanged geometry keeps the previous state object).\n\t\tupdatePath();\n\n\t\treturn () => {\n\t\t\tresizeObserver.disconnect();\n\t\t};\n\t}, [containerRef, updatePath]);\n\n\t// First trace, and a re-trace when the endpoints or the geometry props\n\t// change (the source's `$effect` tracks the three targets through\n\t// `updatePath`). Layout phase, not passive: until the measurement lands the\n\t// svg is 0x0 with an empty `d`, so a post-paint first measure shows one\n\t// frame with no beam.\n\tuseIsomorphicLayoutEffect(() => {\n\t\tupdatePath();\n\t}, [\n\t\tcontainerRef,\n\t\tfromRef,\n\t\ttoRef,\n\t\tcurvature,\n\t\treverse,\n\t\tstartXOffset,\n\t\tstartYOffset,\n\t\tendXOffset,\n\t\tendYOffset,\n\t\tupdatePath,\n\t]);\n\n\treturn (\n\t\t<svg\n\t\t\tfill=\"none\"\n\t\t\taria-hidden=\"true\"\n\t\t\twidth={geo.width}\n\t\t\theight={geo.height}\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\tclassName={cn(\n\t\t\t\t\"animated-beam pointer-events-none absolute top-0 left-0 transform-gpu stroke-2\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tviewBox={`0 0 ${geo.width} ${geo.height}`}\n\t\t\tdata-duration={cycle.toFixed(3)}\n\t\t\tstyle={{ \"--_dur\": `${cycle.toFixed(3)}s` } as CSSProperties}\n\t\t>\n\t\t\t<defs>\n\t\t\t\t<linearGradient\n\t\t\t\t\tid={id}\n\t\t\t\t\tgradientUnits=\"userSpaceOnUse\"\n\t\t\t\t\tx1={launch.x}\n\t\t\t\t\ty1={launch.y}\n\t\t\t\t\tx2={landing.x}\n\t\t\t\t\ty2={landing.y}\n\t\t\t\t>\n\t\t\t\t\t<stop offset=\"0%\" style={{ stopColor: hotColor }} stopOpacity=\"0.9\" />\n\t\t\t\t\t<stop offset=\"30%\" style={{ stopColor: gradientStartColor }} />\n\t\t\t\t\t<stop offset=\"65%\" style={{ stopColor: midColor }} />\n\t\t\t\t\t<stop offset=\"100%\" style={{ stopColor: gradientStopColor }} />\n\t\t\t\t</linearGradient>\n\t\t\t\t<filter\n\t\t\t\t\tid={glowId}\n\t\t\t\t\tfilterUnits=\"userSpaceOnUse\"\n\t\t\t\t\tx={-40}\n\t\t\t\t\ty={-40}\n\t\t\t\t\twidth={geo.width + 80}\n\t\t\t\t\theight={geo.height + 80}\n\t\t\t\t>\n\t\t\t\t\t<feGaussianBlur stdDeviation={1.5 + 4 * glowAmount} />\n\t\t\t\t</filter>\n\t\t\t\t<filter\n\t\t\t\t\tid={bloomId}\n\t\t\t\t\tfilterUnits=\"userSpaceOnUse\"\n\t\t\t\t\tx={-40}\n\t\t\t\t\ty={-40}\n\t\t\t\t\twidth={geo.width + 80}\n\t\t\t\t\theight={geo.height + 80}\n\t\t\t\t>\n\t\t\t\t\t<feGaussianBlur stdDeviation={5 + 6 * glowAmount} />\n\t\t\t\t</filter>\n\t\t\t</defs>\n\n\t\t\t{/* The fibre: a sheath, a darker core, and a hair of glint down the middle. */}\n\t\t\t<g className=\"fibre\" opacity={fibreOpacity}>\n\t\t\t\t<path\n\t\t\t\t\tclassName=\"fibre-sheath\"\n\t\t\t\t\td={pathD}\n\t\t\t\t\tstrokeWidth={pathWidth + 2}\n\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\tstyle={fibreStyle}\n\t\t\t\t\tstrokeOpacity={pathColor ? 0.5 : 1}\n\t\t\t\t/>\n\t\t\t\t<path\n\t\t\t\t\tclassName=\"fibre-core\"\n\t\t\t\t\td={pathD}\n\t\t\t\t\tstrokeWidth={pathWidth}\n\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\tstyle={fibreStyle}\n\t\t\t\t/>\n\t\t\t\t{!pathColor && (\n\t\t\t\t\t<path\n\t\t\t\t\t\tclassName=\"fibre-glint\"\n\t\t\t\t\t\td={pathD}\n\t\t\t\t\t\tstrokeWidth={Math.max(0.5, pathWidth * 0.3)}\n\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</g>\n\n\t\t\t{reduced ? (\n\t\t\t\t/* Still frame: the fibre holds a soft gradient along its length. */\n\t\t\t\t<g className=\"still\" data-still=\"\">\n\t\t\t\t\t<path\n\t\t\t\t\t\td={pathD}\n\t\t\t\t\t\tstroke={`url(#${id})`}\n\t\t\t\t\t\tstrokeWidth={pathWidth * 2.5 + 3}\n\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\topacity={0.35 * glowAmount}\n\t\t\t\t\t\tfilter={`url(#${glowId})`}\n\t\t\t\t\t/>\n\t\t\t\t\t<path\n\t\t\t\t\t\td={pathD}\n\t\t\t\t\t\tstroke={`url(#${id})`}\n\t\t\t\t\t\tstrokeWidth={pathWidth}\n\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\topacity=\"0.55\"\n\t\t\t\t\t/>\n\t\t\t\t</g>\n\t\t\t) : (\n\t\t\t\tpackets.map((packetDelay, i) => (\n\t\t\t\t\t<g\n\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\tclassName=\"packet\"\n\t\t\t\t\t\tdata-packet={i}\n\t\t\t\t\t\tstyle={{ \"--_delay\": `${packetDelay}s` } as CSSProperties}\n\t\t\t\t\t>\n\t\t\t\t\t\t{/* Arrival bloom behind the destination node. */}\n\t\t\t\t\t\t<g className=\"bloom\" opacity={0.3 + 0.7 * glowAmount}>\n\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\tclassName=\"bloom-disc\"\n\t\t\t\t\t\t\t\tcx={landing.x}\n\t\t\t\t\t\t\t\tcy={landing.y}\n\t\t\t\t\t\t\t\tr={landingRadius + 6}\n\t\t\t\t\t\t\t\tstyle={{ fill: `color-mix(in oklab, ${gradientStopColor} 75%, white)` }}\n\t\t\t\t\t\t\t\tfilter={`url(#${bloomId})`}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\tclassName=\"bloom-ring\"\n\t\t\t\t\t\t\t\tcx={landing.x}\n\t\t\t\t\t\t\t\tcy={landing.y}\n\t\t\t\t\t\t\t\tr={landingRadius + 4}\n\t\t\t\t\t\t\t\tstrokeWidth=\"1\"\n\t\t\t\t\t\t\t\tstyle={{ stroke: `color-mix(in oklab, ${gradientStopColor} 55%, white)` }}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</g>\n\t\t\t\t\t\t{/* Soft glow, blurred, under the crisp packet. */}\n\t\t\t\t\t\t<g filter={`url(#${glowId})`} opacity={glowAmount}>\n\t\t\t\t\t\t\t{glows.map((layer, j) => (\n\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\tkey={j}\n\t\t\t\t\t\t\t\t\tclassName=\"packet-layer\"\n\t\t\t\t\t\t\t\t\td={flowD}\n\t\t\t\t\t\t\t\t\tpathLength={1000}\n\t\t\t\t\t\t\t\t\tstrokeWidth={pathWidth * layer.width + 2}\n\t\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\t\tstyle={{ ...dashVars(layer), stroke: layer.color } as CSSProperties}\n\t\t\t\t\t\t\t\t\topacity={layer.opacity}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t\t{layers.map((layer, j) => (\n\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\tkey={j}\n\t\t\t\t\t\t\t\tclassName={layer.core ? \"packet-layer packet-core\" : \"packet-layer\"}\n\t\t\t\t\t\t\t\tdata-layer={layer.key}\n\t\t\t\t\t\t\t\td={flowD}\n\t\t\t\t\t\t\t\tpathLength={1000}\n\t\t\t\t\t\t\t\tstrokeWidth={pathWidth * layer.width}\n\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\tstyle={\n\t\t\t\t\t\t\t\t\t(layer.core\n\t\t\t\t\t\t\t\t\t\t? { ...dashVars(layer), \"--_core\": gradientStartColor }\n\t\t\t\t\t\t\t\t\t\t: { ...dashVars(layer), stroke: layer.color }) as CSSProperties\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\topacity={layer.opacity}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</g>\n\t\t\t\t))\n\t\t\t)}\n\t\t</svg>\n\t);\n}\n","type":"registry:ui","target":"@ui/fancy/components/animated-beam/AnimatedBeam.tsx"},{"path":"react/src/components/animated-beam/animated-beam.css","content":"/*\n * `.animated-beam` is a port-added anchor: the source has no root class and\n * relied on compiler scoping, while plain CSS is global. Every rule below is\n * anchored on it; class names, custom properties and keyframe names are\n * identical to the source.\n */\n\n/* Theme-aware glass. Public vars first, house defaults as fallbacks. */\n.animated-beam .fibre-sheath {\n\tstroke: var(--beam-fibre-rim, rgba(0, 0, 0, 0.07));\n}\n.animated-beam .fibre-core {\n\tstroke: var(--beam-fibre, rgba(0, 0, 0, 0.18));\n}\n.animated-beam .fibre-glint {\n\tstroke: var(--beam-fibre-glint, rgba(255, 255, 255, 0.75));\n}\n.dark .animated-beam .fibre-sheath {\n\tstroke: var(--beam-fibre-rim, rgba(255, 255, 255, 0.06));\n}\n.dark .animated-beam .fibre-core {\n\tstroke: var(--beam-fibre, rgba(255, 255, 255, 0.14));\n}\n.dark .animated-beam .fibre-glint {\n\tstroke: var(--beam-fibre-glint, rgba(255, 255, 255, 0.16));\n}\n\n/* On a light surface a white core disappears; keep more colour there. */\n.animated-beam .packet-core {\n\tstroke: color-mix(in oklab, var(--_core) 72%, white);\n}\n.dark .animated-beam .packet-core {\n\tstroke: color-mix(in oklab, var(--_core) 45%, white);\n}\n\n.animated-beam .packet-layer {\n\tstroke-dasharray: var(--_len) 3000;\n\tstroke-dashoffset: var(--_o0);\n\tanimation: beam-packet var(--_dur, 5s) linear var(--_delay, 0s) infinite backwards;\n}\n\n/* Glide out, settle into the node at ARRIVAL (78%), then drain the tail. */\n@keyframes beam-packet {\n\t0% {\n\t\tstroke-dashoffset: var(--_o0);\n\t\tanimation-timing-function: cubic-bezier(0.45, 0, 0.2, 1);\n\t}\n\t78% {\n\t\tstroke-dashoffset: var(--_o1);\n\t\tanimation-timing-function: cubic-bezier(0.5, 0, 1, 1);\n\t}\n\t100% {\n\t\tstroke-dashoffset: var(--_o2);\n\t}\n}\n\n.animated-beam .bloom-disc,\n.animated-beam .bloom-ring {\n\ttransform-box: fill-box;\n\ttransform-origin: center;\n\topacity: 0;\n\tanimation: beam-bloom var(--_dur, 5s) linear var(--_delay, 0s) infinite backwards;\n}\n.animated-beam .bloom-ring {\n\tanimation-name: beam-ring;\n}\n\n/* The head lands at 78%: the halo swells behind the node, then lets go. */\n@keyframes beam-bloom {\n\t0%,\n\t74% {\n\t\topacity: 0;\n\t\ttransform: scale(0.6);\n\t\tanimation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);\n\t}\n\t84% {\n\t\topacity: 0.85;\n\t\ttransform: scale(1);\n\t\tanimation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n\t}\n\t100% {\n\t\topacity: 0;\n\t\ttransform: scale(1.15);\n\t}\n}\n\n/* A single ripple leaves the node on arrival. */\n@keyframes beam-ring {\n\t0%,\n\t77% {\n\t\topacity: 0;\n\t\ttransform: scale(0.96);\n\t\tanimation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);\n\t}\n\t81% {\n\t\topacity: 0.7;\n\t\tanimation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);\n\t}\n\t100% {\n\t\topacity: 0;\n\t\ttransform: scale(1.5);\n\t}\n}\n\n/* Before hydration can ask the browser, keep reduced-motion users still. */\n@media (prefers-reduced-motion: reduce) {\n\t.animated-beam .packet {\n\t\tdisplay: none;\n\t}\n}\n","type":"registry:ui","target":"@ui/fancy/components/animated-beam/animated-beam.css"},{"path":"react/src/components/animated-beam/index.ts","content":"\"use client\";\n\nexport { AnimatedBeam } from \"./AnimatedBeam\";\nexport type { AnimatedBeamProps, AnimatedBeamTarget } from \"./AnimatedBeam\";\n","type":"registry:ui","target":"@ui/fancy/components/animated-beam/index.ts"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/animated-beam?framework=react"}}