{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "WakeSlider-JS-TW",
	"title": "WakeSlider",
	"description": "Range slider drawn as thin bars with no thumb: drag speed raises a wake that trails behind the handle and flattens again at rest.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "WakeSlider/WakeSlider.jsx",
			"content": "'use client';\n\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport { frame, useMotionValue, useMotionValueEvent, useReducedMotion, useSpring, useVelocity } from 'motion/react';\n\nconst SETTLE = 9.23;\nconst FULL_SPEED = 320;\nconst MIN_REACH = 1.5;\nconst FLAT = 0.002;\n\nconst clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));\nconst smoothstep = s => s * s * (3 - 2 * s);\nconst toPct = (v, min, max) => (max > min ? ((v - min) / (max - min)) * 100 : 0);\nconst snap = (v, min, max, step) => {\n  if (!(max > min)) return min;\n  if (!(step > 0)) return clamp(v, min, max);\n  const lastWhole = min + Math.floor(+((max - min) / step).toFixed(6)) * step;\n  const grid = clamp(Math.round((v - min) / step) * step + min, min, lastWhole);\n  return +(lastWhole < max && Math.abs(v - max) <= Math.abs(v - grid) ? max : grid).toFixed(6);\n};\n\nexport default function WakeSlider({\n  value: valueProp,\n  defaultValue = 50,\n  onChange,\n  min = 0,\n  max = 100,\n  step = 1,\n  bars = 32,\n  height = 56,\n  restHeight = 12,\n  gap = 4,\n  fillColor = '#f5f5f5',\n  trackColor = '#27272a',\n  crestColor = '',\n  sensitivity = 1,\n  reach = 6,\n  skew = 0.6,\n  glide = 0.3,\n  smoothing = 100,\n  showValue = false,\n  formatValue,\n  disabled = false,\n  ariaLabel = 'Value',\n  className = ''\n}) {\n  const [inner, setInner] = useState(defaultValue);\n  const value = clamp(valueProp ?? inner, min, max);\n  const pct = toPct(value, min, max);\n  const reduce = useReducedMotion();\n  const format = formatValue ?? String;\n  const rest = Math.min(restHeight, height - 1) / height;\n\n  const rootRef = useRef(null);\n  const trackRef = useRef(null);\n  const handleRef = useRef(null);\n  const barEls = useRef([]);\n  const crestEls = useRef([]);\n  const pointerId = useRef(null);\n  const lastAmp = useRef(0);\n  const latest = useRef(value);\n  latest.current = value;\n\n  const target = useMotionValue(pct);\n  const w0 = SETTLE / glide;\n  const head = useSpring(target, { stiffness: w0 * w0, damping: 2 * w0, mass: 1 });\n  const rawSpeed = useVelocity(head);\n  const wv = 2000 / smoothing;\n  const speed = useSpring(rawSpeed, { stiffness: wv * wv, damping: 2.5 * wv, mass: 1 });\n\n  useEffect(() => {\n    target.set(pct);\n  }, [pct, target]);\n\n  const paint = (force = false) => {\n    const h = ((reduce ? target.get() : head.get()) / 100) * (bars - 1);\n    const v = reduce ? 0 : speed.get();\n    const amp = smoothstep(clamp((Math.abs(v) * sensitivity) / FULL_SPEED, 0, 1));\n    const dir = Math.sign(v) || 1;\n    const r = MIN_REACH + (reach - MIN_REACH) * amp;\n    const behind = r * (1 + skew);\n    const ahead = r * (1 - 0.5 * skew);\n    const lit = Math.round(h);\n    const flat = !force && amp < FLAT && lastAmp.current < FLAT;\n    for (let i = 0; i < bars; i++) {\n      const el = barEls.current[i];\n      if (!el) continue;\n      const on = i <= lit ? 'true' : 'false';\n      if (el.dataset.on !== on) el.dataset.on = on;\n      if (flat) continue;\n      const d = i - h;\n      const R = d * dir < 0 ? behind : ahead;\n      const lift = Math.abs(d) < R ? amp * Math.cos((Math.PI * d) / (2 * R)) ** 2 : 0;\n      el.style.transform = `scaleY(${rest + lift * (1 - rest)})`;\n      const crest = crestEls.current[i];\n      if (crest) crest.style.opacity = String(lift);\n    }\n    lastAmp.current = amp;\n  };\n  const paintRef = useRef(paint);\n  paintRef.current = paint;\n  const run = useCallback(() => paintRef.current(), []);\n  const schedule = useCallback(() => frame.render(run, false, true), [run]);\n  useMotionValueEvent(head, 'change', schedule);\n  useMotionValueEvent(speed, 'change', schedule);\n  useLayoutEffect(() => {\n    paintRef.current(true);\n  });\n\n  const commit = next => {\n    const clean = snap(next, min, max, step);\n    if (clean === latest.current) return;\n    latest.current = clean;\n    if (valueProp === undefined) setInner(clean);\n    onChange?.(clean);\n  };\n  const commitFromX = x => {\n    const track = trackRef.current;\n    if (!track) return;\n    const rect = track.getBoundingClientRect();\n    if (!rect.width) return;\n    let ratio = clamp((x - rect.left) / rect.width, 0, 1);\n    if (getComputedStyle(track).direction === 'rtl') ratio = 1 - ratio;\n    commit(min + ratio * (max - min));\n  };\n  const onPointerDown = e => {\n    if (disabled || pointerId.current !== null) return;\n    pointerId.current = e.pointerId;\n    try {\n      e.currentTarget.setPointerCapture(e.pointerId);\n    } catch {}\n    rootRef.current?.removeAttribute('data-instant');\n    handleRef.current?.focus({ preventScroll: true });\n    commitFromX(e.clientX);\n  };\n  const onPointerMove = e => {\n    if (e.pointerId === pointerId.current) commitFromX(e.clientX);\n  };\n  const endDrag = e => {\n    if (e.pointerId !== pointerId.current) return;\n    try {\n      e.currentTarget.releasePointerCapture(e.pointerId);\n    } catch {}\n    pointerId.current = null;\n  };\n  const onKeyDown = e => {\n    if (disabled) return;\n    const jumps = {\n      ArrowRight: value + step,\n      ArrowUp: value + step,\n      ArrowLeft: value - step,\n      ArrowDown: value - step,\n      PageUp: value + step * 10,\n      PageDown: value - step * 10,\n      Home: min,\n      End: max\n    };\n    if (!(e.key in jumps)) return;\n    e.preventDefault();\n    const clean = snap(jumps[e.key], min, max, step);\n    const p = toPct(clean, min, max);\n    rootRef.current?.setAttribute('data-instant', 'true');\n    head.jump(p);\n    speed.jump(0);\n    target.jump(p);\n    commit(clean);\n  };\n\n  const barNodes = useMemo(() => {\n    barEls.current.length = bars;\n    crestEls.current.length = bars;\n    return Array.from({ length: bars }, (_, i) => (\n      <span\n        key={i}\n        ref={el => {\n          barEls.current[i] = el;\n        }}\n        className=\"relative h-[var(--ws-height)] min-w-0 flex-1 origin-center rounded-full bg-[var(--ws-track)] [transform:scaleY(var(--ws-rest))] [transition:background-color_120ms_ease] data-[on=true]:bg-[var(--ws-fill)] group-data-[instant=true]:[transition-duration:0ms] motion-reduce:[transform:scaleY(var(--ws-rest))]!\"\n      >\n        {crestColor ? (\n          <span\n            ref={el => {\n              crestEls.current[i] = el;\n            }}\n            className=\"absolute inset-0 rounded-[inherit] bg-[var(--ws-crest)] opacity-0\"\n          />\n        ) : null}\n      </span>\n    ));\n  }, [bars, crestColor]);\n\n  return (\n    <div\n      ref={rootRef}\n      className={`group inline-flex w-full items-center gap-3 [color:inherit] aria-disabled:pointer-events-none aria-disabled:opacity-50${className ? ` ${className}` : ''}`}\n      aria-disabled={disabled || undefined}\n      style={{\n        '--ws-fill': fillColor,\n        '--ws-track': trackColor,\n        '--ws-crest': crestColor || fillColor,\n        '--ws-height': `${height}px`,\n        '--ws-gap': `${gap}px`,\n        '--ws-rest': rest\n      }}\n    >\n      <div\n        ref={trackRef}\n        className=\"relative flex h-[var(--ws-height)] min-h-11 flex-1 touch-none items-center gap-[var(--ws-gap)] select-none [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [@media(hover:hover)_and_(pointer:fine)]:cursor-grab [@media(hover:hover)_and_(pointer:fine)]:active:cursor-grabbing\"\n        onPointerDown={onPointerDown}\n        onPointerMove={onPointerMove}\n        onPointerUp={endDrag}\n        onPointerCancel={endDrag}\n        onLostPointerCapture={endDrag}\n      >\n        {barNodes}\n        <button\n          ref={handleRef}\n          type=\"button\"\n          role=\"slider\"\n          className=\"absolute inset-0 m-0 cursor-[inherit] touch-none rounded-xl border-0 bg-transparent p-0 outline-none [-webkit-tap-highlight-color:transparent]\"\n          tabIndex={disabled ? -1 : 0}\n          aria-label={ariaLabel}\n          aria-valuemin={min}\n          aria-valuemax={max}\n          aria-valuenow={value}\n          aria-valuetext={format(value)}\n          aria-disabled={disabled || undefined}\n          onKeyDown={onKeyDown}\n        />\n      </div>\n      {showValue ? (\n        <span className=\"min-w-[3ch] text-right text-[13px] tabular-nums opacity-60\" aria-hidden=\"true\">\n          {format(value)}\n        </span>\n      ) : null}\n    </div>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}