{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "throughline",
  "type": "registry:ui",
  "title": "Throughline",
  "description": "Glass tiles that ride dotted lines through your headline. Each tile slides out from under a small station, grows as it travels, turns a little on the way, and dives into the far station, and a tile passing over a colored letter picks up its color through the glass. Rest the pointer on one and its whole line comes to a stop, like a cable car line, while the tile turns upright and shows its label. Lines can run behind the content as well as over it.",
  "author": "KeyframeUI <https://keyframeui.com>",
  "categories": [
    "hero",
    "interaction",
    "motion"
  ],
  "docs": "Live preview, examples and props: https://keyframeui.com/components/throughline",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "components/ui/throughline.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Keep:\n * - Each ride is a Web Animation on the tile's transform, plus one on its glass's opacity, run\n *   linearly through `RIDE.samples` poses so tiles keep one pace off the main thread.\n * - Braking and releasing step a line's animations by hand through `currentTime`, one rAF per line\n *   with speed as u²; a line goes back to running on its own only at full speed.\n * - A hold starts from `pointermove` after the hover intent and lets go after the linger, so a sweep\n *   across, or a tile drifting under a still pointer, never stops a line.\n * - Off screen every line pauses where it is, and a rebuild (a resize, a new `duration`) puts each\n *   tile back at the place saved in `fracs`, so nothing jumps to a start.\n * - Reduced motion builds no rides: tiles sit full size at their `restingPoints`, and hover, focus\n *   and tap still show labels.\n */\nconst TIMING = {\n  /** Seconds to ride a line as long as the box is wide (the `duration` prop). */\n  duration: 26,\n  /** The ride's easing between its sampled poses, so every tile keeps one pace. */\n  ride: \"linear\",\n  /** A held line slowing to a stop, in seconds (the `brakeDuration` prop). */\n  brakeDuration: 0.3,\n  /** The line getting back up to speed once let go, in seconds (the `releaseDuration` prop). */\n  releaseDuration: 0.6,\n  /** The pointer resting on a tile before its line stops, in ms. */\n  intent: 100,\n  /** After the pointer leaves, before the line moves again, in ms. */\n  linger: 250,\n  /** The first fade-in: its duration and curve as Tailwind classes, and ms between tiles. */\n  enter: { fade: \"duration-500 ease-out\", stagger: 60 },\n  /** A held tile turning upright and lifting, as Tailwind classes. */\n  face: \"duration-[240ms] ease-[cubic-bezier(0.23,1,0.32,1)]\",\n  /** The held tile's label rising in and dropping away, as Tailwind classes. */\n  label: \"duration-[180ms] ease-[cubic-bezier(0.23,1,0.32,1)]\",\n} as const;\n\n/* ─────────────────────────────────────────────────────────\n * THROUGHLINE STORYBOARD\n *\n *  rest      dotted lines thread across the box, each running\n *            from one small square station to another, over (or\n *            under) whatever you put inside: a headline, a card.\n *            Glass tiles ride them, spaced evenly. A tile over a\n *            colored letter picks up its color through the glass\n *  ride      one steady pace, linear: a line as long as the box\n *            is wide takes `duration` (26s) end to end. Each line\n *            starts at its own point in the loop, so no two lines\n *            move in step\n *  launch    a tile slides out from under its station at 16% of\n *            its size and grows to full size over the first 14%\n *            of the line, turning slowly as it goes (7° over the\n *            whole ride). Over the last 14% it dives back into\n *            the far station, fading out in the last 4%\n *  view      on first paint the tiles fade in where they already\n *            are, 60ms apart, over 0.5s. Nothing jumps to a start\n *  brake     rest the pointer on a tile for 100ms and its whole\n *            line stops, like a cable car line: every tile on it\n *            slows to a stop in 0.3s (speed falls as (1 − t)²).\n *            A sweep across does nothing; a tile drifting under a\n *            still pointer does nothing. Other lines keep going\n *  face      the held tile turns upright and lifts to 1.08 in\n *            0.24s, and its label rises 6px under it in 0.18s\n *  release   0.25s after the pointer leaves, the line picks up\n *            speed again over 0.6s (t²) and the tile tilts back\n *  touch     a tap holds a tile like a hover; a second tap, or a\n *            tap anywhere else, lets it go\n *  keys      tiles with `href` are links: Tab reaches them, focus\n *            holds the line the same way, Enter follows the link\n *  sleep     off screen every line pauses where it is and goes on\n *            from there when it's back. The ride runs as Web\n *            Animations on transform, off the main thread\n *  resize    lines live in % of the box; on resize each ride is\n *            rebuilt for the new size and keeps its place\n *  reduced   nothing rides: the tiles sit full size, spread along\n *            their lines. Hover, focus and tap still show labels\n * ───────────────────────────────────────────────────────── */\n\nconst RIDE = {\n  samples: 120, // keyframes per ride; linear between them\n  launch: 0.14, // share of a line over which a tile grows out of its station (and dives back in)\n  far: 0.16, // a tile's scale at a station\n  fade: 0.04, // share of the line at each end over which a tile fades\n  tilt: 11, // deg, the largest resting tilt\n  sway: 7, // deg a tile turns over its ride\n  lift: 1.08, // scale of a held tile\n} as const;\n\nexport type ThroughlinePoint = [x: number, y: number];\n\nexport interface ThroughlineLine {\n  /** Where tiles leave from: `[x, y]` in % of the box. */\n  from: ThroughlinePoint;\n  /** Where they arrive: `[x, y]` in % of the box. */\n  to: ThroughlinePoint;\n  /** How far the line bows, as a share of its length. Positive bows it to the right of its direction (down, for a line running left to right). Default `0`. */\n  bend?: number;\n  /** Run this line and its tiles behind the content instead of over it. */\n  behind?: boolean;\n}\n\nexport interface ThroughlineItem {\n  /** What the tile stands for. Shown under it when held, and read by screen readers. */\n  label: string;\n  /** What sits in the tile: an svg, a logo image, an avatar. It fills 56% of the tile (`--throughline-icon`). */\n  icon: React.ReactNode;\n  /** Which line it rides, by index. Items without one are dealt round the lines in order. */\n  line?: number;\n  /** Makes the tile a link, reachable with Tab. */\n  href?: string;\n  /** Classes for this tile, e.g. a solid dark tile instead of glass. */\n  className?: string;\n}\n\nexport interface ThroughlineProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n  /** The dotted lines, in % of the box. Tiles ride from `from` to `to`. */\n  lines: ThroughlineLine[];\n  /** The tiles. Each line spaces its own tiles evenly. */\n  items: ThroughlineItem[];\n  /** Seconds a tile takes to ride a line as long as the box is wide. Longer lines take longer, so every tile keeps one pace. */\n  duration?: number;\n  /** Brings every line to a stop (and back up to speed when it turns false). */\n  paused?: boolean;\n  /** Seconds a held line takes to slow to a stop. */\n  brakeDuration?: number;\n  /** Seconds a line takes to get back up to speed once it's let go. */\n  releaseDuration?: number;\n  /** Draws the small square stations at each end of a line. */\n  stations?: boolean;\n  /** What the lines thread through: a headline, a card, a whole hero. It sets the box's size. */\n  children?: React.ReactNode;\n}\n\nconst clamp01 = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);\nconst fract = (v: number) => v - Math.floor(v);\n/** A repeatable 0–1 number per index, so tilts and offsets survive a reload. */\nconst rand = (i: number) => fract(Math.sin(i * 127.1 + 311.7) * 43758.5453);\n\nconst REDUCED_QUERY = \"(prefers-reduced-motion: reduce)\";\nconst subscribeReduced = (onChange: () => void) => {\n  const mq = window.matchMedia(REDUCED_QUERY);\n  mq.addEventListener(\"change\", onChange);\n  return () => mq.removeEventListener(\"change\", onChange);\n};\n/** Live, and false on the server and in the hydration render, so markup never mismatches. */\nfunction usePrefersReducedMotion() {\n  return React.useSyncExternalStore(\n    subscribeReduced,\n    () => window.matchMedia(REDUCED_QUERY).matches,\n    () => false,\n  );\n}\n\n/* ── Geometry ───────────────────────────────────────────── */\n\ntype Box = { w: number; h: number };\ntype Track = { len: number; at: (p: number) => [number, number] };\n\n/** The quadratic's control point, in % of the box. */\nfunction bendPoint({ from, to, bend = 0 }: ThroughlineLine): ThroughlinePoint {\n  const [x0, y0] = from;\n  const [x1, y1] = to;\n  return [(x0 + x1) / 2 - (y1 - y0) * bend, (y0 + y1) / 2 + (x1 - x0) * bend];\n}\n\n/** A line in pixels, walked by distance so tiles keep one speed round the bend. */\nfunction makeTrack(line: ThroughlineLine, { w, h }: Box): Track {\n  const [cx, cy] = bendPoint(line);\n  const x0 = (line.from[0] * w) / 100;\n  const y0 = (line.from[1] * h) / 100;\n  const x1 = (line.to[0] * w) / 100;\n  const y1 = (line.to[1] * h) / 100;\n  const qx = (cx * w) / 100;\n  const qy = (cy * h) / 100;\n  const point = (t: number): [number, number] => {\n    const u = 1 - t;\n    return [u * u * x0 + 2 * u * t * qx + t * t * x1, u * u * y0 + 2 * u * t * qy + t * t * y1];\n  };\n  const steps = 240;\n  const acc = new Float64Array(steps + 1);\n  let [px, py] = point(0);\n  for (let i = 1; i <= steps; i++) {\n    const [x, y] = point(i / steps);\n    acc[i] = acc[i - 1] + Math.hypot(x - px, y - py);\n    px = x;\n    py = y;\n  }\n  const len = acc[steps];\n  const at = (p: number) => {\n    const target = clamp01(p) * len;\n    let lo = 0;\n    let hi = steps;\n    while (hi - lo > 1) {\n      const mid = (lo + hi) >> 1;\n      if (acc[mid] < target) lo = mid;\n      else hi = mid;\n    }\n    const span = acc[hi] - acc[lo] || 1;\n    return point((lo + (target - acc[lo]) / span) / steps);\n  };\n  return { len, at };\n}\n\n/** Grows out of a station fast and settles; dives back in the same way. */\nfunction scaleAt(p: number) {\n  const e = Math.min(p, 1 - p) / RIDE.launch;\n  if (e >= 1) return 1;\n  return RIDE.far + (1 - RIDE.far) * (1 - (1 - e) ** 3);\n}\n\ntype Pose = { x: number; y: number; rotate: number; scale: number };\n\nconst poseCss = ({ x, y, rotate, scale }: Pose) =>\n  `translate(${x.toFixed(2)}px, ${y.toFixed(2)}px) rotate(${rotate.toFixed(2)}deg) scale(${scale.toFixed(4)})`;\n\n/* ── Riders ─────────────────────────────────────────────── */\n\ntype Seat = { line: number; phase: number; tilt: number; sway: number };\n\n/** Deals items onto lines and spaces each line's tiles evenly, from a different start per line. */\nfunction seat(items: ThroughlineItem[], lineCount: number): Seat[] {\n  if (lineCount === 0) return [];\n  const lineOf = items.map((item, i) => ((item.line ?? i) % lineCount + lineCount) % lineCount);\n  const counts = new Array<number>(lineCount).fill(0);\n  const order = lineOf.map((l) => counts[l]++);\n  return lineOf.map((line, i) => ({\n    line,\n    phase: fract(order[i] / counts[line] + rand(line + 1)),\n    tilt: (rand(i * 7 + 3) * 2 - 1) * RIDE.tilt,\n    sway: rand(i * 13 + 5) > 0.5 ? 1 : -1,\n  }));\n}\n\nfunction poseAt(track: Track, s: Seat, p: number): Pose {\n  const [x, y] = track.at(p);\n  return { x, y, rotate: s.tilt + RIDE.sway * s.sway * (p - 0.5), scale: scaleAt(p) };\n}\n\n/**\n * Where the tiles sit when nothing rides: full size, evenly spaced along the\n * middle of each line, with each line's spacing slid to keep its tiles as far\n * as it can from tiles already placed, so they don't pile up where lines cross.\n */\nfunction restingPoints(seats: Seat[], tracks: Track[]) {\n  const points = new Array<number>(seats.length).fill(0.5);\n  const placed: [number, number][] = [];\n  tracks.forEach((track, line) => {\n    const mine = seats.map((s, i) => (s.line === line ? i : -1)).filter((i) => i >= 0);\n    if (mine.length === 0) return;\n    let best: number[] = [];\n    let bestGap = -1;\n    for (let k = 0; k < 24; k++) {\n      const ps = mine.map((_, j) => 0.14 + 0.72 * fract(k / 24 / mine.length + j / mine.length));\n      let gap = Infinity;\n      for (const p of ps) {\n        const [x, y] = track.at(p);\n        for (const [px, py] of placed) gap = Math.min(gap, Math.hypot(x - px, y - py));\n      }\n      if (gap > bestGap) {\n        bestGap = gap;\n        best = ps;\n      }\n    }\n    mine.forEach((i, j) => {\n      points[i] = best[j];\n      placed.push(track.at(best[j]));\n    });\n  });\n  return points;\n}\n\ntype Rider = {\n  index: number;\n  seat: Seat;\n  track: Track;\n  ms: number; // one ride, in ms\n  move: Animation;\n  fade: Animation;\n};\n\ntype Lane = {\n  riders: Rider[];\n  u: number; // throttle, 0–1; speed is u²\n  raf: number;\n  last: number;\n  native: boolean; // running on its own (off the main thread) rather than stepped\n};\n\nconst timeOf = (a: Animation) => Number(a.currentTime ?? 0);\n\n/* ── Throughline ────────────────────────────────────────── */\n\ntype Held = { index: number; x: number; y: number; rotate: number; scale: number };\n\nexport function Throughline({\n  lines,\n  items,\n  duration = TIMING.duration,\n  paused = false,\n  brakeDuration = TIMING.brakeDuration,\n  releaseDuration = TIMING.releaseDuration,\n  stations = true,\n  className,\n  children,\n  style,\n  ...props\n}: ThroughlineProps) {\n  const reduced = usePrefersReducedMotion();\n  const rootRef = React.useRef<HTMLDivElement>(null);\n  const outerRefs = React.useRef<(HTMLElement | null)[]>([]);\n  const glassRefs = React.useRef<(HTMLElement | null)[]>([]);\n  const [box, setBox] = React.useState<Box | null>(null);\n  const [held, setHeld] = React.useState<Held | null>(null);\n  const [shownLabel, setShownLabel] = React.useState<Held | null>(null);\n\n  const seats = React.useMemo(() => seat(items, lines.length), [items, lines.length]);\n  const tracks = React.useMemo(() => (box ? lines.map((l) => makeTrack(l, box)) : null), [lines, box]);\n  const rest = React.useMemo(() => (reduced && tracks ? restingPoints(seats, tracks) : null), [reduced, seats, tracks]);\n\n  // The controller lives across renders; handlers reach it through this ref.\n  const ctl = React.useRef<{\n    hold: (i: number) => Held | null;\n    letGo: (i: number) => void;\n    drive: () => void;\n  } | null>(null);\n  const holds = React.useRef(new Set<number>());\n  const pausedRef = React.useRef(paused);\n  const brakeRef = React.useRef({ brake: brakeDuration, release: releaseDuration });\n  const fracs = React.useRef(new Map<number, number>());\n\n  // Size of the box, in pixels.\n  React.useLayoutEffect(() => {\n    const root = rootRef.current;\n    if (!root) return;\n    const measure = () => {\n      const w = root.clientWidth;\n      const h = root.clientHeight;\n      setBox((b) => (b && Math.abs(b.w - w) < 0.5 && Math.abs(b.h - h) < 0.5 ? b : { w, h }));\n    };\n    measure();\n    const ro = new ResizeObserver(measure);\n    ro.observe(root);\n    return () => ro.disconnect();\n  }, []);\n\n  // A stable key for the ride geometry, so inline `lines` / `items` arrays don't rebuild every render.\n  const geometry = box && tracks ? `${box.w}x${box.h}|${JSON.stringify(lines)}|${seats.map((s) => s.line).join()}` : \"\";\n\n  // Build the rides: one transform animation per tile, one opacity animation on its glass.\n  React.useEffect(() => {\n    const root = rootRef.current;\n    if (!root || !box || !tracks || reduced) {\n      ctl.current = null;\n      return;\n    }\n    const saved = fracs.current;\n    const lanes: Lane[] = lines.map(() => ({ riders: [], u: 1, raf: 0, last: 0, native: true }));\n    const riders: (Rider | null)[] = seats.map((s, i) => {\n      const outer = outerRefs.current[i];\n      const glass = glassRefs.current[i];\n      const track = tracks[s.line];\n      if (!outer || !glass || !track || track.len < 1) return null;\n      const ms = ((duration * track.len) / box.w) * 1000;\n      const frames: Keyframe[] = [];\n      for (let k = 0; k <= RIDE.samples; k++) {\n        const p = k / RIDE.samples;\n        frames.push({ offset: p, transform: poseCss(poseAt(track, s, p)) });\n      }\n      const timing: KeyframeAnimationOptions = { duration: ms, iterations: Infinity, easing: TIMING.ride };\n      const move = outer.animate(frames, timing);\n      const fade = glass.animate(\n        [\n          { offset: 0, opacity: 0 },\n          { offset: RIDE.fade, opacity: 1 },\n          { offset: 1 - RIDE.fade, opacity: 1 },\n          { offset: 1, opacity: 0 },\n        ],\n        timing,\n      );\n      move.currentTime = fade.currentTime = (saved.get(i) ?? s.phase) * ms;\n      const rider = { index: i, seat: s, track, ms, move, fade };\n      lanes[s.line].riders.push(rider);\n      return rider;\n    });\n\n    let visible = true;\n    const want = (lane: Lane) =>\n      !pausedRef.current && lane.riders.every((r) => !holds.current.has(r.index)) ? 1 : 0;\n    const setNative = (lane: Lane, on: boolean) => {\n      lane.native = on;\n      for (const r of lane.riders) {\n        if (on) {\n          r.move.play();\n          r.fade.play();\n        } else {\n          r.move.pause();\n          r.fade.pause();\n        }\n      }\n    };\n    // While a line brakes or picks up speed, its tiles are stepped by hand.\n    const step = (lane: Lane) => (now: number) => {\n      const dt = Math.min(0.05, (now - lane.last) / 1000);\n      lane.last = now;\n      const target = want(lane);\n      lane.u =\n        target > lane.u\n          ? Math.min(target, lane.u + dt / brakeRef.current.release)\n          : Math.max(target, lane.u - dt / brakeRef.current.brake);\n      const ahead = dt * lane.u * lane.u * 1000;\n      for (const r of lane.riders) {\n        r.move.currentTime = timeOf(r.move) + ahead;\n        r.fade.currentTime = timeOf(r.fade) + ahead;\n      }\n      if (lane.u === target) {\n        lane.raf = 0;\n        if (target === 1) setNative(lane, true);\n        return;\n      }\n      lane.raf = requestAnimationFrame(step(lane));\n    };\n    const drive = () => {\n      if (!visible) return;\n      for (const lane of lanes) {\n        if (lane.raf || lane.u === want(lane)) continue;\n        if (lane.native) setNative(lane, false);\n        lane.last = performance.now();\n        lane.raf = requestAnimationFrame(step(lane));\n      }\n    };\n    const sleep = (asleep: boolean) => {\n      visible = !asleep;\n      for (const lane of lanes) {\n        cancelAnimationFrame(lane.raf);\n        lane.raf = 0;\n        lane.u = asleep ? lane.u : want(lane);\n        setNative(lane, !asleep && lane.u === 1);\n      }\n    };\n    lanes.forEach((lane) => {\n      lane.u = want(lane);\n      if (lane.u === 0) setNative(lane, false);\n    });\n\n    const hold = (i: number): Held | null => {\n      const r = riders[i];\n      if (!r) return null;\n      const lane = lanes[r.seat.line];\n      // Where it will come to rest: the brake covers u³ · brake / 3 of the ride.\n      const moving = visible && (lane.native || lane.raf !== 0);\n      const ahead = moving ? ((lane.u ** 3 * brakeRef.current.brake) / 3) * 1000 : 0;\n      const p = fract((timeOf(r.move) + ahead) / r.ms);\n      holds.current.add(i);\n      drive();\n      return { index: i, ...poseAt(r.track, r.seat, p) };\n    };\n    const letGo = (i: number) => {\n      holds.current.delete(i);\n      drive();\n    };\n    ctl.current = { hold, letGo, drive };\n\n    const io = new IntersectionObserver(([entry]) => sleep(!entry.isIntersecting));\n    io.observe(root);\n\n    return () => {\n      io.disconnect();\n      ctl.current = null;\n      riders.forEach((r, i) => {\n        if (!r) return;\n        saved.set(i, fract(timeOf(r.move) / r.ms));\n        r.move.cancel();\n        r.fade.cancel();\n      });\n      lanes.forEach((lane) => cancelAnimationFrame(lane.raf));\n    };\n    // `geometry` stands in for box, tracks, lines and seats.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [geometry, duration, reduced]);\n\n  React.useEffect(() => {\n    pausedRef.current = paused;\n    ctl.current?.drive();\n  }, [paused]);\n\n  React.useEffect(() => {\n    brakeRef.current = { brake: Math.max(0.01, brakeDuration), release: Math.max(0.01, releaseDuration) };\n  }, [brakeDuration, releaseDuration]);\n\n  // Hold and let go, with an intent delay on the way in and a linger on the way out.\n  const engaged = React.useRef(new Set<number>());\n  const intents = React.useRef(new Map<number, number>());\n  const lingers = React.useRef(new Map<number, number>());\n  const touchHeld = React.useRef<number | null>(null);\n\n  const cancel = (timers: Map<number, number>, i: number) => {\n    window.clearTimeout(timers.get(i));\n    timers.delete(i);\n  };\n\n  const engage = React.useCallback(\n    (i: number) => {\n      cancel(intents.current, i);\n      cancel(lingers.current, i);\n      engaged.current.add(i);\n      let pose: Held | null = null;\n      if (ctl.current) {\n        pose = ctl.current.hold(i);\n      } else if (tracks && rest && seats[i]) {\n        // Reduced motion: the tile is already still.\n        const s = seats[i];\n        pose = { index: i, ...poseAt(tracks[s.line], s, rest[i]), scale: 1 };\n      }\n      if (pose) {\n        setHeld(pose);\n        setShownLabel(pose);\n      }\n    },\n    [tracks, seats, rest],\n  );\n\n  const disengage = React.useCallback((i: number) => {\n    cancel(intents.current, i);\n    if (!engaged.current.delete(i)) return;\n    setHeld((h) => (h?.index === i ? null : h));\n    cancel(lingers.current, i);\n    lingers.current.set(\n      i,\n      window.setTimeout(() => {\n        lingers.current.delete(i);\n        ctl.current?.letGo(i);\n      }, TIMING.linger),\n    );\n  }, []);\n\n  // A tap elsewhere lets go of a tile held by touch.\n  React.useEffect(() => {\n    const onDown = (e: PointerEvent) => {\n      const i = touchHeld.current;\n      if (i === null) return;\n      const tile = outerRefs.current[i];\n      if (tile && e.target instanceof Node && tile.contains(e.target)) return;\n      touchHeld.current = null;\n      disengage(i);\n    };\n    document.addEventListener(\"pointerdown\", onDown);\n    return () => document.removeEventListener(\"pointerdown\", onDown);\n  }, [disengage]);\n\n  React.useEffect(() => {\n    const pending = [intents.current, lingers.current];\n    return () => pending.forEach((timers) => timers.forEach((id) => window.clearTimeout(id)));\n  }, []);\n\n  const ready = box !== null;\n  const plain = items.filter((item) => !item.href);\n\n  const renderLayer = (behind: boolean) => {\n    const layerLines = lines.map((l, i) => ({ l, i })).filter(({ l }) => !!l.behind === behind);\n    if (layerLines.length === 0) return null;\n    const onLayer = new Set(layerLines.map(({ i }) => i));\n    return (\n      <div\n        className={cn(\"pointer-events-none absolute inset-0\", behind ? \"z-0\" : \"z-20\")}\n        data-throughline-layer={behind ? \"behind\" : \"front\"}\n      >\n        {box && (\n          <svg\n            aria-hidden\n            viewBox={`0 0 ${box.w} ${box.h}`}\n            className=\"absolute inset-0 size-full overflow-visible\"\n            fill=\"none\"\n          >\n            {layerLines.map(({ l, i }) => {\n              const [cx, cy] = bendPoint(l);\n              const sx = box.w / 100;\n              const sy = box.h / 100;\n              return (\n                <path\n                  key={i}\n                  d={`M ${l.from[0] * sx} ${l.from[1] * sy} Q ${cx * sx} ${cy * sy} ${l.to[0] * sx} ${l.to[1] * sy}`}\n                  stroke=\"var(--throughline-line)\"\n                  strokeWidth={1.75}\n                  strokeLinecap=\"round\"\n                  strokeDasharray=\"0 6\"\n                />\n              );\n            })}\n          </svg>\n        )}\n\n        {items.map((item, i) => {\n          const s = seats[i];\n          if (!s || !onLayer.has(s.line)) return null;\n          const isHeld = held?.index === i;\n          const still = tracks && rest ? poseAt(tracks[s.line], s, rest[i]) : null;\n          const Tag = item.href ? \"a\" : \"span\";\n          return (\n            <span\n              key={i}\n              ref={(el) => {\n                outerRefs.current[i] = el;\n              }}\n              className=\"absolute left-0 top-0 size-0 will-change-transform\"\n              style={still ? { transform: poseCss({ ...still, scale: 1 }) } : undefined}\n            >\n              <span\n                className={cn(\n                  `absolute size-(--throughline-tile) transition-transform ${TIMING.face} will-change-transform motion-reduce:transition-none`,\n                  \"left-[calc(var(--throughline-tile)/-2)] top-[calc(var(--throughline-tile)/-2)]\",\n                )}\n                style={isHeld ? { transform: `rotate(${(-held.rotate).toFixed(2)}deg) scale(${RIDE.lift})` } : undefined}\n              >\n                <Tag\n                  ref={(el: HTMLElement | null) => {\n                    glassRefs.current[i] = el;\n                  }}\n                  {...(item.href\n                    ? { href: item.href, \"aria-label\": item.label }\n                    : { \"aria-hidden\": true })}\n                  className={cn(\n                    \"pointer-events-auto flex size-full items-center justify-center rounded-[28%]\",\n                    \"border border-white/70 bg-white/35 backdrop-blur-[5px] backdrop-saturate-[1.8]\",\n                    \"shadow-[0_14px_30px_-14px_rgb(20_16_40/0.45),inset_0_1px_0_rgb(255_255_255/0.75)]\",\n                    \"dark:border-white/15 dark:bg-white/[0.07] dark:shadow-[0_14px_30px_-14px_rgb(0_0_0/0.8),inset_0_1px_0_rgb(255_255_255/0.14)]\",\n                    \"focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-current\",\n                    `transition-[filter] ${TIMING.enter.fade}`,\n                    ready ? \"filter-none\" : \"[filter:opacity(0)]\",\n                    item.className,\n                  )}\n                  style={{ transitionDelay: `${i * TIMING.enter.stagger}ms` }}\n                  onPointerMove={(e: React.PointerEvent) => {\n                    if (e.pointerType !== \"mouse\" || engaged.current.has(i) || intents.current.has(i)) return;\n                    // Back on it before the line moved off: hold again at once.\n                    if (lingers.current.has(i)) engage(i);\n                    else intents.current.set(i, window.setTimeout(() => engage(i), TIMING.intent));\n                  }}\n                  onPointerLeave={(e: React.PointerEvent) => {\n                    if (e.pointerType === \"mouse\") disengage(i);\n                  }}\n                  onPointerDown={(e: React.PointerEvent) => {\n                    if (e.pointerType === \"mouse\") return;\n                    if (touchHeld.current === i) {\n                      touchHeld.current = null;\n                      disengage(i);\n                      return;\n                    }\n                    if (touchHeld.current !== null) disengage(touchHeld.current);\n                    touchHeld.current = i;\n                    engage(i);\n                  }}\n                  onFocus={item.href ? () => engage(i) : undefined}\n                  onBlur={item.href ? () => disengage(i) : undefined}\n                >\n                  <span className=\"pointer-events-none flex size-(--throughline-icon) items-center justify-center *:size-full *:object-contain\">\n                    {item.icon}\n                  </span>\n                </Tag>\n              </span>\n            </span>\n          );\n        })}\n\n        {stations &&\n          layerLines.map(({ l, i }) =>\n            [l.from, l.to].map(([x, y], end) => (\n              <span\n                key={`${i}-${end}`}\n                aria-hidden\n                className=\"absolute size-[7px] -translate-x-1/2 -translate-y-1/2 rounded-[2px] bg-(--throughline-station) shadow-[0_0_0_3px_var(--throughline-station-ring)]\"\n                style={{ left: `${x}%`, top: `${y}%` }}\n              />\n            )),\n          )}\n      </div>\n    );\n  };\n\n  const label = shownLabel ? items[shownLabel.index]?.label : undefined;\n\n  return (\n    <div\n      ref={rootRef}\n      className={cn(\n        \"@container/throughline relative isolate overflow-x-clip\",\n        \"[--throughline-tile:clamp(2rem,6.5cqi,4.5rem)] [--throughline-icon:56%]\",\n        \"[--throughline-line:color-mix(in_oklab,currentColor_42%,transparent)]\",\n        \"[--throughline-station:currentColor]\",\n        \"[--throughline-station-ring:color-mix(in_oklab,currentColor_14%,transparent)]\",\n        className,\n      )}\n      style={style}\n      {...props}\n    >\n      {renderLayer(true)}\n      <div className=\"relative z-10\">{children}</div>\n      {renderLayer(false)}\n\n      <div\n        aria-hidden\n        className=\"pointer-events-none absolute left-0 top-0 z-30\"\n        style={\n          shownLabel\n            ? {\n                transform: `translate(${shownLabel.x.toFixed(1)}px, ${shownLabel.y.toFixed(1)}px)`,\n                [\"--throughline-held\" as string]: shownLabel.scale * RIDE.lift,\n              }\n            : undefined\n        }\n      >\n        <span\n          className={cn(\n            \"absolute left-0 top-[calc(var(--throughline-tile)*var(--throughline-held,1)/2+10px)] -translate-x-1/2 whitespace-nowrap rounded-full px-2.5 py-1.5 text-xs font-medium leading-none\",\n            \"bg-zinc-950 text-white shadow-[0_6px_16px_-6px_rgb(0_0_0/0.4)] dark:bg-white dark:text-zinc-950\",\n            `transition-[opacity,translate] ${TIMING.label} will-change-[opacity,translate]`,\n            held ? \"translate-y-0 opacity-100\" : \"translate-y-1.5 opacity-0\",\n          )}\n          onTransitionEnd={() => {\n            if (!held) setShownLabel(null);\n          }}\n        >\n          {label}\n        </span>\n      </div>\n\n      {plain.length > 0 && (\n        <ul className=\"sr-only\">\n          {plain.map((item, i) => (\n            <li key={i}>{item.label}</li>\n          ))}\n        </ul>\n      )}\n    </div>\n  );\n}\n"
    }
  ]
}