{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "slug": "slider",
  "name": "Slider",
  "title": "Slider",
  "type": "registry:ui",
  "description": "Precision spring-physics slider and range component with rubber-band resistance, release inertia, odometer rolling numbers, and floating tooltips.",
  "summary": "An ultra-smooth, physics-driven slider and range component with 1:1 pointer tracking, iOS-style rubber band resistance past limits, release momentum projection onto the step grid, rolling digit odometer wheels with motion blur, catch-up spring seek on track clicks, and tactile kick springs.",
  "category": "sliders-and-toggles",
  "tags": [
    "slider",
    "range",
    "physics",
    "spring",
    "rubber-band",
    "odometer",
    "motion"
  ],
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "motion"
  ],
  "version": "1.0.0",
  "createdDate": "2026-09-30",
  "updatedDate": "2026-09-30",
  "interactive": true,
  "supportsColor": false,
  "highlights": [
    "Ultra-responsive 1:1 pointer tracking with zero lag and sub-pixel spring settling",
    "iOS-style rubber-band resistance beyond boundaries and between dual range thumbs",
    "Deceleration velocity projection on pointer release with smooth snap-to-grid settlement",
    "Rolling number odometer with spring-driven digit columns, direction awareness, and motion blur",
    "Floating value bubble shown while dragging, on keyboard focus, and for 700ms after release",
    "Catch-up spring physics on track press that glides the thumb smoothly to finger without jumps",
    "Boundary kick bounce animation on keyboard limit hit",
    "Full dual-thumb range support with minimum step distance enforcement",
    "Tick dots and mouse-clickable mark labels with edge alignment",
    "Pure Tailwind CSS styling without external CSS module dependencies"
  ],
  "anatomy": [
    "<div className=\"relative flex flex-col gap-2 w-full\"> (Root container)",
    "<div className=\"flex items-center justify-between\"> (Header with label and RollingNumber readout)",
    "<div className=\"relative flex items-center w-full h-9 py-2 touch-none cursor-pointer\"> (Pointer-capture interactive track area)",
    "<div className=\"relative w-full h-1.5 rounded-full bg-zinc-800\"> (Background track with tick dots)",
    "<motion.div className=\"absolute inset-0 bg-zinc-400\"> (Active progress fill clipped to thumb bounds)",
    "<Thumb> (White circular thumb pill with spring-driven scale, shadow, and floating bubble tooltip)"
  ],
  "physics": {
    "engine": "Time-derived Harmonic Damped Springs with Inertial Deceleration",
    "description": "Custom analytical spring integration converting visual duration and bounce into exact stiffness and damping ratios, coupled with asymptotic rubber-banding and release velocity projection.",
    "parameters": [
      {
        "label": "Thumb Spring",
        "value": "visualDuration: 0.35s, bounce: 0.15"
      },
      {
        "label": "Kick Spring",
        "value": "visualDuration: 0.45s, bounce: 0.10"
      },
      {
        "label": "Rubber Limit",
        "value": "9px asymptotic resistance"
      },
      {
        "label": "Release Carry",
        "value": "rate: 0.099 projected inertia"
      }
    ]
  },
  "accessibility": {
    "role": "slider",
    "aria": "ARIA slider semantics with aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext, and focus-visible indicators.",
    "reducedMotion": "With prefers-reduced-motion, thumb springs, rubber-banding, edge kicks and digit rolls are skipped. The bubble fades in over 150ms with no blur.",
    "keyboard": [
      {
        "key": "ArrowLeft / ArrowDown",
        "description": "Step down by single increment."
      },
      {
        "key": "ArrowRight / ArrowUp",
        "description": "Step up by single increment."
      },
      {
        "key": "Shift + Arrow / PageDown / PageUp",
        "description": "Jump by large step increment."
      },
      {
        "key": "Home / End",
        "description": "Jump to minimum or maximum bound."
      }
    ]
  },
  "props": [
    {
      "name": "label",
      "type": "string",
      "defaultValue": "undefined",
      "description": "Accessible and visible label for the slider.",
      "required": true
    },
    {
      "name": "value",
      "type": "number | [number, number]",
      "defaultValue": "undefined",
      "description": "Controlled value for single thumb or two-thumb range."
    },
    {
      "name": "defaultValue",
      "type": "number | [number, number]",
      "defaultValue": "undefined",
      "description": "Initial uncontrolled value."
    },
    {
      "name": "onValueChange",
      "type": "(value: T) => void",
      "description": "Called on every value change while dragging or using the keyboard."
    },
    {
      "name": "onValueCommit",
      "type": "(value: T) => void",
      "description": "Called when the user finishes a change (pointer release or key press)."
    },
    {
      "name": "min",
      "type": "number",
      "defaultValue": "0",
      "description": "Minimum value boundary."
    },
    {
      "name": "max",
      "type": "number",
      "defaultValue": "100",
      "description": "Maximum value boundary."
    },
    {
      "name": "step",
      "type": "number",
      "defaultValue": "1",
      "description": "Snap step increment."
    },
    {
      "name": "largeStep",
      "type": "number",
      "defaultValue": "about 10% of the range",
      "description": "Step used by Page Up and Page Down."
    },
    {
      "name": "marks",
      "type": "(number | SliderMark)[]",
      "defaultValue": "undefined",
      "description": "Ticks and interactive labels along the track."
    },
    {
      "name": "minStepsBetweenThumbs",
      "type": "number",
      "defaultValue": "0",
      "description": "Minimum distance in steps between the two thumbs of a range slider."
    },
    {
      "name": "format",
      "type": "(value: number) => string",
      "defaultValue": "undefined",
      "description": "Custom number formatting function for readout and bubbles."
    },
    {
      "name": "showValue",
      "type": "boolean",
      "defaultValue": "true",
      "description": "Shows the formatted value readout."
    },
    {
      "name": "thumbLabels",
      "type": "[string, string]",
      "description": "Accessible names for the two thumbs of a range slider. Defaults to \"<label>, minimum\" and \"<label>, maximum\"."
    },
    {
      "name": "start",
      "type": "ReactNode",
      "defaultValue": "undefined",
      "description": "Slot for start icon or controls (e.g. volume mute button)."
    },
    {
      "name": "end",
      "type": "ReactNode",
      "defaultValue": "undefined",
      "description": "Slot for end controls."
    },
    {
      "name": "description",
      "type": "ReactNode",
      "defaultValue": "undefined",
      "description": "Helper or subtitle text placed below the track."
    },
    {
      "name": "name",
      "type": "string",
      "description": "Form field name. Renders hidden inputs so the value is submitted with a form."
    },
    {
      "name": "disabled",
      "type": "boolean",
      "description": "Disables interaction and dims the slider."
    },
    {
      "name": "className",
      "type": "string",
      "description": "Additional CSS classes for the root element."
    }
  ],
  "usage": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Slider } from \"@/components/ui/slider\";\n\nexport function SliderDemo() {\n  const [volume, setVolume] = useState(40);\n\n  return <Slider label=\"Volume\" value={volume} onValueChange={setVolume} />;\n}",
  "files": [
    {
      "name": "slider.tsx",
      "path": "registry/ui/slider.tsx",
      "target": "@ui/slider.tsx",
      "type": "registry:ui",
      "code": "\"use client\";\n\nimport {\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n  type FocusEvent,\n  type KeyboardEvent,\n  type PointerEvent,\n  type ReactNode,\n} from \"react\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n  type MotionStyle,\n  type MotionValue,\n  type Variants,\n} from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type SliderValue = number | [number, number];\n\nexport interface SliderMark {\n  value: number;\n  label?: string;\n}\n\nexport interface SliderProps<T extends SliderValue = number> {\n  label: string;\n  value?: T;\n  defaultValue?: T;\n  onValueChange?: (value: T) => void;\n  onValueCommit?: (value: T) => void;\n  min?: number;\n  max?: number;\n  step?: number;\n  largeStep?: number;\n  marks?: (number | SliderMark)[];\n  minStepsBetweenThumbs?: number;\n  format?: (value: number) => string;\n  showValue?: boolean;\n  thumbLabels?: [string, string];\n  start?: ReactNode;\n  end?: ReactNode;\n  description?: ReactNode;\n  name?: string;\n  disabled?: boolean;\n  className?: string;\n}\n\ntype Part = { key: string; digit: number } | { key: string; text: string };\n\ntype Drag = {\n  pointer: number;\n  index: number | null;\n  grab: number;\n  x: number;\n  raw: number;\n  samples: { t: number; p: number }[];\n};\n\nconst motionTokens = {\n  spring: {\n    snappy: { visualDuration: 0.35, bounce: 0.15 },\n    morph: { visualDuration: 0.45, bounce: 0.1 },\n    smooth: { visualDuration: 0.4, bounce: 0 },\n  },\n  duration: {\n    instant: 0.08,\n    fast: 0.18,\n    normal: 0.3,\n  },\n  blur: {\n    subtle: 1.5,\n    soft: 3,\n  },\n  ease: {\n    enter: [0.16, 1, 0.3, 1] as const,\n    standard: [0.2, 0, 0, 1] as const,\n  },\n};\n\nconst enterEase = [...motionTokens.ease.enter] as [number, number, number, number];\nconst exitEase = [...motionTokens.ease.standard] as [number, number, number, number];\nconst DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];\n\nconst useIsomorphicLayoutEffect =\n  typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\nconst physical = ({\n  visualDuration,\n  bounce,\n}: {\n  visualDuration: number;\n  bounce: number;\n}) => {\n  const root = (2 * Math.PI) / (visualDuration * 1.2);\n  return {\n    type: \"spring\" as const,\n    stiffness: root * root,\n    damping: 2 * (1 - bounce) * root,\n    restDelta: 0.002,\n    restSpeed: 0.02,\n  };\n};\n\nconst thumbSpring = physical(motionTokens.spring.snappy);\nconst kickSpring = physical(motionTokens.spring.morph);\nconst STRETCH = 9;\nconst BUMP_PX = 150;\nconst SNAP_PX = 12;\n\nconst rubber = (distance: number, limit = STRETCH) =>\n  Math.sign(distance) *\n  (1 - 1 / ((Math.abs(distance) * 0.55) / limit + 1)) *\n  limit;\n\nconst project = (velocity: number) => velocity * 0.099;\nconst decimalsOf = (value: number) => (String(value).split(\".\")[1] ?? \"\").length;\nconst clamp = (value: number, low: number, high: number) =>\n  Math.min(high, Math.max(low, value));\nconst isDigit = (char: string) => char >= \"0\" && char <= \"9\";\n\nfunction partsOf(text: string): Part[] {\n  const chars = [...text];\n  const first = chars.findIndex(isDigit);\n  const last = chars.length - 1 - [...chars].reverse().findIndex(isDigit);\n  let place = chars.filter(isDigit).length;\n  return chars.map((char, index): Part => {\n    if (first < 0 || index < first) return { key: `p${index}${char}`, text: char };\n    if (index > last) return { key: `s${chars.length - index}${char}`, text: char };\n    if (isDigit(char)) return { key: `d${--place}`, digit: Number(char) };\n    return { key: `g${place}${char}`, text: char };\n  });\n}\n\nconst slot: Variants = {\n  enter: (direction: number) => ({\n    width: 0,\n    scale: 0.6,\n    opacity: 0,\n    y: `${direction * 0.3}em`,\n    filter: `blur(${motionTokens.blur.soft}px)`,\n  }),\n  center: {\n    width: \"auto\",\n    scale: 1,\n    opacity: 1,\n    y: 0,\n    filter: \"blur(0px)\",\n    transitionEnd: { filter: \"none\" },\n    transition: {\n      width: motionTokens.spring.morph,\n      scale: motionTokens.spring.morph,\n      opacity: motionTokens.spring.morph,\n      y: motionTokens.spring.snappy,\n      filter: { duration: 0.22, ease: enterEase },\n    },\n  },\n  exit: (direction: number) => ({\n    width: 0,\n    scale: 0.6,\n    opacity: 0,\n    y: `${direction * -0.3}em`,\n    filter: `blur(${motionTokens.blur.subtle}px)`,\n    transition: {\n      width: motionTokens.spring.smooth,\n      scale: motionTokens.spring.smooth,\n      y: { duration: motionTokens.duration.fast, ease: exitEase },\n      opacity: { duration: motionTokens.duration.instant },\n      filter: { duration: motionTokens.duration.instant },\n    },\n  }),\n};\n\nconst still: Variants = {\n  enter: { width: \"auto\", scale: 1, opacity: 0, y: 0, filter: \"none\" },\n  center: {\n    width: \"auto\",\n    scale: 1,\n    opacity: 1,\n    y: 0,\n    filter: \"none\",\n    transition: { duration: motionTokens.duration.instant },\n  },\n  exit: { width: 0, opacity: 0, transition: { duration: 0 } },\n};\n\nfunction Glyph({\n  position,\n  digit,\n}: {\n  position: MotionValue<number>;\n  digit: number;\n}) {\n  const offset = (current: number) => ((((digit - current) % 10) + 15) % 10) - 5;\n  const y = useTransform(position, (current) => `${offset(current) * 1.05}em`);\n  const opacity = useTransform(\n    position,\n    (current) => Math.max(0, 1 - Math.abs(offset(current)) ** 1.5 * 1.1)\n  );\n  const visibility = useTransform(position, (current) =>\n    Math.abs(offset(current)) >= 1 ? \"hidden\" : \"visible\"\n  );\n  const filter = useTransform(position, (current) => {\n    const distance = Math.abs(offset(current));\n    return distance < 0.02 || distance >= 1\n      ? \"none\"\n      : `blur(${(distance * motionTokens.blur.soft * 0.75).toFixed(2)}px)`;\n  });\n\n  return (\n    <motion.span\n      className=\"absolute inset-0 flex items-center justify-center pointer-events-none select-none will-change-transform\"\n      style={{ y, opacity, filter, visibility }}\n    >\n      {digit}\n    </motion.span>\n  );\n}\n\nfunction Wheel({ digit, direction }: { digit: number; direction: number }) {\n  const reduced = useReducedMotion();\n  const position = useMotionValue(digit);\n  const wheel = useRef({ digit, target: digit });\n\n  useIsomorphicLayoutEffect(() => {\n    const state = wheel.current;\n    if (state.digit === digit) return;\n    state.target +=\n      direction > 0\n        ? (digit - state.digit + 10) % 10\n        : -((state.digit - digit + 10) % 10);\n    state.digit = digit;\n    if (reduced) position.jump(state.target);\n    else animate(position, state.target, thumbSpring);\n  }, [digit, direction, position, reduced]);\n\n  return (\n    <>\n      <span className=\"invisible pointer-events-none select-none tabular-nums font-medium\">\n        0\n      </span>\n      {DIGITS.map((item) => (\n        <Glyph key={item} position={position} digit={item} />\n      ))}\n    </>\n  );\n}\n\nfunction RollingNumber({ value, text }: { value: number; text: string }) {\n  const reduced = useReducedMotion();\n  const [trail, setTrail] = useState({ value, direction: 1 });\n\n  if (trail.value !== value) {\n    setTrail({ value, direction: value > trail.value ? 1 : -1 });\n  }\n  const direction =\n    trail.value === value ? trail.direction : value > trail.value ? 1 : -1;\n\n  return (\n    <span className=\"inline-flex items-baseline overflow-hidden tabular-nums leading-none\">\n      <AnimatePresence initial={false} custom={direction}>\n        {partsOf(text).map((part) => (\n          <motion.span\n            key={part.key}\n            className={\n              \"digit\" in part\n                ? \"relative inline-flex items-center justify-center overflow-hidden h-[1.15em] align-baseline\"\n                : \"inline-flex items-center justify-center align-baseline\"\n            }\n            custom={direction}\n            variants={reduced ? still : slot}\n            initial=\"enter\"\n            animate=\"center\"\n            exit=\"exit\"\n          >\n            {\"digit\" in part ? (\n              <Wheel digit={part.digit} direction={direction} />\n            ) : (\n              part.text\n            )}\n          </motion.span>\n        ))}\n      </AnimatePresence>\n    </span>\n  );\n}\n\nconst bubbleIn = {\n  opacity: 0,\n  scale: 0.6,\n  y: 6,\n  filter: `blur(${motionTokens.blur.subtle}px)`,\n};\n\nconst bubbleRest = {\n  opacity: 1,\n  scale: 1,\n  y: 0,\n  filter: \"blur(0px)\",\n  transitionEnd: { filter: \"none\" },\n};\n\nconst bubbleOut = {\n  opacity: 0,\n  scale: 0.8,\n  y: 4,\n  filter: `blur(${motionTokens.blur.subtle}px)`,\n  transition: { duration: motionTokens.duration.instant, ease: exitEase },\n};\n\nconst bubbleEnter = {\n  ...motionTokens.spring.snappy,\n  opacity: { duration: motionTokens.duration.fast, ease: enterEase },\n  filter: { duration: motionTokens.duration.fast, ease: enterEase },\n};\n\ninterface ThumbProps {\n  quiet: boolean;\n  index: number;\n  shown: MotionValue<number>;\n  value: number;\n  text: string;\n  low: number;\n  high: number;\n  ariaLabel?: string;\n  labelledBy?: string;\n  active: boolean;\n  lifted: boolean;\n  bubble: boolean;\n  disabled?: boolean;\n  onKeyDown: (event: KeyboardEvent<HTMLDivElement>) => void;\n  onFocus: (event: FocusEvent<HTMLDivElement>) => void;\n  onBlur: () => void;\n}\n\nfunction Thumb({\n  quiet,\n  index,\n  shown,\n  value,\n  text,\n  low,\n  high,\n  ariaLabel,\n  labelledBy,\n  active,\n  lifted,\n  bubble,\n  disabled,\n  onKeyDown,\n  onFocus,\n  onBlur,\n}: ThumbProps) {\n  const reduced = useReducedMotion();\n  const x = useTransform(shown, (current) => `${current - 100}%`);\n\n  return (\n    <motion.div\n      className=\"absolute inset-0 pointer-events-none\"\n      style={{ x, zIndex: active ? 20 : 10 }}\n    >\n      <motion.div\n        role=\"slider\"\n        data-thumb=\"\"\n        data-index={index}\n        data-active={lifted || undefined}\n        data-quiet={quiet || undefined}\n        tabIndex={disabled ? -1 : 0}\n        aria-label={ariaLabel}\n        aria-labelledby={ariaLabel ? undefined : labelledBy}\n        aria-valuemin={low}\n        aria-valuemax={high}\n        aria-valuenow={value}\n        aria-valuetext={text}\n        aria-orientation=\"horizontal\"\n        aria-disabled={disabled || undefined}\n        initial={false}\n        animate={{ scale: lifted ? 1.15 : 1 }}\n        transition={reduced ? { duration: 0 } : motionTokens.spring.snappy}\n        onKeyDown={onKeyDown}\n        onFocus={onFocus}\n        onBlur={onBlur}\n        className={cn(\n          \"pointer-events-auto absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2\",\n          \"w-5 h-5 rounded-full bg-white dark:bg-white shadow-[0_1px_4px_rgba(0,0,0,0.35),0_3px_10px_rgba(0,0,0,0.2)]\",\n          \"before:absolute before:-inset-2.5 before:rounded-full before:content-['']\",\n          \"border border-black/10 dark:border-white/10 ring-offset-background\",\n          \"cursor-grab active:cursor-grabbing\",\n          \"focus:outline-none focus-visible:ring-2 focus-visible:ring-zinc-500 dark:focus-visible:ring-white/70 focus-visible:ring-offset-2\",\n          \"transition-shadow\",\n          lifted && \"shadow-[0_2px_12px_rgba(0,0,0,0.45)] cursor-grabbing\",\n          disabled && \"cursor-not-allowed opacity-50\"\n        )}\n      />\n      <motion.span\n        className=\"absolute right-0 bottom-full mb-2.5 translate-x-1/2 flex items-center justify-center pointer-events-none\"\n        style={{ \"--at\": shown } as MotionStyle}\n      >\n        <AnimatePresence>\n          {bubble && (\n            <motion.span\n              key=\"bubble\"\n              aria-hidden=\"true\"\n              initial={reduced ? { opacity: 0 } : bubbleIn}\n              animate={bubbleRest}\n              exit={\n                reduced\n                  ? {\n                      opacity: 0,\n                      transition: { duration: motionTokens.duration.instant },\n                    }\n                  : bubbleOut\n              }\n              transition={reduced ? { duration: 0.15 } : bubbleEnter}\n              className=\"relative px-2.5 py-1 rounded-md bg-zinc-900/95 dark:bg-zinc-800/95 text-white dark:text-zinc-100 text-xs font-medium tabular-nums shadow-xl border border-white/10 backdrop-blur-sm pointer-events-none whitespace-nowrap select-none flex items-center justify-center\"\n            >\n              <RollingNumber value={value} text={text} />\n              <span className=\"absolute top-full left-1/2 -translate-x-1/2 border-[3px] border-transparent border-t-zinc-900/95 dark:border-t-zinc-800/95\" />\n            </motion.span>\n          )}\n        </AnimatePresence>\n      </motion.span>\n    </motion.div>\n  );\n}\n\nexport function Slider<T extends SliderValue = number>({\n  label,\n  value,\n  defaultValue,\n  onValueChange,\n  onValueCommit,\n  min = 0,\n  max = 100,\n  step: stepProp = 1,\n  largeStep,\n  marks,\n  minStepsBetweenThumbs = 0,\n  format,\n  showValue = true,\n  thumbLabels,\n  start,\n  end,\n  description,\n  name,\n  disabled,\n  className,\n}: SliderProps<T>) {\n  const reduced = useReducedMotion();\n  const labelId = useId();\n  const step = stepProp > 0 ? stepProp : 1;\n  const span = max - min || 1;\n  const isRange = Array.isArray(value ?? defaultValue);\n\n  const toArray = (input: SliderValue | undefined) =>\n    input === undefined\n      ? [min, max].slice(0, isRange ? 2 : 1)\n      : Array.isArray(input)\n      ? [input[0], input[1]]\n      : [input];\n\n  const [internal, setInternal] = useState(() => toArray(defaultValue));\n  const values = value === undefined ? internal : toArray(value);\n  const decimals = Math.max(decimalsOf(step), decimalsOf(min));\n  const formatValue =\n    format ??\n    ((input: number) =>\n      input.toLocaleString(\"en-US\", {\n        minimumFractionDigits: decimals,\n        maximumFractionDigits: decimals,\n      }));\n\n  const gap = isRange ? minStepsBetweenThumbs * step : 0;\n  const pct = (input: number) => ((input - min) / span) * 100;\n  const valueAt = (percent: number) => min + (percent / 100) * span;\n  const snap = (input: number) =>\n    Number((min + Math.round((input - min) / step) * step).toFixed(decimals));\n\n  const lowOf = (index: number, current: number[]) =>\n    index === 1 ? current[0] + gap : min;\n  const highOf = (index: number, current: number[]) =>\n    index === 0 && isRange ? current[1] - gap : max;\n  const settle = (index: number, input: number, current: number[]) =>\n    clamp(\n      snap(clamp(input, min, max)),\n      lowOf(index, current),\n      highOf(index, current)\n    );\n\n  const trackRef = useRef<HTMLDivElement>(null);\n  const thumbsRef = useRef<HTMLDivElement>(null);\n  const latest = useRef(values);\n  const goal = useRef(values.map(pct));\n  const drag = useRef<Drag | null>(null);\n  const pointerFocus = useRef(false);\n  const lingerTimer = useRef<number | undefined>(undefined);\n\n  const [dragging, setDragging] = useState<number | null>(null);\n  const [keyFocus, setKeyFocus] = useState<number | null>(null);\n  const [linger, setLinger] = useState<number | null>(null);\n  const [lastActive, setLastActive] = useState(isRange ? 1 : 0);\n  const [quiet, setQuiet] = useState<number | null>(null);\n\n  const pos0 = useMotionValue(pct(values[0]));\n  const pos1 = useMotionValue(pct(values[1] ?? values[0]));\n  const lag0 = useMotionValue(0);\n  const lag1 = useMotionValue(0);\n\n  const shown0 = useTransform(() => pos0.get() + lag0.get());\n  const shown1 = useTransform(() => pos1.get() + lag1.get());\n\n  const clipPath = useTransform(() => {\n    const s0 = clamp(shown0.get(), 0, 100);\n    const s1 = clamp(shown1.get(), 0, 100);\n    const from = isRange ? Math.min(s0, s1) : 0;\n    const to = isRange ? Math.max(s0, s1) : s0;\n    return `inset(0 ${(100 - to).toFixed(3)}% 0 ${from.toFixed(3)}% round 999px)`;\n  });\n\n  const thumbs = [\n    { pos: pos0, lag: lag0, shown: shown0 },\n    { pos: pos1, lag: lag1, shown: shown1 },\n  ];\n\n  const emit = (next: number[]) => (isRange ? [next[0], next[1]] : next[0]) as T;\n\n  function commit(index: number, next: number) {\n    const current = latest.current;\n    if (current[index] === next) return;\n    const updated = current.map((item, at) => (at === index ? next : item));\n    latest.current = updated;\n    if (value === undefined) setInternal(updated);\n    onValueChange?.(emit(updated));\n  }\n\n  useIsomorphicLayoutEffect(() => {\n    latest.current = values;\n    values.forEach((item, index) => {\n      const target = pct(item);\n      const thumb = thumbs[index];\n      if (drag.current?.index === index || goal.current[index] === target) return;\n      goal.current[index] = target;\n      const from = thumb.pos.get() + thumb.lag.get();\n      thumb.lag.jump(0);\n      thumb.pos.jump(from);\n      if (reduced) thumb.pos.jump(target);\n      else animate(thumb.pos, target, thumbSpring);\n    });\n  });\n\n  useEffect(() => () => window.clearTimeout(lingerTimer.current), []);\n\n  const thumbNode = (index: number) =>\n    thumbsRef.current?.querySelector<HTMLElement>(`[data-index=\"${index}\"]`);\n\n  function focusFromPointer(index: number) {\n    const node = thumbNode(index);\n    if (!node || document.activeElement === node) return;\n    setQuiet(index);\n    pointerFocus.current = true;\n    node.focus({ preventScroll: true });\n    pointerFocus.current = false;\n  }\n\n  const trackWidth = () => trackRef.current?.getBoundingClientRect().width || 1;\n\n  function nearest(at: number, current: number[]) {\n    if (!isRange) return 0;\n    const low = Math.abs(at - pct(current[0]));\n    const high = Math.abs(at - pct(current[1]));\n    return low === high ? (at < pct(current[0]) ? 0 : 1) : low < high ? 0 : 1;\n  }\n\n  function holdBubble(index: number) {\n    window.clearTimeout(lingerTimer.current);\n    setLinger(index);\n    lingerTimer.current = window.setTimeout(() => setLinger(null), 700);\n  }\n\n  function begin(state: Drag, index: number, at: number, press: boolean) {\n    const thumb = thumbs[index];\n    const from = thumb.pos.get() + thumb.lag.get();\n    state.index = index;\n    state.grab = press ? 0 : at - from;\n    thumb.lag.jump(0);\n    thumb.pos.jump(from);\n    setDragging(index);\n    setLastActive(index);\n    window.clearTimeout(lingerTimer.current);\n    setLinger(null);\n    focusFromPointer(index);\n    if (press) follow(state, at, 0, true);\n  }\n\n  function follow(state: Drag, at: number, time: number, press = false) {\n    const index = state.index;\n    if (index === null) return;\n    const thumb = thumbs[index];\n    const current = latest.current;\n    const width = trackWidth();\n    const raw = at - state.grab;\n    const low = pct(lowOf(index, current));\n    const high = pct(highOf(index, current));\n    const edge = clamp(raw, low, high);\n    const placed = reduced\n      ? edge\n      : edge + (rubber(((raw - edge) / 100) * width) / width) * 100;\n\n    if (press && !reduced) {\n      const from = thumb.pos.get();\n      thumb.pos.jump(placed);\n      thumb.lag.jump(from - placed);\n      animate(thumb.lag, 0, thumbSpring);\n    } else {\n      thumb.pos.set(placed);\n    }\n\n    state.raw = raw;\n    state.samples.push({ t: time, p: placed });\n    if (state.samples.length > 6) state.samples.shift();\n    commit(index, settle(index, valueAt(raw), current));\n  }\n\n  function onPointerDown(event: PointerEvent<HTMLDivElement>) {\n    if (disabled || event.button !== 0 || !event.isPrimary) return;\n    const rect = trackRef.current?.getBoundingClientRect();\n    if (!rect) return;\n    const at = ((event.clientX - rect.left) / (rect.width || 1)) * 100;\n    const grabbed = (event.target as HTMLElement).closest<HTMLElement>(\n      \"[data-thumb]\"\n    );\n    const current = latest.current;\n    event.currentTarget.setPointerCapture(event.pointerId);\n    const state: Drag = {\n      pointer: event.pointerId,\n      index: null,\n      grab: 0,\n      x: event.clientX,\n      raw: at,\n      samples: [],\n    };\n    drag.current = state;\n\n    if (grabbed && isRange && current[0] === current[1]) {\n      focusFromPointer(Number(grabbed.dataset.index));\n      return;\n    }\n\n    begin(\n      state,\n      grabbed ? Number(grabbed.dataset.index) : nearest(at, current),\n      at,\n      !grabbed\n    );\n    state.samples = [\n      { t: event.timeStamp, p: thumbs[state.index ?? 0].pos.get() },\n    ];\n  }\n\n  function onPointerMove(event: PointerEvent<HTMLDivElement>) {\n    const state = drag.current;\n    if (!state || state.pointer !== event.pointerId) return;\n    const rect = trackRef.current?.getBoundingClientRect();\n    if (!rect) return;\n    const at = ((event.clientX - rect.left) / (rect.width || 1)) * 100;\n    if (state.index === null) {\n      const dx = event.clientX - state.x;\n      if (Math.abs(dx) < 2) return;\n      begin(state, dx < 0 ? 0 : 1, at - (dx / (rect.width || 1)) * 100, false);\n    }\n    follow(state, at, event.timeStamp);\n  }\n\n  function onPointerEnd(event: PointerEvent<HTMLDivElement>) {\n    const state = drag.current;\n    if (!state || state.pointer !== event.pointerId) return;\n    drag.current = null;\n    const index = state.index;\n    if (index === null) return;\n    const thumb = thumbs[index];\n    const current = latest.current;\n    const width = trackWidth();\n    const samples = state.samples;\n    const first = samples[0];\n    const last = samples[samples.length - 1];\n    const elapsed = last && first ? (last.t - first.t) / 1000 : 0;\n    const velocity =\n      (elapsed > 0.008 && event.timeStamp - last.t < 60\n        ? (last.p - first.p) / elapsed\n        : 0) + thumb.lag.getVelocity();\n\n    const stepPct = (step / span) * 100;\n    const carry =\n      event.type === \"pointerup\" && (stepPct / 100) * width >= SNAP_PX\n        ? clamp(project(velocity), -stepPct, stepPct)\n        : 0;\n\n    const next = settle(index, valueAt(state.raw + carry), current);\n    const from = thumb.pos.get() + thumb.lag.get();\n    const target = pct(next);\n\n    thumb.lag.jump(0);\n    thumb.pos.jump(from);\n    goal.current[index] = target;\n\n    if (reduced) thumb.pos.jump(target);\n    else animate(thumb.pos, target, { ...thumbSpring, velocity });\n\n    commit(index, next);\n    setDragging(null);\n    holdBubble(index);\n    onValueCommit?.(emit(latest.current));\n  }\n\n  function onKeyDown(index: number, event: KeyboardEvent<HTMLDivElement>) {\n    if (disabled) return;\n    const current = latest.current;\n    const now = current[index];\n    const large = largeStep ?? Math.max(step, snap(min + span / 10) - min);\n    const moves: Record<string, number> = {\n      ArrowRight: step,\n      ArrowUp: step,\n      ArrowLeft: -step,\n      ArrowDown: -step,\n      PageUp: large,\n      PageDown: -large,\n    };\n\n    let wanted: number;\n    if (event.key === \"Home\") wanted = lowOf(index, current);\n    else if (event.key === \"End\") wanted = highOf(index, current);\n    else if (event.key in moves)\n      wanted =\n        now +\n        (event.shiftKey && /^Arrow/.test(event.key)\n          ? Math.sign(moves[event.key]) * large\n          : moves[event.key]);\n    else return;\n\n    event.preventDefault();\n    setKeyFocus(index);\n    setQuiet(null);\n    setLastActive(index);\n    const next = settle(index, wanted, current);\n\n    if (next === now) {\n      const toward = Math.sign(wanted - now);\n      if (toward && !reduced)\n        animate(thumbs[index].pos, goal.current[index], {\n          ...kickSpring,\n          velocity: ((toward * BUMP_PX) / trackWidth()) * 100,\n        });\n      return;\n    }\n\n    commit(index, next);\n    onValueCommit?.(emit(latest.current));\n  }\n\n  function jumpTo(target: number) {\n    if (disabled) return;\n    const current = latest.current;\n    const index = nearest(pct(target), current);\n    const next = settle(index, target, current);\n    setLastActive(index);\n    focusFromPointer(index);\n    if (next === current[index]) return;\n    commit(index, next);\n    onValueCommit?.(emit(latest.current));\n  }\n\n  const markList = (marks ?? [])\n    .map((mark) => (typeof mark === \"number\" ? ({ value: mark } as SliderMark) : mark))\n    .filter((mark) => mark.value >= min && mark.value <= max);\n\n  const ticks = markList.filter((mark) => mark.value > min && mark.value < max);\n  const labelled = markList.filter((mark) => mark.label);\n  const inRange = (mark: number) =>\n    isRange\n      ? mark >= values[0] && mark <= values[1]\n      : mark <= values[0];\n\n  const names = thumbLabels ?? [\n    `${label}, minimum`,\n    `${label}, maximum`,\n  ];\n\n  return (\n    <div\n      className={cn(\n        \"relative flex flex-col gap-2 w-full select-none\",\n        disabled && \"opacity-50 pointer-events-none\",\n        className\n      )}\n      data-disabled={disabled || undefined}\n      data-dragging={dragging !== null || undefined}\n      data-marks={labelled.length > 0 || undefined}\n    >\n      <div className=\"flex items-center justify-between gap-3 select-none mb-1\">\n        <span\n          id={labelId}\n          className=\"text-sm font-medium text-foreground dark:text-zinc-200 tracking-tight\"\n        >\n          {label}\n        </span>\n        {showValue && (\n          <div\n            className=\"flex items-center text-sm font-medium text-foreground dark:text-zinc-200 tabular-nums select-none tracking-tight\"\n            aria-hidden=\"true\"\n          >\n            <RollingNumber value={values[0]} text={formatValue(values[0])} />\n            {isRange && (\n              <>\n                <span className=\"mx-1.5 text-muted-foreground select-none font-normal\">–</span>\n                <RollingNumber value={values[1]} text={formatValue(values[1])} />\n              </>\n            )}\n          </div>\n        )}\n      </div>\n\n      <div className=\"w-full flex flex-col\">\n        <div className=\"flex items-start gap-3 w-full\">\n          {start && (\n            <div className=\"shrink-0 h-9 flex items-center justify-center\">\n              {start}\n            </div>\n          )}\n          <div className=\"flex-1 flex flex-col min-w-0\">\n            <div\n              className=\"relative flex items-center w-full h-9 py-2 touch-none cursor-pointer\"\n              onPointerDown={onPointerDown}\n              onPointerMove={onPointerMove}\n              onPointerUp={onPointerEnd}\n              onPointerCancel={onPointerEnd}\n              onLostPointerCapture={onPointerEnd}\n              onMouseDown={(event) => event.preventDefault()}\n            >\n              <div\n                ref={trackRef}\n                className=\"relative w-full h-1.5 rounded-full bg-zinc-800 dark:bg-zinc-800 overflow-hidden\"\n              >\n                {ticks.map((mark) => (\n                  <span\n                    key={mark.value}\n                    className=\"absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-zinc-600 dark:bg-zinc-600 pointer-events-none\"\n                    style={{ left: `${pct(mark.value)}%` }}\n                  />\n                ))}\n                <motion.div\n                  className=\"absolute inset-0 bg-zinc-400 dark:bg-zinc-300 rounded-full pointer-events-none\"\n                  style={{ clipPath }}\n                >\n                  {ticks.map((mark) => (\n                    <span\n                      key={mark.value}\n                      className=\"absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-zinc-700 dark:bg-zinc-700 pointer-events-none\"\n                      style={{ left: `${pct(mark.value)}%` }}\n                    />\n                  ))}\n                </motion.div>\n              </div>\n\n              <div ref={thumbsRef} className=\"absolute inset-0 pointer-events-none\">\n                {values.map((item, index) => (\n                  <Thumb\n                    key={index}\n                    quiet={quiet === index}\n                    index={index}\n                    shown={thumbs[index].shown}\n                    value={item}\n                    text={formatValue(item)}\n                    low={lowOf(index, values)}\n                    high={highOf(index, values)}\n                    ariaLabel={isRange ? names[index] : undefined}\n                    labelledBy={labelId}\n                    active={lastActive === index}\n                    lifted={dragging === index}\n                    bubble={\n                      dragging === index ||\n                      keyFocus === index ||\n                      linger === index\n                    }\n                    disabled={disabled}\n                    onKeyDown={(event) => onKeyDown(index, event)}\n                    onFocus={(event) => {\n                      if (\n                        !pointerFocus.current &&\n                        event.currentTarget.matches(\":focus-visible\")\n                      ) {\n                        setKeyFocus(index);\n                      }\n                    }}\n                    onBlur={() => {\n                      setKeyFocus((current) =>\n                        current === index ? null : current\n                      );\n                      setQuiet((current) =>\n                        current === index ? null : current\n                      );\n                    }}\n                  />\n                ))}\n              </div>\n            </div>\n\n            {labelled.length > 0 && (\n              <div\n                className=\"relative w-full h-5 mt-1 select-none\"\n                aria-hidden=\"true\"\n              >\n                {labelled.map((mark) => {\n                  const isStart = mark.value === min;\n                  const isEnd = mark.value === max;\n                  return (\n                    <span\n                      key={mark.value}\n                      onClick={() => jumpTo(mark.value)}\n                      className={cn(\n                        \"absolute top-0 text-xs font-medium tabular-nums transition-colors cursor-pointer select-none whitespace-nowrap\",\n                        inRange(mark.value)\n                          ? \"text-foreground dark:text-zinc-200\"\n                          : \"text-muted-foreground hover:text-foreground dark:text-zinc-500 dark:hover:text-zinc-300\",\n                        isStart\n                          ? \"left-0 translate-x-0 text-left\"\n                          : isEnd\n                          ? \"right-0 translate-x-0 text-right\"\n                          : \"-translate-x-1/2 text-center\"\n                      )}\n                      style={\n                        !isStart && !isEnd\n                          ? { left: `${pct(mark.value)}%` }\n                          : undefined\n                      }\n                    >\n                      {mark.label}\n                    </span>\n                  );\n                })}\n              </div>\n            )}\n          </div>\n          {end && (\n            <div className=\"shrink-0 h-9 flex items-center justify-center\">\n              {end}\n            </div>\n          )}\n        </div>\n      </div>\n\n      {description && (\n        <p className=\"text-xs text-muted-foreground dark:text-zinc-400 mt-0.5 tracking-tight\">\n          {description}\n        </p>\n      )}\n\n      {name &&\n        values.map((item, index) => (\n          <input\n            key={index}\n            type=\"hidden\"\n            name={isRange ? `${name}[${index}]` : name}\n            value={item}\n            disabled={disabled}\n          />\n        ))}\n    </div>\n  );\n}\n\nexport default Slider;\n",
      "content": "\"use client\";\n\nimport {\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n  type FocusEvent,\n  type KeyboardEvent,\n  type PointerEvent,\n  type ReactNode,\n} from \"react\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n  type MotionStyle,\n  type MotionValue,\n  type Variants,\n} from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type SliderValue = number | [number, number];\n\nexport interface SliderMark {\n  value: number;\n  label?: string;\n}\n\nexport interface SliderProps<T extends SliderValue = number> {\n  label: string;\n  value?: T;\n  defaultValue?: T;\n  onValueChange?: (value: T) => void;\n  onValueCommit?: (value: T) => void;\n  min?: number;\n  max?: number;\n  step?: number;\n  largeStep?: number;\n  marks?: (number | SliderMark)[];\n  minStepsBetweenThumbs?: number;\n  format?: (value: number) => string;\n  showValue?: boolean;\n  thumbLabels?: [string, string];\n  start?: ReactNode;\n  end?: ReactNode;\n  description?: ReactNode;\n  name?: string;\n  disabled?: boolean;\n  className?: string;\n}\n\ntype Part = { key: string; digit: number } | { key: string; text: string };\n\ntype Drag = {\n  pointer: number;\n  index: number | null;\n  grab: number;\n  x: number;\n  raw: number;\n  samples: { t: number; p: number }[];\n};\n\nconst motionTokens = {\n  spring: {\n    snappy: { visualDuration: 0.35, bounce: 0.15 },\n    morph: { visualDuration: 0.45, bounce: 0.1 },\n    smooth: { visualDuration: 0.4, bounce: 0 },\n  },\n  duration: {\n    instant: 0.08,\n    fast: 0.18,\n    normal: 0.3,\n  },\n  blur: {\n    subtle: 1.5,\n    soft: 3,\n  },\n  ease: {\n    enter: [0.16, 1, 0.3, 1] as const,\n    standard: [0.2, 0, 0, 1] as const,\n  },\n};\n\nconst enterEase = [...motionTokens.ease.enter] as [number, number, number, number];\nconst exitEase = [...motionTokens.ease.standard] as [number, number, number, number];\nconst DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];\n\nconst useIsomorphicLayoutEffect =\n  typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\nconst physical = ({\n  visualDuration,\n  bounce,\n}: {\n  visualDuration: number;\n  bounce: number;\n}) => {\n  const root = (2 * Math.PI) / (visualDuration * 1.2);\n  return {\n    type: \"spring\" as const,\n    stiffness: root * root,\n    damping: 2 * (1 - bounce) * root,\n    restDelta: 0.002,\n    restSpeed: 0.02,\n  };\n};\n\nconst thumbSpring = physical(motionTokens.spring.snappy);\nconst kickSpring = physical(motionTokens.spring.morph);\nconst STRETCH = 9;\nconst BUMP_PX = 150;\nconst SNAP_PX = 12;\n\nconst rubber = (distance: number, limit = STRETCH) =>\n  Math.sign(distance) *\n  (1 - 1 / ((Math.abs(distance) * 0.55) / limit + 1)) *\n  limit;\n\nconst project = (velocity: number) => velocity * 0.099;\nconst decimalsOf = (value: number) => (String(value).split(\".\")[1] ?? \"\").length;\nconst clamp = (value: number, low: number, high: number) =>\n  Math.min(high, Math.max(low, value));\nconst isDigit = (char: string) => char >= \"0\" && char <= \"9\";\n\nfunction partsOf(text: string): Part[] {\n  const chars = [...text];\n  const first = chars.findIndex(isDigit);\n  const last = chars.length - 1 - [...chars].reverse().findIndex(isDigit);\n  let place = chars.filter(isDigit).length;\n  return chars.map((char, index): Part => {\n    if (first < 0 || index < first) return { key: `p${index}${char}`, text: char };\n    if (index > last) return { key: `s${chars.length - index}${char}`, text: char };\n    if (isDigit(char)) return { key: `d${--place}`, digit: Number(char) };\n    return { key: `g${place}${char}`, text: char };\n  });\n}\n\nconst slot: Variants = {\n  enter: (direction: number) => ({\n    width: 0,\n    scale: 0.6,\n    opacity: 0,\n    y: `${direction * 0.3}em`,\n    filter: `blur(${motionTokens.blur.soft}px)`,\n  }),\n  center: {\n    width: \"auto\",\n    scale: 1,\n    opacity: 1,\n    y: 0,\n    filter: \"blur(0px)\",\n    transitionEnd: { filter: \"none\" },\n    transition: {\n      width: motionTokens.spring.morph,\n      scale: motionTokens.spring.morph,\n      opacity: motionTokens.spring.morph,\n      y: motionTokens.spring.snappy,\n      filter: { duration: 0.22, ease: enterEase },\n    },\n  },\n  exit: (direction: number) => ({\n    width: 0,\n    scale: 0.6,\n    opacity: 0,\n    y: `${direction * -0.3}em`,\n    filter: `blur(${motionTokens.blur.subtle}px)`,\n    transition: {\n      width: motionTokens.spring.smooth,\n      scale: motionTokens.spring.smooth,\n      y: { duration: motionTokens.duration.fast, ease: exitEase },\n      opacity: { duration: motionTokens.duration.instant },\n      filter: { duration: motionTokens.duration.instant },\n    },\n  }),\n};\n\nconst still: Variants = {\n  enter: { width: \"auto\", scale: 1, opacity: 0, y: 0, filter: \"none\" },\n  center: {\n    width: \"auto\",\n    scale: 1,\n    opacity: 1,\n    y: 0,\n    filter: \"none\",\n    transition: { duration: motionTokens.duration.instant },\n  },\n  exit: { width: 0, opacity: 0, transition: { duration: 0 } },\n};\n\nfunction Glyph({\n  position,\n  digit,\n}: {\n  position: MotionValue<number>;\n  digit: number;\n}) {\n  const offset = (current: number) => ((((digit - current) % 10) + 15) % 10) - 5;\n  const y = useTransform(position, (current) => `${offset(current) * 1.05}em`);\n  const opacity = useTransform(\n    position,\n    (current) => Math.max(0, 1 - Math.abs(offset(current)) ** 1.5 * 1.1)\n  );\n  const visibility = useTransform(position, (current) =>\n    Math.abs(offset(current)) >= 1 ? \"hidden\" : \"visible\"\n  );\n  const filter = useTransform(position, (current) => {\n    const distance = Math.abs(offset(current));\n    return distance < 0.02 || distance >= 1\n      ? \"none\"\n      : `blur(${(distance * motionTokens.blur.soft * 0.75).toFixed(2)}px)`;\n  });\n\n  return (\n    <motion.span\n      className=\"absolute inset-0 flex items-center justify-center pointer-events-none select-none will-change-transform\"\n      style={{ y, opacity, filter, visibility }}\n    >\n      {digit}\n    </motion.span>\n  );\n}\n\nfunction Wheel({ digit, direction }: { digit: number; direction: number }) {\n  const reduced = useReducedMotion();\n  const position = useMotionValue(digit);\n  const wheel = useRef({ digit, target: digit });\n\n  useIsomorphicLayoutEffect(() => {\n    const state = wheel.current;\n    if (state.digit === digit) return;\n    state.target +=\n      direction > 0\n        ? (digit - state.digit + 10) % 10\n        : -((state.digit - digit + 10) % 10);\n    state.digit = digit;\n    if (reduced) position.jump(state.target);\n    else animate(position, state.target, thumbSpring);\n  }, [digit, direction, position, reduced]);\n\n  return (\n    <>\n      <span className=\"invisible pointer-events-none select-none tabular-nums font-medium\">\n        0\n      </span>\n      {DIGITS.map((item) => (\n        <Glyph key={item} position={position} digit={item} />\n      ))}\n    </>\n  );\n}\n\nfunction RollingNumber({ value, text }: { value: number; text: string }) {\n  const reduced = useReducedMotion();\n  const [trail, setTrail] = useState({ value, direction: 1 });\n\n  if (trail.value !== value) {\n    setTrail({ value, direction: value > trail.value ? 1 : -1 });\n  }\n  const direction =\n    trail.value === value ? trail.direction : value > trail.value ? 1 : -1;\n\n  return (\n    <span className=\"inline-flex items-baseline overflow-hidden tabular-nums leading-none\">\n      <AnimatePresence initial={false} custom={direction}>\n        {partsOf(text).map((part) => (\n          <motion.span\n            key={part.key}\n            className={\n              \"digit\" in part\n                ? \"relative inline-flex items-center justify-center overflow-hidden h-[1.15em] align-baseline\"\n                : \"inline-flex items-center justify-center align-baseline\"\n            }\n            custom={direction}\n            variants={reduced ? still : slot}\n            initial=\"enter\"\n            animate=\"center\"\n            exit=\"exit\"\n          >\n            {\"digit\" in part ? (\n              <Wheel digit={part.digit} direction={direction} />\n            ) : (\n              part.text\n            )}\n          </motion.span>\n        ))}\n      </AnimatePresence>\n    </span>\n  );\n}\n\nconst bubbleIn = {\n  opacity: 0,\n  scale: 0.6,\n  y: 6,\n  filter: `blur(${motionTokens.blur.subtle}px)`,\n};\n\nconst bubbleRest = {\n  opacity: 1,\n  scale: 1,\n  y: 0,\n  filter: \"blur(0px)\",\n  transitionEnd: { filter: \"none\" },\n};\n\nconst bubbleOut = {\n  opacity: 0,\n  scale: 0.8,\n  y: 4,\n  filter: `blur(${motionTokens.blur.subtle}px)`,\n  transition: { duration: motionTokens.duration.instant, ease: exitEase },\n};\n\nconst bubbleEnter = {\n  ...motionTokens.spring.snappy,\n  opacity: { duration: motionTokens.duration.fast, ease: enterEase },\n  filter: { duration: motionTokens.duration.fast, ease: enterEase },\n};\n\ninterface ThumbProps {\n  quiet: boolean;\n  index: number;\n  shown: MotionValue<number>;\n  value: number;\n  text: string;\n  low: number;\n  high: number;\n  ariaLabel?: string;\n  labelledBy?: string;\n  active: boolean;\n  lifted: boolean;\n  bubble: boolean;\n  disabled?: boolean;\n  onKeyDown: (event: KeyboardEvent<HTMLDivElement>) => void;\n  onFocus: (event: FocusEvent<HTMLDivElement>) => void;\n  onBlur: () => void;\n}\n\nfunction Thumb({\n  quiet,\n  index,\n  shown,\n  value,\n  text,\n  low,\n  high,\n  ariaLabel,\n  labelledBy,\n  active,\n  lifted,\n  bubble,\n  disabled,\n  onKeyDown,\n  onFocus,\n  onBlur,\n}: ThumbProps) {\n  const reduced = useReducedMotion();\n  const x = useTransform(shown, (current) => `${current - 100}%`);\n\n  return (\n    <motion.div\n      className=\"absolute inset-0 pointer-events-none\"\n      style={{ x, zIndex: active ? 20 : 10 }}\n    >\n      <motion.div\n        role=\"slider\"\n        data-thumb=\"\"\n        data-index={index}\n        data-active={lifted || undefined}\n        data-quiet={quiet || undefined}\n        tabIndex={disabled ? -1 : 0}\n        aria-label={ariaLabel}\n        aria-labelledby={ariaLabel ? undefined : labelledBy}\n        aria-valuemin={low}\n        aria-valuemax={high}\n        aria-valuenow={value}\n        aria-valuetext={text}\n        aria-orientation=\"horizontal\"\n        aria-disabled={disabled || undefined}\n        initial={false}\n        animate={{ scale: lifted ? 1.15 : 1 }}\n        transition={reduced ? { duration: 0 } : motionTokens.spring.snappy}\n        onKeyDown={onKeyDown}\n        onFocus={onFocus}\n        onBlur={onBlur}\n        className={cn(\n          \"pointer-events-auto absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2\",\n          \"w-5 h-5 rounded-full bg-white dark:bg-white shadow-[0_1px_4px_rgba(0,0,0,0.35),0_3px_10px_rgba(0,0,0,0.2)]\",\n          \"before:absolute before:-inset-2.5 before:rounded-full before:content-['']\",\n          \"border border-black/10 dark:border-white/10 ring-offset-background\",\n          \"cursor-grab active:cursor-grabbing\",\n          \"focus:outline-none focus-visible:ring-2 focus-visible:ring-zinc-500 dark:focus-visible:ring-white/70 focus-visible:ring-offset-2\",\n          \"transition-shadow\",\n          lifted && \"shadow-[0_2px_12px_rgba(0,0,0,0.45)] cursor-grabbing\",\n          disabled && \"cursor-not-allowed opacity-50\"\n        )}\n      />\n      <motion.span\n        className=\"absolute right-0 bottom-full mb-2.5 translate-x-1/2 flex items-center justify-center pointer-events-none\"\n        style={{ \"--at\": shown } as MotionStyle}\n      >\n        <AnimatePresence>\n          {bubble && (\n            <motion.span\n              key=\"bubble\"\n              aria-hidden=\"true\"\n              initial={reduced ? { opacity: 0 } : bubbleIn}\n              animate={bubbleRest}\n              exit={\n                reduced\n                  ? {\n                      opacity: 0,\n                      transition: { duration: motionTokens.duration.instant },\n                    }\n                  : bubbleOut\n              }\n              transition={reduced ? { duration: 0.15 } : bubbleEnter}\n              className=\"relative px-2.5 py-1 rounded-md bg-zinc-900/95 dark:bg-zinc-800/95 text-white dark:text-zinc-100 text-xs font-medium tabular-nums shadow-xl border border-white/10 backdrop-blur-sm pointer-events-none whitespace-nowrap select-none flex items-center justify-center\"\n            >\n              <RollingNumber value={value} text={text} />\n              <span className=\"absolute top-full left-1/2 -translate-x-1/2 border-[3px] border-transparent border-t-zinc-900/95 dark:border-t-zinc-800/95\" />\n            </motion.span>\n          )}\n        </AnimatePresence>\n      </motion.span>\n    </motion.div>\n  );\n}\n\nexport function Slider<T extends SliderValue = number>({\n  label,\n  value,\n  defaultValue,\n  onValueChange,\n  onValueCommit,\n  min = 0,\n  max = 100,\n  step: stepProp = 1,\n  largeStep,\n  marks,\n  minStepsBetweenThumbs = 0,\n  format,\n  showValue = true,\n  thumbLabels,\n  start,\n  end,\n  description,\n  name,\n  disabled,\n  className,\n}: SliderProps<T>) {\n  const reduced = useReducedMotion();\n  const labelId = useId();\n  const step = stepProp > 0 ? stepProp : 1;\n  const span = max - min || 1;\n  const isRange = Array.isArray(value ?? defaultValue);\n\n  const toArray = (input: SliderValue | undefined) =>\n    input === undefined\n      ? [min, max].slice(0, isRange ? 2 : 1)\n      : Array.isArray(input)\n      ? [input[0], input[1]]\n      : [input];\n\n  const [internal, setInternal] = useState(() => toArray(defaultValue));\n  const values = value === undefined ? internal : toArray(value);\n  const decimals = Math.max(decimalsOf(step), decimalsOf(min));\n  const formatValue =\n    format ??\n    ((input: number) =>\n      input.toLocaleString(\"en-US\", {\n        minimumFractionDigits: decimals,\n        maximumFractionDigits: decimals,\n      }));\n\n  const gap = isRange ? minStepsBetweenThumbs * step : 0;\n  const pct = (input: number) => ((input - min) / span) * 100;\n  const valueAt = (percent: number) => min + (percent / 100) * span;\n  const snap = (input: number) =>\n    Number((min + Math.round((input - min) / step) * step).toFixed(decimals));\n\n  const lowOf = (index: number, current: number[]) =>\n    index === 1 ? current[0] + gap : min;\n  const highOf = (index: number, current: number[]) =>\n    index === 0 && isRange ? current[1] - gap : max;\n  const settle = (index: number, input: number, current: number[]) =>\n    clamp(\n      snap(clamp(input, min, max)),\n      lowOf(index, current),\n      highOf(index, current)\n    );\n\n  const trackRef = useRef<HTMLDivElement>(null);\n  const thumbsRef = useRef<HTMLDivElement>(null);\n  const latest = useRef(values);\n  const goal = useRef(values.map(pct));\n  const drag = useRef<Drag | null>(null);\n  const pointerFocus = useRef(false);\n  const lingerTimer = useRef<number | undefined>(undefined);\n\n  const [dragging, setDragging] = useState<number | null>(null);\n  const [keyFocus, setKeyFocus] = useState<number | null>(null);\n  const [linger, setLinger] = useState<number | null>(null);\n  const [lastActive, setLastActive] = useState(isRange ? 1 : 0);\n  const [quiet, setQuiet] = useState<number | null>(null);\n\n  const pos0 = useMotionValue(pct(values[0]));\n  const pos1 = useMotionValue(pct(values[1] ?? values[0]));\n  const lag0 = useMotionValue(0);\n  const lag1 = useMotionValue(0);\n\n  const shown0 = useTransform(() => pos0.get() + lag0.get());\n  const shown1 = useTransform(() => pos1.get() + lag1.get());\n\n  const clipPath = useTransform(() => {\n    const s0 = clamp(shown0.get(), 0, 100);\n    const s1 = clamp(shown1.get(), 0, 100);\n    const from = isRange ? Math.min(s0, s1) : 0;\n    const to = isRange ? Math.max(s0, s1) : s0;\n    return `inset(0 ${(100 - to).toFixed(3)}% 0 ${from.toFixed(3)}% round 999px)`;\n  });\n\n  const thumbs = [\n    { pos: pos0, lag: lag0, shown: shown0 },\n    { pos: pos1, lag: lag1, shown: shown1 },\n  ];\n\n  const emit = (next: number[]) => (isRange ? [next[0], next[1]] : next[0]) as T;\n\n  function commit(index: number, next: number) {\n    const current = latest.current;\n    if (current[index] === next) return;\n    const updated = current.map((item, at) => (at === index ? next : item));\n    latest.current = updated;\n    if (value === undefined) setInternal(updated);\n    onValueChange?.(emit(updated));\n  }\n\n  useIsomorphicLayoutEffect(() => {\n    latest.current = values;\n    values.forEach((item, index) => {\n      const target = pct(item);\n      const thumb = thumbs[index];\n      if (drag.current?.index === index || goal.current[index] === target) return;\n      goal.current[index] = target;\n      const from = thumb.pos.get() + thumb.lag.get();\n      thumb.lag.jump(0);\n      thumb.pos.jump(from);\n      if (reduced) thumb.pos.jump(target);\n      else animate(thumb.pos, target, thumbSpring);\n    });\n  });\n\n  useEffect(() => () => window.clearTimeout(lingerTimer.current), []);\n\n  const thumbNode = (index: number) =>\n    thumbsRef.current?.querySelector<HTMLElement>(`[data-index=\"${index}\"]`);\n\n  function focusFromPointer(index: number) {\n    const node = thumbNode(index);\n    if (!node || document.activeElement === node) return;\n    setQuiet(index);\n    pointerFocus.current = true;\n    node.focus({ preventScroll: true });\n    pointerFocus.current = false;\n  }\n\n  const trackWidth = () => trackRef.current?.getBoundingClientRect().width || 1;\n\n  function nearest(at: number, current: number[]) {\n    if (!isRange) return 0;\n    const low = Math.abs(at - pct(current[0]));\n    const high = Math.abs(at - pct(current[1]));\n    return low === high ? (at < pct(current[0]) ? 0 : 1) : low < high ? 0 : 1;\n  }\n\n  function holdBubble(index: number) {\n    window.clearTimeout(lingerTimer.current);\n    setLinger(index);\n    lingerTimer.current = window.setTimeout(() => setLinger(null), 700);\n  }\n\n  function begin(state: Drag, index: number, at: number, press: boolean) {\n    const thumb = thumbs[index];\n    const from = thumb.pos.get() + thumb.lag.get();\n    state.index = index;\n    state.grab = press ? 0 : at - from;\n    thumb.lag.jump(0);\n    thumb.pos.jump(from);\n    setDragging(index);\n    setLastActive(index);\n    window.clearTimeout(lingerTimer.current);\n    setLinger(null);\n    focusFromPointer(index);\n    if (press) follow(state, at, 0, true);\n  }\n\n  function follow(state: Drag, at: number, time: number, press = false) {\n    const index = state.index;\n    if (index === null) return;\n    const thumb = thumbs[index];\n    const current = latest.current;\n    const width = trackWidth();\n    const raw = at - state.grab;\n    const low = pct(lowOf(index, current));\n    const high = pct(highOf(index, current));\n    const edge = clamp(raw, low, high);\n    const placed = reduced\n      ? edge\n      : edge + (rubber(((raw - edge) / 100) * width) / width) * 100;\n\n    if (press && !reduced) {\n      const from = thumb.pos.get();\n      thumb.pos.jump(placed);\n      thumb.lag.jump(from - placed);\n      animate(thumb.lag, 0, thumbSpring);\n    } else {\n      thumb.pos.set(placed);\n    }\n\n    state.raw = raw;\n    state.samples.push({ t: time, p: placed });\n    if (state.samples.length > 6) state.samples.shift();\n    commit(index, settle(index, valueAt(raw), current));\n  }\n\n  function onPointerDown(event: PointerEvent<HTMLDivElement>) {\n    if (disabled || event.button !== 0 || !event.isPrimary) return;\n    const rect = trackRef.current?.getBoundingClientRect();\n    if (!rect) return;\n    const at = ((event.clientX - rect.left) / (rect.width || 1)) * 100;\n    const grabbed = (event.target as HTMLElement).closest<HTMLElement>(\n      \"[data-thumb]\"\n    );\n    const current = latest.current;\n    event.currentTarget.setPointerCapture(event.pointerId);\n    const state: Drag = {\n      pointer: event.pointerId,\n      index: null,\n      grab: 0,\n      x: event.clientX,\n      raw: at,\n      samples: [],\n    };\n    drag.current = state;\n\n    if (grabbed && isRange && current[0] === current[1]) {\n      focusFromPointer(Number(grabbed.dataset.index));\n      return;\n    }\n\n    begin(\n      state,\n      grabbed ? Number(grabbed.dataset.index) : nearest(at, current),\n      at,\n      !grabbed\n    );\n    state.samples = [\n      { t: event.timeStamp, p: thumbs[state.index ?? 0].pos.get() },\n    ];\n  }\n\n  function onPointerMove(event: PointerEvent<HTMLDivElement>) {\n    const state = drag.current;\n    if (!state || state.pointer !== event.pointerId) return;\n    const rect = trackRef.current?.getBoundingClientRect();\n    if (!rect) return;\n    const at = ((event.clientX - rect.left) / (rect.width || 1)) * 100;\n    if (state.index === null) {\n      const dx = event.clientX - state.x;\n      if (Math.abs(dx) < 2) return;\n      begin(state, dx < 0 ? 0 : 1, at - (dx / (rect.width || 1)) * 100, false);\n    }\n    follow(state, at, event.timeStamp);\n  }\n\n  function onPointerEnd(event: PointerEvent<HTMLDivElement>) {\n    const state = drag.current;\n    if (!state || state.pointer !== event.pointerId) return;\n    drag.current = null;\n    const index = state.index;\n    if (index === null) return;\n    const thumb = thumbs[index];\n    const current = latest.current;\n    const width = trackWidth();\n    const samples = state.samples;\n    const first = samples[0];\n    const last = samples[samples.length - 1];\n    const elapsed = last && first ? (last.t - first.t) / 1000 : 0;\n    const velocity =\n      (elapsed > 0.008 && event.timeStamp - last.t < 60\n        ? (last.p - first.p) / elapsed\n        : 0) + thumb.lag.getVelocity();\n\n    const stepPct = (step / span) * 100;\n    const carry =\n      event.type === \"pointerup\" && (stepPct / 100) * width >= SNAP_PX\n        ? clamp(project(velocity), -stepPct, stepPct)\n        : 0;\n\n    const next = settle(index, valueAt(state.raw + carry), current);\n    const from = thumb.pos.get() + thumb.lag.get();\n    const target = pct(next);\n\n    thumb.lag.jump(0);\n    thumb.pos.jump(from);\n    goal.current[index] = target;\n\n    if (reduced) thumb.pos.jump(target);\n    else animate(thumb.pos, target, { ...thumbSpring, velocity });\n\n    commit(index, next);\n    setDragging(null);\n    holdBubble(index);\n    onValueCommit?.(emit(latest.current));\n  }\n\n  function onKeyDown(index: number, event: KeyboardEvent<HTMLDivElement>) {\n    if (disabled) return;\n    const current = latest.current;\n    const now = current[index];\n    const large = largeStep ?? Math.max(step, snap(min + span / 10) - min);\n    const moves: Record<string, number> = {\n      ArrowRight: step,\n      ArrowUp: step,\n      ArrowLeft: -step,\n      ArrowDown: -step,\n      PageUp: large,\n      PageDown: -large,\n    };\n\n    let wanted: number;\n    if (event.key === \"Home\") wanted = lowOf(index, current);\n    else if (event.key === \"End\") wanted = highOf(index, current);\n    else if (event.key in moves)\n      wanted =\n        now +\n        (event.shiftKey && /^Arrow/.test(event.key)\n          ? Math.sign(moves[event.key]) * large\n          : moves[event.key]);\n    else return;\n\n    event.preventDefault();\n    setKeyFocus(index);\n    setQuiet(null);\n    setLastActive(index);\n    const next = settle(index, wanted, current);\n\n    if (next === now) {\n      const toward = Math.sign(wanted - now);\n      if (toward && !reduced)\n        animate(thumbs[index].pos, goal.current[index], {\n          ...kickSpring,\n          velocity: ((toward * BUMP_PX) / trackWidth()) * 100,\n        });\n      return;\n    }\n\n    commit(index, next);\n    onValueCommit?.(emit(latest.current));\n  }\n\n  function jumpTo(target: number) {\n    if (disabled) return;\n    const current = latest.current;\n    const index = nearest(pct(target), current);\n    const next = settle(index, target, current);\n    setLastActive(index);\n    focusFromPointer(index);\n    if (next === current[index]) return;\n    commit(index, next);\n    onValueCommit?.(emit(latest.current));\n  }\n\n  const markList = (marks ?? [])\n    .map((mark) => (typeof mark === \"number\" ? ({ value: mark } as SliderMark) : mark))\n    .filter((mark) => mark.value >= min && mark.value <= max);\n\n  const ticks = markList.filter((mark) => mark.value > min && mark.value < max);\n  const labelled = markList.filter((mark) => mark.label);\n  const inRange = (mark: number) =>\n    isRange\n      ? mark >= values[0] && mark <= values[1]\n      : mark <= values[0];\n\n  const names = thumbLabels ?? [\n    `${label}, minimum`,\n    `${label}, maximum`,\n  ];\n\n  return (\n    <div\n      className={cn(\n        \"relative flex flex-col gap-2 w-full select-none\",\n        disabled && \"opacity-50 pointer-events-none\",\n        className\n      )}\n      data-disabled={disabled || undefined}\n      data-dragging={dragging !== null || undefined}\n      data-marks={labelled.length > 0 || undefined}\n    >\n      <div className=\"flex items-center justify-between gap-3 select-none mb-1\">\n        <span\n          id={labelId}\n          className=\"text-sm font-medium text-foreground dark:text-zinc-200 tracking-tight\"\n        >\n          {label}\n        </span>\n        {showValue && (\n          <div\n            className=\"flex items-center text-sm font-medium text-foreground dark:text-zinc-200 tabular-nums select-none tracking-tight\"\n            aria-hidden=\"true\"\n          >\n            <RollingNumber value={values[0]} text={formatValue(values[0])} />\n            {isRange && (\n              <>\n                <span className=\"mx-1.5 text-muted-foreground select-none font-normal\">–</span>\n                <RollingNumber value={values[1]} text={formatValue(values[1])} />\n              </>\n            )}\n          </div>\n        )}\n      </div>\n\n      <div className=\"w-full flex flex-col\">\n        <div className=\"flex items-start gap-3 w-full\">\n          {start && (\n            <div className=\"shrink-0 h-9 flex items-center justify-center\">\n              {start}\n            </div>\n          )}\n          <div className=\"flex-1 flex flex-col min-w-0\">\n            <div\n              className=\"relative flex items-center w-full h-9 py-2 touch-none cursor-pointer\"\n              onPointerDown={onPointerDown}\n              onPointerMove={onPointerMove}\n              onPointerUp={onPointerEnd}\n              onPointerCancel={onPointerEnd}\n              onLostPointerCapture={onPointerEnd}\n              onMouseDown={(event) => event.preventDefault()}\n            >\n              <div\n                ref={trackRef}\n                className=\"relative w-full h-1.5 rounded-full bg-zinc-800 dark:bg-zinc-800 overflow-hidden\"\n              >\n                {ticks.map((mark) => (\n                  <span\n                    key={mark.value}\n                    className=\"absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-zinc-600 dark:bg-zinc-600 pointer-events-none\"\n                    style={{ left: `${pct(mark.value)}%` }}\n                  />\n                ))}\n                <motion.div\n                  className=\"absolute inset-0 bg-zinc-400 dark:bg-zinc-300 rounded-full pointer-events-none\"\n                  style={{ clipPath }}\n                >\n                  {ticks.map((mark) => (\n                    <span\n                      key={mark.value}\n                      className=\"absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-zinc-700 dark:bg-zinc-700 pointer-events-none\"\n                      style={{ left: `${pct(mark.value)}%` }}\n                    />\n                  ))}\n                </motion.div>\n              </div>\n\n              <div ref={thumbsRef} className=\"absolute inset-0 pointer-events-none\">\n                {values.map((item, index) => (\n                  <Thumb\n                    key={index}\n                    quiet={quiet === index}\n                    index={index}\n                    shown={thumbs[index].shown}\n                    value={item}\n                    text={formatValue(item)}\n                    low={lowOf(index, values)}\n                    high={highOf(index, values)}\n                    ariaLabel={isRange ? names[index] : undefined}\n                    labelledBy={labelId}\n                    active={lastActive === index}\n                    lifted={dragging === index}\n                    bubble={\n                      dragging === index ||\n                      keyFocus === index ||\n                      linger === index\n                    }\n                    disabled={disabled}\n                    onKeyDown={(event) => onKeyDown(index, event)}\n                    onFocus={(event) => {\n                      if (\n                        !pointerFocus.current &&\n                        event.currentTarget.matches(\":focus-visible\")\n                      ) {\n                        setKeyFocus(index);\n                      }\n                    }}\n                    onBlur={() => {\n                      setKeyFocus((current) =>\n                        current === index ? null : current\n                      );\n                      setQuiet((current) =>\n                        current === index ? null : current\n                      );\n                    }}\n                  />\n                ))}\n              </div>\n            </div>\n\n            {labelled.length > 0 && (\n              <div\n                className=\"relative w-full h-5 mt-1 select-none\"\n                aria-hidden=\"true\"\n              >\n                {labelled.map((mark) => {\n                  const isStart = mark.value === min;\n                  const isEnd = mark.value === max;\n                  return (\n                    <span\n                      key={mark.value}\n                      onClick={() => jumpTo(mark.value)}\n                      className={cn(\n                        \"absolute top-0 text-xs font-medium tabular-nums transition-colors cursor-pointer select-none whitespace-nowrap\",\n                        inRange(mark.value)\n                          ? \"text-foreground dark:text-zinc-200\"\n                          : \"text-muted-foreground hover:text-foreground dark:text-zinc-500 dark:hover:text-zinc-300\",\n                        isStart\n                          ? \"left-0 translate-x-0 text-left\"\n                          : isEnd\n                          ? \"right-0 translate-x-0 text-right\"\n                          : \"-translate-x-1/2 text-center\"\n                      )}\n                      style={\n                        !isStart && !isEnd\n                          ? { left: `${pct(mark.value)}%` }\n                          : undefined\n                      }\n                    >\n                      {mark.label}\n                    </span>\n                  );\n                })}\n              </div>\n            )}\n          </div>\n          {end && (\n            <div className=\"shrink-0 h-9 flex items-center justify-center\">\n              {end}\n            </div>\n          )}\n        </div>\n      </div>\n\n      {description && (\n        <p className=\"text-xs text-muted-foreground dark:text-zinc-400 mt-0.5 tracking-tight\">\n          {description}\n        </p>\n      )}\n\n      {name &&\n        values.map((item, index) => (\n          <input\n            key={index}\n            type=\"hidden\"\n            name={isRange ? `${name}[${index}]` : name}\n            value={item}\n            disabled={disabled}\n          />\n        ))}\n    </div>\n  );\n}\n\nexport default Slider;\n"
    }
  ]
}
