{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "color-function",
  "title": "Color Function",
  "description": "Ridiculously typed editor for the modern CSS color functions — color-mix(), light-dark(), and relative color — each with its own compile-time grammar and per-space channel strictness. Reuses color-picker and ships a result-swatch preview.",
  "dependencies": [],
  "registryDependencies": [
    "ridiculous-type-kit",
    "color-picker",
    "button",
    "popover",
    "input",
    "label",
    "select",
    "slider"
  ],
  "files": [
    {
      "path": "src/components/ui/color-function/index.ts",
      "content": "export type {\n  ColorFunctionPanelProps,\n  ColorFunctionPreviewProps,\n  ColorFunctionProps,\n  ColorMixEditorProps,\n  LightDarkEditorProps,\n  RelativeColorEditorProps,\n} from \"./color-function\"\nexport {\n  ColorFunction,\n  ColorFunctionPanel,\n  ColorFunctionPreview,\n  ColorMixEditor,\n  LightDarkEditor,\n  RelativeColorEditor,\n} from \"./color-function\"\nexport {\n  CHANNEL_KEYWORDS,\n  CYLINDRICAL_SPACES,\n  colorFunctionKind,\n  defaultState,\n  formatColorFunction,\n  HUE_METHODS,\n  MIX_COLOR_SPACES,\n  parseColorFunction,\n  RELATIVE_FNS,\n} from \"./color-function.helpers\"\nexport type {\n  ColorFunctionLiteral,\n  ColorFunctionMode,\n  ColorFunctionState,\n  ColorFunctionString,\n  ColorFunctionStringMap,\n  ColorMixLiteral,\n  ColorMixState,\n  ColorMixString,\n  ColorsOf,\n  HueMethod,\n  KindOf,\n  LightDarkLiteral,\n  LightDarkState,\n  LightDarkString,\n  MixColorSpace,\n  MixSpaceOf,\n  RelativeColorLiteral,\n  RelativeColorState,\n  RelativeColorString,\n  RelativeFn,\n  RelativeFnOf,\n} from \"./color-function.types\"\nexport { cssColorFn } from \"./color-function.types\"\n",
      "type": "registry:ui",
      "target": "components/ui/color-function/index.ts"
    },
    {
      "path": "src/components/ui/color-function/color-function.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\nimport { cn } from \"@/lib/utils\"\nimport {\n  defaultState,\n  formatColorFunction,\n  parseColorFunction,\n} from \"./color-function.helpers\"\nimport type {\n  ColorFunctionMode,\n  ColorFunctionState,\n  ColorFunctionStringMap,\n} from \"./color-function.types\"\nimport { ColorFunctionPreview } from \"./color-function-preview\"\nimport { ColorMixEditor } from \"./color-mix-editor\"\nimport { FamilySelect } from \"./family-select\"\nimport { LightDarkEditor } from \"./light-dark-editor\"\nimport { LiveString } from \"./live-string\"\nimport { RelativeColorEditor } from \"./relative-color-editor\"\n\nexport type { ColorFunctionPreviewProps } from \"./color-function-preview\"\n// Re-export the public sub-components so consumers (and tests) can import them\n// either from this entry or from the package index — the split is invisible.\nexport { ColorFunctionPreview } from \"./color-function-preview\"\nexport type { ColorMixEditorProps } from \"./color-mix-editor\"\nexport { ColorMixEditor } from \"./color-mix-editor\"\nexport type { LightDarkEditorProps } from \"./light-dark-editor\"\nexport { LightDarkEditor } from \"./light-dark-editor\"\nexport type { RelativeColorEditorProps } from \"./relative-color-editor\"\nexport { RelativeColorEditor } from \"./relative-color-editor\"\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface ColorFunctionPanelProps<\n  TMode extends ColorFunctionMode = \"color-mix\",\n> {\n  /**\n   * Which family to edit. When omitted, the panel shows a family selector\n   * and edits any of the three at runtime; the `onChange` string type then\n   * defaults to the `color-mix` suggestion shape for typing purposes.\n   */\n  mode?: TMode\n  value: ColorFunctionStringMap[TMode] | (string & {})\n  onChange: (value: ColorFunctionStringMap[TMode]) => void\n  className?: string\n  \"aria-label\"?: string\n}\n\nexport interface ColorFunctionProps<\n  TMode extends ColorFunctionMode = \"color-mix\",\n> extends ColorFunctionPanelProps<TMode> {}\n\n// ---------------------------------------------------------------------------\n// ColorFunction — popover-wrapped\n// ---------------------------------------------------------------------------\n\nexport function ColorFunction<TMode extends ColorFunctionMode = \"color-mix\">(\n  props: ColorFunctionProps<TMode>,\n) {\n  const {\n    value,\n    className,\n    \"aria-label\": ariaLabel = \"Edit a CSS color function\",\n  } = props\n\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <Button\n          variant=\"outline\"\n          className={cn(\"h-9 gap-2 px-3 font-mono\", className)}\n          aria-label={ariaLabel}\n        >\n          <span\n            className=\"size-5 shrink-0 rounded border\"\n            style={{ background: String(value) }}\n            aria-hidden=\"true\"\n          />\n          <span className=\"max-w-[220px] truncate text-xs\">\n            {String(value)}\n          </span>\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-auto p-0\" align=\"start\">\n        <ColorFunctionPanel {...props} />\n      </PopoverContent>\n    </Popover>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// ColorFunctionPanel — inline\n// ---------------------------------------------------------------------------\n\nexport function ColorFunctionPanel<\n  TMode extends ColorFunctionMode = \"color-mix\",\n>({\n  value,\n  onChange,\n  mode,\n  className,\n  \"aria-label\": ariaLabel = \"CSS color-function editor\",\n}: ColorFunctionPanelProps<TMode>) {\n  // The runtime family: a fixed `mode` prop wins; otherwise the parsed kind\n  // of the current value, falling back to color-mix.\n  const parsedKind = parseColorFunction(String(value))?.kind\n  const initialKind = mode ?? parsedKind ?? \"color-mix\"\n\n  const [state, setState] = useState<ColorFunctionState>(\n    () => parseColorFunction(String(value)) ?? defaultState(initialKind),\n  )\n  const lastEmittedRef = useRef<string | null>(null)\n\n  // Resync from external value (skip our own emits).\n  useEffect(() => {\n    if (value === lastEmittedRef.current) return\n    const parsed = parseColorFunction(String(value))\n    if (parsed !== null) setState(parsed)\n  }, [value])\n\n  const commit = (next: ColorFunctionState) => {\n    setState(next)\n    const str = formatColorFunction(next)\n    lastEmittedRef.current = str\n    onChange(str as ColorFunctionStringMap[TMode])\n  }\n\n  const switchFamily = (next: ColorFunctionMode) => {\n    commit(defaultState(next))\n  }\n\n  return (\n    <fieldset\n      className={cn(\n        \"m-0 w-[420px] space-y-3 border-0 bg-background p-3\",\n        className,\n      )}\n      aria-label={ariaLabel}\n    >\n      {mode === undefined && (\n        <FamilySelect kind={state.kind} onChange={switchFamily} />\n      )}\n\n      {state.kind === \"color-mix\" && (\n        <ColorMixEditor state={state} onChange={commit} />\n      )}\n      {state.kind === \"relative\" && (\n        <RelativeColorEditor state={state} onChange={commit} />\n      )}\n      {state.kind === \"light-dark\" && (\n        <LightDarkEditor state={state} onChange={commit} />\n      )}\n\n      <LiveString value={formatColorFunction(state)} />\n      <ColorFunctionPreview value={formatColorFunction(state)} />\n    </fieldset>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/color-function/color-function.tsx"
    },
    {
      "path": "src/components/ui/color-function/color-function.types.ts",
      "content": "// =====================================================================\n// color-function.types.ts\n//\n// The \"ridiculous\" tier: compile-time validation of MODERN CSS COLOR\n// FUNCTIONS, dispatched on the LEADING FUNCTION NAME into three\n// independent grammars:\n//\n//   color-mix(in <space> [<hue> hue]?, <color> <pct>?, <color> <pct>?)\n//   light-dark(<color>, <color>)\n//   <fn>(from <color> <c1> <c2> <c3> [/ <alpha>]?)   fn ∈ relative set\n//\n// Built on `ridiculous-type-kit` plus the color-picker's `ColorLiteral`\n// for every nested <color> argument (bare keyword colors like `red` are\n// NOT in ColorLiteral — the strict tier rejects them; the runtime parser\n// accepts them). `var(...)` is accepted anywhere a <color> is expected.\n//\n//   \"color-mix(in oklch shorter hue, #f00, #00f)\"  →  the literal\n//   \"light-dark(#fff, #000)\"                        →  the literal\n//   \"oklch(from #f00 l c h / 50%)\"                  →  the literal\n//   \"oklch(from #f00 r g b)\"                        →  never (wrong kw)\n//   \"rgb(255 0 0)\"                                   →  never (no `from`)\n// =====================================================================\n\nimport type {\n  CYLINDRICAL_SPACES,\n  HUE_METHODS,\n  MIX_COLOR_SPACES,\n  RELATIVE_FNS,\n} from \"@/components/ui/color-function/color-function.helpers\"\nimport type { ColorLiteral } from \"@/components/ui/color-picker/color-picker.types\"\nimport type {\n  And,\n  IsNumber,\n  IsPercentage,\n  ParseFunction,\n  SplitByComma,\n  SplitBySpace,\n  StartsWith,\n  Trim,\n} from \"@/lib/ridiculous-type-kit\"\n\n// =====================================================================\n// 1. CONSTANT UNIONS (exported where useful for advanced composition)\n//\n// Each union DERIVES from the runtime `as const` array of the same set in\n// `color-function.helpers` — single source of truth. Add a member to the\n// array and the union (and every validator built on it) follows. The\n// `import type` is fully erased: `typeof ARRAY` is a type-only use, so no\n// runtime import cycle with helpers (which imports only types from here).\n// =====================================================================\n\n/** The CSS Color 5 `color-mix` interpolation colorspace set. */\nexport type MixColorSpace = (typeof MIX_COLOR_SPACES)[number]\n\n/** The polar spaces that carry a hue component. */\ntype CylindricalSpace = (typeof CYLINDRICAL_SPACES)[number]\n\n/** Hue-interpolation methods (precede the literal `hue` keyword). */\nexport type HueMethod = (typeof HUE_METHODS)[number]\n\n/** The relative-color function names. */\nexport type RelativeFn = (typeof RELATIVE_FNS)[number]\n\n// =====================================================================\n// 2. PER-TOKEN PREDICATE ALIASES\n// =====================================================================\n\n/** True when `S` is a `var(...)` reference. */\ntype IsVar<S extends string> = Trim<S> extends `var(${string})` ? true : false\n\n/** A <color> argument: a ColorLiteral OR a var() reference. */\ntype IsColorArg<S extends string> =\n  ColorLiteral<Trim<S>> extends never ? IsVar<S> : true\n\n/** A lenient `calc(...)` token (body NOT parsed — documented relaxation). */\ntype IsCalc<S extends string> = Trim<S> extends `calc(${string})` ? true : false\n\n// =====================================================================\n// 3. color-mix — FULL validation\n// =====================================================================\n\n// Part 1: the interpolation spec. `in <space>` or `in <space> <method> hue`.\ntype ValidInterp<Tokens extends string[]> = Tokens extends [\"in\", infer Sp]\n  ? Sp extends MixColorSpace\n    ? true\n    : false\n  : Tokens extends [\"in\", infer Sp, infer M, \"hue\"]\n    ? Sp extends CylindricalSpace\n      ? M extends HueMethod\n        ? true\n        : false\n      : false\n    : false\n\n// Parts 2 & 3: a <color> with an optional trailing <percentage>.\ntype ValidMixColor<Tokens extends string[]> = Tokens extends [\n  infer C extends string,\n]\n  ? IsColorArg<C>\n  : Tokens extends [infer C extends string, infer P extends string]\n    ? And<IsColorArg<C>, IsPercentage<Trim<P>>>\n    : false\n\ntype ValidMixParts<Parts extends string[]> = Parts extends [\n  infer I extends string,\n  infer A extends string,\n  infer B extends string,\n]\n  ? And<\n      ValidInterp<SplitBySpace<I>>,\n      And<ValidMixColor<SplitBySpace<A>>, ValidMixColor<SplitBySpace<B>>>\n    >\n  : false\n\n/**\n * Strict `color-mix()` validator. Resolves to `S` when `S` is a fully\n * valid color-mix (`in <space>` with an optional `<method> hue` on a\n * cylindrical space, then exactly two `<color> <pct>?` arguments), else\n * `never`.\n */\nexport type ColorMixLiteral<S extends string> =\n  ParseFunction<Trim<S>> extends {\n    name: \"color-mix\"\n    args: infer Args extends string\n  }\n    ? ValidMixParts<SplitByComma<Args>> extends true\n      ? S\n      : never\n    : never\n\n// =====================================================================\n// 4. light-dark — FULL validation\n// =====================================================================\n\ntype ValidLightDark<Parts extends string[]> = Parts extends [\n  infer L extends string,\n  infer D extends string,\n]\n  ? And<IsColorArg<L>, IsColorArg<D>>\n  : false\n\n/**\n * Strict `light-dark()` validator. Resolves to `S` for exactly two valid\n * `<color>` arguments, else `never`.\n */\nexport type LightDarkLiteral<S extends string> =\n  ParseFunction<Trim<S>> extends {\n    name: \"light-dark\"\n    args: infer Args extends string\n  }\n    ? ValidLightDark<SplitByComma<Args>> extends true\n      ? S\n      : never\n    : never\n\n// =====================================================================\n// 5. relative color — per-space channel-keyword strictness + relaxations\n// =====================================================================\n\n// Per-fn channel keyword sets. Validated against the DESTINATION fn's\n// space (CSS resolves the `from` color into it), not the source color.\ntype ChannelKw<Fn extends RelativeFn> = Fn extends \"rgb\"\n  ? \"r\" | \"g\" | \"b\"\n  : Fn extends \"hsl\"\n    ? \"h\" | \"s\" | \"l\"\n    : Fn extends \"hwb\"\n      ? \"h\" | \"w\" | \"b\"\n      : Fn extends \"lab\" | \"oklab\"\n        ? \"l\" | \"a\" | \"b\"\n        : Fn extends \"lch\" | \"oklch\"\n          ? \"l\" | \"c\" | \"h\"\n          : // color() — predefined-rgb channels\n            \"r\" | \"g\" | \"b\"\n\n// A channel token: a channel keyword for the fn, `none`, a number, a\n// percentage, or a lenient calc(). Angles for hue are subsumed by the\n// keyword/number cases (CSS permits a bare number for hue).\ntype IsChannelTok<Fn extends RelativeFn, T extends string> =\n  Trim<T> extends ChannelKw<Fn> | \"none\"\n    ? true\n    : IsNumber<Trim<T>> extends true\n      ? true\n      : IsPercentage<Trim<T>> extends true\n        ? true\n        : IsCalc<T>\n\n// The alpha after `/`: number | percentage | the `alpha` keyword | none | calc.\ntype IsAlphaTok<T extends string> =\n  Trim<T> extends \"none\" | \"alpha\"\n    ? true\n    : IsNumber<Trim<T>> extends true\n      ? true\n      : IsPercentage<Trim<T>> extends true\n        ? true\n        : IsCalc<T>\n\n// Validate the three channel tokens + optional `/ alpha`. The body has\n// ALREADY had `from <color>` (and, for color(), the space ident) peeled.\ntype ValidChannels<\n  Fn extends RelativeFn,\n  Tokens extends string[],\n> = Tokens extends [\n  infer C1 extends string,\n  infer C2 extends string,\n  infer C3 extends string,\n]\n  ? And<IsChannelTok<Fn, C1>, And<IsChannelTok<Fn, C2>, IsChannelTok<Fn, C3>>>\n  : Tokens extends [\n        infer C1 extends string,\n        infer C2 extends string,\n        infer C3 extends string,\n        \"/\",\n        infer A extends string,\n      ]\n    ? And<\n        IsChannelTok<Fn, C1>,\n        And<IsChannelTok<Fn, C2>, And<IsChannelTok<Fn, C3>, IsAlphaTok<A>>>\n      >\n    : false\n\n// After `from`, the rest is `<color> <channels…>`. For color() the\n// channels may be preceded by a colorspace ident (peeled leniently).\ntype ValidFromBody<\n  Fn extends RelativeFn,\n  Tokens extends string[],\n> = Tokens extends [\n  \"from\",\n  infer Src extends string,\n  ...infer Rest extends string[],\n]\n  ? IsColorArg<Src> extends true\n    ? Fn extends \"color\"\n      ? // color(from <c> <space> <c1> <c2> <c3> …): peel the space ident.\n        Rest extends [string, ...infer Ch extends string[]]\n        ? ValidChannels<Fn, Ch>\n        : false\n      : ValidChannels<Fn, Rest>\n    : false\n  : false\n\n/**\n * Strict relative-color validator. Resolves to `S` for `<fn>(from <color>\n * <c1> <c2> <c3> [/ <alpha>]?)` with per-space channel keywords enforced,\n * else `never`. Lenient on calc() bodies and channel magnitudes\n * (documented relaxation).\n */\nexport type RelativeColorLiteral<S extends string> =\n  ParseFunction<Trim<S>> extends {\n    name: infer N extends string\n    args: infer Args extends string\n  }\n    ? N extends RelativeFn\n      ? ValidFromBody<N, SplitBySpace<Args>> extends true\n        ? S\n        : never\n      : never\n    : never\n\n// =====================================================================\n// 6. TOP-LEVEL DISPATCH + CALL-SITE HELPER\n// =====================================================================\n\n/**\n * Strict literal validator for the three modern color-function families,\n * dispatched on the leading function name. Resolves to `S` on success,\n * `never` otherwise. A bare color literal (`rgb(255 0 0)` with no `from`)\n * is NOT in scope — that is `color-picker`'s domain.\n *\n * @example\n * type A = ColorFunctionLiteral<\"color-mix(in srgb, #f00, #00f)\"> // the literal\n * type B = ColorFunctionLiteral<\"rgb(255 0 0)\">                    // never\n */\nexport type ColorFunctionLiteral<S extends string> =\n  ParseFunction<Trim<S>> extends {\n    name: infer N extends string\n    args: infer Args extends string\n  }\n    ? N extends \"color-mix\"\n      ? ColorMixLiteral<S>\n      : N extends \"light-dark\"\n        ? LightDarkLiteral<S>\n        : N extends RelativeFn\n          ? StartsWith<Trim<Args>, \"from \"> extends true\n            ? RelativeColorLiteral<S>\n            : never\n          : never\n    : never\n\n/**\n * Call-site validator helper. Mirrors `cssBoxShadow()` / `cssFilter()` /\n * `color()`. An invalid color function becomes a type error at the\n * argument.\n */\nexport const cssColorFn = <S extends string>(\n  value: S & ColorFunctionLiteral<S>,\n): S => value\n\n// =====================================================================\n// 7. SUGGESTION STRINGS — IntelliSense + onChange return types\n// =====================================================================\n\n/** Suggestion union for `color-mix(...)`. The STRICT tier is the gate. */\nexport type ColorMixString = `color-mix(in ${string}, ${string}, ${string})`\n\n/** Suggestion union for a relative color. */\nexport type RelativeColorString = `${RelativeFn}(from ${string})`\n\n/** Suggestion union for `light-dark(...)`. */\nexport type LightDarkString = `light-dark(${string}, ${string})`\n\n/** Any color-function suggestion string. */\nexport type ColorFunctionString =\n  | ColorMixString\n  | RelativeColorString\n  | LightDarkString\n\n/** Mode → output-string map. Narrows `onChange` when `mode` is set. */\nexport interface ColorFunctionStringMap {\n  \"color-mix\": ColorMixString\n  relative: RelativeColorString\n  \"light-dark\": LightDarkString\n}\n\n/** The `mode` prop key type. */\nexport type ColorFunctionMode = keyof ColorFunctionStringMap\n\n// =====================================================================\n// 8. UTILITY TYPES — operate on color-function literals at the type level\n// =====================================================================\n\n/**\n * Which family a literal is, or `never`.\n *\n * @example\n * type A = KindOf<\"oklch(from #f00 l c h)\"> // \"relative\"\n */\nexport type KindOf<S extends string> =\n  ParseFunction<Trim<S>> extends {\n    name: infer N extends string\n    args: infer Args extends string\n  }\n    ? N extends \"color-mix\"\n      ? \"color-mix\"\n      : N extends \"light-dark\"\n        ? \"light-dark\"\n        : N extends RelativeFn\n          ? StartsWith<Trim<Args>, \"from \"> extends true\n            ? \"relative\"\n            : never\n          : never\n    : never\n\n/**\n * The interpolation colorspace of a `color-mix` literal, or `never`.\n *\n * @example\n * type A = MixSpaceOf<\"color-mix(in oklch, #f00, #00f)\"> // \"oklch\"\n */\nexport type MixSpaceOf<S extends string> =\n  ParseFunction<Trim<S>> extends {\n    name: \"color-mix\"\n    args: infer Args extends string\n  }\n    ? SplitByComma<Args> extends [infer I extends string, ...string[]]\n      ? SplitBySpace<I> extends [\"in\", infer Sp, ...string[]]\n        ? Sp extends MixColorSpace\n          ? Sp\n          : never\n        : never\n      : never\n    : never\n\n/**\n * The function name of a relative-color literal, or `never`.\n *\n * @example\n * type A = RelativeFnOf<\"oklch(from #f00 l c h)\"> // \"oklch\"\n */\nexport type RelativeFnOf<S extends string> =\n  ParseFunction<Trim<S>> extends {\n    name: infer N extends string\n    args: infer Args extends string\n  }\n    ? N extends RelativeFn\n      ? StartsWith<Trim<Args>, \"from \"> extends true\n        ? N\n        : never\n      : never\n    : never\n\n// Strip an optional trailing `<percentage>` token off a color-mix arg,\n// keeping just the color string.\ntype MixColorOf<Tokens extends string[]> = Tokens extends [\n  infer C extends string,\n  ...string[],\n]\n  ? C\n  : never\n\n/**\n * The raw color-argument strings of a literal.\n *\n * @example\n * type A = ColorsOf<\"light-dark(#fff, #000)\">              // [\"#fff\", \"#000\"]\n * type B = ColorsOf<\"color-mix(in srgb, #f00 30%, #00f)\">  // [\"#f00\", \"#00f\"]\n * type C = ColorsOf<\"oklch(from #f00 l c h)\">              // [\"#f00\"]\n */\nexport type ColorsOf<S extends string> =\n  KindOf<S> extends \"light-dark\"\n    ? ParseFunction<Trim<S>> extends { args: infer Args extends string }\n      ? SplitByComma<Args>\n      : never\n    : KindOf<S> extends \"color-mix\"\n      ? ParseFunction<Trim<S>> extends { args: infer Args extends string }\n        ? SplitByComma<Args> extends [\n            string,\n            infer A extends string,\n            infer B extends string,\n          ]\n          ? [MixColorOf<SplitBySpace<A>>, MixColorOf<SplitBySpace<B>>]\n          : never\n        : never\n      : KindOf<S> extends \"relative\"\n        ? ParseFunction<Trim<S>> extends { args: infer Args extends string }\n          ? SplitBySpace<Args> extends [\n              \"from\",\n              infer Src extends string,\n              ...string[],\n            ]\n            ? [Src]\n            : never\n          : never\n        : never\n\n// =====================================================================\n// 9. INTERNAL STATE — exported discriminated union, keyed by `kind`\n//\n// The tolerant superset the editor drives off. Values are kept as strings\n// (they carry units / colors / calc), mirroring how the literal preserves\n// raw text. Exported for advanced use (custom serialization).\n// =====================================================================\n\nexport interface ColorMixState {\n  kind: \"color-mix\"\n  /** Interpolation colorspace. */\n  space: string\n  /** Optional hue-interpolation method (cylindrical spaces only). */\n  hue?: string\n  /** First color. */\n  colorA: string\n  /** Optional weight for the first color. */\n  pctA?: string\n  /** Second color. */\n  colorB: string\n  /** Optional weight for the second color. */\n  pctB?: string\n}\n\nexport interface RelativeColorState {\n  kind: \"relative\"\n  /** Relative-color function name. */\n  fn: string\n  /** Source color (after `from`). */\n  from: string\n  /** Optional colorspace ident (color() form only). */\n  space?: string\n  /** First channel token. */\n  c1: string\n  /** Second channel token. */\n  c2: string\n  /** Third channel token. */\n  c3: string\n  /** Optional alpha (after `/`). */\n  alpha?: string\n}\n\nexport interface LightDarkState {\n  kind: \"light-dark\"\n  /** Color used in a light color-scheme. */\n  light: string\n  /** Color used in a dark color-scheme. */\n  dark: string\n}\n\n/** The editor's internal state — a discriminated union keyed by `kind`. */\nexport type ColorFunctionState =\n  | ColorMixState\n  | RelativeColorState\n  | LightDarkState\n",
      "type": "registry:ui",
      "target": "components/ui/color-function/color-function.types.ts"
    },
    {
      "path": "src/components/ui/color-function/color-function.helpers.ts",
      "content": "// =====================================================================\n// color-function.helpers.ts\n//\n// Pure runtime parse / format for the three modern color-function\n// families. This is the TOLERANT SUPERSET of the strict type tier: it\n// keeps calc()/var() verbatim (opaque), accepts bare keyword colors\n// (`red`) as color arguments (valid CSS even though not in ColorLiteral),\n// and is lenient on channel magnitudes — exactly the relaxations the spec\n// documents. Dispatch is on the leading function name (paren-aware), into\n// a `ColorFunctionState` discriminated union the UI drives off.\n// =====================================================================\n\nimport type {\n  ColorFunctionMode,\n  ColorFunctionState,\n} from \"./color-function.types\"\n\n// ---------------------------------------------------------------------------\n// Constant tables — single source of truth, mirror the type-level unions.\n// ---------------------------------------------------------------------------\n\n/** The 14-member `color-mix` interpolation colorspace set. */\nexport const MIX_COLOR_SPACES = [\n  \"srgb\",\n  \"srgb-linear\",\n  \"display-p3\",\n  \"a98-rgb\",\n  \"prophoto-rgb\",\n  \"rec2020\",\n  \"lab\",\n  \"oklab\",\n  \"xyz\",\n  \"xyz-d50\",\n  \"xyz-d65\",\n  \"hsl\",\n  \"hwb\",\n  \"lch\",\n  \"oklch\",\n] as const\n\n/** The four polar spaces that may carry a hue-interpolation method. */\nexport const CYLINDRICAL_SPACES = [\"hsl\", \"hwb\", \"lch\", \"oklch\"] as const\n\n/** Hue-interpolation methods. */\nexport const HUE_METHODS = [\n  \"shorter\",\n  \"longer\",\n  \"increasing\",\n  \"decreasing\",\n] as const\n\n/** The eight relative-color function names. */\nexport const RELATIVE_FNS = [\n  \"rgb\",\n  \"hsl\",\n  \"hwb\",\n  \"lab\",\n  \"lch\",\n  \"oklab\",\n  \"oklch\",\n  \"color\",\n] as const\n\n/** Default mix weight (percent) — the midpoint a fresh ratio seeds to. */\nexport const DEFAULT_PCT = 50\n\n/** Channel keywords per relative-color function (destination space). */\nexport const CHANNEL_KEYWORDS: Record<\n  string,\n  readonly [string, string, string]\n> = {\n  rgb: [\"r\", \"g\", \"b\"],\n  hsl: [\"h\", \"s\", \"l\"],\n  hwb: [\"h\", \"w\", \"b\"],\n  lab: [\"l\", \"a\", \"b\"],\n  lch: [\"l\", \"c\", \"h\"],\n  oklab: [\"l\", \"a\", \"b\"],\n  oklch: [\"l\", \"c\", \"h\"],\n  color: [\"r\", \"g\", \"b\"],\n}\n\nconst MIX_SPACE_SET = new Set<string>(MIX_COLOR_SPACES)\nconst CYLINDRICAL_SET = new Set<string>(CYLINDRICAL_SPACES)\nconst HUE_METHOD_SET = new Set<string>(HUE_METHODS)\nconst RELATIVE_FN_SET = new Set<string>(RELATIVE_FNS)\n\n// ---------------------------------------------------------------------------\n// Paren-aware splitters (runtime mirror of the kit combinators)\n// ---------------------------------------------------------------------------\n\n/** Split on `sep` only at bracket depth 0. */\nfunction splitTopLevel(src: string, sep: string): string[] {\n  const out: string[] = []\n  let depth = 0\n  let cur = \"\"\n  for (const ch of src) {\n    if (ch === \"(\" || ch === \"[\") depth++\n    else if (ch === \")\" || ch === \"]\") depth--\n    if (ch === sep && depth === 0) {\n      out.push(cur)\n      cur = \"\"\n    } else {\n      cur += ch\n    }\n  }\n  out.push(cur)\n  return out\n}\n\n/** Split a comma list at depth 0, trimming each part (keeps empties). */\nfunction splitCommas(src: string): string[] {\n  return splitTopLevel(src, \",\").map((s) => s.trim())\n}\n\n/** Split a space list at depth 0, trimming and dropping empty runs. */\nfunction splitSpaces(src: string): string[] {\n  return splitTopLevel(src, \" \")\n    .map((s) => s.trim())\n    .filter((s) => s.length > 0)\n}\n\n// Name regex allows the hyphen so `color-mix` / `light-dark` match.\nconst CALL_RE = /^([a-zA-Z][a-zA-Z-]*)\\((.*)\\)$/s\n\n// ---------------------------------------------------------------------------\n// Per-family parsers — return their state variant or null on a structural\n// / arity / unknown-token error.\n// ---------------------------------------------------------------------------\n\nfunction parseColorMix(args: string): ColorFunctionState | null {\n  const parts = splitCommas(args)\n  if (parts.length !== 3) return null\n\n  // Part 1: `in <space>` or `in <space> <method> hue`.\n  const interp = splitSpaces(parts[0])\n  if (interp[0] !== \"in\") return null\n  const space = interp[1]\n  if (space === undefined || !MIX_SPACE_SET.has(space)) return null\n\n  let hue: string | undefined\n  if (interp.length === 4) {\n    const method = interp[2]\n    if (!CYLINDRICAL_SET.has(space)) return null\n    if (!HUE_METHOD_SET.has(method) || interp[3] !== \"hue\") return null\n    hue = method\n  } else if (interp.length !== 2) {\n    return null\n  }\n\n  // Parts 2 & 3: `<color> <pct>?`.\n  const a = parseColorWithPct(parts[1])\n  const b = parseColorWithPct(parts[2])\n  if (a === null || b === null) return null\n\n  const state: ColorFunctionState = {\n    kind: \"color-mix\",\n    space,\n    colorA: a.color,\n    colorB: b.color,\n  }\n  if (hue !== undefined) state.hue = hue\n  if (a.pct !== undefined) state.pctA = a.pct\n  if (b.pct !== undefined) state.pctB = b.pct\n  return state\n}\n\n/** A `<color> <pct>?` argument. Tolerant: any non-empty head is a color. */\nfunction parseColorWithPct(\n  part: string,\n): { color: string; pct?: string } | null {\n  const tokens = splitSpaces(part)\n  if (tokens.length === 0) return null\n  const last = tokens[tokens.length - 1]\n  if (tokens.length >= 2 && last.endsWith(\"%\")) {\n    return { color: tokens.slice(0, -1).join(\" \"), pct: last }\n  }\n  return { color: tokens.join(\" \") }\n}\n\nfunction parseLightDark(args: string): ColorFunctionState | null {\n  const parts = splitCommas(args)\n  if (parts.length !== 2) return null\n  const light = parts[0]\n  const dark = parts[1]\n  if (light === \"\" || dark === \"\") return null\n  return { kind: \"light-dark\", light, dark }\n}\n\nfunction parseRelative(fn: string, args: string): ColorFunctionState | null {\n  const tokens = splitSpaces(args)\n  if (tokens[0] !== \"from\") return null\n  const from = tokens[1]\n  if (from === undefined) return null\n\n  // color() carries a colorspace ident between the source color and the\n  // channels: `color(from <c> <space> r g b ...)`.\n  let rest = tokens.slice(2)\n  let space: string | undefined\n  if (fn === \"color\") {\n    space = rest[0]\n    if (space === undefined) return null\n    rest = rest.slice(1)\n  }\n\n  // Exactly 3 channels, then an optional `/ alpha`.\n  let channels = rest\n  let alpha: string | undefined\n  const slash = rest.indexOf(\"/\")\n  if (slash !== -1) {\n    channels = rest.slice(0, slash)\n    const alphaTokens = rest.slice(slash + 1)\n    if (alphaTokens.length !== 1) return null\n    alpha = alphaTokens[0]\n  }\n  if (channels.length !== 3) return null\n\n  const state: ColorFunctionState = {\n    kind: \"relative\",\n    fn,\n    from,\n    c1: channels[0],\n    c2: channels[1],\n    c3: channels[2],\n  }\n  if (space !== undefined) state.space = space\n  if (alpha !== undefined) state.alpha = alpha\n  return state\n}\n\n/**\n * Parse a modern CSS color function into typed state, or `null` on any\n * syntax / unknown-function / arity error. Tolerant of calc()/var() and\n * bare keyword colors, lenient on channel magnitudes.\n */\nexport function parseColorFunction(src: string): ColorFunctionState | null {\n  const trimmed = src.trim()\n  if (trimmed === \"\") return null\n  const m = trimmed.match(CALL_RE)\n  if (!m) return null\n  const name = m[1]\n  const args = m[2]\n\n  if (name === \"color-mix\") return parseColorMix(args)\n  if (name === \"light-dark\") return parseLightDark(args)\n  if (RELATIVE_FN_SET.has(name)) {\n    // Only the `from`-prefixed relative form is in scope.\n    if (splitSpaces(args)[0] !== \"from\") return null\n    return parseRelative(name, args)\n  }\n  return null\n}\n\n/** Runtime mirror of `KindOf` — the family of a value, or null. */\nexport function colorFunctionKind(\n  src: string,\n): ColorFunctionState[\"kind\"] | null {\n  const state = parseColorFunction(src)\n  return state === null ? null : state.kind\n}\n\n// ---------------------------------------------------------------------------\n// formatColorFunction — canonical re-serialization\n// ---------------------------------------------------------------------------\n\n/** Canonical re-serialization of a color-function state. */\nexport function formatColorFunction(state: ColorFunctionState): string {\n  switch (state.kind) {\n    case \"color-mix\": {\n      const interp = state.hue\n        ? `in ${state.space} ${state.hue} hue`\n        : `in ${state.space}`\n      const a = state.pctA ? `${state.colorA} ${state.pctA}` : state.colorA\n      const b = state.pctB ? `${state.colorB} ${state.pctB}` : state.colorB\n      return `color-mix(${interp}, ${a}, ${b})`\n    }\n    case \"light-dark\":\n      return `light-dark(${state.light}, ${state.dark})`\n    case \"relative\": {\n      const head = state.space\n        ? `from ${state.from} ${state.space}`\n        : `from ${state.from}`\n      const channels = `${state.c1} ${state.c2} ${state.c3}`\n      const alpha = state.alpha ? ` / ${state.alpha}` : \"\"\n      return `${state.fn}(${head} ${channels}${alpha})`\n    }\n  }\n}\n\n// ---------------------------------------------------------------------------\n// defaultState — seed a fresh value per mode (each round-trips)\n// ---------------------------------------------------------------------------\n\n/** A sensible default state for a freshly-selected mode. */\nexport function defaultState(mode: ColorFunctionMode): ColorFunctionState {\n  switch (mode) {\n    case \"color-mix\":\n      return {\n        kind: \"color-mix\",\n        space: \"oklch\",\n        colorA: \"#ff0000\",\n        pctA: \"50%\",\n        colorB: \"#0000ff\",\n        pctB: \"50%\",\n      }\n    case \"relative\":\n      return {\n        kind: \"relative\",\n        fn: \"oklch\",\n        from: \"#ff0000\",\n        c1: \"l\",\n        c2: \"c\",\n        c3: \"h\",\n      }\n    case \"light-dark\":\n      return { kind: \"light-dark\", light: \"#ffffff\", dark: \"#000000\" }\n  }\n}\n",
      "type": "registry:ui",
      "target": "components/ui/color-function/color-function.helpers.ts"
    },
    {
      "path": "src/components/ui/color-function/color-function-preview.tsx",
      "content": "import { useId, useState } from \"react\"\nimport { cn } from \"@/lib/utils\"\n\n// ---------------------------------------------------------------------------\n// ColorFunctionPreview (public) — renders the value as a swatch background,\n// with a light/dark color-scheme toggle for `light-dark()` values.\n// ---------------------------------------------------------------------------\n\nexport interface ColorFunctionPreviewProps {\n  value: string\n  className?: string\n}\n\nexport function ColorFunctionPreview({\n  value,\n  className,\n}: ColorFunctionPreviewProps) {\n  const [scheme, setScheme] = useState<\"light\" | \"dark\">(\"light\")\n  const isLightDark = value.trimStart().startsWith(\"light-dark(\")\n  const headingId = useId()\n\n  return (\n    <section className={cn(\"space-y-2\", className)} aria-labelledby={headingId}>\n      <div className=\"flex items-center justify-between\">\n        <span id={headingId} className=\"text-[10px] text-muted-foreground\">\n          Preview\n        </span>\n        {isLightDark && (\n          <button\n            type=\"button\"\n            aria-label=\"Toggle color scheme\"\n            onClick={() => setScheme((s) => (s === \"light\" ? \"dark\" : \"light\"))}\n            className=\"rounded border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground\"\n          >\n            scheme: {scheme}\n          </button>\n        )}\n      </div>\n      <div\n        data-testid=\"cf-preview-scheme\"\n        style={{ colorScheme: scheme }}\n        className=\"rounded border p-2\"\n      >\n        <div\n          data-testid=\"cf-preview\"\n          style={{ background: value }}\n          className=\"h-16 w-full rounded\"\n        />\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/color-function/color-function-preview.tsx"
    },
    {
      "path": "src/components/ui/color-function/color-mix-editor.tsx",
      "content": "import { ColorPicker } from \"@/components/ui/color-picker\"\nimport { cn } from \"@/lib/utils\"\nimport {\n  CYLINDRICAL_SPACES,\n  DEFAULT_PCT,\n  HUE_METHODS,\n  MIX_COLOR_SPACES,\n} from \"./color-function.helpers\"\nimport type { ColorMixState } from \"./color-function.types\"\n\nconst CYLINDRICAL_SET = new Set<string>(CYLINDRICAL_SPACES)\n\n/** Parse a percentage like \"30%\" to a 0–100 number (fallback `DEFAULT_PCT`). */\nfunction pctToNumber(pct: string | undefined): number {\n  if (!pct) return DEFAULT_PCT\n  const n = Number.parseFloat(pct)\n  return Number.isFinite(n) ? n : DEFAULT_PCT\n}\n\n// ---------------------------------------------------------------------------\n// ColorMixEditor (public) — interpolation space, optional hue method, and two\n// `<color> <pct>?` rows with a swap control.\n// ---------------------------------------------------------------------------\n\nexport interface ColorMixEditorProps {\n  state: ColorMixState\n  onChange: (state: ColorMixState) => void\n  className?: string\n}\n\nexport function ColorMixEditor({\n  state,\n  onChange,\n  className,\n}: ColorMixEditorProps) {\n  const isCylindrical = CYLINDRICAL_SET.has(state.space)\n\n  const setSpace = (space: string) => {\n    const next: ColorMixState = { ...state, space }\n    // Drop the hue method when the new space cannot carry one.\n    if (!CYLINDRICAL_SET.has(space)) next.hue = undefined\n    onChange(next)\n  }\n\n  return (\n    <div className={cn(\"space-y-3\", className)}>\n      <div className=\"flex flex-wrap items-center gap-2\">\n        <label className=\"flex items-center gap-1.5 text-xs\">\n          <span className=\"text-muted-foreground\">in</span>\n          <select\n            aria-label=\"Interpolation colorspace\"\n            value={state.space}\n            onChange={(e) => setSpace(e.target.value)}\n            className=\"h-8 rounded border bg-background px-2 font-mono text-xs\"\n          >\n            {MIX_COLOR_SPACES.map((s) => (\n              <option key={s} value={s}>\n                {s}\n              </option>\n            ))}\n          </select>\n        </label>\n\n        {isCylindrical && (\n          <label className=\"flex items-center gap-1.5 text-xs\">\n            <span className=\"text-muted-foreground\">hue</span>\n            <select\n              aria-label=\"Hue interpolation method\"\n              value={state.hue ?? \"\"}\n              onChange={(e) =>\n                onChange({ ...state, hue: e.target.value || undefined })\n              }\n              className=\"h-8 rounded border bg-background px-2 font-mono text-xs\"\n            >\n              <option value=\"\">(default)</option>\n              {HUE_METHODS.map((m) => (\n                <option key={m} value={m}>\n                  {m}\n                </option>\n              ))}\n            </select>\n          </label>\n        )}\n      </div>\n\n      <MixColorRow\n        label=\"First\"\n        color={state.colorA}\n        pct={state.pctA}\n        onColor={(colorA) => onChange({ ...state, colorA })}\n        onPct={(pctA) => onChange({ ...state, pctA })}\n      />\n      <MixColorRow\n        label=\"Second\"\n        color={state.colorB}\n        pct={state.pctB}\n        onColor={(colorB) => onChange({ ...state, colorB })}\n        onPct={(pctB) => onChange({ ...state, pctB })}\n      />\n\n      <button\n        type=\"button\"\n        onClick={() =>\n          onChange({\n            ...state,\n            colorA: state.colorB,\n            colorB: state.colorA,\n            pctA: state.pctB,\n            pctB: state.pctA,\n          })\n        }\n        className=\"rounded border px-2 py-1 text-[10px] text-muted-foreground hover:text-foreground\"\n      >\n        ⇄ swap colors\n      </button>\n    </div>\n  )\n}\n\ninterface MixColorRowProps {\n  label: string\n  color: string\n  pct: string | undefined\n  onColor: (color: string) => void\n  onPct: (pct: string | undefined) => void\n}\n\nfunction MixColorRow({ label, color, pct, onColor, onPct }: MixColorRowProps) {\n  const lower = label.toLowerCase()\n  return (\n    <div className=\"flex items-center gap-2\">\n      <span className=\"w-12 text-[10px] text-muted-foreground uppercase\">\n        {label}\n      </span>\n      <ColorPicker\n        native\n        value={color}\n        onChange={(c) => onColor(String(c))}\n        aria-label={`${lower} color`}\n      />\n      <input\n        type=\"range\"\n        min={0}\n        max={100}\n        value={pctToNumber(pct)}\n        onChange={(e) => onPct(`${e.target.value}%`)}\n        aria-label={`${lower} color ratio`}\n        className=\"flex-1 accent-foreground\"\n      />\n      <span className=\"w-10 text-right font-mono text-[10px] text-muted-foreground\">\n        {pct ?? \"—\"}\n      </span>\n      <button\n        type=\"button\"\n        aria-label={`Toggle ${lower} ratio`}\n        onClick={() => onPct(pct ? undefined : `${DEFAULT_PCT}%`)}\n        className=\"rounded border px-1.5 py-0.5 text-[10px] text-muted-foreground hover:text-foreground\"\n      >\n        {pct ? \"×%\" : \"+%\"}\n      </button>\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/color-function/color-mix-editor.tsx"
    },
    {
      "path": "src/components/ui/color-function/family-select.tsx",
      "content": "import type {\n  ColorFunctionMode,\n  ColorFunctionState,\n} from \"./color-function.types\"\n\n// ---------------------------------------------------------------------------\n// FamilySelect — switches the edited color-function family (shown only when\n// the panel has no fixed `mode` prop).\n// ---------------------------------------------------------------------------\n\nconst MODES: readonly ColorFunctionMode[] = [\n  \"color-mix\",\n  \"relative\",\n  \"light-dark\",\n]\n\ninterface FamilySelectProps {\n  kind: ColorFunctionState[\"kind\"]\n  onChange: (next: ColorFunctionMode) => void\n}\n\nexport function FamilySelect({ kind, onChange }: FamilySelectProps) {\n  return (\n    <label className=\"flex items-center gap-2 text-xs\">\n      <span className=\"text-muted-foreground\">Family</span>\n      <select\n        aria-label=\"Color-function family\"\n        value={kind}\n        onChange={(e) => onChange(e.target.value as ColorFunctionMode)}\n        className=\"h-8 rounded border bg-background px-2 font-mono text-xs\"\n      >\n        {MODES.map((m) => (\n          <option key={m} value={m}>\n            {m}\n          </option>\n        ))}\n      </select>\n    </label>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/color-function/family-select.tsx"
    },
    {
      "path": "src/components/ui/color-function/light-dark-editor.tsx",
      "content": "import { ColorPicker } from \"@/components/ui/color-picker\"\nimport { cn } from \"@/lib/utils\"\nimport type { LightDarkState } from \"./color-function.types\"\n\n// ---------------------------------------------------------------------------\n// LightDarkEditor (public) — two color pickers, one per color-scheme slot.\n// ---------------------------------------------------------------------------\n\nexport interface LightDarkEditorProps {\n  state: LightDarkState\n  onChange: (state: LightDarkState) => void\n  className?: string\n}\n\nexport function LightDarkEditor({\n  state,\n  onChange,\n  className,\n}: LightDarkEditorProps) {\n  return (\n    <div className={cn(\"flex items-center gap-4\", className)}>\n      <div className=\"flex items-center gap-2 text-xs\">\n        <span className=\"text-muted-foreground\">Light color</span>\n        <ColorPicker\n          native\n          value={state.light}\n          onChange={(c) => onChange({ ...state, light: String(c) })}\n          aria-label=\"Light color\"\n        />\n      </div>\n      <div className=\"flex items-center gap-2 text-xs\">\n        <span className=\"text-muted-foreground\">Dark color</span>\n        <ColorPicker\n          native\n          value={state.dark}\n          onChange={(c) => onChange({ ...state, dark: String(c) })}\n          aria-label=\"Dark color\"\n        />\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/color-function/light-dark-editor.tsx"
    },
    {
      "path": "src/components/ui/color-function/live-string.tsx",
      "content": "// ---------------------------------------------------------------------------\n// LiveString — read-only echo of the canonical serialized color function.\n// ---------------------------------------------------------------------------\n\ninterface LiveStringProps {\n  value: string\n}\n\nexport function LiveString({ value }: LiveStringProps) {\n  return (\n    <code className=\"block break-all rounded bg-muted px-2 py-1.5 font-mono text-[11px]\">\n      {value}\n    </code>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/color-function/live-string.tsx"
    },
    {
      "path": "src/components/ui/color-function/relative-color-editor.tsx",
      "content": "import { ColorPicker } from \"@/components/ui/color-picker\"\nimport { cn } from \"@/lib/utils\"\nimport { CHANNEL_KEYWORDS, RELATIVE_FNS } from \"./color-function.helpers\"\nimport type { RelativeColorState } from \"./color-function.types\"\n\n// ---------------------------------------------------------------------------\n// RelativeColorEditor (public) — picks the relative fn, source color, an\n// optional color() space ident, three channel tokens, and an optional alpha.\n// ---------------------------------------------------------------------------\n\nexport interface RelativeColorEditorProps {\n  state: RelativeColorState\n  onChange: (state: RelativeColorState) => void\n  className?: string\n}\n\nexport function RelativeColorEditor({\n  state,\n  onChange,\n  className,\n}: RelativeColorEditorProps) {\n  const keywords = CHANNEL_KEYWORDS[state.fn] ?? [\"c1\", \"c2\", \"c3\"]\n\n  const setFn = (fn: string) => {\n    // Reset channels to the new function's keyword defaults.\n    const kw = CHANNEL_KEYWORDS[fn] ?? [\"c1\", \"c2\", \"c3\"]\n    const next: RelativeColorState = {\n      ...state,\n      fn,\n      c1: kw[0],\n      c2: kw[1],\n      c3: kw[2],\n    }\n    if (fn !== \"color\") next.space = undefined\n    else if (next.space === undefined) next.space = \"srgb\"\n    onChange(next)\n  }\n\n  return (\n    <div className={cn(\"space-y-3\", className)}>\n      <div className=\"flex items-center gap-2\">\n        <label className=\"flex items-center gap-1.5 text-xs\">\n          <span className=\"text-muted-foreground\">fn</span>\n          <select\n            aria-label=\"Relative function\"\n            value={state.fn}\n            onChange={(e) => setFn(e.target.value)}\n            className=\"h-8 rounded border bg-background px-2 font-mono text-xs\"\n          >\n            {RELATIVE_FNS.map((f) => (\n              <option key={f} value={f}>\n                {f}\n              </option>\n            ))}\n          </select>\n        </label>\n\n        <span className=\"text-muted-foreground text-xs\">from</span>\n        <ColorPicker\n          native\n          value={state.from}\n          onChange={(c) => onChange({ ...state, from: String(c) })}\n          aria-label=\"Source color\"\n        />\n\n        {state.fn === \"color\" && (\n          <input\n            type=\"text\"\n            value={state.space ?? \"srgb\"}\n            onChange={(e) => onChange({ ...state, space: e.target.value })}\n            aria-label=\"Color space\"\n            className=\"h-8 w-24 rounded border bg-background px-2 font-mono text-xs\"\n          />\n        )}\n      </div>\n\n      <div className=\"grid grid-cols-3 gap-2\">\n        <ChannelInput\n          index={1}\n          placeholder={keywords[0]}\n          value={state.c1}\n          onChange={(c1) => onChange({ ...state, c1 })}\n        />\n        <ChannelInput\n          index={2}\n          placeholder={keywords[1]}\n          value={state.c2}\n          onChange={(c2) => onChange({ ...state, c2 })}\n        />\n        <ChannelInput\n          index={3}\n          placeholder={keywords[2]}\n          value={state.c3}\n          onChange={(c3) => onChange({ ...state, c3 })}\n        />\n      </div>\n\n      <label className=\"flex items-center gap-2 text-xs\">\n        <span className=\"w-12 text-[10px] text-muted-foreground uppercase\">\n          Alpha\n        </span>\n        <input\n          type=\"text\"\n          value={state.alpha ?? \"\"}\n          placeholder=\"(none)\"\n          onChange={(e) =>\n            onChange({ ...state, alpha: e.target.value || undefined })\n          }\n          aria-label=\"Alpha channel\"\n          className=\"h-8 flex-1 rounded border bg-background px-2 font-mono text-xs\"\n        />\n      </label>\n    </div>\n  )\n}\n\ninterface ChannelInputProps {\n  index: number\n  placeholder: string\n  value: string\n  onChange: (value: string) => void\n}\n\nfunction ChannelInput({\n  index,\n  placeholder,\n  value,\n  onChange,\n}: ChannelInputProps) {\n  return (\n    <label className=\"flex flex-col gap-1\">\n      <span className=\"text-[10px] text-muted-foreground\">channel {index}</span>\n      <input\n        type=\"text\"\n        value={value}\n        placeholder={placeholder}\n        onChange={(e) => onChange(e.target.value)}\n        aria-label={`Channel ${index} (${placeholder})`}\n        className=\"h-8 rounded border bg-background px-2 font-mono text-xs\"\n      />\n    </label>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/color-function/relative-color-editor.tsx"
    }
  ],
  "type": "registry:ui"
}