{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "proximity-sidebar",
  "title": "Proximity Sidebar",
  "description": "A dash-style scroll sidebar whose items expand as the pointer approaches.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "components/ui/proximity-sidebar.tsx",
      "content": "\"use client\"\n\nimport React, {\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\"\nimport {\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n  useTransform,\n  type MotionValue,\n} from \"motion/react\"\n\ntype Side = \"left\" | \"right\"\ntype SectionKind = \"title\" | \"subtitle\" | \"section\" | \"body\"\ntype SectionLevel = 1 | 2 | 3 | 4 | 5 | 6\n\nexport type ProximitySection = {\n  id: string\n  label: string\n  kind?: SectionKind\n  level?: SectionLevel\n}\n\ntype DashPreset = {\n  base: number\n  bump: number\n  thickness: number\n  className: string\n}\n\ntype DashProps = {\n  active: boolean\n  mouseY: MotionValue<number>\n  onSelect: (id: string) => void\n  registerDash: (id: string, node: HTMLButtonElement | null) => void\n  section: ProximitySection\n  sectionKind: SectionKind\n  side: Side\n}\n\ntype ProximitySidebarProps = {\n  activeOffset?: number\n  className?: string\n  sections: ProximitySection[]\n  side?: Side\n}\n\nconst RADIUS = 40\nconst MAX_DASH_WIDTH = 110\nconst SCROLL_IDLE_RESET_DELAY = 80\n\nconst DASH_PRESETS: Record<SectionKind, DashPreset> = {\n  title: {\n    base: 40,\n    bump: 70,\n    thickness: 1,\n    className: \"bg-foreground\",\n  },\n  subtitle: {\n    base: 36,\n    bump: 64,\n    thickness: 1,\n    className: \"bg-foreground\",\n  },\n  section: {\n    base: 30,\n    bump: 56,\n    thickness: 1,\n    className: \"bg-muted-foreground/40\",\n  },\n  body: {\n    base: 24,\n    bump: 50,\n    thickness: 1,\n    className: \"bg-muted-foreground/40\",\n  },\n}\n\nconst getSectionElement = (id: string) =>\n  typeof document === \"undefined\" ? null : document.getElementById(id)\n\nconst getSectionKind = (section: ProximitySection): SectionKind => {\n  if (section.kind) return section.kind\n  if (section.level === 1) return \"title\"\n  if (section.level === 2) return \"subtitle\"\n  if (section.level === 3) return \"section\"\n  return \"body\"\n}\n\nconst getElementSectionKind = (id: string): SectionKind | undefined => {\n  const heading = getSectionElement(id)?.querySelector(\"h1, h2, h3, h4, h5, h6\")\n  const tagName = heading?.tagName.toLowerCase()\n\n  if (tagName === \"h1\") return \"title\"\n  if (tagName === \"h2\") return \"subtitle\"\n  if (tagName === \"h3\") return \"section\"\n  if (tagName) return \"body\"\n}\n\nconst getScrollParent = (element: HTMLElement) => {\n  let parent = element.parentElement\n\n  while (parent) {\n    const { overflowY } = window.getComputedStyle(parent)\n\n    if (/(auto|scroll|overlay)/.test(overflowY)) {\n      return parent\n    }\n\n    parent = parent.parentElement\n  }\n\n  return window\n}\n\nconst Dash = ({\n  active,\n  mouseY,\n  onSelect,\n  registerDash,\n  section,\n  sectionKind,\n  side,\n}: DashProps) => {\n  const ref = useRef<HTMLButtonElement>(null)\n  const preset = DASH_PRESETS[sectionKind]\n  const activeWidth = preset.base + preset.bump\n\n  useEffect(() => {\n    registerDash(section.id, ref.current)\n    return () => registerDash(section.id, null)\n  }, [registerDash, section.id])\n\n  const distance = useTransform(mouseY, (y) => {\n    const rect = ref.current?.getBoundingClientRect()\n    if (!rect) return RADIUS\n    return y - (rect.top + rect.height / 2)\n  })\n\n  const targetScaleX = useTransform(\n    distance,\n    [-RADIUS, 0, RADIUS],\n    [\n      preset.base / MAX_DASH_WIDTH,\n      activeWidth / MAX_DASH_WIDTH,\n      preset.base / MAX_DASH_WIDTH,\n    ],\n    { clamp: true }\n  )\n\n  const scaleX = useSpring(targetScaleX, {\n    stiffness: 320,\n    damping: 34,\n    mass: 0.7,\n  })\n\n  return (\n    <button\n      ref={ref}\n      type=\"button\"\n      aria-current={active ? \"location\" : undefined}\n      aria-label={`Go to ${section.label}`}\n      title={section.label}\n      className=\"group flex h-px w-[110px] items-center border-0 bg-transparent p-0 outline-none\"\n      onClick={() => onSelect(section.id)}\n    >\n      <motion.span\n        className={`block transition-colors duration-150 ease-out group-focus-visible:ring-2 group-focus-visible:ring-ring group-focus-visible:ring-offset-2 ${preset.className}`}\n        style={{\n          height: preset.thickness,\n          scaleX,\n          transformOrigin: side === \"left\" ? \"left center\" : \"right center\",\n          width: MAX_DASH_WIDTH,\n        }}\n      />\n    </button>\n  )\n}\n\nconst ProximitySidebar = ({\n  activeOffset = 0.4,\n  className = \"\",\n  side = \"left\",\n  sections,\n}: ProximitySidebarProps) => {\n  const mouseY = useMotionValue(Infinity)\n  const shouldReduceMotion = useReducedMotion()\n  const dashRefs = useRef(new Map<string, HTMLButtonElement>())\n  const pointerInside = useRef(false)\n  const resetTimer = useRef<number | null>(null)\n  const [activeId, setActiveId] = useState(sections[0]?.id)\n  const [detectedKinds, setDetectedKinds] = useState<Record<string, SectionKind>>(\n    {}\n  )\n\n  const sectionIds = useMemo(\n    () => sections.map((section) => section.id).join(\"|\"),\n    [sections]\n  )\n\n  const registerDash = useCallback(\n    (id: string, node: HTMLButtonElement | null) => {\n      if (node) {\n        dashRefs.current.set(id, node)\n        return\n      }\n\n      dashRefs.current.delete(id)\n    },\n    []\n  )\n\n  const clearPendingReset = useCallback(() => {\n    if (!resetTimer.current) return\n\n    window.clearTimeout(resetTimer.current)\n    resetTimer.current = null\n  }, [])\n\n  const setMouseToDash = useCallback(\n    (id?: string) => {\n      if (!id) {\n        mouseY.set(Infinity)\n        return\n      }\n\n      const node = dashRefs.current.get(id)\n      if (!node) return\n\n      const rect = node.getBoundingClientRect()\n      mouseY.set(rect.top + rect.height / 2)\n    },\n    [mouseY]\n  )\n\n  const pulseDash = useCallback(\n    (id?: string) => {\n      setMouseToDash(id)\n      clearPendingReset()\n\n      if (!id || pointerInside.current) return\n\n      resetTimer.current = window.setTimeout(() => {\n        mouseY.set(Infinity)\n        resetTimer.current = null\n      }, SCROLL_IDLE_RESET_DELAY)\n    },\n    [clearPendingReset, mouseY, setMouseToDash]\n  )\n\n  const selectSection = useCallback(\n    (id: string) => {\n      const element = getSectionElement(id)\n      if (!element) return\n\n      element.scrollIntoView({\n        behavior: shouldReduceMotion ? \"auto\" : \"smooth\",\n        block: \"start\",\n      })\n\n      window.history.replaceState(null, \"\", `#${id}`)\n      setActiveId(id)\n      pulseDash(id)\n    },\n    [pulseDash, shouldReduceMotion]\n  )\n\n  useEffect(() => () => clearPendingReset(), [clearPendingReset])\n\n  useEffect(() => {\n    const kinds = sections.reduce<Record<string, SectionKind>>(\n      (nextKinds, section) => {\n        nextKinds[section.id] =\n          section.kind || section.level\n            ? getSectionKind(section)\n            : getElementSectionKind(section.id) ?? getSectionKind(section)\n\n        return nextKinds\n      },\n      {}\n    )\n\n    setDetectedKinds(kinds)\n  }, [sectionIds, sections])\n\n  useEffect(() => {\n    if (!sections.length) return\n\n    let frame = 0\n\n    const updateActiveSection = () => {\n      frame = 0\n\n      const anchorY = window.innerHeight * activeOffset\n      let nextActiveId = sections[0]?.id\n      let shortestDistance = Number.POSITIVE_INFINITY\n\n      for (const section of sections) {\n        const element = getSectionElement(section.id)\n        if (!element) continue\n\n        const rect = element.getBoundingClientRect()\n        const containsAnchor = rect.top <= anchorY && rect.bottom >= anchorY\n        const distance = containsAnchor\n          ? 0\n          : Math.min(Math.abs(rect.top - anchorY), Math.abs(rect.bottom - anchorY))\n\n        if (distance < shortestDistance) {\n          shortestDistance = distance\n          nextActiveId = section.id\n        }\n      }\n\n      setActiveId(nextActiveId)\n\n      if (!pointerInside.current) {\n        pulseDash(nextActiveId)\n      }\n    }\n\n    const scheduleUpdate = () => {\n      if (frame) return\n      frame = window.requestAnimationFrame(updateActiveSection)\n    }\n\n    const scrollParents = new Set<EventTarget>([window])\n\n    for (const section of sections) {\n      const element = getSectionElement(section.id)\n      if (element) scrollParents.add(getScrollParent(element))\n    }\n\n    updateActiveSection()\n\n    for (const parent of scrollParents) {\n      parent.addEventListener(\"scroll\", scheduleUpdate, { passive: true })\n    }\n\n    window.addEventListener(\"resize\", scheduleUpdate)\n\n    return () => {\n      if (frame) window.cancelAnimationFrame(frame)\n\n      for (const parent of scrollParents) {\n        parent.removeEventListener(\"scroll\", scheduleUpdate)\n      }\n\n      window.removeEventListener(\"resize\", scheduleUpdate)\n    }\n  }, [activeOffset, pulseDash, sectionIds, sections])\n\n  return (\n    <nav\n      aria-label=\"Page sections\"\n      className={`flex h-full min-h-0 items-center ${\n        side === \"left\" ? \"justify-start\" : \"justify-end\"\n      } ${className}`}\n    >\n      <div\n        className={`new-home_minimap__dDggR mx-8 flex flex-col ${\n          side === \"right\" ? \"items-end\" : \"items-start\"\n        }`}\n        style={{ gap: 8 }}\n        onPointerMove={(event) => {\n          clearPendingReset()\n          pointerInside.current = true\n          mouseY.set(event.clientY)\n        }}\n        onPointerLeave={() => {\n          pointerInside.current = false\n          mouseY.set(Infinity)\n        }}\n      >\n        {sections.map((section) => (\n          <Dash\n            key={section.id}\n            active={section.id === activeId}\n            mouseY={mouseY}\n            onSelect={selectSection}\n            registerDash={registerDash}\n            section={section}\n            sectionKind={detectedKinds[section.id] ?? getSectionKind(section)}\n            side={side}\n          />\n        ))}\n      </div>\n    </nav>\n  )\n}\n\nexport default ProximitySidebar\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}