{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "remocn-ui",
  "title": "remocn UI Core",
  "description": "Shared timeline-fold hook, theme context, and color math for remocn UI primitives.",
  "dependencies": ["remotion", "culori"],
  "files": [
    {
      "path": "registry/remocn-ui/core/timeline.ts",
      "content": "import { useCurrentFrame, useVideoConfig } from \"remotion\";\nimport type { Step } from \"./types\";\n\nexport function framesFor(\n  d: number | { seconds: number },\n  fps: number,\n): number {\n  return typeof d === \"number\" ? d : Math.round(d.seconds * fps);\n}\n\nexport function revealCount(\n  localFrame: number,\n  fps: number,\n  len: number,\n  cps: number,\n): number {\n  const over = (len / cps) * fps;\n  if (over <= 0) return len;\n  return Math.max(0, Math.min(len, Math.floor((localFrame / over) * len)));\n}\n\nexport function clamp01(t: number): number {\n  return Math.max(0, Math.min(1, t));\n}\n\nexport function revealedText(full: string, count: number): string {\n  const c = Math.max(0, Math.min(full.length, Math.floor(count)));\n  return full.slice(0, c);\n}\n\nexport interface TypewriterOptions {\n  cps?: number;\n  speed?: number;\n  startFrame?: number;\n}\n\nexport interface TypewriterState {\n  text: string;\n  count: number;\n  done: boolean;\n  typing: boolean;\n}\n\nexport function useTypewriter(\n  full: string,\n  options: TypewriterOptions = {},\n): TypewriterState {\n  const { cps = 20, speed = 1, startFrame = 0 } = options;\n  const frame = useCurrentFrame();\n  const { fps } = useVideoConfig();\n  const local = frame * speed - startFrame;\n  const count = local <= 0 ? 0 : revealCount(local, fps, full.length, cps);\n  return {\n    text: revealedText(full, count),\n    count,\n    done: count >= full.length,\n    typing: count > 0 && count < full.length,\n  };\n}\n\nexport function useCurrentState<S extends string>(\n  steps: Step<S>[],\n  defaultState: S,\n  speed = 1,\n): S {\n  const effectiveFrame = useCurrentFrame() * speed;\n  let current = defaultState;\n  let bestAt = -Infinity;\n  steps.forEach((step) => {\n    if (step.at <= effectiveFrame && step.at >= bestAt) {\n      bestAt = step.at;\n      current = step.state;\n    }\n  });\n  return current;\n}\n\nexport function useStateTransition<S extends string>(\n  steps: Step<S>[],\n  defaultState: S,\n  speed = 1,\n  defaultDuration = 8,\n): { from: S; to: S; progress: number } {\n  const effectiveFrame = useCurrentFrame() * speed;\n  const started = steps\n    .map((step, index) => ({ step, index }))\n    .sort((a, b) => a.step.at - b.step.at || a.index - b.index)\n    .filter((e) => e.step.at <= effectiveFrame);\n  if (started.length === 0)\n    return { from: defaultState, to: defaultState, progress: 1 };\n  const to = started[started.length - 1].step;\n  const from = started.length >= 2 ? started[started.length - 2].step : null;\n  const dur = to.duration ?? defaultDuration;\n  const progress = dur > 0 ? clamp01((effectiveFrame - to.at) / dur) : 1;\n  return { from: from ? from.state : defaultState, to: to.state, progress };\n}\n",
      "type": "registry:lib",
      "target": "lib/remocn-ui/timeline.ts"
    },
    {
      "path": "registry/remocn-ui/core/theme.ts",
      "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { createContext, createElement, useContext } from \"react\";\n\nexport interface RemocnTheme {\n  background: string;\n  foreground: string;\n  card: string;\n  cardForeground: string;\n  popover: string;\n  popoverForeground: string;\n  primary: string;\n  primaryForeground: string;\n  secondary: string;\n  secondaryForeground: string;\n  muted: string;\n  mutedForeground: string;\n  accent: string;\n  accentForeground: string;\n  destructive: string;\n  destructiveForeground: string;\n  border: string;\n  input: string;\n  ring: string;\n  radius: number;\n}\n\nexport const defaultLightTheme: RemocnTheme = {\n  background: \"oklch(1 0 0)\",\n  foreground: \"oklch(0.145 0 0)\",\n  card: \"oklch(1 0 0)\",\n  cardForeground: \"oklch(0.145 0 0)\",\n  popover: \"oklch(1 0 0)\",\n  popoverForeground: \"oklch(0.145 0 0)\",\n  primary: \"oklch(0.205 0 0)\",\n  primaryForeground: \"oklch(0.985 0 0)\",\n  secondary: \"oklch(0.97 0 0)\",\n  secondaryForeground: \"oklch(0.205 0 0)\",\n  muted: \"oklch(0.97 0 0)\",\n  mutedForeground: \"oklch(0.556 0 0)\",\n  accent: \"oklch(0.97 0 0)\",\n  accentForeground: \"oklch(0.205 0 0)\",\n  destructive: \"oklch(0.577 0.245 27.325)\",\n  destructiveForeground: \"oklch(0.985 0 0)\",\n  border: \"oklch(0.922 0 0)\",\n  input: \"oklch(0.922 0 0)\",\n  ring: \"oklch(0.708 0 0)\",\n  radius: 10,\n};\n\nexport const defaultDarkTheme: RemocnTheme = {\n  background: \"oklch(0.145 0 0)\",\n  foreground: \"oklch(0.985 0 0)\",\n  card: \"oklch(0.205 0 0)\",\n  cardForeground: \"oklch(0.985 0 0)\",\n  popover: \"oklch(0.205 0 0)\",\n  popoverForeground: \"oklch(0.985 0 0)\",\n  primary: \"oklch(0.922 0 0)\",\n  primaryForeground: \"oklch(0.205 0 0)\",\n  secondary: \"oklch(0.269 0 0)\",\n  secondaryForeground: \"oklch(0.985 0 0)\",\n  muted: \"oklch(0.269 0 0)\",\n  mutedForeground: \"oklch(0.708 0 0)\",\n  accent: \"oklch(0.269 0 0)\",\n  accentForeground: \"oklch(0.985 0 0)\",\n  destructive: \"oklch(0.704 0.191 22.216)\",\n  destructiveForeground: \"oklch(0.985 0 0)\",\n  border: \"oklch(1 0 0 / 10%)\",\n  input: \"oklch(1 0 0 / 15%)\",\n  ring: \"oklch(0.556 0 0)\",\n  radius: 10,\n};\n\ninterface RemocnThemeContextValue {\n  theme?: Partial<RemocnTheme>;\n  mode?: \"light\" | \"dark\";\n}\n\nconst RemocnThemeContext = createContext<RemocnThemeContextValue>({});\n\nexport interface RemocnUIProviderProps {\n  theme?: Partial<RemocnTheme>;\n  mode?: \"light\" | \"dark\";\n  children: ReactNode;\n}\n\nexport function RemocnUIProvider({\n  theme,\n  mode,\n  children,\n}: RemocnUIProviderProps) {\n  return createElement(\n    RemocnThemeContext.Provider,\n    { value: { theme, mode } },\n    children,\n  );\n}\n\nexport function useRemocnTheme(\n  override?: Partial<RemocnTheme>,\n  modeOverride?: \"light\" | \"dark\",\n): RemocnTheme {\n  const ctx = useContext(RemocnThemeContext);\n  const mode = modeOverride ?? ctx.mode ?? \"light\";\n  const base = mode === \"dark\" ? defaultDarkTheme : defaultLightTheme;\n  return { ...base, ...ctx.theme, ...override };\n}\n",
      "type": "registry:lib",
      "target": "lib/remocn-ui/theme.ts"
    },
    {
      "path": "registry/remocn-ui/core/color.ts",
      "content": "import {\n  clampChroma,\n  converter,\n  formatRgb,\n  interpolate,\n  type Oklch,\n  parse,\n  type Rgb,\n} from \"culori\";\n\nconst toRgb = converter(\"rgb\");\nconst toOklch = converter(\"oklch\");\n\nconst BLACK: Rgb = { mode: \"rgb\", r: 0, g: 0, b: 0, alpha: 1 };\n\nexport function parseColor(c: string): Rgb {\n  const s = c.trim();\n\n  if (s.startsWith(\"var(\")) {\n    if (process.env.NODE_ENV !== \"production\") {\n      console.warn(\n        `[remocn-ui] parseColor cannot resolve CSS variable \"${s}\" under Remotion's per-frame render. ` +\n          \"Animated colors must be concrete oklch/hex/rgb values supplied via the theme. \" +\n          \"Falling back to the JS default.\",\n      );\n    }\n    return { ...BLACK };\n  }\n\n  const rgb = toRgb(parse(s));\n  if (!rgb) {\n    if (process.env.NODE_ENV !== \"production\") {\n      console.warn(\n        `[remocn-ui] parseColor could not parse \"${s}\"; using black.`,\n      );\n    }\n    return { ...BLACK };\n  }\n\n  return { mode: \"rgb\", r: rgb.r, g: rgb.g, b: rgb.b, alpha: rgb.alpha ?? 1 };\n}\n\nexport function oklchToRgb(l: number, c: number, h: number): Rgb {\n  const mapped = clampChroma({ mode: \"oklch\", l, c, h }, \"oklch\", \"rgb\");\n  return toRgb(mapped);\n}\n\nexport function rgbToOklch(rgb: Rgb): Oklch {\n  const { l, c, h } = toOklch(rgb);\n  return { mode: \"oklch\", l, c, h: Number.isFinite(h) ? h : 0 };\n}\n\nfunction resolveColorString(s: string): string {\n  const trimmed = s.trim();\n  if (trimmed.startsWith(\"var(\")) {\n    if (process.env.NODE_ENV !== \"production\") {\n      console.warn(\n        `[remocn-ui] mixOklch cannot resolve CSS variable \"${trimmed}\" under Remotion's per-frame render. ` +\n          \"Animated colors must be concrete oklch/hex/rgb values supplied via the theme. \" +\n          \"Falling back to the JS default.\",\n      );\n    }\n    return \"#000\";\n  }\n  if (!parse(trimmed)) {\n    if (process.env.NODE_ENV !== \"production\") {\n      console.warn(\n        `[remocn-ui] mixOklch could not parse \"${trimmed}\"; using black.`,\n      );\n    }\n    return \"#000\";\n  }\n  return trimmed;\n}\n\nexport function mixOklch(a: string, b: string, t: number): string {\n  const mixed = clampChroma(\n    interpolate([resolveColorString(a), resolveColorString(b)], \"oklch\")(t),\n    \"oklch\",\n    \"rgb\",\n  );\n  return toCss(toRgb(mixed));\n}\n\nexport function toCss(color: Rgb): string {\n  return formatRgb(color);\n}\n",
      "type": "registry:lib",
      "target": "lib/remocn-ui/color.ts"
    },
    {
      "path": "registry/remocn-ui/core/motion.ts",
      "content": "export const easings = {\n  linear: (t: number): number => t,\n  out: (t: number): number => 1 - (1 - t) ** 3,\n  in: (t: number): number => t * t * t,\n  inOut: (t: number): number =>\n    t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2,\n} as const;\n\nexport type EasingName = keyof typeof easings;\n\nexport const springs = {\n  snappy: { damping: 18, stiffness: 220, mass: 0.7 },\n  soft: { damping: 14, stiffness: 120, mass: 0.9 },\n  bouncy: { damping: 10, stiffness: 180, mass: 0.8 },\n} as const;\n\nexport type SpringName = keyof typeof springs;\n",
      "type": "registry:lib",
      "target": "lib/remocn-ui/motion.ts"
    },
    {
      "path": "registry/remocn-ui/core/types.ts",
      "content": "export interface Step<S extends string = string> {\n  at: number;\n  state: S;\n  duration?: number;\n}\n",
      "type": "registry:lib",
      "target": "lib/remocn-ui/types.ts"
    },
    {
      "path": "registry/remocn-ui/core/index.ts",
      "content": "export {\n  mixOklch,\n  oklchToRgb,\n  parseColor,\n  rgbToOklch,\n  toCss,\n} from \"./color\";\nexport type { EasingName, SpringName } from \"./motion\";\nexport { easings, springs } from \"./motion\";\nexport type { RemocnTheme, RemocnUIProviderProps } from \"./theme\";\nexport {\n  defaultDarkTheme,\n  defaultLightTheme,\n  RemocnUIProvider,\n  useRemocnTheme,\n} from \"./theme\";\nexport type { TypewriterOptions, TypewriterState } from \"./timeline\";\nexport {\n  clamp01,\n  framesFor,\n  revealCount,\n  revealedText,\n  useCurrentState,\n  useStateTransition,\n  useTypewriter,\n} from \"./timeline\";\nexport type { Step } from \"./types\";\n",
      "type": "registry:lib",
      "target": "lib/remocn-ui/index.ts"
    }
  ],
  "type": "registry:lib"
}
