{
  "slug": "proximity-sidebar",
  "name": "Proximity Sidebar",
  "description": "Dynamic document navigation minimap with smooth spring-physics cursor proximity expansion and scroll tracking.",
  "summary": "An ultra-fluid document outline and navigation minimap engineered for content-heavy pages, documentation portals, and long-form articles. Features hierarchical dash indicators that smoothly expand horizontally as the pointer approaches vertically, driven by cosine falloff curves and spring damping physics. Fully synchronized with viewport and container scrolling, supporting bidirectional click-to-scroll, keyboard navigation, and customizable accent themes with zero layout thrashing.",
  "category": "navigation",
  "tags": [
    "sidebar",
    "navigation",
    "proximity",
    "minimap",
    "spring",
    "motion",
    "a11y"
  ],
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "motion"
  ],
  "version": "1.0.0",
  "createdDate": "2026-09-22",
  "updatedDate": "2026-09-22",
  "interactive": true,
  "supportsColor": true,
  "highlights": [
    "Harmonic spring physics (stiffness 350, damping 32, mass 0.6) with cosine proximity falloff",
    "Zero layout thrashing with cached element geometry during pointer interaction",
    "Hierarchical dash indicators for title, subtitle, section, and body heading depths",
    "Bidirectional scroll synchronization with requestAnimationFrame debouncing",
    "Pure minimalist aesthetics with zero container boxes or borders",
    "Accessible keyboard navigation with Tab and ArrowUp / ArrowDown support"
  ],
  "anatomy": [
    "<nav> (Root container with data-slot='proximity-sidebar' and accessible role)",
    "<button> (Focusable item with generous interactive hit target)",
    "<motion.span> (GPU-accelerated scaleX transformed hairline indicator)"
  ],
  "physics": {
    "engine": "Motion Spring Dynamics + Cosine Proximity Falloff",
    "description": "Cosine-smoothed proximity distance mapping feeding a damped harmonic spring oscillator, resulting in zero-crease elastic expansion and retraction.",
    "parameters": [
      {
        "label": "Proximity Radius",
        "value": "32px default (configurable)"
      },
      {
        "label": "Max Dash Width",
        "value": "90px default (configurable)"
      },
      {
        "label": "Spring Stiffness",
        "value": "350"
      },
      {
        "label": "Damping Coefficient",
        "value": "32"
      },
      {
        "label": "Mass Coefficient",
        "value": "0.6"
      },
      {
        "label": "Falloff Function",
        "value": "cos(dist / radius * (PI / 2))"
      }
    ]
  },
  "accessibility": {
    "role": "navigation",
    "aria": "aria-label on nav container and proximity group, aria-current='location' on active section dash, and descriptive aria-label on all interactive buttons.",
    "reducedMotion": "Bypasses spring physics and transitions instantly when prefers-reduced-motion is active.",
    "keyboard": [
      {
        "key": "Tab / Shift+Tab",
        "description": "Focus into and move through the section dashes."
      },
      {
        "key": "ArrowUp / ArrowDown",
        "description": "Move to the previous or next section and scroll to it."
      },
      {
        "key": "Enter / Space",
        "description": "Activate the focused section and smooth-scroll to its heading."
      }
    ]
  },
  "guidelines": {
    "recommended": [
      "Right or left document minimap for technical documentation, RFCs, and API references",
      "Chapter and section outline for interactive tutorials, case studies, and engineering blog posts",
      "Executive summary navigation for dashboard reports and analytics breakdowns"
    ],
    "bestPractices": [
      "Ensure target section elements have matching id attributes corresponding to section.id",
      "Use appropriate kind or level values to convey clear document hierarchy",
      "Add scroll-margin-top to section targets if using a fixed top navigation bar"
    ]
  },
  "props": [
    {
      "name": "sections",
      "type": "ProximitySection[]",
      "required": true,
      "description": "Array of sections with id, label, and optional kind ('title' | 'subtitle' | 'section' | 'body') or level (1-6)."
    },
    {
      "name": "side",
      "type": "'left' | 'right'",
      "defaultValue": "'left'",
      "description": "Orientation of the sidebar and direction from which dashes expand."
    },
    {
      "name": "color",
      "type": "string",
      "defaultValue": "undefined",
      "description": "Custom accent color for the active section dash."
    },
    {
      "name": "radius",
      "type": "number",
      "defaultValue": "32",
      "description": "Proximity influence radius in pixels around the pointer position."
    },
    {
      "name": "maxDashWidth",
      "type": "number",
      "defaultValue": "90",
      "description": "Maximum horizontal width of the dash in pixels at peak proximity."
    },
    {
      "name": "activeOffset",
      "type": "number",
      "defaultValue": "0.4",
      "description": "Viewport height fraction (0-1) used as the detection anchor for scroll synchronization."
    },
    {
      "name": "value",
      "type": "string",
      "defaultValue": "undefined",
      "description": "Controlled active section id."
    },
    {
      "name": "defaultValue",
      "type": "string",
      "defaultValue": "undefined",
      "description": "Initial section id highlighted when rendered in uncontrolled mode."
    },
    {
      "name": "onChange",
      "type": "(id: string) => void",
      "defaultValue": "undefined",
      "description": "Callback invoked whenever a section is activated by click or keyboard navigation."
    },
    {
      "name": "className",
      "type": "string",
      "defaultValue": "undefined",
      "description": "Additional CSS classes applied to the outer <nav> element."
    }
  ],
  "usage": "import { ProximitySidebar } from \"@/components/ui/proximity-sidebar\";\n\nconst sections = [\n  { id: \"intro\", label: \"Introduction\" },\n  { id: \"install\", label: \"Installation\" },\n  { id: \"usage\", label: \"Usage\" },\n];\n\nexport function ProximitySidebarDemo() {\n  return <ProximitySidebar sections={sections} />;\n}",
  "files": [
    {
      "name": "proximity-sidebar.tsx",
      "path": "registry/ui/proximity-sidebar.tsx",
      "code": "\"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\";\nimport { cn } from \"@/lib/utils\";\n\nexport type Side = \"left\" | \"right\";\nexport type SectionKind = \"title\" | \"subtitle\" | \"section\" | \"body\";\nexport type 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  color?: string;\n  maxDashWidth: number;\n  mouseY: MotionValue<number>;\n  onSelect: (id: string) => void;\n  radius: number;\n  registerDash: (id: string, node: HTMLButtonElement | null) => void;\n  section: ProximitySection;\n  sectionKind: SectionKind;\n  side: Side;\n};\n\nexport type ProximitySidebarProps = {\n  activeOffset?: number;\n  className?: string;\n  color?: string;\n  defaultValue?: string;\n  maxDashWidth?: number;\n  onChange?: (id: string) => void;\n  radius?: number;\n  sections: ProximitySection[];\n  side?: Side;\n  value?: string;\n};\n\nconst DEFAULT_RADIUS = 32;\nconst DEFAULT_MAX_DASH_WIDTH = 90;\nconst SCROLL_IDLE_RESET_DELAY = 80;\n\nconst DASH_PRESETS: Record<SectionKind, DashPreset> = {\n  title: {\n    base: 54,\n    bump: 36,\n    thickness: 1.5,\n    className: \"bg-zinc-900 dark:bg-white\",\n  },\n  subtitle: {\n    base: 40,\n    bump: 42,\n    thickness: 1.25,\n    className: \"bg-zinc-700 dark:bg-zinc-300\",\n  },\n  section: {\n    base: 26,\n    bump: 48,\n    thickness: 1,\n    className: \"bg-zinc-400 dark:bg-zinc-600\",\n  },\n  body: {\n    base: 22,\n    bump: 48,\n    thickness: 1,\n    className: \"bg-zinc-400 dark:bg-zinc-600\",\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 getScrollParent = (element: HTMLElement): EventTarget => {\n  let parent = element.parentElement;\n\n  while (parent) {\n    const { overflowY } = window.getComputedStyle(parent);\n    if (/(auto|scroll|overlay)/.test(overflowY)) {\n      return parent;\n    }\n    parent = parent.parentElement;\n  }\n\n  return window;\n};\n\nconst Dash = ({\n  active,\n  color,\n  maxDashWidth,\n  mouseY,\n  onSelect,\n  radius,\n  registerDash,\n  section,\n  sectionKind,\n  side,\n}: DashProps) => {\n  const ref = useRef<HTMLButtonElement>(null);\n  const centerCoord = useRef<number | null>(null);\n  const shouldReduceMotion = useReducedMotion();\n\n  const preset = DASH_PRESETS[sectionKind];\n\n  useEffect(() => {\n    registerDash(section.id, ref.current);\n    return () => registerDash(section.id, null);\n  }, [registerDash, section.id]);\n\n  useEffect(() => {\n    const invalidate = () => {\n      centerCoord.current = null;\n    };\n\n    window.addEventListener(\"resize\", invalidate, { passive: true });\n    window.addEventListener(\"scroll\", invalidate, { passive: true });\n\n    return () => {\n      window.removeEventListener(\"resize\", invalidate);\n      window.removeEventListener(\"scroll\", invalidate);\n    };\n  }, []);\n\n  const targetScaleX = useTransform(mouseY, (y) => {\n    if (!ref.current || y === Infinity) {\n      return preset.base / maxDashWidth;\n    }\n\n    if (centerCoord.current === null) {\n      const rect = ref.current.getBoundingClientRect();\n      centerCoord.current = rect.top + rect.height / 2;\n    }\n\n    const dist = Math.abs(y - centerCoord.current);\n    if (dist >= radius) {\n      return preset.base / maxDashWidth;\n    }\n\n    const factor = Math.cos((dist / radius) * (Math.PI / 2));\n    const width = preset.base + preset.bump * factor;\n    return Math.min(1, width / maxDashWidth);\n  });\n\n  const springScaleX = useSpring(targetScaleX, {\n    stiffness: 350,\n    damping: 32,\n    mass: 0.6,\n  });\n\n  const scaleX = shouldReduceMotion ? targetScaleX : springScaleX;\n\n  return (\n    <button\n      ref={ref}\n      type=\"button\"\n      data-slot=\"proximity-dash\"\n      data-active={active}\n      aria-current={active ? \"location\" : undefined}\n      aria-label={`Go to ${section.label}`}\n      title={section.label}\n      className={cn(\n        \"group relative flex h-1.5 pointer-coarse:h-6 items-center border-0 bg-transparent p-0 outline-none select-none cursor-pointer\",\n        side === \"right\" ? \"justify-end\" : \"justify-start\",\n      )}\n      style={{ width: maxDashWidth }}\n      onClick={() => onSelect(section.id)}\n    >\n      <motion.span\n        className={cn(\n          \"block rounded-full transition-colors duration-200 group-focus-visible:ring-2 group-focus-visible:ring-white group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-black will-change-transform\",\n          active\n            ? color\n              ? \"\"\n              : \"bg-zinc-950 dark:bg-white shadow-[0_0_8px_rgba(0,0,0,0.3)] dark:shadow-[0_0_8px_rgba(255,255,255,0.7)]\"\n            : cn(\n                preset.className,\n                \"group-hover:bg-zinc-950 dark:group-hover:bg-zinc-200\",\n              ),\n        )}\n        style={{\n          backgroundColor: active && color ? color : undefined,\n          boxShadow: active && color ? `0 0 10px ${color}80` : undefined,\n          height: preset.thickness,\n          scaleX,\n          transformOrigin: side === \"left\" ? \"left center\" : \"right center\",\n          width: maxDashWidth,\n        }}\n      />\n    </button>\n  );\n};\n\nexport function ProximitySidebar({\n  activeOffset = 0.4,\n  className,\n  color,\n  defaultValue,\n  maxDashWidth = DEFAULT_MAX_DASH_WIDTH,\n  onChange,\n  radius = DEFAULT_RADIUS,\n  sections,\n  side = \"left\",\n  value,\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\n  const [internalActiveId, setInternalActiveId] = useState<string>(\n    defaultValue ?? sections[0]?.id ?? \"\",\n  );\n\n  const activeId = value !== undefined ? value : internalActiveId;\n\n  const sectionKinds = useMemo(() => {\n    return sections.reduce<Record<string, SectionKind>>(\n      (nextKinds, section) => {\n        nextKinds[section.id] = getSectionKind(section);\n        return nextKinds;\n      },\n      {},\n    );\n  }, [sections]);\n\n  const registerDash = useCallback(\n    (id: string, node: HTMLButtonElement | null) => {\n      if (node) {\n        dashRefs.current.set(id, node);\n      } else {\n        dashRefs.current.delete(id);\n      }\n    },\n    [],\n  );\n\n  const clearPendingReset = useCallback(() => {\n    if (!resetTimer.current) return;\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) {\n        element.scrollIntoView({\n          behavior: shouldReduceMotion ? \"auto\" : \"smooth\",\n          block: \"start\",\n        });\n        window.history.replaceState(null, \"\", `#${id}`);\n      }\n\n      if (value === undefined) {\n        setInternalActiveId(id);\n      }\n      onChange?.(id);\n      pulseDash(id);\n    },\n    [onChange, pulseDash, shouldReduceMotion, value],\n  );\n\n  const handleKeyDown = useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (event.key !== \"ArrowDown\" && event.key !== \"ArrowUp\") return;\n      const focusedId = sections.find(\n        (s) => dashRefs.current.get(s.id) === event.target,\n      )?.id;\n      const currentIndex = sections.findIndex(\n        (s) => s.id === (focusedId ?? activeId),\n      );\n      if (currentIndex === -1) return;\n\n      event.preventDefault();\n      const nextIndex =\n        event.key === \"ArrowDown\"\n          ? Math.min(sections.length - 1, currentIndex + 1)\n          : Math.max(0, currentIndex - 1);\n      const nextId = sections[nextIndex].id;\n      selectSection(nextId);\n      dashRefs.current.get(nextId)?.focus();\n    },\n    [activeId, sections, selectSection],\n  );\n\n  useEffect(() => () => clearPendingReset(), [clearPendingReset]);\n\n  useEffect(() => {\n    if (!sections.length) return;\n\n    let frame = 0;\n\n    const scrollParents = new Set<EventTarget>([window]);\n\n    for (const section of sections) {\n      const element = getSectionElement(section.id);\n      if (element) {\n        scrollParents.add(getScrollParent(element));\n      }\n    }\n\n    const updateActiveSection = () => {\n      frame = 0;\n\n      let primaryScrollElement: HTMLElement | null = null;\n      for (const parent of scrollParents) {\n        if (\n          parent instanceof HTMLElement &&\n          parent !== document.body &&\n          parent !== document.documentElement\n        ) {\n          primaryScrollElement = parent;\n          break;\n        }\n      }\n\n      if (primaryScrollElement) {\n        if (primaryScrollElement.scrollTop <= 15) {\n          const firstId = sections[0]?.id;\n          if (firstId) {\n            if (value === undefined) setInternalActiveId(firstId);\n            if (!pointerInside.current) pulseDash(firstId);\n          }\n          return;\n        }\n\n        if (\n          primaryScrollElement.scrollTop + primaryScrollElement.clientHeight >=\n          primaryScrollElement.scrollHeight - 15\n        ) {\n          const lastId = sections[sections.length - 1]?.id;\n          if (lastId) {\n            if (value === undefined) setInternalActiveId(lastId);\n            if (!pointerInside.current) pulseDash(lastId);\n          }\n          return;\n        }\n      } else if (typeof window !== \"undefined\") {\n        if (window.scrollY <= 15) {\n          const firstId = sections[0]?.id;\n          if (firstId) {\n            if (value === undefined) setInternalActiveId(firstId);\n            if (!pointerInside.current) pulseDash(firstId);\n          }\n          return;\n        }\n\n        if (\n          window.scrollY + window.innerHeight >=\n          document.documentElement.scrollHeight - 15\n        ) {\n          const lastId = sections[sections.length - 1]?.id;\n          if (lastId) {\n            if (value === undefined) setInternalActiveId(lastId);\n            if (!pointerInside.current) pulseDash(lastId);\n          }\n          return;\n        }\n      }\n\n      const anchorY = primaryScrollElement\n        ? primaryScrollElement.getBoundingClientRect().top + 50\n        : 60;\n\n      let nextActiveId = sections[0]?.id;\n\n      for (const section of sections) {\n        const element = getSectionElement(section.id);\n        if (!element) continue;\n\n        const rect = element.getBoundingClientRect();\n        if (rect.top <= anchorY) {\n          nextActiveId = section.id;\n        }\n      }\n\n      if (nextActiveId) {\n        if (value === undefined) {\n          setInternalActiveId(nextActiveId);\n        }\n        if (!pointerInside.current) {\n          pulseDash(nextActiveId);\n        }\n      }\n    };\n\n    const scheduleUpdate = () => {\n      if (frame) return;\n      frame = window.requestAnimationFrame(updateActiveSection);\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, sections, value]);\n\n  return (\n    <nav\n      data-slot=\"proximity-sidebar\"\n      aria-label=\"Page sections\"\n      className={cn(\n        \"flex select-none\",\n        side === \"left\" ? \"justify-start\" : \"justify-end\",\n        className,\n      )}\n    >\n      <div\n        role=\"group\"\n        tabIndex={0}\n        aria-label=\"Proximity navigation\"\n        onKeyDown={handleKeyDown}\n        className={cn(\n          \"flex flex-col outline-none gap-1 pointer-coarse:gap-0 py-0\",\n          side === \"right\" ? \"items-end\" : \"items-start\",\n        )}\n        onPointerEnter={(event) => {\n          clearPendingReset();\n          pointerInside.current = true;\n          mouseY.set(event.clientY);\n        }}\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            color={color}\n            maxDashWidth={maxDashWidth}\n            mouseY={mouseY}\n            onSelect={selectSection}\n            radius={radius}\n            registerDash={registerDash}\n            section={section}\n            sectionKind={sectionKinds[section.id] ?? getSectionKind(section)}\n            side={side}\n          />\n        ))}\n      </div>\n    </nav>\n  );\n}\n\nexport default ProximitySidebar;\n",
      "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\";\nimport { cn } from \"@/lib/utils\";\n\nexport type Side = \"left\" | \"right\";\nexport type SectionKind = \"title\" | \"subtitle\" | \"section\" | \"body\";\nexport type 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  color?: string;\n  maxDashWidth: number;\n  mouseY: MotionValue<number>;\n  onSelect: (id: string) => void;\n  radius: number;\n  registerDash: (id: string, node: HTMLButtonElement | null) => void;\n  section: ProximitySection;\n  sectionKind: SectionKind;\n  side: Side;\n};\n\nexport type ProximitySidebarProps = {\n  activeOffset?: number;\n  className?: string;\n  color?: string;\n  defaultValue?: string;\n  maxDashWidth?: number;\n  onChange?: (id: string) => void;\n  radius?: number;\n  sections: ProximitySection[];\n  side?: Side;\n  value?: string;\n};\n\nconst DEFAULT_RADIUS = 32;\nconst DEFAULT_MAX_DASH_WIDTH = 90;\nconst SCROLL_IDLE_RESET_DELAY = 80;\n\nconst DASH_PRESETS: Record<SectionKind, DashPreset> = {\n  title: {\n    base: 54,\n    bump: 36,\n    thickness: 1.5,\n    className: \"bg-zinc-900 dark:bg-white\",\n  },\n  subtitle: {\n    base: 40,\n    bump: 42,\n    thickness: 1.25,\n    className: \"bg-zinc-700 dark:bg-zinc-300\",\n  },\n  section: {\n    base: 26,\n    bump: 48,\n    thickness: 1,\n    className: \"bg-zinc-400 dark:bg-zinc-600\",\n  },\n  body: {\n    base: 22,\n    bump: 48,\n    thickness: 1,\n    className: \"bg-zinc-400 dark:bg-zinc-600\",\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 getScrollParent = (element: HTMLElement): EventTarget => {\n  let parent = element.parentElement;\n\n  while (parent) {\n    const { overflowY } = window.getComputedStyle(parent);\n    if (/(auto|scroll|overlay)/.test(overflowY)) {\n      return parent;\n    }\n    parent = parent.parentElement;\n  }\n\n  return window;\n};\n\nconst Dash = ({\n  active,\n  color,\n  maxDashWidth,\n  mouseY,\n  onSelect,\n  radius,\n  registerDash,\n  section,\n  sectionKind,\n  side,\n}: DashProps) => {\n  const ref = useRef<HTMLButtonElement>(null);\n  const centerCoord = useRef<number | null>(null);\n  const shouldReduceMotion = useReducedMotion();\n\n  const preset = DASH_PRESETS[sectionKind];\n\n  useEffect(() => {\n    registerDash(section.id, ref.current);\n    return () => registerDash(section.id, null);\n  }, [registerDash, section.id]);\n\n  useEffect(() => {\n    const invalidate = () => {\n      centerCoord.current = null;\n    };\n\n    window.addEventListener(\"resize\", invalidate, { passive: true });\n    window.addEventListener(\"scroll\", invalidate, { passive: true });\n\n    return () => {\n      window.removeEventListener(\"resize\", invalidate);\n      window.removeEventListener(\"scroll\", invalidate);\n    };\n  }, []);\n\n  const targetScaleX = useTransform(mouseY, (y) => {\n    if (!ref.current || y === Infinity) {\n      return preset.base / maxDashWidth;\n    }\n\n    if (centerCoord.current === null) {\n      const rect = ref.current.getBoundingClientRect();\n      centerCoord.current = rect.top + rect.height / 2;\n    }\n\n    const dist = Math.abs(y - centerCoord.current);\n    if (dist >= radius) {\n      return preset.base / maxDashWidth;\n    }\n\n    const factor = Math.cos((dist / radius) * (Math.PI / 2));\n    const width = preset.base + preset.bump * factor;\n    return Math.min(1, width / maxDashWidth);\n  });\n\n  const springScaleX = useSpring(targetScaleX, {\n    stiffness: 350,\n    damping: 32,\n    mass: 0.6,\n  });\n\n  const scaleX = shouldReduceMotion ? targetScaleX : springScaleX;\n\n  return (\n    <button\n      ref={ref}\n      type=\"button\"\n      data-slot=\"proximity-dash\"\n      data-active={active}\n      aria-current={active ? \"location\" : undefined}\n      aria-label={`Go to ${section.label}`}\n      title={section.label}\n      className={cn(\n        \"group relative flex h-1.5 pointer-coarse:h-6 items-center border-0 bg-transparent p-0 outline-none select-none cursor-pointer\",\n        side === \"right\" ? \"justify-end\" : \"justify-start\",\n      )}\n      style={{ width: maxDashWidth }}\n      onClick={() => onSelect(section.id)}\n    >\n      <motion.span\n        className={cn(\n          \"block rounded-full transition-colors duration-200 group-focus-visible:ring-2 group-focus-visible:ring-white group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-black will-change-transform\",\n          active\n            ? color\n              ? \"\"\n              : \"bg-zinc-950 dark:bg-white shadow-[0_0_8px_rgba(0,0,0,0.3)] dark:shadow-[0_0_8px_rgba(255,255,255,0.7)]\"\n            : cn(\n                preset.className,\n                \"group-hover:bg-zinc-950 dark:group-hover:bg-zinc-200\",\n              ),\n        )}\n        style={{\n          backgroundColor: active && color ? color : undefined,\n          boxShadow: active && color ? `0 0 10px ${color}80` : undefined,\n          height: preset.thickness,\n          scaleX,\n          transformOrigin: side === \"left\" ? \"left center\" : \"right center\",\n          width: maxDashWidth,\n        }}\n      />\n    </button>\n  );\n};\n\nexport function ProximitySidebar({\n  activeOffset = 0.4,\n  className,\n  color,\n  defaultValue,\n  maxDashWidth = DEFAULT_MAX_DASH_WIDTH,\n  onChange,\n  radius = DEFAULT_RADIUS,\n  sections,\n  side = \"left\",\n  value,\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\n  const [internalActiveId, setInternalActiveId] = useState<string>(\n    defaultValue ?? sections[0]?.id ?? \"\",\n  );\n\n  const activeId = value !== undefined ? value : internalActiveId;\n\n  const sectionKinds = useMemo(() => {\n    return sections.reduce<Record<string, SectionKind>>(\n      (nextKinds, section) => {\n        nextKinds[section.id] = getSectionKind(section);\n        return nextKinds;\n      },\n      {},\n    );\n  }, [sections]);\n\n  const registerDash = useCallback(\n    (id: string, node: HTMLButtonElement | null) => {\n      if (node) {\n        dashRefs.current.set(id, node);\n      } else {\n        dashRefs.current.delete(id);\n      }\n    },\n    [],\n  );\n\n  const clearPendingReset = useCallback(() => {\n    if (!resetTimer.current) return;\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) {\n        element.scrollIntoView({\n          behavior: shouldReduceMotion ? \"auto\" : \"smooth\",\n          block: \"start\",\n        });\n        window.history.replaceState(null, \"\", `#${id}`);\n      }\n\n      if (value === undefined) {\n        setInternalActiveId(id);\n      }\n      onChange?.(id);\n      pulseDash(id);\n    },\n    [onChange, pulseDash, shouldReduceMotion, value],\n  );\n\n  const handleKeyDown = useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (event.key !== \"ArrowDown\" && event.key !== \"ArrowUp\") return;\n      const focusedId = sections.find(\n        (s) => dashRefs.current.get(s.id) === event.target,\n      )?.id;\n      const currentIndex = sections.findIndex(\n        (s) => s.id === (focusedId ?? activeId),\n      );\n      if (currentIndex === -1) return;\n\n      event.preventDefault();\n      const nextIndex =\n        event.key === \"ArrowDown\"\n          ? Math.min(sections.length - 1, currentIndex + 1)\n          : Math.max(0, currentIndex - 1);\n      const nextId = sections[nextIndex].id;\n      selectSection(nextId);\n      dashRefs.current.get(nextId)?.focus();\n    },\n    [activeId, sections, selectSection],\n  );\n\n  useEffect(() => () => clearPendingReset(), [clearPendingReset]);\n\n  useEffect(() => {\n    if (!sections.length) return;\n\n    let frame = 0;\n\n    const scrollParents = new Set<EventTarget>([window]);\n\n    for (const section of sections) {\n      const element = getSectionElement(section.id);\n      if (element) {\n        scrollParents.add(getScrollParent(element));\n      }\n    }\n\n    const updateActiveSection = () => {\n      frame = 0;\n\n      let primaryScrollElement: HTMLElement | null = null;\n      for (const parent of scrollParents) {\n        if (\n          parent instanceof HTMLElement &&\n          parent !== document.body &&\n          parent !== document.documentElement\n        ) {\n          primaryScrollElement = parent;\n          break;\n        }\n      }\n\n      if (primaryScrollElement) {\n        if (primaryScrollElement.scrollTop <= 15) {\n          const firstId = sections[0]?.id;\n          if (firstId) {\n            if (value === undefined) setInternalActiveId(firstId);\n            if (!pointerInside.current) pulseDash(firstId);\n          }\n          return;\n        }\n\n        if (\n          primaryScrollElement.scrollTop + primaryScrollElement.clientHeight >=\n          primaryScrollElement.scrollHeight - 15\n        ) {\n          const lastId = sections[sections.length - 1]?.id;\n          if (lastId) {\n            if (value === undefined) setInternalActiveId(lastId);\n            if (!pointerInside.current) pulseDash(lastId);\n          }\n          return;\n        }\n      } else if (typeof window !== \"undefined\") {\n        if (window.scrollY <= 15) {\n          const firstId = sections[0]?.id;\n          if (firstId) {\n            if (value === undefined) setInternalActiveId(firstId);\n            if (!pointerInside.current) pulseDash(firstId);\n          }\n          return;\n        }\n\n        if (\n          window.scrollY + window.innerHeight >=\n          document.documentElement.scrollHeight - 15\n        ) {\n          const lastId = sections[sections.length - 1]?.id;\n          if (lastId) {\n            if (value === undefined) setInternalActiveId(lastId);\n            if (!pointerInside.current) pulseDash(lastId);\n          }\n          return;\n        }\n      }\n\n      const anchorY = primaryScrollElement\n        ? primaryScrollElement.getBoundingClientRect().top + 50\n        : 60;\n\n      let nextActiveId = sections[0]?.id;\n\n      for (const section of sections) {\n        const element = getSectionElement(section.id);\n        if (!element) continue;\n\n        const rect = element.getBoundingClientRect();\n        if (rect.top <= anchorY) {\n          nextActiveId = section.id;\n        }\n      }\n\n      if (nextActiveId) {\n        if (value === undefined) {\n          setInternalActiveId(nextActiveId);\n        }\n        if (!pointerInside.current) {\n          pulseDash(nextActiveId);\n        }\n      }\n    };\n\n    const scheduleUpdate = () => {\n      if (frame) return;\n      frame = window.requestAnimationFrame(updateActiveSection);\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, sections, value]);\n\n  return (\n    <nav\n      data-slot=\"proximity-sidebar\"\n      aria-label=\"Page sections\"\n      className={cn(\n        \"flex select-none\",\n        side === \"left\" ? \"justify-start\" : \"justify-end\",\n        className,\n      )}\n    >\n      <div\n        role=\"group\"\n        tabIndex={0}\n        aria-label=\"Proximity navigation\"\n        onKeyDown={handleKeyDown}\n        className={cn(\n          \"flex flex-col outline-none gap-1 pointer-coarse:gap-0 py-0\",\n          side === \"right\" ? \"items-end\" : \"items-start\",\n        )}\n        onPointerEnter={(event) => {\n          clearPendingReset();\n          pointerInside.current = true;\n          mouseY.set(event.clientY);\n        }}\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            color={color}\n            maxDashWidth={maxDashWidth}\n            mouseY={mouseY}\n            onSelect={selectSection}\n            radius={radius}\n            registerDash={registerDash}\n            section={section}\n            sectionKind={sectionKinds[section.id] ?? getSectionKind(section)}\n            side={side}\n          />\n        ))}\n      </div>\n    </nav>\n  );\n}\n\nexport default ProximitySidebar;\n",
      "type": "registry:ui",
      "target": "@ui/proximity-sidebar.tsx"
    }
  ],
  "type": "registry:ui",
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "title": "Proximity Sidebar"
}
