{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tokens",
  "type": "registry:lib",
  "title": "Tokens",
  "description": "Typed design tokens per surface and theme, plus applyTheme() for the artifact theme handoff.",
  "files": [
    {
      "path": "src/tokens/index.ts",
      "type": "registry:lib",
      "target": "lib/wibo-tokens.ts",
      "content": "/**\n * Wibo UI design tokens as a typed object.\n *\n * `src/styles.css` is the stylesheet every app and artifact imports; this\n * module mirrors its per-surface colour and elevation tokens so that the\n * artifact bridge can hand a theme to an iframe, and so that code can read\n * token values (charts, canvas). A test fails when the two drift apart.\n *\n * The neutral ramp, surfaces, borders, status, chart and elevation values are\n * derived from the Mastra Studio console (mastra-ai/mastra,\n * packages/playground-ui/theme, Apache-2.0); the accent is Wibo indigo\n * (#6452d9, measured from the wordmark). Both are to be confirmed with Matteo\n * (design-system.md, open points). Source values and the mapping are in\n * docs/mastra-reference.md.\n */\n\nexport const surfaces = [\"app\", \"marketing\"] as const;\n/** `data-surface` value: the product UI or the public website. */\nexport type Surface = (typeof surfaces)[number];\n\nexport const themes = [\"light\", \"dark\"] as const;\n/** `data-theme` value. */\nexport type Theme = (typeof themes)[number];\n\n/** A theme choice that may defer to the operating system. */\nexport type ThemePreference = Theme | \"system\";\n\n/** Every token defined per surface and theme, without the leading `--`. */\nexport const themeTokenNames = [\n  \"gray-1\",\n  \"gray-2\",\n  \"gray-3\",\n  \"gray-4\",\n  \"gray-5\",\n  \"gray-6\",\n  \"gray-7\",\n  \"gray-8\",\n  \"gray-9\",\n  \"gray-10\",\n  \"gray-11\",\n  \"gray-12\",\n  \"background\",\n  \"foreground\",\n  \"card\",\n  \"card-foreground\",\n  \"popover\",\n  \"popover-foreground\",\n  \"primary\",\n  \"primary-foreground\",\n  \"primary-hover\",\n  \"secondary\",\n  \"secondary-foreground\",\n  \"muted\",\n  \"muted-foreground\",\n  \"accent\",\n  \"accent-foreground\",\n  \"accent-hover\",\n  \"border\",\n  \"input\",\n  \"ring\",\n  \"sidebar\",\n  \"sidebar-foreground\",\n  \"sidebar-hover\",\n  \"sidebar-active\",\n  \"sidebar-border\",\n  \"success\",\n  \"success-foreground\",\n  \"warning\",\n  \"warning-foreground\",\n  \"danger\",\n  \"danger-foreground\",\n  \"info\",\n  \"info-foreground\",\n  \"chart-1\",\n  \"chart-2\",\n  \"chart-3\",\n  \"chart-4\",\n  \"chart-5\",\n  \"chart-6\",\n  \"avatar-red\",\n  \"avatar-orange\",\n  \"avatar-amber\",\n  \"avatar-green\",\n  \"avatar-teal\",\n  \"avatar-blue\",\n  \"avatar-indigo\",\n  \"avatar-pink\",\n  \"avatar-face\",\n  \"overlay\",\n  \"elevation-card\",\n  \"elevation-overlay\",\n] as const;\n\nexport type ThemeTokenName = (typeof themeTokenNames)[number];\nexport type ThemeTokens = Readonly<Record<ThemeTokenName, string>>;\n\n/*\n * Neutral ramp: 12 achromatic steps (chroma 0), Mastra's background-1..3 and\n * gray-1..10 with its gray-9 dropped. Light: gray-1 is the card (white),\n * gray-2 the canvas, gray-3 the sidebar, gray-11 secondary text, gray-12 ink.\n */\nconst grayLight = {\n  \"gray-1\": \"oklch(1 0 0)\",\n  \"gray-2\": \"oklch(0.9851 0 0)\",\n  \"gray-3\": \"oklch(0.9642 0 0)\",\n  \"gray-4\": \"oklch(0.9431 0 0)\",\n  \"gray-5\": \"oklch(0.9189 0 0)\",\n  \"gray-6\": \"oklch(0.8945 0 0)\",\n  \"gray-7\": \"oklch(0.8452 0 0)\",\n  \"gray-8\": \"oklch(0.7604 0 0)\",\n  \"gray-9\": \"oklch(0.6201 0 0)\",\n  \"gray-10\": \"oklch(0.5486 0 0)\",\n  \"gray-11\": \"oklch(0.4459 0 0)\",\n  \"gray-12\": \"oklch(0.1286 0 0)\",\n} as const;\n\n/* Dark: gray-1 is the sidebar, gray-2 the canvas, gray-3 the card. */\nconst grayDark = {\n  \"gray-1\": \"oklch(0.1382 0 0)\",\n  \"gray-2\": \"oklch(0.1591 0 0)\",\n  \"gray-3\": \"oklch(0.1913 0 0)\",\n  \"gray-4\": \"oklch(0.2178 0 0)\",\n  \"gray-5\": \"oklch(0.2435 0 0)\",\n  \"gray-6\": \"oklch(0.2686 0 0)\",\n  \"gray-7\": \"oklch(0.3092 0 0)\",\n  \"gray-8\": \"oklch(0.3715 0 0)\",\n  \"gray-9\": \"oklch(0.4495 0 0)\",\n  \"gray-10\": \"oklch(0.5208 0 0)\",\n  \"gray-11\": \"oklch(0.65 0 0)\",\n  \"gray-12\": \"oklch(0.9851 0 0)\",\n} as const;\n\n/* Status colours from Mastra's ramps, picked for text contrast (4.5:1 or more on cards). */\nconst statusLight = {\n  success: \"oklch(0.47 0.129 150)\",\n  \"success-foreground\": \"var(--gray-1)\",\n  warning: \"oklch(0.5 0.12 54)\",\n  \"warning-foreground\": \"var(--gray-1)\",\n  danger: \"oklch(0.56 0.22 24)\",\n  \"danger-foreground\": \"var(--gray-1)\",\n  info: \"oklch(0.55 0.21 260)\",\n  \"info-foreground\": \"var(--gray-1)\",\n} as const;\n\nconst statusDark = {\n  success: \"oklch(0.7 0.15 150)\",\n  \"success-foreground\": \"var(--gray-1)\",\n  warning: \"oklch(0.86 0.146 84)\",\n  \"warning-foreground\": \"var(--gray-1)\",\n  danger: \"oklch(0.7 0.18 24)\",\n  \"danger-foreground\": \"var(--gray-1)\",\n  info: \"oklch(0.7 0.157 260)\",\n  \"info-foreground\": \"var(--gray-1)\",\n} as const;\n\n/* Ordered data series: blue, orange, aqua, yellow, magenta, green. Validated for colour-vision deficiency and contrast on both surfaces (dataviz palette check, 2026-10-13); assigned in this order, never cycled. */\nconst chartLight = {\n  \"chart-1\": \"oklch(0.575 0.163 255.5)\",\n  \"chart-2\": \"oklch(0.671 0.175 40.6)\",\n  \"chart-3\": \"oklch(0.669 0.141 162.1)\",\n  \"chart-4\": \"oklch(0.764 0.161 75.1)\",\n  \"chart-5\": \"oklch(0.716 0.141 357.4)\",\n  \"chart-6\": \"oklch(0.529 0.18 142.5)\",\n} as const;\n\nconst chartDark = {\n  \"chart-1\": \"oklch(0.622 0.161 255.1)\",\n  \"chart-2\": \"oklch(0.622 0.173 40.1)\",\n  \"chart-3\": \"oklch(0.621 0.128 163.1)\",\n  \"chart-4\": \"oklch(0.67 0.143 73.2)\",\n  \"chart-5\": \"oklch(0.622 0.171 0.8)\",\n  \"chart-6\": \"oklch(0.529 0.18 142.5)\",\n} as const;\n\n/*\n * Agent avatars (`AgentAvatar`): eight discs that carry a white head, so\n * each sits between 2.4:1 and 5.6:1 against white. Red, green and blue\n * follow the concept drawings, indigo is the Wibo accent, orange, amber,\n * teal and pink fill the wheel between them at a similar weight. Dark lifts\n * every disc a little and softens the white, so the faces do not glare on\n * the dark canvas. Assigned by id (`agentAvatarDefaults`), never cycled.\n */\nconst avatarLight = {\n  \"avatar-red\": \"oklch(0.6 0.19 29)\",\n  \"avatar-orange\": \"oklch(0.68 0.165 50)\",\n  \"avatar-amber\": \"oklch(0.74 0.15 76)\",\n  \"avatar-green\": \"oklch(0.66 0.15 142)\",\n  \"avatar-teal\": \"oklch(0.62 0.105 192)\",\n  \"avatar-blue\": \"oklch(0.55 0.15 256)\",\n  \"avatar-indigo\": \"oklch(0.534 0.198 283.5)\",\n  \"avatar-pink\": \"oklch(0.64 0.19 355)\",\n  \"avatar-face\": \"oklch(1 0 0)\",\n} as const;\n\nconst avatarDark = {\n  \"avatar-red\": \"oklch(0.6 0.175 29)\",\n  \"avatar-orange\": \"oklch(0.67 0.15 50)\",\n  \"avatar-amber\": \"oklch(0.72 0.135 76)\",\n  \"avatar-green\": \"oklch(0.64 0.135 142)\",\n  \"avatar-teal\": \"oklch(0.61 0.095 192)\",\n  \"avatar-blue\": \"oklch(0.58 0.14 256)\",\n  \"avatar-indigo\": \"oklch(0.58 0.18 283.5)\",\n  \"avatar-pink\": \"oklch(0.63 0.17 355)\",\n  \"avatar-face\": \"oklch(0.97 0 0)\",\n} as const;\n\n/*\n * Accent: Wibo indigo #6452d9 = oklch(0.534 0.198 283.5), white text 5.6:1.\n * Dark lifts it to stay legible on the dark canvas (6.5:1) and takes dark text.\n * Hover is one step darker in light, one step lower in dark.\n */\nconst accentLight = {\n  accent: \"oklch(0.534 0.198 283.5)\",\n  \"accent-foreground\": \"var(--gray-1)\",\n  \"accent-hover\": \"oklch(0.48 0.198 283.5)\",\n} as const;\n\nconst accentDark = {\n  accent: \"oklch(0.68 0.15 283.5)\",\n  \"accent-foreground\": \"var(--gray-1)\",\n  \"accent-hover\": \"oklch(0.62 0.15 283.5)\",\n} as const;\n\n/* Lines: alphas of the ink (Mastra's boundary ladder), so an edge reads the same on every surface. */\nconst linesLight = {\n  border: \"oklch(0.205 0 0 / 0.09)\",\n  input: \"oklch(0.205 0 0 / 0.14)\",\n  ring: \"var(--accent)\",\n} as const;\n\nconst linesDark = {\n  border: \"oklch(1 0 0 / 0.09)\",\n  input: \"oklch(1 0 0 / 0.14)\",\n  ring: \"var(--accent)\",\n} as const;\n\n/* Sidebar: hover and active rows are the fill ladder's 4 % and 6 % rungs. */\nconst sidebarLight = {\n  sidebar: \"var(--gray-3)\",\n  \"sidebar-foreground\": \"var(--gray-12)\",\n  \"sidebar-hover\": \"oklch(0.205 0 0 / 0.04)\",\n  \"sidebar-active\": \"oklch(0.205 0 0 / 0.06)\",\n  \"sidebar-border\": \"var(--border)\",\n} as const;\n\nconst sidebarDark = {\n  sidebar: \"var(--gray-1)\",\n  \"sidebar-foreground\": \"var(--gray-12)\",\n  \"sidebar-hover\": \"oklch(1 0 0 / 0.04)\",\n  \"sidebar-active\": \"oklch(1 0 0 / 0.06)\",\n  \"sidebar-border\": \"var(--border)\",\n} as const;\n\nconst overlayLight = {\n  overlay: \"oklch(0.208 0 0 / 0.45)\",\n  \"elevation-overlay\":\n    \"0 2px 4px -1px oklch(0 0 0 / 0.08), 0 4px 8px -2px oklch(0 0 0 / 0.06), 0 8px 16px -4px oklch(0 0 0 / 0.06)\",\n} as const;\n\nconst overlayDark = {\n  overlay: \"oklch(0 0 0 / 0.75)\",\n  \"elevation-overlay\":\n    \"inset 0 1px 0 oklch(1 0 0 / 0.03), 0 2px 2px oklch(0 0 0 / 0.1), 0 4px 4px oklch(0 0 0 / 0.1), 0 8px 8px oklch(0 0 0 / 0.1)\",\n} as const;\n\n/* The primary's hover: the opaque mix of the primary toward the canvas (Mastra's fill-inverse-hover). */\nconst primaryHover = \"color-mix(in srgb, var(--primary) 75%, var(--background))\";\n\nconst appLight: ThemeTokens = {\n  ...grayLight,\n  background: \"var(--gray-2)\",\n  foreground: \"var(--gray-12)\",\n  card: \"var(--gray-1)\",\n  \"card-foreground\": \"var(--gray-12)\",\n  popover: \"var(--gray-1)\",\n  \"popover-foreground\": \"var(--gray-12)\",\n  primary: \"var(--gray-12)\",\n  \"primary-foreground\": \"var(--gray-1)\",\n  \"primary-hover\": primaryHover,\n  secondary: \"var(--gray-4)\",\n  \"secondary-foreground\": \"var(--gray-12)\",\n  muted: \"var(--gray-4)\",\n  \"muted-foreground\": \"var(--gray-11)\",\n  ...accentLight,\n  ...linesLight,\n  ...sidebarLight,\n  ...statusLight,\n  ...chartLight,\n  ...avatarLight,\n  ...overlayLight,\n  \"elevation-card\": \"0 1px 2px -1px oklch(0 0 0 / 0.05), 0 1px 3px -2px oklch(0 0 0 / 0.04)\",\n};\n\nconst appDark: ThemeTokens = {\n  ...grayDark,\n  background: \"var(--gray-2)\",\n  foreground: \"var(--gray-12)\",\n  card: \"var(--gray-3)\",\n  \"card-foreground\": \"var(--gray-12)\",\n  popover: \"var(--gray-3)\",\n  \"popover-foreground\": \"var(--gray-12)\",\n  primary: \"var(--gray-12)\",\n  \"primary-foreground\": \"var(--gray-2)\",\n  \"primary-hover\": primaryHover,\n  secondary: \"var(--gray-5)\",\n  \"secondary-foreground\": \"var(--gray-12)\",\n  muted: \"var(--gray-4)\",\n  \"muted-foreground\": \"var(--gray-11)\",\n  ...accentDark,\n  ...linesDark,\n  ...sidebarDark,\n  ...statusDark,\n  ...chartDark,\n  ...avatarDark,\n  ...overlayDark,\n  \"elevation-card\":\n    \"inset 0 1px 0 oklch(1 0 0 / 0.03), 0 1px 2px -1px oklch(0 0 0 / 0.22), 0 2px 3px -2px oklch(0 0 0 / 0.14)\",\n};\n\nconst marketingLight: ThemeTokens = {\n  ...grayLight,\n  background: \"var(--gray-1)\",\n  foreground: \"var(--gray-12)\",\n  card: \"var(--gray-2)\",\n  \"card-foreground\": \"var(--gray-12)\",\n  popover: \"var(--gray-1)\",\n  \"popover-foreground\": \"var(--gray-12)\",\n  primary: \"var(--accent)\",\n  \"primary-foreground\": \"var(--accent-foreground)\",\n  \"primary-hover\": \"var(--accent-hover)\",\n  secondary: \"var(--gray-4)\",\n  \"secondary-foreground\": \"var(--gray-12)\",\n  muted: \"var(--gray-3)\",\n  \"muted-foreground\": \"var(--gray-11)\",\n  ...accentLight,\n  ...linesLight,\n  ...sidebarLight,\n  ...statusLight,\n  ...chartLight,\n  ...avatarLight,\n  ...overlayLight,\n  \"elevation-card\": \"0 1px 2px -1px oklch(0 0 0 / 0.05), 0 16px 40px -16px oklch(0 0 0 / 0.12)\",\n};\n\nconst marketingDark: ThemeTokens = {\n  ...grayDark,\n  background: \"var(--gray-1)\",\n  foreground: \"var(--gray-12)\",\n  card: \"var(--gray-2)\",\n  \"card-foreground\": \"var(--gray-12)\",\n  popover: \"var(--gray-3)\",\n  \"popover-foreground\": \"var(--gray-12)\",\n  primary: \"var(--accent)\",\n  \"primary-foreground\": \"var(--accent-foreground)\",\n  \"primary-hover\": \"var(--accent-hover)\",\n  secondary: \"var(--gray-5)\",\n  \"secondary-foreground\": \"var(--gray-12)\",\n  muted: \"var(--gray-4)\",\n  \"muted-foreground\": \"var(--gray-11)\",\n  ...accentDark,\n  ...linesDark,\n  ...sidebarDark,\n  ...statusDark,\n  ...chartDark,\n  ...avatarDark,\n  ...overlayDark,\n  \"elevation-card\":\n    \"inset 0 1px 0 oklch(1 0 0 / 0.03), 0 1px 2px -1px oklch(0 0 0 / 0.3), 0 16px 40px -16px oklch(0 0 0 / 0.6)\",\n};\n\n/**\n * Corner radii. `control` is the shared corner of buttons, inputs, selects,\n * comboboxes, date ranges, textareas, filter chips and the pill tab track\n * (`rounded-control`); badges, avatars and inner controls `md`; sidebar and\n * menu rows `lg`; cards, popovers, menus and the app frame `xl`.\n */\nexport const radius = {\n  sm: \"4px\",\n  md: \"6px\",\n  lg: \"10px\",\n  xl: \"14px\",\n  control: \"8px\",\n} as const;\n\n/** Control heights: buttons, inputs and select triggers (`md` is the default); sidebar rows use `nav`. */\nexport const controlHeight = {\n  sm: \"1.75rem\",\n  nav: \"1.875rem\",\n  md: \"2.125rem\",\n  lg: \"2.25rem\",\n} as const;\n\n/** Spacing base: every Tailwind spacing step is a multiple of this. */\nexport const spacing = {\n  base: \"0.25rem\",\n} as const;\n\n/**\n * Motion: short, disabled under `prefers-reduced-motion`. `easing` for colour\n * and opacity, `glide` for anything that moves; `press` is the scale a\n * control settles to while pressed.\n */\nexport const motion = {\n  fast: \"150ms\",\n  base: \"200ms\",\n  easing: \"cubic-bezier(0, 0, 0.2, 1)\",\n  glide: \"cubic-bezier(0.22, 1, 0.36, 1)\",\n  press: \"0.97\",\n} as const;\n\n/** Type families, the 7-step UI scale, the 13 px control label and two marketing display sizes. */\nexport const typography = {\n  fontSans:\n    'var(--font-geist-sans, \"Geist\"), ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif',\n  fontMono:\n    'var(--font-geist-mono, \"Geist Mono\"), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace',\n  scale: {\n    xs: { size: \"0.75rem\", lineHeight: \"1rem\" },\n    label: { size: \"0.8125rem\", lineHeight: \"1.25rem\" },\n    sm: { size: \"0.875rem\", lineHeight: \"1.25rem\" },\n    base: { size: \"1rem\", lineHeight: \"1.5rem\" },\n    lg: { size: \"1.125rem\", lineHeight: \"1.75rem\" },\n    xl: { size: \"1.25rem\", lineHeight: \"1.75rem\" },\n    \"2xl\": { size: \"1.5rem\", lineHeight: \"2rem\" },\n    \"3xl\": { size: \"1.875rem\", lineHeight: \"2.25rem\" },\n    display: { size: \"4rem\", lineHeight: \"1.05\" },\n    \"display-lg\": { size: \"6rem\", lineHeight: \"1\" },\n  },\n} as const;\n\n/** The full token set. */\nexport const tokens = {\n  surfaces: {\n    app: { light: appLight, dark: appDark },\n    marketing: { light: marketingLight, dark: marketingDark },\n  },\n  radius,\n  controlHeight,\n  spacing,\n  motion,\n  typography,\n} as const satisfies {\n  surfaces: Record<Surface, Record<Theme, ThemeTokens>>;\n  radius: Record<string, string>;\n  controlHeight: Record<string, string>;\n  spacing: Record<string, string>;\n  motion: Record<string, string>;\n  typography: object;\n};\n\n/** The tokens for one surface and theme, keyed as CSS custom properties. */\nexport function cssVariables(surface: Surface, theme: Theme): Record<`--${ThemeTokenName}`, string> {\n  const set = tokens.surfaces[surface][theme];\n  const out = {} as Record<`--${ThemeTokenName}`, string>;\n  for (const name of themeTokenNames) out[`--${name}`] = set[name];\n  return out;\n}\n\n/** The theme the operating system prefers; `light` outside the browser. */\nexport function systemTheme(): Theme {\n  if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return \"light\";\n  return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\nexport interface ThemeSelection {\n  surface: Surface;\n  theme: ThemePreference;\n}\n\nexport interface ApplyThemeOptions {\n  /**\n   * Also write every token as an inline custom property on `root`. Use it\n   * when the document does not load `@wibo/ui/styles.css` (for example an\n   * artifact iframe that receives the theme through the bridge).\n   */\n  inline?: boolean;\n}\n\n/**\n * Select a surface and theme on a root element (usually\n * `document.documentElement`). Sets `data-surface` and `data-theme`; with\n * `theme: \"system\"` the attribute is removed so the stylesheet follows\n * `prefers-color-scheme`.\n */\nexport function applyTheme(\n  root: HTMLElement,\n  { surface, theme }: ThemeSelection,\n  { inline = false }: ApplyThemeOptions = {},\n): void {\n  root.setAttribute(\"data-surface\", surface);\n  if (theme === \"system\") root.removeAttribute(\"data-theme\");\n  else root.setAttribute(\"data-theme\", theme);\n\n  const resolved = theme === \"system\" ? systemTheme() : theme;\n  if (inline) {\n    const vars = cssVariables(surface, resolved);\n    for (const [name, value] of Object.entries(vars)) root.style.setProperty(name, value);\n    root.style.colorScheme = resolved;\n  } else {\n    for (const name of themeTokenNames) root.style.removeProperty(`--${name}`);\n    root.style.removeProperty(\"color-scheme\");\n  }\n}\n"
    }
  ]
}
