{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "vice",
  "type": "registry:ui",
  "title": "Vice",
  "description": "A self-contained synthwave theme preset — card, buttons, stat, and tag in hot-magenta → cyan neon on deep indigo, with a sunset gradient, a scanlined sun rising over a perspective grid, and a chrome-italic display heading. A keyboard-operable radiogroup of vibe chips re-tokens the whole panel live; every glow pins to a bold static frame under reduced motion.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://crucibleui.com/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/default/themes/vice/vice.tsx",
      "type": "registry:ui",
      "target": "components/crucible/vice.tsx",
      "content": "\"use client\";\n\nimport { animate, motion, useMotionValue } from \"motion/react\";\nimport * as React from \"react\";\n\nimport { useReducedMotion } from \"@/hooks/use-reduced-motion\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface VicePalette {\n  /** Deep panel base — the indigo night behind everything. */\n  base: string;\n  /** Upper sunset band stop. */\n  horizon: string;\n  /** Lower sunset band stop, just above the grid. */\n  horizonLow: string;\n  /** Primary neon — heading glow, filled buttons, the horizon grid. */\n  primary: string;\n  /** Secondary neon accent — outlines, stat value, tagline glow. */\n  secondary: string;\n  /** Sun disc top color. */\n  sun: string;\n  /** Foreground text. */\n  text: string;\n  /** Muted / secondary text. */\n  muted: string;\n}\n\nexport interface VicePreset {\n  /** Stable id used for controlled selection and React keys. */\n  id: string;\n  /** Chip label shown in the switcher. */\n  label: string;\n  /** Token set applied when this preset is active. */\n  palette: VicePalette;\n}\n\n/** Built-in vibes — a spectrum from balanced magenta/cyan to hot red/amber to cool violet. */\nconst PRESETS: VicePreset[] = [\n  {\n    id: \"miami\",\n    label: \"Miami\",\n    palette: {\n      base: \"#160b2e\",\n      horizon: \"#ff5db1\",\n      horizonLow: \"#7d1e9c\",\n      primary: \"#ff2d95\",\n      secondary: \"#2de2e6\",\n      sun: \"#ffcf5c\",\n      text: \"#f6e9ff\",\n      muted: \"#b79fd4\",\n    },\n  },\n  {\n    id: \"outrun\",\n    label: \"Outrun\",\n    palette: {\n      base: \"#1a0730\",\n      horizon: \"#ff7a3d\",\n      horizonLow: \"#a01f7a\",\n      primary: \"#ff2d55\",\n      secondary: \"#ffcf3d\",\n      sun: \"#ff8a3d\",\n      text: \"#ffeede\",\n      muted: \"#d09fb0\",\n    },\n  },\n  {\n    id: \"twilight\",\n    label: \"Twilight\",\n    palette: {\n      base: \"#0d0322\",\n      horizon: \"#7b2ff7\",\n      horizonLow: \"#3a1078\",\n      primary: \"#b46bff\",\n      secondary: \"#4cc9f0\",\n      sun: \"#e05fd6\",\n      text: \"#ece7ff\",\n      muted: \"#9d97cf\",\n    },\n  },\n];\n\nexport interface ViceProps\n  extends Omit<React.ComponentPropsWithoutRef<\"section\">, \"onChange\"> {\n  /** Controlled active preset id (matches a preset's `id`). */\n  variant?: string;\n  /** Initial preset id when uncontrolled. @default \"miami\" (first preset) */\n  defaultVariant?: string;\n  /** Fires with the next preset id whenever the switcher changes. */\n  onVariantChange?: (id: string) => void;\n  /** Override the built-in preset set (chips + palettes). @default the three built-in vibes */\n  presets?: VicePreset[];\n  /** Token overrides merged over the active preset — makes any palette reachable. */\n  colors?: Partial<VicePalette>;\n  /** Glow-pulse and grid-scroll speed multiplier. @default 1 */\n  speed?: number;\n  /** Neon glow strength multiplier. @default 1 */\n  intensity?: number;\n  /** Freeze all animation at the resting neon frame. @default false */\n  paused?: boolean;\n  /** Small mono label above the heading. @default \"TRACK 07\" */\n  tagline?: string;\n  /** Chrome-italic display heading. @default \"AFTERGLOW\" */\n  heading?: string;\n  /** Body line under the heading. @default \"Neon on deep indigo. Pick a vibe.\" */\n  description?: string;\n  /** Stat figure. @default \"128\" */\n  statValue?: string;\n  /** Stat unit / label. @default \"BPM\" */\n  statLabel?: string;\n  /** Small pill tag. @default \"SIDE A\" */\n  tag?: string;\n  /** Filled button label. @default \"Play\" */\n  primaryLabel?: string;\n  /** Outline button label. @default \"Queue\" */\n  secondaryLabel?: string;\n  /** Accessible label for the panel region. @default \"Synthwave theme preset\" */\n  label?: string;\n  /** Accessible label for the switcher group. @default \"Theme vibe\" */\n  switcherLabel?: string;\n}\n\nconst GRID_CELL = 34;\n\n/**\n * Vice — a self-contained synthwave / retrowave theme preset. A card, buttons,\n * stat, and tag painted in hot-magenta → cyan neon on deep indigo, with a\n * sunset gradient, a scanlined sun rising over a perspective grid, and a\n * chrome-italic display heading. A keyboard-operable radiogroup of vibe chips\n * re-tokens the whole panel live; the palette is fully exposed via `presets`\n * and `colors`. Every glow drives off one CSS-variable pulse, so reduced motion\n * simply pins it to a bold static neon frame — no pulsing, no grid scroll.\n */\nexport function Vice({\n  variant,\n  defaultVariant,\n  onVariantChange,\n  presets,\n  colors,\n  speed = 1,\n  intensity = 1,\n  paused = false,\n  tagline = \"TRACK 07\",\n  heading = \"AFTERGLOW\",\n  description = \"Neon on deep indigo. Pick a vibe.\",\n  statValue = \"128\",\n  statLabel = \"BPM\",\n  tag = \"SIDE A\",\n  primaryLabel = \"Play\",\n  secondaryLabel = \"Queue\",\n  label = \"Synthwave theme preset\",\n  switcherLabel = \"Theme vibe\",\n  className,\n  style,\n  ...rest\n}: ViceProps) {\n  const reducedMotion = useReducedMotion();\n\n  const list = presets && presets.length > 0 ? presets : PRESETS;\n  const isControlled = variant !== undefined;\n  const [internal, setInternal] = React.useState(\n    defaultVariant ?? list[0].id\n  );\n  const activeId = isControlled ? variant : internal;\n  const active =\n    list.find((p) => p.id === activeId) ?? list[0];\n  const t: VicePalette = { ...active.palette, ...colors };\n\n  const spd = Math.max(0.1, speed);\n  const amt = Math.max(0, intensity);\n\n  // One pulse (scaled by intensity) modulates every glow via a CSS variable;\n  // grid scroll is a second variable. Colors live in their own vars so\n  // switching vibes recolors instantly even while the pulse is frozen. Reduced\n  // motion pins the pulse to full intensity — a bold static neon frame.\n  const pulse = useMotionValue(amt);\n  const shift = useMotionValue(0);\n\n  React.useEffect(() => {\n    if (reducedMotion || paused) {\n      pulse.set(amt);\n      shift.set(0);\n      return;\n    }\n    const p = animate(pulse, [amt, 0.62 * amt, amt], {\n      duration: 3.4 / spd,\n      repeat: Infinity,\n      ease: \"easeInOut\",\n    });\n    const g = animate(shift, GRID_CELL, {\n      duration: 2.8 / spd,\n      repeat: Infinity,\n      ease: \"linear\",\n    });\n    return () => {\n      p.stop();\n      g.stop();\n    };\n  }, [reducedMotion, paused, spd, amt, pulse, shift]);\n\n  const activeIdx = Math.max(\n    0,\n    list.findIndex((p) => p.id === activeId)\n  );\n  const chipRefs = React.useRef<(HTMLButtonElement | null)[]>([]);\n\n  const select = React.useCallback(\n    (i: number) => {\n      const next = list[i];\n      if (!next) return;\n      if (!isControlled) setInternal(next.id);\n      onVariantChange?.(next.id);\n    },\n    [list, isControlled, onVariantChange]\n  );\n\n  const onSwitcherKey = React.useCallback(\n    (e: React.KeyboardEvent<HTMLDivElement>) => {\n      let next = activeIdx;\n      switch (e.key) {\n        case \"ArrowRight\":\n        case \"ArrowDown\":\n          next = (activeIdx + 1) % list.length;\n          break;\n        case \"ArrowLeft\":\n        case \"ArrowUp\":\n          next = (activeIdx - 1 + list.length) % list.length;\n          break;\n        case \"Home\":\n          next = 0;\n          break;\n        case \"End\":\n          next = list.length - 1;\n          break;\n        default:\n          return;\n      }\n      e.preventDefault();\n      select(next);\n      chipRefs.current[next]?.focus();\n    },\n    [activeIdx, list, select]\n  );\n\n  // Colors are plain CSS vars (recolor instantly on switch); --vpulse and\n  // --vshift are motion values, so this element must be a motion component.\n  const vars = {\n    \"--vp\": t.primary,\n    \"--vs\": t.secondary,\n    \"--vbase\": t.base,\n    \"--vhorizon\": t.horizon,\n    \"--vhorizonlow\": t.horizonLow,\n    \"--vsun\": t.sun,\n    \"--vtext\": t.text,\n    \"--vmuted\": t.muted,\n    \"--vpulse\": pulse,\n    \"--vshift\": shift,\n    background: \"var(--vbase)\",\n  } as React.CSSProperties;\n\n  const focusRing =\n    \"outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--vs)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--vbase)]\";\n\n  return (\n    <section\n      aria-label={label}\n      data-crucible=\"vice\"\n      data-variant={active.id}\n      className={cn(\"relative isolate w-full max-w-sm\", className)}\n      style={style}\n      {...rest}\n    >\n      <motion.div\n        className=\"relative isolate flex w-full flex-col overflow-hidden rounded-2xl\"\n        style={vars}\n      >\n      {/* Hero sky — sunset, scanlined sun, perspective grid. Decorative. */}\n      <div\n        aria-hidden\n        className=\"pointer-events-none absolute inset-x-0 top-0 z-0 h-[58%] overflow-hidden\"\n      >\n        <div\n          className=\"absolute inset-0\"\n          style={{\n            background:\n              \"linear-gradient(180deg, var(--vbase) 0%, var(--vhorizon) 62%, var(--vhorizonlow) 100%)\",\n          }}\n        />\n        {/* Sun */}\n        <div\n          className=\"absolute left-1/2 bottom-[30%] h-28 w-28 -translate-x-1/2 overflow-hidden rounded-full\"\n          style={{\n            background: \"linear-gradient(180deg, var(--vsun) 0%, var(--vp) 100%)\",\n            boxShadow:\n              \"0 0 calc(var(--vpulse) * 60px) color-mix(in srgb, var(--vp) 55%, transparent)\",\n          }}\n        >\n          <div\n            className=\"absolute inset-x-0 bottom-0 top-1/2\"\n            style={{\n              background:\n                \"repeating-linear-gradient(180deg, transparent 0 5px, var(--vbase) 5px 8px)\",\n              maskImage: \"linear-gradient(180deg, transparent, #000 90%)\",\n              WebkitMaskImage: \"linear-gradient(180deg, transparent, #000 90%)\",\n            }}\n          />\n        </div>\n        {/* Perspective grid */}\n        <motion.div\n          className=\"absolute inset-x-[-25%] bottom-0 top-[62%]\"\n          style={{\n            backgroundImage:\n              \"linear-gradient(var(--vp) 1px, transparent 1px), linear-gradient(90deg, var(--vp) 1px, transparent 1px)\",\n            backgroundSize: `${GRID_CELL}px ${GRID_CELL}px`,\n            backgroundPosition: \"center calc(var(--vshift) * 1px)\",\n            transform: \"perspective(150px) rotateX(66deg)\",\n            transformOrigin: \"center bottom\",\n            opacity: 0.55,\n            maskImage: \"linear-gradient(180deg, transparent, #000 45%)\",\n            WebkitMaskImage: \"linear-gradient(180deg, transparent, #000 45%)\",\n          }}\n        />\n        {/* Blend the hero into the base so content stays legible. */}\n        <div\n          className=\"absolute inset-0\"\n          style={{\n            background:\n              \"linear-gradient(180deg, transparent 45%, var(--vbase) 100%)\",\n          }}\n        />\n      </div>\n\n      {/* Content */}\n      <div className=\"relative z-10 flex flex-col gap-4 p-5 pt-8\">\n        <div className=\"flex flex-col gap-1\">\n          <p\n            className=\"font-mono text-[0.62rem] font-semibold uppercase tracking-[0.32em]\"\n            style={{\n              color: \"var(--vs)\",\n              textShadow:\n                \"0 0 8px color-mix(in srgb, var(--vs) 60%, transparent)\",\n            }}\n          >\n            {tagline}\n          </p>\n          <motion.h3\n            className=\"text-4xl font-extrabold italic leading-none tracking-tight font-stretch-expanded\"\n            style={{\n              backgroundImage:\n                \"linear-gradient(180deg, #ffffff 0%, var(--vs) 42%, var(--vtext) 50%, var(--vp) 100%)\",\n              WebkitBackgroundClip: \"text\",\n              backgroundClip: \"text\",\n              WebkitTextFillColor: \"transparent\",\n              color: \"transparent\",\n              filter:\n                \"drop-shadow(0 0 calc(var(--vpulse) * 14px) var(--vp)) drop-shadow(0 0 calc(var(--vpulse) * 5px) var(--vs))\",\n            }}\n          >\n            {heading}\n          </motion.h3>\n          <p className=\"text-sm\" style={{ color: \"var(--vmuted)\" }}>\n            {description}\n          </p>\n        </div>\n\n        {/* Stat + tag */}\n        <div className=\"flex items-center gap-3\">\n          <div className=\"flex items-baseline gap-1.5\">\n            <span\n              className=\"text-2xl font-bold tabular-nums\"\n              style={{\n                color: \"var(--vp)\",\n                textShadow:\n                  \"0 0 calc(var(--vpulse) * 10px) color-mix(in srgb, var(--vp) 60%, transparent)\",\n              }}\n            >\n              {statValue}\n            </span>\n            <span\n              className=\"font-mono text-[0.6rem] uppercase tracking-widest\"\n              style={{ color: \"var(--vmuted)\" }}\n            >\n              {statLabel}\n            </span>\n          </div>\n          <span\n            className=\"ml-auto rounded-full px-2.5 py-0.5 font-mono text-[0.6rem] font-semibold uppercase tracking-widest\"\n            style={{\n              color: \"var(--vs)\",\n              border: \"1px solid color-mix(in srgb, var(--vs) 55%, transparent)\",\n            }}\n          >\n            {tag}\n          </span>\n        </div>\n\n        {/* Buttons */}\n        <div className=\"flex gap-3\">\n          <button\n            type=\"button\"\n            className={cn(\n              \"rounded-md px-4 py-2 text-sm font-semibold transition-transform active:scale-[0.97]\",\n              focusRing\n            )}\n            style={{\n              background: \"var(--vp)\",\n              color: \"var(--vbase)\",\n              boxShadow:\n                \"0 0 calc(var(--vpulse) * 13px) color-mix(in srgb, var(--vp) 65%, transparent)\",\n            }}\n          >\n            {primaryLabel}\n          </button>\n          <button\n            type=\"button\"\n            className={cn(\n              \"rounded-md px-4 py-2 text-sm font-semibold transition-transform active:scale-[0.97]\",\n              focusRing\n            )}\n            style={{\n              color: \"var(--vs)\",\n              border: \"1px solid var(--vs)\",\n              boxShadow:\n                \"0 0 calc(var(--vpulse) * 11px) color-mix(in srgb, var(--vs) 45%, transparent)\",\n            }}\n          >\n            {secondaryLabel}\n          </button>\n        </div>\n\n        {/* Vibe switcher — a real radiogroup with roving focus. */}\n        <div\n          role=\"radiogroup\"\n          aria-label={switcherLabel}\n          onKeyDown={onSwitcherKey}\n          className=\"mt-1 flex flex-wrap gap-2\"\n        >\n          {list.map((p, i) => {\n            const selected = p.id === active.id;\n            return (\n              <button\n                key={p.id}\n                ref={(el) => {\n                  chipRefs.current[i] = el;\n                }}\n                type=\"button\"\n                role=\"radio\"\n                aria-checked={selected}\n                tabIndex={selected ? 0 : -1}\n                onClick={() => select(i)}\n                className={cn(\n                  \"rounded-full px-3 py-1 text-xs font-semibold transition-transform active:scale-[0.97]\",\n                  focusRing\n                )}\n                style={\n                  selected\n                    ? {\n                        background: \"var(--vp)\",\n                        color: \"var(--vbase)\",\n                        boxShadow:\n                          \"0 0 12px color-mix(in srgb, var(--vp) 55%, transparent)\",\n                      }\n                    : {\n                        color: \"var(--vmuted)\",\n                        border:\n                          \"1px solid color-mix(in srgb, var(--vmuted) 45%, transparent)\",\n                      }\n                }\n              >\n                {p.label}\n              </button>\n            );\n          })}\n        </div>\n      </div>\n\n      {/* Neon border ring — pulses with everything else. */}\n      <div\n        aria-hidden\n        className=\"pointer-events-none absolute inset-0 z-20 rounded-2xl\"\n        style={{\n          border: \"1px solid color-mix(in srgb, var(--vp) 50%, transparent)\",\n          boxShadow:\n            \"0 0 calc(var(--vpulse) * 16px) color-mix(in srgb, var(--vp) 55%, transparent), inset 0 0 calc(var(--vpulse) * 14px) color-mix(in srgb, var(--vs) 22%, transparent)\",\n        }}\n      />\n      </motion.div>\n    </section>\n  );\n}\n"
    }
  ]
}