{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mono",
  "type": "registry:ui",
  "title": "Mono",
  "description": "A self-contained Swiss-minimal theme preset — a stark black-and-white (or paper-and-ink) panel on a strict grid: tight neo-grotesque heading, numbered hairline spec rows, generous negative space, and exactly one restrained accent. A keyboard-operable radiogroup of accent swatches re-tokens the panel live and a role=switch flips light/dark; the palette is fully exposed via accents and colors. The only motion is an accent rule that draws across the top, pinned to its full frame under reduced motion.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://crucibleui.com/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/default/themes/mono/mono.tsx",
      "type": "registry:ui",
      "target": "components/crucible/mono.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 type MonoMode = \"light\" | \"dark\";\n\nexport interface MonoPalette {\n  /** Recessed base tone — fills the tag chip (also exposed as `--m-bg` for a host page behind the card). */\n  bg: string;\n  /** Card surface. */\n  surface: string;\n  /** Primary foreground text. */\n  text: string;\n  /** Muted / secondary text. */\n  muted: string;\n  /** Hairline divider / border color. */\n  line: string;\n  /** The single restrained accent — index numbers, filled button, draw rule. */\n  accent: string;\n  /** Text/ink color that sits on top of the accent fill. */\n  accentInk: string;\n}\n\nexport interface MonoAccent {\n  /** Stable id used for controlled selection + React keys. */\n  id: string;\n  /** Chip label shown in the switcher. */\n  label: string;\n  /** The accent hue applied across the panel when active. */\n  color: string;\n  /** Optional ink color for text on the accent fill. @default \"#ffffff\" */\n  ink?: string;\n}\n\nexport interface MonoSpec {\n  /** Row caption, e.g. \"Leading\". */\n  label: string;\n  /** Row value, e.g. \"1.4\". */\n  value: string;\n}\n\n/** Base tokens per mode — accent is layered in from the active preset. */\nconst DARK_BASE: Omit<MonoPalette, \"accent\" | \"accentInk\"> = {\n  bg: \"#0a0a0a\",\n  surface: \"#111111\",\n  text: \"#fafafa\",\n  muted: \"#8a8a8a\",\n  line: \"rgba(255,255,255,0.14)\",\n};\nconst LIGHT_BASE: Omit<MonoPalette, \"accent\" | \"accentInk\"> = {\n  bg: \"#f4f4f2\",\n  surface: \"#ffffff\",\n  text: \"#0a0a0a\",\n  muted: \"#6b6b6b\",\n  line: \"rgba(0,0,0,0.14)\",\n};\n\n/** Built-in accents — one restrained signal color, Swiss-poster primaries. */\nconst ACCENTS: MonoAccent[] = [\n  { id: \"red\", label: \"Red\", color: \"#e5322b\" },\n  { id: \"blue\", label: \"Blue\", color: \"#2563eb\" },\n  { id: \"green\", label: \"Green\", color: \"#0e9f6e\" },\n];\n\nconst DEFAULT_SPECS: MonoSpec[] = [\n  { label: \"Weight\", value: \"Medium\" },\n  { label: \"Leading\", value: \"1.40\" },\n  { label: \"Measure\", value: \"62ch\" },\n  { label: \"Columns\", value: \"12\" },\n];\n\nexport interface MonoProps\n  extends Omit<React.ComponentPropsWithoutRef<\"section\">, \"onChange\"> {\n  /** Controlled active accent id (matches an accent's `id`). */\n  variant?: string;\n  /** Initial accent id when uncontrolled. @default \"red\" (first accent) */\n  defaultVariant?: string;\n  /** Fires with the next accent id whenever the switcher changes. */\n  onVariantChange?: (id: string) => void;\n  /** Override the built-in accent set (chips + colors). @default the three built-in accents */\n  accents?: MonoAccent[];\n  /** Controlled light/dark mode. */\n  mode?: MonoMode;\n  /** Initial mode when uncontrolled. @default \"dark\" */\n  defaultMode?: MonoMode;\n  /** Fires with the next mode whenever the light/dark switch flips. */\n  onModeChange?: (mode: MonoMode) => void;\n  /** Token overrides merged over the resolved palette — makes any palette reachable. */\n  colors?: Partial<MonoPalette>;\n  /** Draw-rule animation speed multiplier. @default 1 */\n  speed?: number;\n  /** Freeze the draw rule at its resting (full) frame. @default false */\n  paused?: boolean;\n  /** Small mono eyebrow above the heading. @default \"SPECIMEN — 001\" */\n  eyebrow?: string;\n  /** Tight neo-grotesque display heading. @default \"Form Follows\" */\n  heading?: string;\n  /** Body line under the heading. @default \"A strict grid, hairline rules, and one restrained accent.\" */\n  description?: string;\n  /** Numbered spec rows (auto-indexed 01…N). @default four type-spec rows */\n  specs?: MonoSpec[];\n  /** Small square tag label. @default \"GRID\" */\n  tag?: string;\n  /** Filled (accent) button label. @default \"Continue\" */\n  primaryLabel?: string;\n  /** Outline button label. @default \"Reset\" */\n  secondaryLabel?: string;\n  /** Accessible label for the panel region. @default \"Monochrome theme preset\" */\n  label?: string;\n  /** Accessible label for the accent switcher group. @default \"Accent color\" */\n  switcherLabel?: string;\n  /** Accessible label for the light/dark switch. @default \"Toggle light mode\" */\n  modeLabel?: string;\n}\n\n/**\n * Mono — a self-contained Swiss-minimal theme preset. A stark black-and-white\n * (or paper-and-ink) panel built on a strict grid: a tight neo-grotesque\n * heading, numbered hairline spec rows, generous negative space, and exactly\n * one restrained accent. A keyboard-operable radiogroup of accent chips\n * re-tokens the whole panel live, and a real `role=\"switch\"` flips light↔dark;\n * the palette is fully exposed via `accents` and `colors`. The only motion is a\n * single accent rule that draws across the top on change — reduced motion (or\n * `paused`) pins it to its full resting frame, so the layout is otherwise\n * completely static.\n */\nexport function Mono({\n  variant,\n  defaultVariant,\n  onVariantChange,\n  accents,\n  mode,\n  defaultMode = \"dark\",\n  onModeChange,\n  colors,\n  speed = 1,\n  paused = false,\n  eyebrow = \"SPECIMEN — 001\",\n  heading = \"Form Follows\",\n  description = \"A strict grid, hairline rules, and one restrained accent.\",\n  specs,\n  tag = \"GRID\",\n  primaryLabel = \"Continue\",\n  secondaryLabel = \"Reset\",\n  label = \"Monochrome theme preset\",\n  switcherLabel = \"Accent color\",\n  modeLabel = \"Toggle light mode\",\n  className,\n  style,\n  ...rest\n}: MonoProps) {\n  const reducedMotion = useReducedMotion();\n\n  const list = accents && accents.length > 0 ? accents : ACCENTS;\n  const rows = specs && specs.length > 0 ? specs : DEFAULT_SPECS;\n\n  // Accent — controlled or uncontrolled.\n  const isVarControlled = variant !== undefined;\n  const [internalVar, setInternalVar] = React.useState(\n    defaultVariant ?? list[0].id\n  );\n  const activeId = isVarControlled ? variant : internalVar;\n  const activeAccent = list.find((a) => a.id === activeId) ?? list[0];\n\n  // Mode — controlled or uncontrolled.\n  const isModeControlled = mode !== undefined;\n  const [internalMode, setInternalMode] = React.useState<MonoMode>(defaultMode);\n  const activeMode = isModeControlled ? mode : internalMode;\n  const isDark = activeMode === \"dark\";\n\n  const base = isDark ? DARK_BASE : LIGHT_BASE;\n  const t: MonoPalette = {\n    ...base,\n    accent: activeAccent.color,\n    accentInk: activeAccent.ink ?? \"#ffffff\",\n    ...colors,\n  };\n\n  const spd = Math.max(0.1, speed);\n\n  // The single motion: a hairline accent rule that draws across the top of the\n  // card. It re-runs when the accent or mode changes (change = re-index feel).\n  // Reduced motion / paused pin it to a full-width resting frame.\n  const draw = useMotionValue(reducedMotion || paused ? 1 : 0);\n  React.useEffect(() => {\n    if (reducedMotion || paused) {\n      draw.set(1);\n      return;\n    }\n    draw.set(0);\n    const controls = animate(draw, 1, {\n      duration: 0.55 / spd,\n      ease: [0.16, 1, 0.3, 1],\n    });\n    return controls.stop;\n    // Re-draw on accent/mode change.\n  }, [draw, reducedMotion, paused, spd, activeId, activeMode]);\n\n  // Roving-focus radiogroup wiring.\n  const activeIdx = Math.max(\n    0,\n    list.findIndex((a) => a.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 (!isVarControlled) setInternalVar(next.id);\n      onVariantChange?.(next.id);\n    },\n    [list, isVarControlled, 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  const toggleMode = React.useCallback(() => {\n    const next: MonoMode = isDark ? \"light\" : \"dark\";\n    if (!isModeControlled) setInternalMode(next);\n    onModeChange?.(next);\n  }, [isDark, isModeControlled, onModeChange]);\n\n  const vars = {\n    \"--m-bg\": t.bg,\n    \"--m-surface\": t.surface,\n    \"--m-text\": t.text,\n    \"--m-muted\": t.muted,\n    \"--m-line\": t.line,\n    \"--m-accent\": t.accent,\n    \"--m-ink\": t.accentInk,\n  } as React.CSSProperties;\n\n  const focusRing =\n    \"outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--m-accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--m-surface)]\";\n\n  return (\n    <section\n      aria-label={label}\n      data-crucible=\"mono\"\n      data-variant={activeAccent.id}\n      data-mode={activeMode}\n      className={cn(\"relative isolate w-full max-w-sm\", className)}\n      style={{ ...vars, ...style }}\n      {...rest}\n    >\n      <div\n        className=\"relative isolate flex w-full flex-col overflow-hidden rounded-lg border\"\n        style={{\n          background: \"var(--m-surface)\",\n          borderColor: \"var(--m-line)\",\n          color: \"var(--m-text)\",\n        }}\n      >\n        {/* The one moving part: an accent rule that draws across the top. */}\n        <div\n          aria-hidden\n          className=\"absolute inset-x-0 top-0 h-[2px]\"\n          style={{ background: \"var(--m-line)\" }}\n        >\n          <motion.div\n            className=\"h-full w-full origin-left\"\n            style={{ background: \"var(--m-accent)\", scaleX: draw }}\n          />\n        </div>\n\n        <div className=\"flex flex-col gap-6 p-6 pt-7\">\n          {/* Header: eyebrow + light/dark switch. */}\n          <div className=\"flex items-start justify-between gap-4\">\n            <p\n              className=\"font-mono text-[0.62rem] font-medium uppercase tracking-[0.24em]\"\n              style={{ color: \"var(--m-muted)\" }}\n            >\n              {eyebrow}\n            </p>\n            <button\n              type=\"button\"\n              role=\"switch\"\n              // The switch is labelled \"light mode\", so it is ON in light mode.\n              aria-checked={!isDark}\n              aria-label={modeLabel}\n              onClick={toggleMode}\n              className={cn(\n                \"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border transition-colors\",\n                focusRing\n              )}\n              style={{\n                borderColor: \"var(--m-line)\",\n                background: isDark ? \"transparent\" : \"var(--m-accent)\",\n              }}\n            >\n              <span\n                className=\"ml-0.5 h-3.5 w-3.5 rounded-full transition-transform\"\n                style={{\n                  background: isDark ? \"var(--m-text)\" : \"var(--m-ink)\",\n                  transform: isDark ? \"translateX(0)\" : \"translateX(16px)\",\n                }}\n              />\n            </button>\n          </div>\n\n          {/* Heading + description. */}\n          <div className=\"flex flex-col gap-2\">\n            <h3 className=\"text-3xl font-medium leading-[1.02] tracking-[-0.02em]\">\n              {heading}\n            </h3>\n            <p\n              className=\"max-w-[34ch] text-sm leading-relaxed\"\n              style={{ color: \"var(--m-muted)\" }}\n            >\n              {description}\n            </p>\n          </div>\n\n          {/* Numbered spec grid — hairline dividers, accent index numbers. */}\n          <div className=\"grid grid-cols-2\">\n            {rows.map((row, i) => (\n              <div\n                key={`${row.label}-${i}`}\n                className=\"flex flex-col gap-1 border-t py-3 pr-4 first:pt-0 [&:nth-child(2)]:pt-0\"\n                style={{ borderColor: \"var(--m-line)\" }}\n              >\n                <span\n                  className=\"font-mono text-[0.62rem] font-semibold tabular-nums\"\n                  style={{ color: \"var(--m-accent)\" }}\n                >\n                  {String(i + 1).padStart(2, \"0\")}\n                </span>\n                <span className=\"text-sm font-medium\">{row.value}</span>\n                <span\n                  className=\"text-[0.7rem] uppercase tracking-wide\"\n                  style={{ color: \"var(--m-muted)\" }}\n                >\n                  {row.label}\n                </span>\n              </div>\n            ))}\n          </div>\n\n          {/* Tag + buttons. */}\n          <div className=\"flex items-center gap-3\">\n            <span\n              className=\"rounded-none border px-2 py-1 font-mono text-[0.6rem] font-semibold uppercase tracking-[0.2em]\"\n              style={{\n                borderColor: \"var(--m-line)\",\n                color: \"var(--m-muted)\",\n                background: \"var(--m-bg)\",\n              }}\n            >\n              {tag}\n            </span>\n            <div className=\"ml-auto flex gap-2\">\n              <button\n                type=\"button\"\n                className={cn(\n                  \"rounded-md px-4 py-1.5 text-sm font-medium transition-transform active:scale-[0.98]\",\n                  focusRing\n                )}\n                style={{ background: \"var(--m-accent)\", color: \"var(--m-ink)\" }}\n              >\n                {primaryLabel}\n              </button>\n              <button\n                type=\"button\"\n                className={cn(\n                  \"rounded-md border px-4 py-1.5 text-sm font-medium transition-colors active:scale-[0.98]\",\n                  focusRing\n                )}\n                style={{ borderColor: \"var(--m-line)\", color: \"var(--m-text)\" }}\n              >\n                {secondaryLabel}\n              </button>\n            </div>\n          </div>\n\n          {/* Hairline divider before the switcher. */}\n          <div\n            className=\"h-px w-full\"\n            style={{ background: \"var(--m-line)\" }}\n            aria-hidden\n          />\n\n          {/* Accent switcher — a real radiogroup with roving focus. */}\n          <div className=\"flex items-center justify-between gap-3\">\n            <span\n              className=\"font-mono text-[0.6rem] uppercase tracking-[0.22em]\"\n              style={{ color: \"var(--m-muted)\" }}\n            >\n              {switcherLabel}\n            </span>\n            <div\n              role=\"radiogroup\"\n              aria-label={switcherLabel}\n              onKeyDown={onSwitcherKey}\n              className=\"flex gap-2\"\n            >\n              {list.map((a, i) => {\n                const selected = a.id === activeAccent.id;\n                return (\n                  <button\n                    key={a.id}\n                    ref={(el) => {\n                      chipRefs.current[i] = el;\n                    }}\n                    type=\"button\"\n                    role=\"radio\"\n                    aria-checked={selected}\n                    aria-label={a.label}\n                    tabIndex={selected ? 0 : -1}\n                    onClick={() => select(i)}\n                    className={cn(\n                      \"relative h-5 w-5 rounded-full border transition-transform active:scale-95\",\n                      focusRing\n                    )}\n                    style={{\n                      background: a.color,\n                      borderColor: selected\n                        ? \"var(--m-text)\"\n                        : \"var(--m-line)\",\n                      boxShadow: selected\n                        ? \"inset 0 0 0 2px var(--m-surface)\"\n                        : \"none\",\n                    }}\n                  />\n                );\n              })}\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
    }
  ]
}