{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slider",
  "title": "UI Slider",
  "description": "A slider whose fill is a numeric value channel plus a thumb idle/hover/press channel (dual-channel value-channel deviation). The Slider renderer is pure (clamped 0–100 value, thumb scale, grab-ring opacity); useSliderTransition reads the frame and eases both channels — value as a numeric lerp, the thumb visual from its state presets. Muted track + primary range + bordered thumb with a derived grab ring.",
  "dependencies": ["remotion"],
  "registryDependencies": ["@remocn/remocn-ui"],
  "files": [
    {
      "path": "registry/remocn-ui/slider/index.tsx",
      "content": "\"use client\";\n\nimport {\n  clamp01,\n  mixOklch,\n  type RemocnTheme,\n  useRemocnTheme,\n} from \"@/lib/remocn-ui\";\n\nexport type SliderThumbState = \"idle\" | \"hover\" | \"press\";\n\nexport interface SliderStyle {\n  value: number;\n  thumbScale: number;\n  ringOpacity: number;\n}\n\nexport interface SliderProps {\n  value?: number;\n  thumbState?: SliderThumbState;\n  style?: SliderStyle;\n  width?: number;\n  showValue?: boolean;\n  theme?: Partial<RemocnTheme>;\n  className?: string;\n}\n\nconst TRACK_HEIGHT = 8;\nconst THUMB_HEIGHT = 16;\nconst THUMB_WIDTH = 24;\nconst THUMB_RADIUS = THUMB_HEIGHT / 2;\nconst RING_WIDTH = 4;\n\nfunction clampValue(value: number): number {\n  return clamp01(value / 100) * 100;\n}\n\nexport function sliderThumbStyle(thumbState: SliderThumbState): {\n  thumbScale: number;\n  ringOpacity: number;\n} {\n  switch (thumbState) {\n    case \"hover\":\n      return { thumbScale: 1.1, ringOpacity: 1 };\n    case \"press\":\n      return { thumbScale: 1.15, ringOpacity: 1 };\n    default:\n      return { thumbScale: 1, ringOpacity: 0 };\n  }\n}\n\nexport interface SliderStyleContext {\n  track: string;\n  range: string;\n  thumbBg: string;\n  thumbRing: string;\n  ring: string;\n  valueText: string;\n}\n\nexport function sliderStyleContext(theme: RemocnTheme): SliderStyleContext {\n  return {\n    track: mixOklch(theme.input, theme.background, 0.1),\n    range: theme.primary,\n    thumbBg: \"oklch(1 0 0)\",\n    thumbRing: \"rgba(0, 0, 0, 0.1)\",\n    ring: mixOklch(theme.ring, theme.background, 0.7),\n    valueText: theme.foreground,\n  };\n}\n\nexport function Slider({\n  value = 0,\n  thumbState = \"idle\",\n  style,\n  width = 320,\n  showValue = false,\n  theme: themeOverride,\n  className,\n}: SliderProps) {\n  const theme = useRemocnTheme(themeOverride, \"light\");\n  const ctx = sliderStyleContext(theme);\n\n  const thumb = sliderThumbStyle(thumbState);\n  const v: SliderStyle = style ?? {\n    value,\n    thumbScale: thumb.thumbScale,\n    ringOpacity: thumb.ringOpacity,\n  };\n  const pct = clampValue(v.value);\n\n  return (\n    <div\n      className={className}\n      style={{\n        position: \"relative\",\n        display: \"inline-flex\",\n        alignItems: \"center\",\n        width,\n        height: THUMB_HEIGHT,\n        fontFamily:\n          \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\",\n      }}\n    >\n      {}\n      <div\n        style={{\n          position: \"relative\",\n          width: \"100%\",\n          height: TRACK_HEIGHT,\n          borderRadius: TRACK_HEIGHT / 2,\n          background: ctx.track,\n        }}\n      >\n        {}\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            top: 0,\n            bottom: 0,\n            width: `${pct}%`,\n            borderRadius: TRACK_HEIGHT / 2,\n            background: ctx.range,\n          }}\n        />\n      </div>\n      {}\n      <div\n        style={{\n          position: \"absolute\",\n          left: `${pct}%`,\n          top: \"50%\",\n          transform: `translate(-50%, -50%) scale(${v.thumbScale})`,\n        }}\n      >\n        {}\n        <div\n          style={{\n            position: \"absolute\",\n            left: \"50%\",\n            top: \"50%\",\n            width: THUMB_WIDTH + RING_WIDTH * 2,\n            height: THUMB_HEIGHT + RING_WIDTH * 2,\n            transform: \"translate(-50%, -50%)\",\n            borderRadius: THUMB_RADIUS + RING_WIDTH,\n            background: ctx.ring,\n            opacity: v.ringOpacity,\n          }}\n        />\n        {}\n        <div\n          style={{\n            position: \"relative\",\n            width: THUMB_WIDTH,\n            height: THUMB_HEIGHT,\n            borderRadius: THUMB_RADIUS,\n            background: ctx.thumbBg,\n            boxShadow: `0 0 0 1px ${ctx.thumbRing}, 0 2px 4px rgba(0,0,0,0.18)`,\n          }}\n        />\n        {showValue && (\n          <span\n            style={{\n              position: \"absolute\",\n              left: \"50%\",\n              bottom: \"100%\",\n              marginBottom: 8,\n              transform: \"translateX(-50%)\",\n              fontSize: 12,\n              fontWeight: 500,\n              fontVariantNumeric: \"tabular-nums\",\n              color: ctx.valueText,\n            }}\n          >\n            {Math.round(pct)}\n          </span>\n        )}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/slider.tsx"
    },
    {
      "path": "registry/remocn-ui/slider/use-slider-transition.ts",
      "content": "\"use client\";\n\nimport { useCurrentFrame } from \"remotion\";\nimport {\n  type SliderStyle,\n  type SliderThumbState,\n  sliderThumbStyle,\n} from \"@/components/remocn/slider\";\nimport { clamp01, type EasingName, easings } from \"@/lib/remocn-ui\";\n\nexport interface SliderStep {\n  at: number;\n  value?: number;\n  thumbState?: SliderThumbState;\n  duration?: number;\n  easing?: EasingName;\n}\n\nexport const DEFAULT_DURATION = 18;\n\nexport interface SliderTransitionOptions {\n  speed?: number;\n  defaultDuration?: number;\n}\n\nexport function tweenSliderStyle(\n  a: SliderStyle,\n  b: SliderStyle,\n  t: number,\n): SliderStyle {\n  return {\n    value: a.value + (b.value - a.value) * t,\n    thumbScale: a.thumbScale + (b.thumbScale - a.thumbScale) * t,\n    ringOpacity: a.ringOpacity + (b.ringOpacity - a.ringOpacity) * t,\n  };\n}\n\nexport function useSliderTransition(\n  steps: SliderStep[],\n  opts: SliderTransitionOptions = {},\n): SliderStyle {\n  const { speed = 1 } = opts;\n  const raw = useCurrentFrame() * speed;\n  return sliderStyleAt(steps, raw, opts);\n}\n\nfunction valueAt(\n  steps: SliderStep[],\n  raw: number,\n  defaultDuration: number,\n): number {\n  const valueSteps = steps.filter(\n    (s): s is SliderStep & { value: number } => s.value !== undefined,\n  );\n  if (valueSteps.length === 0) return 0;\n  const first = valueSteps[0];\n  if (raw <= first.at) return first.value;\n\n  let toIndex = valueSteps.length - 1;\n  for (let i = 1; i < valueSteps.length; i++) {\n    if (valueSteps[i].at > raw) {\n      toIndex = i;\n      break;\n    }\n  }\n  const pastLast = raw >= valueSteps[valueSteps.length - 1].at;\n  const to = pastLast ? valueSteps[valueSteps.length - 1] : valueSteps[toIndex];\n  const from = pastLast\n    ? valueSteps[valueSteps.length - 1]\n    : valueSteps[toIndex - 1];\n\n  const dur = to.duration ?? defaultDuration;\n  const ease = easings[to.easing ?? \"out\"];\n  const start = to.at - dur;\n  const t = pastLast || dur <= 0 ? 1 : ease(clamp01((raw - start) / dur));\n  return from.value + (to.value - from.value) * t;\n}\n\nfunction thumbAt(\n  steps: SliderStep[],\n  raw: number,\n  defaultDuration: number,\n): { thumbScale: number; ringOpacity: number } {\n  const thumbSteps = steps.filter(\n    (s): s is SliderStep & { thumbState: SliderThumbState } =>\n      s.thumbState !== undefined,\n  );\n  if (thumbSteps.length === 0) return sliderThumbStyle(\"idle\");\n  const first = thumbSteps[0];\n  if (raw <= first.at) return sliderThumbStyle(first.thumbState);\n\n  let toIndex = thumbSteps.length - 1;\n  for (let i = 1; i < thumbSteps.length; i++) {\n    if (thumbSteps[i].at > raw) {\n      toIndex = i;\n      break;\n    }\n  }\n  const pastLast = raw >= thumbSteps[thumbSteps.length - 1].at;\n  const to = pastLast ? thumbSteps[thumbSteps.length - 1] : thumbSteps[toIndex];\n  const from = pastLast\n    ? thumbSteps[thumbSteps.length - 1]\n    : thumbSteps[toIndex - 1];\n\n  const dur = to.duration ?? defaultDuration;\n  const ease = easings[to.easing ?? \"out\"];\n  const start = to.at - dur;\n  const t = pastLast || dur <= 0 ? 1 : ease(clamp01((raw - start) / dur));\n\n  const a = sliderThumbStyle(from.thumbState);\n  const b = sliderThumbStyle(to.thumbState);\n  return {\n    thumbScale: a.thumbScale + (b.thumbScale - a.thumbScale) * t,\n    ringOpacity: a.ringOpacity + (b.ringOpacity - a.ringOpacity) * t,\n  };\n}\n\nexport function sliderStyleAt(\n  steps: SliderStep[],\n  raw: number,\n  opts: SliderTransitionOptions = {},\n): SliderStyle {\n  const { defaultDuration = DEFAULT_DURATION } = opts;\n  const value = valueAt(steps, raw, defaultDuration);\n  const thumb = thumbAt(steps, raw, defaultDuration);\n  return {\n    value,\n    thumbScale: thumb.thumbScale,\n    ringOpacity: thumb.ringOpacity,\n  };\n}\n",
      "type": "registry:component",
      "target": "components/remocn/use-slider-transition.ts"
    }
  ],
  "type": "registry:component"
}
