{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input-otp-glass",
  "title": "Otp-glass input",
  "description": "Styled input: the Otp-glass set. 5 decorative input variations (FrostOtp, TintOtp, SmokeOtp, CrystalOtp, DepthOtp) on the ai2 token system, driven by CSS token transitions and sized sm to xl. Part of the free styled layer.",
  "dependencies": [],
  "registryDependencies": [
    "@ai2/tokens",
    "@ai2/glass"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/input-otp-glass.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { glassDepth } from \"@/registry/ai2/ui/glass\"\n\n/* Glass OTP family: 5 one-time-code inputs with frosted-glass cells (frost, tint,\n   smoke, crystal, depth). The difference is ONLY on the cell surface. The glass\n   surface comes from the glassDepth scale in @ai2/glass (NO hand-made\n   backdrop-blur, AGENTS.md 4.5): the step gives the surface (blur + translucency +\n   the ai2 inset signature) and the variant gives the character (tint, border,\n   corner radius).\n\n   STRUCTURAL NOTE: unlike the other glass form files in this family there is no\n   wrapper here - the cell ITSELF is the input, so the glass sits directly on the\n   control. That means the signature and the focus ring meet on the same element;\n   since both are box-shadows, it was verified by measurement that the ring stays\n   visible on focus.\n\n   Every cell is a real <input maxLength=1 inputMode=numeric>; typing a digit moves\n   focus forward, Backspace clears and moves back, and pasting fills every cell.\n   Color comes ONLY from tokens, via alpha color-mix. No animation. Self-sufficient:\n   NO input-otp package and NO radix.\n\n   TRAP: cn() is tailwind-merge; any shadow-* coming after the signature silently\n   deletes it. That is why the `filled` state expresses its weight with a border\n   rather than a shadow. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\ntype OtpProps = {\n  className?: string\n  size?: StyledSize\n  length?: number\n  value?: string\n  defaultValue?: string\n  onValueChange?: (v: string) => void\n}\n\nconst cellSize: Record<StyledSize, string> = {\n  sm: \"size-8 text-sm\",\n  md: \"size-10 text-base\",\n  lg: \"size-12 text-lg\",\n  xl: \"size-14 text-xl\",\n}\n\n/* Focus uses OUTLINE, NOT a ring. In this file the glass sits directly on the cell\n   (the input), so the conflict is at its sharpest here: the glassDepth signature\n   writes box-shadow directly as the arbitrary property `[box-shadow:...]` and was\n   overriding Tailwind's --tw-ring-shadow chain entirely; ring-[3px] was drawing\n   nothing at all on a glass cell (measured, 2026-07-15). Since outline is a\n   separate CSS property it does not collide with the signature, and it follows the\n   rounded corner radius. */\nconst cellBase =\n  \"bg-transparent text-center font-medium tabular-nums text-foreground outline-none transition-colors placeholder:text-muted-foreground focus-visible:outline-[3px] focus-visible:outline-offset-0 focus-visible:outline-ring/50 disabled:cursor-not-allowed disabled:opacity-50\"\n\nconst rootBase = \"inline-flex items-center gap-2\"\n\n/* Kontrollu/kontrolsuz OTP durumu + ileri/geri odak, yapistirma. */\nfunction useOtpCode(\n  length: number,\n  { value, defaultValue, onValueChange }: Pick<OtpProps, \"value\" | \"defaultValue\" | \"onValueChange\">\n) {\n  const isControlled = value !== undefined\n  const [internal, setInternal] = React.useState<string>(() => (defaultValue ?? \"\").slice(0, length))\n  const current = (isControlled ? value : internal) ?? \"\"\n\n  const chars = React.useMemo(() => {\n    const arr: string[] = Array(length).fill(\"\")\n    for (let i = 0; i < length; i++) arr[i] = current[i] ?? \"\"\n    return arr\n  }, [current, length])\n\n  const refs = React.useRef<Array<HTMLInputElement | null>>([])\n\n  const commit = (next: string) => {\n    if (!isControlled) setInternal(next)\n    onValueChange?.(next)\n  }\n\n  const setAt = (i: number, raw: string) => {\n    const ch = raw.slice(-1)\n    const arr = [...chars]\n    arr[i] = ch\n    commit(arr.join(\"\"))\n    if (ch && i < length - 1) refs.current[i + 1]?.focus()\n  }\n\n  const onKey = (i: number, e: React.KeyboardEvent<HTMLInputElement>) => {\n    if (e.key === \"Backspace\") {\n      const arr = [...chars]\n      if (chars[i]) {\n        arr[i] = \"\"\n        commit(arr.join(\"\"))\n      } else if (i > 0) {\n        arr[i - 1] = \"\"\n        commit(arr.join(\"\"))\n        refs.current[i - 1]?.focus()\n      }\n      e.preventDefault()\n    } else if (e.key === \"ArrowLeft\" && i > 0) {\n      refs.current[i - 1]?.focus()\n    } else if (e.key === \"ArrowRight\" && i < length - 1) {\n      refs.current[i + 1]?.focus()\n    }\n  }\n\n  const onPaste = (i: number, e: React.ClipboardEvent<HTMLInputElement>) => {\n    e.preventDefault()\n    const text = e.clipboardData.getData(\"text\").replace(/\\s+/g, \"\")\n    if (!text) return\n    const arr = [...chars]\n    for (let k = 0; k < text.length && i + k < length; k++) arr[i + k] = text[k]\n    commit(arr.join(\"\"))\n    const last = Math.min(i + text.length, length - 1)\n    refs.current[last]?.focus()\n  }\n\n  return { chars, refs, setAt, onKey, onPaste }\n}\n\ntype Controller = ReturnType<typeof useOtpCode>\n\nfunction cellHandlers(ctl: Controller, i: number) {\n  return {\n    ref: (el: HTMLInputElement | null) => {\n      ctl.refs.current[i] = el\n    },\n    value: ctl.chars[i],\n    onChange: (e: React.ChangeEvent<HTMLInputElement>) => ctl.setAt(i, e.target.value),\n    onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => ctl.onKey(i, e),\n    onPaste: (e: React.ClipboardEvent<HTMLInputElement>) => ctl.onPaste(i, e),\n    inputMode: \"numeric\" as const,\n    autoComplete: i === 0 ? (\"one-time-code\" as const) : (\"off\" as const),\n    maxLength: 1,\n    \"aria-label\": `Digit ${i + 1}`,\n  }\n}\n\n/* Shared shell: the only difference is the glass surface classes. */\nfunction GlassOtp({\n  surface,\n  filled,\n  className,\n  size = \"md\",\n  length = 6,\n  value,\n  defaultValue,\n  onValueChange,\n}: OtpProps & { surface: string; filled: string }) {\n  const ctl = useOtpCode(length, { value, defaultValue, onValueChange })\n  return (\n    <div\n      data-slot=\"styled-input-otp\"\n      role=\"group\"\n      aria-label=\"Verification code\"\n      className={cn(rootBase, className)}\n    >\n      {ctl.chars.map((ch, i) => (\n        <input\n          key={i}\n          {...cellHandlers(ctl, i)}\n          className={cn(cellBase, cellSize[size], surface, ch ? filled : null)}\n        />\n      ))}\n    </div>\n  )\n}\n\n/* Frost: classic frosted glass. sm (4px) -> the shallowest step in the family; because the cell is small the lightest blur is the baseline here too. Character: a neutral border. */\nexport function FrostOtp(props: OtpProps) {\n  return (\n    <GlassOtp\n      surface={cn(\n        glassDepth.sm,\n        \"rounded-md border border-[color-mix(in_oklab,var(--color-foreground)_12%,transparent)] focus-visible:border-ring\"\n      )}\n      filled=\"border-[color-mix(in_oklab,var(--color-foreground)_28%,transparent)]\"\n      {...props}\n    />\n  )\n}\n\n/* Tint: info-toned translucent glass. md (8px) -> the default step; the hue carries the character. The tint fill takes over the step's bg (the supports-[] variant is overridden too). In the focus and filled states primary is CORRECT: primary is a state role, not a decorative tint (the decorative hue comes from info). */\nexport function TintOtp(props: OtpProps) {\n  return (\n    <GlassOtp\n      surface={cn(\n        glassDepth.md,\n        \"rounded-md border border-[color-mix(in_oklab,var(--color-info)_22%,transparent)] bg-[color-mix(in_oklab,var(--color-info)_10%,transparent)] supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-info)_10%,transparent)] focus-visible:border-primary focus-visible:outline-primary/50\"\n      )}\n      filled=\"border-primary bg-[color-mix(in_oklab,var(--color-info)_18%,transparent)] supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-info)_18%,transparent)]\"\n      {...props}\n    />\n  )\n}\n\n/* Smoke: dark, smoky glass. lg (16px) -> the strong foreground scrim's intent of \"what is behind becomes texture\" is exactly the description of the lg step. */\nexport function SmokeOtp(props: OtpProps) {\n  return (\n    <GlassOtp\n      surface={cn(\n        glassDepth.lg,\n        \"rounded-md border border-[color-mix(in_oklab,var(--color-foreground)_18%,transparent)] bg-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)] supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)] focus-visible:border-ring\"\n      )}\n      filled=\"border-[color-mix(in_oklab,var(--color-foreground)_38%,transparent)] bg-[color-mix(in_oklab,var(--color-foreground)_22%,transparent)] supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-foreground)_22%,transparent)]\"\n      {...props}\n    />\n  )\n}\n\n/* Crystal: clear glass with a sharp edge. md (8px) -> the same step as Tint with a different character: a colourless, very low background fill plus a pronounced border. The hand-written bright-edge inset shadow was REMOVED - the glassDepth signature's top inset highlight is exactly for that job, and a second [box-shadow:] would erase it. */\nexport function CrystalOtp(props: OtpProps) {\n  return (\n    <GlassOtp\n      surface={cn(\n        glassDepth.md,\n        \"rounded-lg border border-[color-mix(in_oklab,var(--color-foreground)_20%,transparent)] bg-[color-mix(in_oklab,var(--color-background)_25%,transparent)] supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-background)_25%,transparent)] focus-visible:border-ring\"\n      )}\n      filled=\"border-[color-mix(in_oklab,var(--color-foreground)_40%,transparent)]\"\n      {...props}\n    />\n  )\n}\n\n/* Depth: layered glass. xl (24px) -> the \"separation from the surface\" that the old\n   shadow-lg + blur-xl was after now comes from the maximum diffusion step. The\n   shadow-lg and the shadow-xl on filled were REMOVED: both would have deleted the\n   signature. The filled cell takes its weight from a primary border instead\n   (primary = the role of the active state). */\nexport function DepthOtp(props: OtpProps) {\n  return (\n    <GlassOtp\n      surface={cn(\n        glassDepth.xl,\n        \"rounded-xl border border-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)] focus-visible:border-ring\"\n      )}\n      filled=\"border-primary\"\n      {...props}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/input-otp-glass.tsx"
    }
  ],
  "categories": [
    "styled",
    "input"
  ],
  "type": "registry:component"
}