{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "LineSidebar-JS-CSS",
	"title": "LineSidebar",
	"description": "Static list navigation with a cursor-proximity effect that shifts and highlights nearby items.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "LineSidebar.css",
			"target": "@components/LineSidebar.css",
			"content": ".line-sidebar {\n  --accent-color: #a855f7;\n  --text-color: #c4c4c4;\n  --marker-color: #6c6c6c;\n  --marker-length: 60px;\n  --marker-gap: 0px;\n  --tick-scale: 0.5;\n  --max-shift: 30px;\n  --item-gap: 20px;\n  --font-size: 1.1rem;\n  --smoothing: 100ms;\n\n  position: relative;\n  display: flex;\n  justify-content: flex-start;\n}\n\n.line-sidebar--markers {\n  padding-left: calc(var(--marker-length) + var(--marker-gap));\n}\n\n.line-sidebar__list {\n  list-style: none;\n  margin: 0;\n  padding: 1rem 0;\n  display: flex;\n  flex-direction: column;\n  gap: var(--item-gap);\n}\n\n/* --effect (0..1) is driven per item by a rAF lerp in JS, so every derived\n   property below reads the same continuously-animating value and stays in\n   step, with no CSS transitions to stagger. */\n.line-sidebar__item {\n  position: relative;\n  cursor: pointer;\n}\n\n/* Widen the pointer target so items react a touch before the cursor arrives */\n.line-sidebar__item::before {\n  content: '';\n  position: absolute;\n  inset: -6px -48px;\n}\n\n.line-sidebar__label {\n  position: relative;\n  display: inline-flex;\n  align-items: baseline;\n  font-size: var(--font-size);\n  line-height: 1.2;\n  color: color-mix(in srgb, var(--accent-color) calc(var(--effect, 0) * 100%), var(--text-color));\n  transform: translateX(calc(var(--effect, 0) * var(--max-shift)));\n}\n\n.line-sidebar__index {\n  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n  margin-right: 0.6rem;\n  font-size: 0.85em;\n  opacity: calc(0.55 + var(--effect, 0) * 0.45);\n}\n\n.line-sidebar__marker {\n  position: absolute;\n  top: 50%;\n  left: calc(-1 * var(--marker-length) - var(--marker-gap));\n  height: 1px;\n  width: var(--marker-length);\n  background-color: color-mix(in srgb, var(--accent-color) calc(var(--effect, 0) * 100%), var(--marker-color));\n  transform-origin: left center;\n  transform: translateY(-50%) scaleX(calc(0.7 + var(--effect, 0) * 0.5));\n}\n\n/* Short static tick centered in the gap between two menu items */\n.line-sidebar--markers .line-sidebar__item:not(:last-child)::after {\n  content: '';\n  position: absolute;\n  top: calc(100% + var(--item-gap) / 2);\n  left: calc(-1 * var(--marker-length) - var(--marker-gap));\n  height: 1px;\n  width: calc(var(--marker-length) * var(--tick-scale));\n  background-color: var(--marker-color);\n  opacity: 0.5;\n  transform: translateY(-50%);\n}\n\n/* When enabled, the in-between ticks grow with cursor proximity too */\n.line-sidebar--scale-tick .line-sidebar__item:not(:last-child)::after {\n  transform-origin: left center;\n  transform: translateY(-50%) scaleX(calc(0.7 + var(--effect, 0) * 0.6));\n}\n"
		},
		{
			"type": "registry:component",
			"path": "LineSidebar.jsx",
			"content": "import { useRef, useState, useCallback, useEffect } from 'react';\nimport './LineSidebar.css';\n\nconst FALLOFF_CURVES = {\n  linear: p => p,\n  smooth: p => p * p * (3 - 2 * p),\n  sharp: p => p * p * p\n};\n\nconst DEFAULT_ITEMS = [\n  'Overview',\n  'Components',\n  'Animations',\n  'Backgrounds',\n  'Showcase',\n  'Playground',\n  'Templates',\n  'Changelog',\n  'Community',\n  'Resources',\n  'Documentation',\n  'Support'\n];\n\nconst LineSidebar = ({\n  items = DEFAULT_ITEMS,\n  accentColor = '#A855F7',\n  textColor = '#c4c4c4',\n  markerColor = '#6c6c6c',\n  showIndex = true,\n  showMarker = true,\n  proximityRadius = 100,\n  maxShift = 30,\n  falloff = 'smooth',\n  markerLength = 60,\n  markerGap = 0,\n  tickScale = 0.5,\n  scaleTick = true,\n  itemGap = 20,\n  fontSize = 1.1,\n  smoothing = 100,\n  defaultActive = null,\n  onItemClick,\n  className = ''\n}) => {\n  const listRef = useRef(null);\n  const itemRefs = useRef([]);\n  const targetsRef = useRef([]);\n  const currentRef = useRef([]);\n  const rafRef = useRef(null);\n  const lastRef = useRef(0);\n  const activeRef = useRef(defaultActive);\n  const smoothingRef = useRef(smoothing);\n  const [activeIndex, setActiveIndex] = useState(defaultActive);\n\n  activeRef.current = activeIndex;\n  smoothingRef.current = smoothing;\n\n  // Single rAF loop that eases every item's --effect toward its target using\n  // frame-rate independent exponential smoothing, so color, shift and scale\n  // all move together without staggering CSS transitions.\n  const runFrame = useCallback(now => {\n    const dt = Math.min((now - lastRef.current) / 1000, 0.05);\n    lastRef.current = now;\n    const tau = Math.max(smoothingRef.current, 1) / 1000;\n    const k = 1 - Math.exp(-dt / tau);\n\n    let moving = false;\n    const items = itemRefs.current;\n    for (let i = 0; i < items.length; i++) {\n      const el = items[i];\n      if (!el) continue;\n      const target = Math.max(targetsRef.current[i] || 0, activeRef.current === i ? 1 : 0);\n      const cur = currentRef.current[i] || 0;\n      const next = cur + (target - cur) * k;\n      const settled = Math.abs(target - next) < 0.0015;\n      const value = settled ? target : next;\n      currentRef.current[i] = value;\n      el.style.setProperty('--effect', value.toFixed(4));\n      if (!settled) moving = true;\n    }\n\n    rafRef.current = moving ? requestAnimationFrame(runFrame) : null;\n  }, []);\n\n  const startLoop = useCallback(() => {\n    if (rafRef.current != null) {\n      cancelAnimationFrame(rafRef.current);\n    }\n\n    lastRef.current = performance.now();\n    rafRef.current = requestAnimationFrame(runFrame);\n  }, [runFrame]);\n\n  const handlePointerMove = useCallback(\n    e => {\n      const list = listRef.current;\n      if (!list) return;\n      const rect = list.getBoundingClientRect();\n      const pointerY = e.clientY - rect.top;\n      const ease = FALLOFF_CURVES[falloff] ?? FALLOFF_CURVES.linear;\n      const items = itemRefs.current;\n      for (let i = 0; i < items.length; i++) {\n        const el = items[i];\n        if (!el) continue;\n        const center = el.offsetTop + el.offsetHeight / 2;\n        const distance = Math.abs(pointerY - center);\n        targetsRef.current[i] = ease(Math.max(0, 1 - distance / proximityRadius));\n      }\n      startLoop();\n    },\n    [falloff, proximityRadius, startLoop]\n  );\n\n  const handlePointerLeave = useCallback(() => {\n    targetsRef.current = targetsRef.current.map(() => 0);\n    startLoop();\n  }, [startLoop]);\n\n  const handleClick = useCallback(\n    (index, label) => {\n      setActiveIndex(index);\n      onItemClick?.(index, label);\n    },\n    [onItemClick]\n  );\n\n  useEffect(() => {\n    startLoop();\n  }, [activeIndex, startLoop]);\n\n  useEffect(\n    () => () => {\n      if (rafRef.current != null) cancelAnimationFrame(rafRef.current);\n      rafRef.current = null;\n    },\n    []\n  );\n\n  return (\n    <nav\n      className={`line-sidebar${showMarker ? ' line-sidebar--markers' : ''}${scaleTick ? ' line-sidebar--scale-tick' : ''}${className ? ` ${className}` : ''}`}\n      style={{\n        '--accent-color': accentColor,\n        '--text-color': textColor,\n        '--marker-color': markerColor,\n        '--marker-length': `${markerLength}px`,\n        '--marker-gap': `${markerGap}px`,\n        '--tick-scale': tickScale,\n        '--max-shift': `${maxShift}px`,\n        '--item-gap': `${itemGap}px`,\n        '--font-size': `${fontSize}rem`,\n        '--smoothing': `${smoothing}ms`\n      }}\n    >\n      <ul ref={listRef} className=\"line-sidebar__list\" onPointerMove={handlePointerMove} onPointerLeave={handlePointerLeave}>\n        {items.map((label, index) => (\n          <li\n            key={`${label}-${index}`}\n            ref={el => {\n              itemRefs.current[index] = el;\n            }}\n            className=\"line-sidebar__item\"\n            aria-current={activeIndex === index ? 'true' : undefined}\n            onClick={() => handleClick(index, label)}\n          >\n            {showMarker && <span className=\"line-sidebar__marker\" aria-hidden=\"true\" />}\n            <span className=\"line-sidebar__label\">\n              {showIndex && <span className=\"line-sidebar__index\">{String(index + 1).padStart(2, '0')}</span>}\n              <span className=\"line-sidebar__text\">{label}</span>\n            </span>\n          </li>\n        ))}\n      </ul>\n    </nav>\n  );\n};\n\nexport default LineSidebar;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}