{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "halo-input",
  "type": "registry:ui",
  "description": "Single-line and multiline inputs with frosted fill and gradient rim for generic forms",
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "files": [
    {
      "path": "registry/default/ui/halo-input.tsx",
      "content": "\"use client\"\n\n/**\n * Single-line and multiline text controls with the same frosted + gradient-rim polish as\n * {@link HaloSegmented} / {@link HaloSwitch} — for generic forms (not search- or\n * composer-specific shaders, staggered placeholders, or trailing send/clear chrome).\n */\nimport { forwardRef, useId, type ComponentProps, type ReactNode } from \"react\"\nimport { Input as InputPrimitive } from \"@base-ui/react/input\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/** Inset 1px inside `rounded-xl` shell → inner radius 12px − 1px = 11px (concentric with `SelectGradientUnderlay`). */\nconst RIM_RADIUS = \"rounded-[11px]\"\n/** Inner padding box inside the 1px border: 11px − 1px = 10px. */\nconst CONTENT_RADIUS = \"rounded-[10px]\"\n\ninterface ShellProps {\n  children: ReactNode\n  className?: string\n  translucent?: boolean\n  invalid?: boolean\n  /** Extra classes on the frosted inner (border + focus ring target). */\n  innerClassName?: string\n}\n\nfunction AnimatedControlShell({\n  children,\n  className,\n  innerClassName,\n  translucent = true,\n  invalid,\n}: ShellProps) {\n  const reduceMotion = useReducedMotion() ?? false\n\n  return (\n    <div\n      className={cn(\n        \"relative isolate w-full max-w-full rounded-xl p-px\",\n        className\n      )}\n      data-slot=\"halo-input-shell\"\n    >\n      {/*\n        Clip to the same 11px round as the shell (not `rounded-xl`) so the rim and gradient share one curve —\n        matches `SelectGradientUnderlay` / `HaloSwitch` underglow.\n      */}\n      <div className=\"pointer-events-none absolute inset-0 z-0 overflow-hidden rounded-[11px]\">\n        {translucent ? (\n          <div\n            className={cn(\n              \"absolute inset-0\",\n              reduceMotion ? \"opacity-[0.38]\" : \"opacity-50\"\n            )}\n          >\n            <div\n              className=\"absolute inset-0 blur-2xl\"\n              style={{\n                background:\n                  \"radial-gradient(90% 70% at 50% 100%, rgba(255,0,128,0.28), rgba(121,40,202,0.12) 48%, transparent 62%)\",\n              }}\n            />\n            <div\n              className=\"absolute inset-0 opacity-70 blur-xl dark:opacity-50\"\n              style={{\n                background:\n                  \"radial-gradient(70% 55% at 30% 0%, rgba(0,212,255,0.22), rgba(0,112,243,0.08) 45%, transparent 58%)\",\n              }}\n            />\n          </div>\n        ) : null}\n        <motion.div\n          animate={{\n            opacity: translucent ? 0.72 : 0.5,\n          }}\n          className=\"absolute inset-x-0 bottom-0 h-1/2\"\n          transition={{ duration: 0.25, ease: \"easeOut\" }}\n        >\n          <motion.div\n            animate={{ left: [\"-5%\", \"75%\", \"-5%\"] }}\n            className=\"-bottom-4 absolute h-14 w-44 blur-xl\"\n            style={{\n              background:\n                \"linear-gradient(90deg, #ff0080, #7928ca, #00d4ff, #0070f3)\",\n            }}\n            transition={{\n              duration: reduceMotion ? 0.01 : 6,\n              ease: \"easeInOut\",\n              repeat: reduceMotion ? 0 : Number.POSITIVE_INFINITY,\n            }}\n          />\n          <motion.div\n            animate={{ left: [\"65%\", \"10%\", \"65%\"] }}\n            className=\"-bottom-3 absolute h-11 w-36 blur-lg\"\n            style={{\n              background: \"linear-gradient(90deg, #ff4d4d, #f9cb28, #ff0080)\",\n            }}\n            transition={{\n              duration: reduceMotion ? 0.01 : 5,\n              ease: \"easeInOut\",\n              repeat: reduceMotion ? 0 : Number.POSITIVE_INFINITY,\n            }}\n          />\n        </motion.div>\n      </div>\n\n      {/*\n        Shell stack (aligned with `HaloSelectTrigger`): border + lift on the surface; inset rim on `::before`\n        so backdrop-filter does not composite the top highlight against the outer glow at corners.\n      */}\n      <div\n        className={cn(\n          \"relative z-1 border border-[color-mix(in_oklch,var(--border)_72%,oklch(0.88_0.06_300))] outline-none\",\n          \"shadow-[0_1px_2px_rgba(0,0,0,0.03),0_6px_18px_rgba(0,0,0,0.04)]\",\n          \"dark:border-border/80 dark:shadow-[0_1px_2px_rgba(0,0,0,0.28),0_6px_22px_rgba(0,0,0,0.32)]\",\n          /* Inset rim only — concentric with 11px border − 1px (see `CONTENT_RADIUS`). */\n          \"before:pointer-events-none before:absolute before:inset-0 before:z-0 before:rounded-[10px] before:shadow-[inset_0_1px_0_rgba(255,255,255,0.55)] before:content-['']\",\n          \"dark:before:shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]\",\n          \"transition-[border-color,box-shadow] duration-200 ease-out\",\n          \"focus-within:border-ring/80 focus-within:ring-2 focus-within:ring-ring/30\",\n          invalid &&\n            \"border-destructive/90 focus-within:border-destructive focus-within:ring-destructive/25 dark:border-destructive/70\",\n          RIM_RADIUS,\n          translucent && \"backdrop-blur-xl backdrop-saturate-150\",\n          innerClassName\n        )}\n        style={{\n          background: translucent\n            ? \"linear-gradient(to bottom, color-mix(in oklch, var(--card) 82%, transparent) 0%, color-mix(in oklch, var(--card) 70%, transparent) 100%)\"\n            : \"linear-gradient(to bottom, var(--card) 0%, color-mix(in oklch, var(--card) 94%, var(--muted)) 100%)\",\n        }}\n      >\n        <div className=\"relative z-1\">{children}</div>\n      </div>\n    </div>\n  )\n}\n\nconst inputInnerClassName = cn(\n  \"min-h-0 w-full min-w-0 flex-1 border-0 bg-transparent px-0 py-0 text-base shadow-none outline-none md:text-sm\",\n  \"text-foreground placeholder:text-muted-foreground\",\n  \"transition-[color,opacity] duration-200 ease-out\",\n  \"focus-visible:ring-0\",\n  \"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50\",\n  \"aria-invalid:ring-0\"\n)\n\nexport type HaloInputProps = Omit<\n  ComponentProps<typeof InputPrimitive>,\n  \"size\"\n> & {\n  /** Frosted fill + gradient rim (matches segmented / switch). */\n  translucent?: boolean\n  invalid?: boolean\n  leadingSlot?: ReactNode\n  trailingSlot?: ReactNode\n  shellClassName?: string\n  /** Classes on the inner flex row (not the outer rim). */\n  innerRowClassName?: string\n}\n\n/**\n * Single-line text input with cult-pro chrome. Use with {@link HaloField} + label `htmlFor`.\n */\nexport const HaloInput = forwardRef<HTMLInputElement, HaloInputProps>(\n  function HaloInput(\n    {\n      className,\n      translucent = true,\n      invalid,\n      leadingSlot,\n      trailingSlot,\n      shellClassName,\n      innerRowClassName,\n      id: idProp,\n      \"aria-invalid\": ariaInvalid,\n      ...inputProps\n    },\n    ref\n  ) {\n    const uid = useId()\n    const id = idProp ?? uid\n    const isInvalid =\n      Boolean(invalid) || ariaInvalid === true || ariaInvalid === \"true\"\n    const invalidFlag = isInvalid || undefined\n\n    return (\n      <AnimatedControlShell\n        className={shellClassName}\n        invalid={isInvalid}\n        translucent={translucent}\n      >\n        <div\n          className={cn(\n            \"flex min-h-11 w-full items-center gap-2 px-3 py-2\",\n            CONTENT_RADIUS,\n            innerRowClassName\n          )}\n        >\n          {leadingSlot ? (\n            <span className=\"flex shrink-0 items-center text-muted-foreground [&_svg]:size-4\">\n              {leadingSlot}\n            </span>\n          ) : null}\n          <InputPrimitive\n            aria-invalid={invalidFlag}\n            className={cn(inputInnerClassName, className)}\n            id={id}\n            ref={ref}\n            {...inputProps}\n          />\n          {trailingSlot ? (\n            <span className=\"flex shrink-0 items-center text-muted-foreground [&_svg]:size-4\">\n              {trailingSlot}\n            </span>\n          ) : null}\n        </div>\n      </AnimatedControlShell>\n    )\n  }\n)\n\nexport type HaloTextareaProps = Omit<ComponentProps<\"textarea\">, \"size\"> & {\n  translucent?: boolean\n  invalid?: boolean\n  shellClassName?: string\n  innerClassName?: string\n}\n\n/**\n * Multiline control with the same shell as {@link HaloInput} (no Paper shader / stagger).\n */\nexport const HaloTextarea = forwardRef<HTMLTextAreaElement, HaloTextareaProps>(\n  function HaloTextarea(\n    {\n      className,\n      translucent = true,\n      invalid,\n      shellClassName,\n      innerClassName,\n      id: idProp,\n      rows = 4,\n      \"aria-invalid\": ariaInvalid,\n      ...textareaProps\n    },\n    ref\n  ) {\n    const uid = useId()\n    const id = idProp ?? uid\n    const isInvalid =\n      Boolean(invalid) || ariaInvalid === true || ariaInvalid === \"true\"\n    const invalidFlag = isInvalid || undefined\n\n    return (\n      <AnimatedControlShell\n        className={shellClassName}\n        innerClassName={innerClassName}\n        invalid={isInvalid}\n        translucent={translucent}\n      >\n        <div className={cn(\"px-3 py-2\", CONTENT_RADIUS)}>\n          <textarea\n            aria-invalid={invalidFlag}\n            className={cn(\n              inputInnerClassName,\n              \"field-sizing-content min-h-[5.5rem] resize-y\",\n              \"focus-visible:ring-0\",\n              className\n            )}\n            data-slot=\"animated-textarea\"\n            id={id}\n            ref={ref}\n            rows={rows}\n            {...textareaProps}\n          />\n        </div>\n      </AnimatedControlShell>\n    )\n  }\n)\n\nHaloInput.displayName = \"HaloInput\"\nHaloTextarea.displayName = \"HaloTextarea\"\n",
      "type": "registry:ui"
    }
  ]
}