@loworbitstudio/visor 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.4.0",
3
- "generated_at": "2026-05-12T03:24:51.368Z",
3
+ "generated_at": "2026-05-12T23:36:26.151Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "changeType": "current",
package/dist/index.js CHANGED
@@ -3840,7 +3840,7 @@ import {
3840
3840
  } from "fs";
3841
3841
  import { join as join15, basename as basename3, resolve as resolve11, sep } from "path";
3842
3842
  import { parse as parseYaml2 } from "yaml";
3843
- import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
3843
+ import { generateThemeData as generateThemeData4, validateFontCoverage } from "@loworbitstudio/visor-theme-engine";
3844
3844
  import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
3845
3845
  var PRIVATE_THEMES_REPO_URL = "git@github.com:low-orbit-studio/visor-themes-private.git";
3846
3846
  var PRIVATE_THEMES_ENV_VAR = "VISOR_THEMES_PRIVATE_PATH";
@@ -4236,6 +4236,15 @@ function themeSyncCommand(cwd, options) {
4236
4236
  const group = extractGroup(yamlContent) ?? (isCustom ? "Custom" : "Visor");
4237
4237
  const defaultMode = extractDefaultMode(yamlContent);
4238
4238
  const css = docsAdapter3({ primitives: data.primitives, tokens: data.tokens, config: data.config });
4239
+ const coverage = validateFontCoverage(css);
4240
+ if (coverage.errors.length > 0) {
4241
+ for (const e of coverage.errors) {
4242
+ errors.push(
4243
+ `${basename3(filePath)}: ${e.declaredAt} declares "${e.family}" with no matching @font-face. Set typography.<slot>.source: visor-fonts (with org:) or google-fonts, or pick a system font.`
4244
+ );
4245
+ }
4246
+ return;
4247
+ }
4239
4248
  const yamlFilename = slugOverride ?? basename3(filePath).replace(/\.visor\.yaml$/, "");
4240
4249
  manifest.push({ slug: slug2, label, group, defaultMode, css, yamlFilename, isCustom });
4241
4250
  };
@@ -3460,7 +3460,7 @@
3460
3460
  {
3461
3461
  "path": "blocks/design-system-specimen/design-system-specimen.tsx",
3462
3462
  "type": "registry:block",
3463
- "content": "\"use client\"\n\nimport { cn } from \"../../lib/utils\"\nimport { Heading } from \"../../components/ui/heading/heading\"\nimport { Text } from \"../../components/ui/text/text\"\nimport { Separator } from \"../../components/ui/separator/separator\"\nimport styles from \"./design-system-specimen.module.css\"\n\nimport {\n ColorPaletteSection,\n TypographySection,\n SpacingSection,\n ShadowSection,\n SurfaceSection,\n RadiusSection,\n} from \"./token-specimens\"\nimport { MotionDurationSection, MotionEasingSection } from \"./motion-specimens\"\nimport { IconGridSection, AccessibilitySection } from \"./utility-specimens\"\nimport {\n ButtonSpecimenSection,\n FormSpecimenSection,\n ComponentShowcaseSection,\n} from \"./component-specimens\"\nimport {\n THEME_COLOR_SCALES,\n STATUS_COLOR_SCALES,\n SEMANTIC_COLORS,\n FONT_FAMILIES,\n TYPE_SPECIMENS,\n SPACING_STEPS,\n SHADOW_LEVELS,\n SURFACES,\n RADIUS_STEPS,\n MOTION_DURATIONS,\n EASINGS,\n CONTRAST_PAIRS,\n ICON_SPECIMENS,\n} from \"./specimen-data\"\n\ninterface DesignSystemSpecimenProps {\n className?: string\n}\n\n/**\n * Design System Specimen\n *\n * A live, interactive design system specimen block that showcases\n * the full Visor token system and component library.\n * Responds dynamically to the active theme.\n */\nexport function DesignSystemSpecimen({\n className,\n}: DesignSystemSpecimenProps) {\n return (\n <div className={cn(styles.root, className)}>\n <div>\n <Heading level={2}>Design System Specimen</Heading>\n <Text color=\"secondary\">\n A live showcase of all Visor design tokens and components.\n Switch themes to see everything update in real-time.\n </Text>\n </div>\n\n <Separator />\n\n <ColorPaletteSection themeScales={THEME_COLOR_SCALES} statusScales={STATUS_COLOR_SCALES} semanticColors={SEMANTIC_COLORS} />\n <Separator />\n\n <TypographySection fontFamilies={FONT_FAMILIES} specimens={TYPE_SPECIMENS} />\n <Separator />\n\n <SpacingSection steps={SPACING_STEPS} />\n <Separator />\n\n <ShadowSection levels={SHADOW_LEVELS} />\n <Separator />\n\n <SurfaceSection surfaces={SURFACES} />\n <Separator />\n\n <RadiusSection steps={RADIUS_STEPS} />\n <Separator />\n\n <MotionDurationSection durations={MOTION_DURATIONS} />\n <Separator />\n\n <MotionEasingSection easings={EASINGS} />\n <Separator />\n\n <IconGridSection icons={ICON_SPECIMENS} />\n <Separator />\n\n <AccessibilitySection pairs={CONTRAST_PAIRS} />\n <Separator />\n\n <ButtonSpecimenSection />\n <Separator />\n\n <FormSpecimenSection />\n <Separator />\n\n <ComponentShowcaseSection />\n </div>\n )\n}\n"
3463
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../../lib/utils\"\nimport { Heading } from \"../../components/ui/heading/heading\"\nimport { Text } from \"../../components/ui/text/text\"\nimport { Separator } from \"../../components/ui/separator/separator\"\nimport styles from \"./design-system-specimen.module.css\"\n\nimport {\n ColorPaletteSection,\n TypographySection,\n SpacingSection,\n ShadowSection,\n SurfaceSection,\n RadiusSection,\n} from \"./token-specimens\"\nimport { MotionDurationSection, MotionEasingSection } from \"./motion-specimens\"\nimport { IconGridSection, AccessibilitySection } from \"./utility-specimens\"\nimport {\n ButtonSpecimenSection,\n FormSpecimenSection,\n ComponentShowcaseSection,\n} from \"./component-specimens\"\nimport {\n THEME_COLOR_SCALES,\n STATUS_COLOR_SCALES,\n SEMANTIC_COLORS,\n FONT_FAMILIES,\n TYPE_SPECIMENS,\n SPACING_STEPS,\n SHADOW_LEVELS,\n SURFACES,\n RADIUS_STEPS,\n MOTION_DURATIONS,\n EASINGS,\n CONTRAST_PAIRS,\n ICON_SPECIMENS,\n deriveFontFamiliesFromTypography,\n type FontFamilyData,\n type ThemeTypographyManifest,\n} from \"./specimen-data\"\n\n/**\n * Subset of the docs-site PRIVATE_THEMES manifest the specimen consumes.\n * Only `slug` + `typography` are required — `label`/`group` are accepted to\n * keep callers from having to remap their manifest objects (VI-356).\n */\nexport interface DesignSystemSpecimenThemeEntry {\n slug: string\n typography?: ThemeTypographyManifest\n}\n\ninterface DesignSystemSpecimenProps {\n className?: string\n /**\n * Optional theme manifest. When provided alongside an active `*-theme` body\n * class that matches an entry's slug, the Font Families specimen renders\n * the theme's actual loaded weights instead of the hardcoded defaults. If\n * absent or no slug matches, defaults are used. VI-356.\n */\n themeManifest?: DesignSystemSpecimenThemeEntry[]\n /**\n * Optional override for the Font Families specimen rows. Takes precedence\n * over `themeManifest` resolution — useful for tests and one-off renders.\n */\n fontFamilies?: FontFamilyData[]\n}\n\nconst THEME_CLASS_PATTERN = /(^|\\s)([\\w-]+)-theme(?=\\s|$)/\n\n/**\n * Reads the active theme slug from `body.className` (`{slug}-theme`) and\n * re-syncs whenever the docs site fires `visor-theme-change` or whenever\n * <body>'s class attribute mutates. Returns null in non-browser contexts.\n */\nfunction useActiveThemeSlug(): string | null {\n const [slug, setSlug] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (typeof document === \"undefined\") return undefined\n const body = document.body\n\n function read() {\n const match = body.className.match(THEME_CLASS_PATTERN)\n setSlug(match ? match[2] : null)\n }\n read()\n\n const handler = () => read()\n document.addEventListener(\"visor-theme-change\", handler)\n const obs = new MutationObserver(read)\n obs.observe(body, { attributes: true, attributeFilter: [\"class\"] })\n\n return () => {\n document.removeEventListener(\"visor-theme-change\", handler)\n obs.disconnect()\n }\n }, [])\n\n return slug\n}\n\n/**\n * Design System Specimen\n *\n * A live, interactive design system specimen block that showcases\n * the full Visor token system and component library.\n * Responds dynamically to the active theme.\n */\nexport function DesignSystemSpecimen({\n className,\n themeManifest,\n fontFamilies,\n}: DesignSystemSpecimenProps) {\n const activeSlug = useActiveThemeSlug()\n\n const resolvedFontFamilies = React.useMemo<FontFamilyData[]>(() => {\n if (fontFamilies) return fontFamilies\n if (!themeManifest || !activeSlug) return FONT_FAMILIES\n const entry = themeManifest.find((t) => t.slug === activeSlug)\n return deriveFontFamiliesFromTypography(entry?.typography, FONT_FAMILIES)\n }, [fontFamilies, themeManifest, activeSlug])\n\n return (\n <div className={cn(styles.root, className)}>\n <div>\n <Heading level={2}>Design System Specimen</Heading>\n <Text color=\"secondary\">\n A live showcase of all Visor design tokens and components.\n Switch themes to see everything update in real-time.\n </Text>\n </div>\n\n <Separator />\n\n <ColorPaletteSection themeScales={THEME_COLOR_SCALES} statusScales={STATUS_COLOR_SCALES} semanticColors={SEMANTIC_COLORS} />\n <Separator />\n\n <TypographySection fontFamilies={resolvedFontFamilies} specimens={TYPE_SPECIMENS} />\n <Separator />\n\n <SpacingSection steps={SPACING_STEPS} />\n <Separator />\n\n <ShadowSection levels={SHADOW_LEVELS} />\n <Separator />\n\n <SurfaceSection surfaces={SURFACES} />\n <Separator />\n\n <RadiusSection steps={RADIUS_STEPS} />\n <Separator />\n\n <MotionDurationSection durations={MOTION_DURATIONS} />\n <Separator />\n\n <MotionEasingSection easings={EASINGS} />\n <Separator />\n\n <IconGridSection icons={ICON_SPECIMENS} />\n <Separator />\n\n <AccessibilitySection pairs={CONTRAST_PAIRS} />\n <Separator />\n\n <ButtonSpecimenSection />\n <Separator />\n\n <FormSpecimenSection />\n <Separator />\n\n <ComponentShowcaseSection />\n </div>\n )\n}\n"
3464
3464
  },
3465
3465
  {
3466
3466
  "path": "blocks/design-system-specimen/design-system-specimen.module.css",
@@ -3470,7 +3470,7 @@
3470
3470
  {
3471
3471
  "path": "blocks/design-system-specimen/specimen-data.ts",
3472
3472
  "type": "registry:block",
3473
- "content": "/**\n * Design System Specimen — Data\n *\n * Typed data arrays for all specimen sections.\n * Values sourced from packages/tokens/src/tokens/primitives.ts and semantic.ts.\n */\n\n// ─── Interfaces ──────────────────────────────────────────────────────────────\n\nexport interface ColorSwatchData {\n token: string\n hex: string\n name: string\n lightText?: boolean\n /** When true, ColorSwatch reads the live computed value instead of displaying the fallback hex */\n dynamic?: boolean\n}\n\nexport interface ColorScaleData {\n name: string\n swatches: ColorSwatchData[]\n /** When set, renders a featured brand swatch above the scale reading this token */\n brandToken?: string\n}\n\nexport interface SemanticColorData {\n token: string\n label: string\n category: string\n}\n\nexport interface TypeSpecimenData {\n token: string\n label: string\n sizePx: number\n sampleText: string\n}\n\nexport interface SpacingStepData {\n token: string\n name: string\n px: number\n rem: string\n}\n\nexport interface ShadowLevelData {\n token: string\n name: string\n value: string\n}\n\nexport interface SurfaceData {\n token: string\n name: string\n lightText?: boolean\n}\n\nexport interface RadiusStepData {\n token: string\n name: string\n px: number\n}\n\nexport interface MotionDurationData {\n token: string\n name: string\n ms: number\n}\n\nexport interface EasingData {\n token: string\n name: string\n value: string\n}\n\nexport interface ContrastPairData {\n fgToken: string\n bgToken: string\n fgLabel: string\n bgLabel: string\n ratio: number\n wcagAA: boolean\n wcagAAA: boolean\n}\n\nexport interface IconSpecimenData {\n name: string\n phosphorName: string\n usage: string\n}\n\nexport interface FontWeightData {\n label: string\n value: number\n}\n\nexport interface FontFamilyData {\n /** CSS custom property token (e.g. \"--font-heading\") */\n token: string\n /** Display role (e.g. \"Heading & Body\", \"Monospace\") */\n role: string\n /** Font family display name — omit to read dynamically from the CSS token */\n familyName?: string\n /** Available weights */\n weights: FontWeightData[]\n}\n\n// ─── Color Scales ────────────────────────────────────────────────────────────\n\nexport interface StatusColorScaleData extends ColorScaleData {\n /** Semantic role label (e.g. \"Success\", \"Warning\") */\n role: string\n}\n\nexport const THEME_COLOR_SCALES: ColorScaleData[] = [\n {\n name: \"Primary\",\n brandToken: \"--interactive-primary-bg\",\n swatches: [\n { token: \"--color-primary-100\", hex: \"#cfdfe7\", name: \"100\", dynamic: true },\n { token: \"--color-primary-200\", hex: \"#adc8d5\", name: \"200\", dynamic: true },\n { token: \"--color-primary-300\", hex: \"#89aec0\", name: \"300\", dynamic: true },\n { token: \"--color-primary-400\", hex: \"#6093aa\", name: \"400\", dynamic: true },\n { token: \"--color-primary-500\", hex: \"#397a96\", name: \"500\", lightText: true, dynamic: true },\n { token: \"--color-primary-600\", hex: \"#2a647c\", name: \"600\", lightText: true, dynamic: true },\n { token: \"--color-primary-700\", hex: \"#1a4e64\", name: \"700\", lightText: true, dynamic: true },\n { token: \"--color-primary-800\", hex: \"#0b3a4c\", name: \"800\", lightText: true, dynamic: true },\n { token: \"--color-primary-900\", hex: \"#002938\", name: \"900\", lightText: true, dynamic: true },\n { token: \"--color-primary-950\", hex: \"#001c29\", name: \"950\", lightText: true, dynamic: true },\n ],\n },\n {\n name: \"Neutral\",\n swatches: [\n { token: \"--color-gray-100\", hex: \"#f3f4f6\", name: \"100\" },\n { token: \"--color-gray-200\", hex: \"#e5e7eb\", name: \"200\" },\n { token: \"--color-gray-300\", hex: \"#d1d5db\", name: \"300\" },\n { token: \"--color-gray-400\", hex: \"#9ca3af\", name: \"400\" },\n { token: \"--color-gray-500\", hex: \"#6b7280\", name: \"500\", lightText: true },\n { token: \"--color-gray-600\", hex: \"#4b5563\", name: \"600\", lightText: true },\n { token: \"--color-gray-700\", hex: \"#374151\", name: \"700\", lightText: true },\n { token: \"--color-gray-800\", hex: \"#1f2937\", name: \"800\", lightText: true },\n { token: \"--color-gray-900\", hex: \"#111827\", name: \"900\", lightText: true },\n { token: \"--color-gray-950\", hex: \"#030712\", name: \"950\", lightText: true },\n ],\n },\n]\n\nexport const STATUS_COLOR_SCALES: StatusColorScaleData[] = [\n {\n name: \"Success\",\n role: \"Success\",\n swatches: [\n { token: \"--color-green-50\", hex: \"#f0fdf4\", name: \"50\" },\n { token: \"--color-green-100\", hex: \"#dcfce7\", name: \"100\" },\n { token: \"--color-green-500\", hex: \"#22c55e\", name: \"500\", lightText: true },\n { token: \"--color-green-600\", hex: \"#16a34a\", name: \"600\", lightText: true },\n { token: \"--color-green-700\", hex: \"#15803d\", name: \"700\", lightText: true },\n { token: \"--color-green-900\", hex: \"#14532d\", name: \"900\", lightText: true },\n ],\n },\n {\n name: \"Warning\",\n role: \"Warning\",\n swatches: [\n { token: \"--color-amber-50\", hex: \"#fffbeb\", name: \"50\" },\n { token: \"--color-amber-100\", hex: \"#fef3c7\", name: \"100\" },\n { token: \"--color-amber-500\", hex: \"#f59e0b\", name: \"500\" },\n { token: \"--color-amber-600\", hex: \"#d97706\", name: \"600\", lightText: true },\n { token: \"--color-amber-700\", hex: \"#b45309\", name: \"700\", lightText: true },\n { token: \"--color-amber-900\", hex: \"#78350f\", name: \"900\", lightText: true },\n ],\n },\n {\n name: \"Error\",\n role: \"Error\",\n swatches: [\n { token: \"--color-red-50\", hex: \"#fef2f2\", name: \"50\" },\n { token: \"--color-red-100\", hex: \"#fee2e2\", name: \"100\" },\n { token: \"--color-red-500\", hex: \"#ef4444\", name: \"500\", lightText: true },\n { token: \"--color-red-600\", hex: \"#dc2626\", name: \"600\", lightText: true },\n { token: \"--color-red-700\", hex: \"#b91c1c\", name: \"700\", lightText: true },\n { token: \"--color-red-900\", hex: \"#7f1d1d\", name: \"900\", lightText: true },\n ],\n },\n {\n name: \"Info\",\n role: \"Info\",\n swatches: [\n { token: \"--color-sky-50\", hex: \"#f0f9ff\", name: \"50\" },\n { token: \"--color-sky-100\", hex: \"#e0f2fe\", name: \"100\" },\n { token: \"--color-sky-500\", hex: \"#0ea5e9\", name: \"500\", lightText: true },\n { token: \"--color-sky-600\", hex: \"#0284c7\", name: \"600\", lightText: true },\n { token: \"--color-sky-700\", hex: \"#0369a1\", name: \"700\", lightText: true },\n { token: \"--color-sky-900\", hex: \"#0c4a6e\", name: \"900\", lightText: true },\n ],\n },\n]\n\nexport const SEMANTIC_COLORS: SemanticColorData[] = [\n // Text\n { token: \"--text-primary\", label: \"text-primary\", category: \"Text\" },\n { token: \"--text-secondary\", label: \"text-secondary\", category: \"Text\" },\n { token: \"--text-tertiary\", label: \"text-tertiary\", category: \"Text\" },\n { token: \"--text-disabled\", label: \"text-disabled\", category: \"Text\" },\n { token: \"--text-inverse\", label: \"text-inverse\", category: \"Text\" },\n { token: \"--text-link\", label: \"text-link\", category: \"Text\" },\n { token: \"--text-success\", label: \"text-success\", category: \"Text\" },\n { token: \"--text-warning\", label: \"text-warning\", category: \"Text\" },\n { token: \"--text-error\", label: \"text-error\", category: \"Text\" },\n { token: \"--text-info\", label: \"text-info\", category: \"Text\" },\n // Surface\n { token: \"--surface-page\", label: \"surface-page\", category: \"Surface\" },\n { token: \"--surface-card\", label: \"surface-card\", category: \"Surface\" },\n { token: \"--surface-subtle\", label: \"surface-subtle\", category: \"Surface\" },\n { token: \"--surface-muted\", label: \"surface-muted\", category: \"Surface\" },\n { token: \"--surface-overlay\", label: \"surface-overlay\", category: \"Surface\" },\n { token: \"--surface-accent-subtle\", label: \"surface-accent-subtle\", category: \"Surface\" },\n { token: \"--surface-accent-default\", label: \"surface-accent-default\", category: \"Surface\" },\n { token: \"--surface-accent-strong\", label: \"surface-accent-strong\", category: \"Surface\" },\n // Border\n { token: \"--border-default\", label: \"border-default\", category: \"Border\" },\n { token: \"--border-muted\", label: \"border-muted\", category: \"Border\" },\n { token: \"--border-strong\", label: \"border-strong\", category: \"Border\" },\n { token: \"--border-focus\", label: \"border-focus\", category: \"Border\" },\n]\n\n// ─── Typography ──────────────────────────────────────────────────────────────\n\nexport const FONT_FAMILIES: FontFamilyData[] = [\n {\n token: \"--font-heading\",\n role: \"Heading & Body\",\n weights: [\n { label: \"Regular\", value: 400 },\n { label: \"Medium\", value: 500 },\n { label: \"Semibold\", value: 600 },\n { label: \"Bold\", value: 700 },\n ],\n },\n {\n token: \"--font-mono\",\n role: \"Monospace\",\n weights: [\n { label: \"Regular\", value: 400 },\n { label: \"Medium\", value: 500 },\n { label: \"Bold\", value: 700 },\n ],\n },\n]\n\nexport const TYPE_SPECIMENS: TypeSpecimenData[] = [\n { token: \"--font-size-4xl\", label: \"4xl\", sizePx: 36, sampleText: \"Display text\" },\n { token: \"--font-size-3xl\", label: \"3xl\", sizePx: 30, sampleText: \"Page heading\" },\n { token: \"--font-size-2xl\", label: \"2xl\", sizePx: 24, sampleText: \"Section heading\" },\n { token: \"--font-size-xl\", label: \"xl\", sizePx: 20, sampleText: \"Subsection heading\" },\n { token: \"--font-size-lg\", label: \"lg\", sizePx: 18, sampleText: \"Large body text\" },\n { token: \"--font-size-base\", label: \"base\", sizePx: 16, sampleText: \"Default body text for reading\" },\n { token: \"--font-size-sm\", label: \"sm\", sizePx: 14, sampleText: \"Small text, labels, and captions\" },\n { token: \"--font-size-xs\", label: \"xs\", sizePx: 12, sampleText: \"Fine print and metadata\" },\n]\n\n// ─── Spacing ─────────────────────────────────────────────────────────────────\n\nexport const SPACING_STEPS: SpacingStepData[] = [\n { token: \"--spacing-0\", name: \"0\", px: 0, rem: \"0\" },\n { token: \"--spacing-1\", name: \"1\", px: 4, rem: \"0.25rem\" },\n { token: \"--spacing-2\", name: \"2\", px: 8, rem: \"0.5rem\" },\n { token: \"--spacing-3\", name: \"3\", px: 12, rem: \"0.75rem\" },\n { token: \"--spacing-4\", name: \"4\", px: 16, rem: \"1rem\" },\n { token: \"--spacing-5\", name: \"5\", px: 20, rem: \"1.25rem\" },\n { token: \"--spacing-6\", name: \"6\", px: 24, rem: \"1.5rem\" },\n { token: \"--spacing-8\", name: \"8\", px: 32, rem: \"2rem\" },\n { token: \"--spacing-10\", name: \"10\", px: 40, rem: \"2.5rem\" },\n { token: \"--spacing-12\", name: \"12\", px: 48, rem: \"3rem\" },\n { token: \"--spacing-16\", name: \"16\", px: 64, rem: \"4rem\" },\n { token: \"--spacing-20\", name: \"20\", px: 80, rem: \"5rem\" },\n { token: \"--spacing-24\", name: \"24\", px: 96, rem: \"6rem\" },\n]\n\n// ─── Shadows ─────────────────────────────────────────────────────────────────\n\nexport const SHADOW_LEVELS: ShadowLevelData[] = [\n { token: \"--shadow-xs\", name: \"xs\", value: \"0 1px 1px 0 rgba(0, 0, 0, 0.04)\" },\n { token: \"--shadow-sm\", name: \"sm\", value: \"0 1px 2px 0 rgba(0, 0, 0, 0.05)\" },\n { token: \"--shadow-md\", name: \"md\", value: \"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)\" },\n { token: \"--shadow-lg\", name: \"lg\", value: \"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)\" },\n { token: \"--shadow-xl\", name: \"xl\", value: \"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)\" },\n]\n\n// ─── Surfaces ────────────────────────────────────────────────────────────────\n\nexport const SURFACES: SurfaceData[] = [\n { token: \"--surface-page\", name: \"Page\" },\n { token: \"--surface-card\", name: \"Card\" },\n { token: \"--surface-subtle\", name: \"Subtle\" },\n { token: \"--surface-muted\", name: \"Muted\" },\n { token: \"--surface-overlay\", name: \"Overlay\", lightText: true },\n { token: \"--surface-accent-subtle\", name: \"Accent Subtle\" },\n { token: \"--surface-accent-default\", name: \"Accent Default\", lightText: true },\n { token: \"--surface-accent-strong\", name: \"Accent Strong\", lightText: true },\n]\n\n// ─── Border Radius ───────────────────────────────────────────────────────────\n\nexport const RADIUS_STEPS: RadiusStepData[] = [\n { token: \"--radius-none\", name: \"none\", px: 0 },\n { token: \"--radius-sm\", name: \"sm\", px: 2 },\n { token: \"--radius-md\", name: \"md\", px: 4 },\n { token: \"--radius-lg\", name: \"lg\", px: 8 },\n { token: \"--radius-xl\", name: \"xl\", px: 12 },\n { token: \"--radius-2xl\", name: \"2xl\", px: 16 },\n { token: \"--radius-3xl\", name: \"3xl\", px: 24 },\n { token: \"--radius-full\", name: \"full\", px: 9999 },\n]\n\n// ─── Motion ──────────────────────────────────────────────────────────────────\n\nexport const MOTION_DURATIONS: MotionDurationData[] = [\n { token: \"--motion-duration-100\", name: \"100\", ms: 100 },\n { token: \"--motion-duration-150\", name: \"150\", ms: 150 },\n { token: \"--motion-duration-200\", name: \"200\", ms: 200 },\n { token: \"--motion-duration-300\", name: \"300\", ms: 300 },\n { token: \"--motion-duration-500\", name: \"500\", ms: 500 },\n { token: \"--motion-duration-800\", name: \"800\", ms: 800 },\n]\n\nexport const EASINGS: EasingData[] = [\n { token: \"--motion-easing-linear\", name: \"linear\", value: \"linear\" },\n { token: \"--motion-easing-ease-in\", name: \"ease-in\", value: \"cubic-bezier(0.4, 0, 1, 1)\" },\n { token: \"--motion-easing-ease-out\", name: \"ease-out\", value: \"cubic-bezier(0, 0, 0.2, 1)\" },\n { token: \"--motion-easing-ease-in-out\", name: \"ease-in-out\", value: \"cubic-bezier(0.4, 0, 0.2, 1)\" },\n { token: \"--motion-easing-spring\", name: \"spring\", value: \"cubic-bezier(0.34, 1.56, 0.64, 1)\" },\n]\n\n// ─── Accessibility ───────────────────────────────────────────────────────────\n\nexport const CONTRAST_PAIRS: ContrastPairData[] = [\n { fgToken: \"--text-primary\", bgToken: \"--surface-page\", fgLabel: \"text-primary\", bgLabel: \"surface-page\", ratio: 15.4, wcagAA: true, wcagAAA: true },\n { fgToken: \"--text-primary\", bgToken: \"--surface-card\", fgLabel: \"text-primary\", bgLabel: \"surface-card\", ratio: 15.4, wcagAA: true, wcagAAA: true },\n { fgToken: \"--text-secondary\", bgToken: \"--surface-page\", fgLabel: \"text-secondary\", bgLabel: \"surface-page\", ratio: 5.74, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-tertiary\", bgToken: \"--surface-page\", fgLabel: \"text-tertiary\", bgLabel: \"surface-page\", ratio: 4.75, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-link\", bgToken: \"--surface-page\", fgLabel: \"text-link\", bgLabel: \"surface-page\", ratio: 4.62, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-inverse\", bgToken: \"--surface-overlay\", fgLabel: \"text-inverse\", bgLabel: \"surface-overlay\", ratio: 14.7, wcagAA: true, wcagAAA: true },\n { fgToken: \"--text-success\", bgToken: \"--surface-page\", fgLabel: \"text-success\", bgLabel: \"surface-page\", ratio: 4.49, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-error\", bgToken: \"--surface-page\", fgLabel: \"text-error\", bgLabel: \"surface-page\", ratio: 5.25, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-warning\", bgToken: \"--surface-page\", fgLabel: \"text-warning\", bgLabel: \"surface-page\", ratio: 4.01, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-primary\", bgToken: \"--surface-subtle\", fgLabel: \"text-primary\", bgLabel: \"surface-subtle\", ratio: 14.9, wcagAA: true, wcagAAA: true },\n { fgToken: \"--text-primary\", bgToken: \"--surface-muted\", fgLabel: \"text-primary\", bgLabel: \"surface-muted\", ratio: 13.8, wcagAA: true, wcagAAA: true },\n]\n\n// ─── Icons ───────────────────────────────────────────────────────────────────\n\nexport const ICON_SPECIMENS: IconSpecimenData[] = [\n { name: \"House\", phosphorName: \"House\", usage: \"Home / dashboard\" },\n { name: \"MagnifyingGlass\", phosphorName: \"MagnifyingGlass\", usage: \"Search\" },\n { name: \"Gear\", phosphorName: \"Gear\", usage: \"Settings\" },\n { name: \"User\", phosphorName: \"User\", usage: \"Profile / account\" },\n { name: \"Bell\", phosphorName: \"Bell\", usage: \"Notifications\" },\n { name: \"EnvelopeSimple\", phosphorName: \"EnvelopeSimple\", usage: \"Messages / email\" },\n { name: \"Plus\", phosphorName: \"Plus\", usage: \"Add / create\" },\n { name: \"X\", phosphorName: \"X\", usage: \"Close / dismiss\" },\n { name: \"Check\", phosphorName: \"Check\", usage: \"Confirm / success\" },\n { name: \"Warning\", phosphorName: \"Warning\", usage: \"Warning / caution\" },\n { name: \"Info\", phosphorName: \"Info\", usage: \"Information\" },\n { name: \"ArrowRight\", phosphorName: \"ArrowRight\", usage: \"Navigate / next\" },\n { name: \"CaretDown\", phosphorName: \"CaretDown\", usage: \"Expand / dropdown\" },\n { name: \"DotsThree\", phosphorName: \"DotsThree\", usage: \"More actions\" },\n { name: \"PencilSimple\", phosphorName: \"PencilSimple\", usage: \"Edit\" },\n { name: \"Trash\", phosphorName: \"Trash\", usage: \"Delete\" },\n]\n\n// ─── Icon sizes for the size scale demo ──────────────────────────────────────\n\nexport const ICON_SIZES = [16, 20, 24, 32] as const\n"
3473
+ "content": "/**\n * Design System Specimen — Data\n *\n * Typed data arrays for all specimen sections.\n * Values sourced from packages/tokens/src/tokens/primitives.ts and semantic.ts.\n */\n\n// ─── Interfaces ──────────────────────────────────────────────────────────────\n\nexport interface ColorSwatchData {\n token: string\n hex: string\n name: string\n lightText?: boolean\n /** When true, ColorSwatch reads the live computed value instead of displaying the fallback hex */\n dynamic?: boolean\n}\n\nexport interface ColorScaleData {\n name: string\n swatches: ColorSwatchData[]\n /** When set, renders a featured brand swatch above the scale reading this token */\n brandToken?: string\n}\n\nexport interface SemanticColorData {\n token: string\n label: string\n category: string\n}\n\nexport interface TypeSpecimenData {\n token: string\n label: string\n sizePx: number\n sampleText: string\n}\n\nexport interface SpacingStepData {\n token: string\n name: string\n px: number\n rem: string\n}\n\nexport interface ShadowLevelData {\n token: string\n name: string\n value: string\n}\n\nexport interface SurfaceData {\n token: string\n name: string\n lightText?: boolean\n}\n\nexport interface RadiusStepData {\n token: string\n name: string\n px: number\n}\n\nexport interface MotionDurationData {\n token: string\n name: string\n ms: number\n}\n\nexport interface EasingData {\n token: string\n name: string\n value: string\n}\n\nexport interface ContrastPairData {\n fgToken: string\n bgToken: string\n fgLabel: string\n bgLabel: string\n ratio: number\n wcagAA: boolean\n wcagAAA: boolean\n}\n\nexport interface IconSpecimenData {\n name: string\n phosphorName: string\n usage: string\n}\n\nexport interface FontWeightData {\n label: string\n value: number\n}\n\nexport interface FontFamilyData {\n /** CSS custom property token (e.g. \"--font-heading\") */\n token: string\n /** Display role (e.g. \"Heading & Body\", \"Monospace\") */\n role: string\n /** Font family display name — omit to read dynamically from the CSS token */\n familyName?: string\n /** Available weights */\n weights: FontWeightData[]\n}\n\n// ─── Color Scales ────────────────────────────────────────────────────────────\n\nexport interface StatusColorScaleData extends ColorScaleData {\n /** Semantic role label (e.g. \"Success\", \"Warning\") */\n role: string\n}\n\nexport const THEME_COLOR_SCALES: ColorScaleData[] = [\n {\n name: \"Primary\",\n brandToken: \"--interactive-primary-bg\",\n swatches: [\n { token: \"--color-primary-100\", hex: \"#cfdfe7\", name: \"100\", dynamic: true },\n { token: \"--color-primary-200\", hex: \"#adc8d5\", name: \"200\", dynamic: true },\n { token: \"--color-primary-300\", hex: \"#89aec0\", name: \"300\", dynamic: true },\n { token: \"--color-primary-400\", hex: \"#6093aa\", name: \"400\", dynamic: true },\n { token: \"--color-primary-500\", hex: \"#397a96\", name: \"500\", lightText: true, dynamic: true },\n { token: \"--color-primary-600\", hex: \"#2a647c\", name: \"600\", lightText: true, dynamic: true },\n { token: \"--color-primary-700\", hex: \"#1a4e64\", name: \"700\", lightText: true, dynamic: true },\n { token: \"--color-primary-800\", hex: \"#0b3a4c\", name: \"800\", lightText: true, dynamic: true },\n { token: \"--color-primary-900\", hex: \"#002938\", name: \"900\", lightText: true, dynamic: true },\n { token: \"--color-primary-950\", hex: \"#001c29\", name: \"950\", lightText: true, dynamic: true },\n ],\n },\n {\n name: \"Neutral\",\n swatches: [\n { token: \"--color-gray-100\", hex: \"#f3f4f6\", name: \"100\" },\n { token: \"--color-gray-200\", hex: \"#e5e7eb\", name: \"200\" },\n { token: \"--color-gray-300\", hex: \"#d1d5db\", name: \"300\" },\n { token: \"--color-gray-400\", hex: \"#9ca3af\", name: \"400\" },\n { token: \"--color-gray-500\", hex: \"#6b7280\", name: \"500\", lightText: true },\n { token: \"--color-gray-600\", hex: \"#4b5563\", name: \"600\", lightText: true },\n { token: \"--color-gray-700\", hex: \"#374151\", name: \"700\", lightText: true },\n { token: \"--color-gray-800\", hex: \"#1f2937\", name: \"800\", lightText: true },\n { token: \"--color-gray-900\", hex: \"#111827\", name: \"900\", lightText: true },\n { token: \"--color-gray-950\", hex: \"#030712\", name: \"950\", lightText: true },\n ],\n },\n]\n\nexport const STATUS_COLOR_SCALES: StatusColorScaleData[] = [\n {\n name: \"Success\",\n role: \"Success\",\n swatches: [\n { token: \"--color-green-50\", hex: \"#f0fdf4\", name: \"50\" },\n { token: \"--color-green-100\", hex: \"#dcfce7\", name: \"100\" },\n { token: \"--color-green-500\", hex: \"#22c55e\", name: \"500\", lightText: true },\n { token: \"--color-green-600\", hex: \"#16a34a\", name: \"600\", lightText: true },\n { token: \"--color-green-700\", hex: \"#15803d\", name: \"700\", lightText: true },\n { token: \"--color-green-900\", hex: \"#14532d\", name: \"900\", lightText: true },\n ],\n },\n {\n name: \"Warning\",\n role: \"Warning\",\n swatches: [\n { token: \"--color-amber-50\", hex: \"#fffbeb\", name: \"50\" },\n { token: \"--color-amber-100\", hex: \"#fef3c7\", name: \"100\" },\n { token: \"--color-amber-500\", hex: \"#f59e0b\", name: \"500\" },\n { token: \"--color-amber-600\", hex: \"#d97706\", name: \"600\", lightText: true },\n { token: \"--color-amber-700\", hex: \"#b45309\", name: \"700\", lightText: true },\n { token: \"--color-amber-900\", hex: \"#78350f\", name: \"900\", lightText: true },\n ],\n },\n {\n name: \"Error\",\n role: \"Error\",\n swatches: [\n { token: \"--color-red-50\", hex: \"#fef2f2\", name: \"50\" },\n { token: \"--color-red-100\", hex: \"#fee2e2\", name: \"100\" },\n { token: \"--color-red-500\", hex: \"#ef4444\", name: \"500\", lightText: true },\n { token: \"--color-red-600\", hex: \"#dc2626\", name: \"600\", lightText: true },\n { token: \"--color-red-700\", hex: \"#b91c1c\", name: \"700\", lightText: true },\n { token: \"--color-red-900\", hex: \"#7f1d1d\", name: \"900\", lightText: true },\n ],\n },\n {\n name: \"Info\",\n role: \"Info\",\n swatches: [\n { token: \"--color-sky-50\", hex: \"#f0f9ff\", name: \"50\" },\n { token: \"--color-sky-100\", hex: \"#e0f2fe\", name: \"100\" },\n { token: \"--color-sky-500\", hex: \"#0ea5e9\", name: \"500\", lightText: true },\n { token: \"--color-sky-600\", hex: \"#0284c7\", name: \"600\", lightText: true },\n { token: \"--color-sky-700\", hex: \"#0369a1\", name: \"700\", lightText: true },\n { token: \"--color-sky-900\", hex: \"#0c4a6e\", name: \"900\", lightText: true },\n ],\n },\n]\n\nexport const SEMANTIC_COLORS: SemanticColorData[] = [\n // Text\n { token: \"--text-primary\", label: \"text-primary\", category: \"Text\" },\n { token: \"--text-secondary\", label: \"text-secondary\", category: \"Text\" },\n { token: \"--text-tertiary\", label: \"text-tertiary\", category: \"Text\" },\n { token: \"--text-disabled\", label: \"text-disabled\", category: \"Text\" },\n { token: \"--text-inverse\", label: \"text-inverse\", category: \"Text\" },\n { token: \"--text-link\", label: \"text-link\", category: \"Text\" },\n { token: \"--text-success\", label: \"text-success\", category: \"Text\" },\n { token: \"--text-warning\", label: \"text-warning\", category: \"Text\" },\n { token: \"--text-error\", label: \"text-error\", category: \"Text\" },\n { token: \"--text-info\", label: \"text-info\", category: \"Text\" },\n // Surface\n { token: \"--surface-page\", label: \"surface-page\", category: \"Surface\" },\n { token: \"--surface-card\", label: \"surface-card\", category: \"Surface\" },\n { token: \"--surface-subtle\", label: \"surface-subtle\", category: \"Surface\" },\n { token: \"--surface-muted\", label: \"surface-muted\", category: \"Surface\" },\n { token: \"--surface-overlay\", label: \"surface-overlay\", category: \"Surface\" },\n { token: \"--surface-accent-subtle\", label: \"surface-accent-subtle\", category: \"Surface\" },\n { token: \"--surface-accent-default\", label: \"surface-accent-default\", category: \"Surface\" },\n { token: \"--surface-accent-strong\", label: \"surface-accent-strong\", category: \"Surface\" },\n // Border\n { token: \"--border-default\", label: \"border-default\", category: \"Border\" },\n { token: \"--border-muted\", label: \"border-muted\", category: \"Border\" },\n { token: \"--border-strong\", label: \"border-strong\", category: \"Border\" },\n { token: \"--border-focus\", label: \"border-focus\", category: \"Border\" },\n]\n\n// ─── Typography ──────────────────────────────────────────────────────────────\n\n/**\n * Canonical CSS weight labels keyed by numeric weight. Used to label specimen\n * rows derived from a theme's actual loaded weights (VI-356, D2). Uses the\n * standard CSS Fonts Module names — \"Regular\" not \"Book\", \"Semibold\" not\n * \"Demibold\" — so the operator sees one consistent vocabulary regardless of\n * what a foundry calls the cut.\n */\nexport const WEIGHT_LABELS: Record<number, string> = {\n 100: \"Thin\",\n 200: \"Extra Light\",\n 300: \"Light\",\n 400: \"Regular\",\n 500: \"Medium\",\n 600: \"Semibold\",\n 700: \"Bold\",\n 800: \"Extra Bold\",\n 900: \"Black\",\n}\n\nexport function labelForWeight(weight: number): string {\n return WEIGHT_LABELS[weight] ?? `W${weight}`\n}\n\n/** Typography slot data — matches the manifest shape emitted by the docs site for private themes (VI-356). */\nexport interface ThemeTypographySlot {\n family: string\n weights: number[]\n}\n\nexport interface ThemeTypographyManifest {\n heading?: ThemeTypographySlot\n display?: ThemeTypographySlot\n body?: ThemeTypographySlot\n mono?: ThemeTypographySlot\n}\n\n/**\n * Derive Font Families specimen rows from a theme's typography manifest.\n *\n * - `--font-heading` row uses the body slot's weights when present (the theme's\n * --font-heading variable resolves to body family in the engine), falling\n * back to display or heading slot weights if body is absent.\n * - `--font-mono` row uses the mono slot's weights when present, falling back\n * to body weights, then the default.\n *\n * Any slot the manifest doesn't cover gets the corresponding fallback row from\n * `defaults`. This keeps stock themes (no `weights` in YAML) on the existing\n * 4+3 grid while themes like Blacklight render their five actual weights.\n */\nexport function deriveFontFamiliesFromTypography(\n manifest: ThemeTypographyManifest | undefined,\n defaults: FontFamilyData[],\n): FontFamilyData[] {\n if (!manifest) return defaults\n\n const headingDefault = defaults.find((f) => f.token === \"--font-heading\")\n const monoDefault = defaults.find((f) => f.token === \"--font-mono\")\n\n const headingSlot = manifest.body ?? manifest.display ?? manifest.heading\n const monoSlot = manifest.mono ?? manifest.body ?? manifest.display ?? manifest.heading\n\n const next: FontFamilyData[] = []\n if (headingDefault) {\n next.push(\n headingSlot\n ? {\n token: \"--font-heading\",\n role: headingDefault.role,\n familyName: headingSlot.family || headingDefault.familyName,\n weights: headingSlot.weights.map((w) => ({ label: labelForWeight(w), value: w })),\n }\n : headingDefault,\n )\n }\n if (monoDefault) {\n next.push(\n monoSlot\n ? {\n token: \"--font-mono\",\n role: monoDefault.role,\n familyName: monoSlot.family || monoDefault.familyName,\n weights: monoSlot.weights.map((w) => ({ label: labelForWeight(w), value: w })),\n }\n : monoDefault,\n )\n }\n return next\n}\n\n/**\n * Fallback weight rows for the Font Families specimen, used when a theme does\n * not declare per-slot `weights` in its `.visor.yaml`. Stock public themes\n * (Blackout, Borderless, Modern Minimal, Neutral, Space) all fall through to\n * these defaults since they rely on system-ui / default weights.\n */\nexport const FONT_FAMILIES: FontFamilyData[] = [\n {\n token: \"--font-heading\",\n role: \"Heading & Body\",\n weights: [\n { label: \"Regular\", value: 400 },\n { label: \"Medium\", value: 500 },\n { label: \"Semibold\", value: 600 },\n { label: \"Bold\", value: 700 },\n ],\n },\n {\n token: \"--font-mono\",\n role: \"Monospace\",\n weights: [\n { label: \"Regular\", value: 400 },\n { label: \"Medium\", value: 500 },\n { label: \"Bold\", value: 700 },\n ],\n },\n]\n\nexport const TYPE_SPECIMENS: TypeSpecimenData[] = [\n { token: \"--font-size-4xl\", label: \"4xl\", sizePx: 36, sampleText: \"Display text\" },\n { token: \"--font-size-3xl\", label: \"3xl\", sizePx: 30, sampleText: \"Page heading\" },\n { token: \"--font-size-2xl\", label: \"2xl\", sizePx: 24, sampleText: \"Section heading\" },\n { token: \"--font-size-xl\", label: \"xl\", sizePx: 20, sampleText: \"Subsection heading\" },\n { token: \"--font-size-lg\", label: \"lg\", sizePx: 18, sampleText: \"Large body text\" },\n { token: \"--font-size-base\", label: \"base\", sizePx: 16, sampleText: \"Default body text for reading\" },\n { token: \"--font-size-sm\", label: \"sm\", sizePx: 14, sampleText: \"Small text, labels, and captions\" },\n { token: \"--font-size-xs\", label: \"xs\", sizePx: 12, sampleText: \"Fine print and metadata\" },\n]\n\n// ─── Spacing ─────────────────────────────────────────────────────────────────\n\nexport const SPACING_STEPS: SpacingStepData[] = [\n { token: \"--spacing-0\", name: \"0\", px: 0, rem: \"0\" },\n { token: \"--spacing-1\", name: \"1\", px: 4, rem: \"0.25rem\" },\n { token: \"--spacing-2\", name: \"2\", px: 8, rem: \"0.5rem\" },\n { token: \"--spacing-3\", name: \"3\", px: 12, rem: \"0.75rem\" },\n { token: \"--spacing-4\", name: \"4\", px: 16, rem: \"1rem\" },\n { token: \"--spacing-5\", name: \"5\", px: 20, rem: \"1.25rem\" },\n { token: \"--spacing-6\", name: \"6\", px: 24, rem: \"1.5rem\" },\n { token: \"--spacing-8\", name: \"8\", px: 32, rem: \"2rem\" },\n { token: \"--spacing-10\", name: \"10\", px: 40, rem: \"2.5rem\" },\n { token: \"--spacing-12\", name: \"12\", px: 48, rem: \"3rem\" },\n { token: \"--spacing-16\", name: \"16\", px: 64, rem: \"4rem\" },\n { token: \"--spacing-20\", name: \"20\", px: 80, rem: \"5rem\" },\n { token: \"--spacing-24\", name: \"24\", px: 96, rem: \"6rem\" },\n]\n\n// ─── Shadows ─────────────────────────────────────────────────────────────────\n\nexport const SHADOW_LEVELS: ShadowLevelData[] = [\n { token: \"--shadow-xs\", name: \"xs\", value: \"0 1px 1px 0 rgba(0, 0, 0, 0.04)\" },\n { token: \"--shadow-sm\", name: \"sm\", value: \"0 1px 2px 0 rgba(0, 0, 0, 0.05)\" },\n { token: \"--shadow-md\", name: \"md\", value: \"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)\" },\n { token: \"--shadow-lg\", name: \"lg\", value: \"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)\" },\n { token: \"--shadow-xl\", name: \"xl\", value: \"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)\" },\n]\n\n// ─── Surfaces ────────────────────────────────────────────────────────────────\n\nexport const SURFACES: SurfaceData[] = [\n { token: \"--surface-page\", name: \"Page\" },\n { token: \"--surface-card\", name: \"Card\" },\n { token: \"--surface-subtle\", name: \"Subtle\" },\n { token: \"--surface-muted\", name: \"Muted\" },\n { token: \"--surface-overlay\", name: \"Overlay\", lightText: true },\n { token: \"--surface-accent-subtle\", name: \"Accent Subtle\" },\n { token: \"--surface-accent-default\", name: \"Accent Default\", lightText: true },\n { token: \"--surface-accent-strong\", name: \"Accent Strong\", lightText: true },\n]\n\n// ─── Border Radius ───────────────────────────────────────────────────────────\n\nexport const RADIUS_STEPS: RadiusStepData[] = [\n { token: \"--radius-none\", name: \"none\", px: 0 },\n { token: \"--radius-sm\", name: \"sm\", px: 2 },\n { token: \"--radius-md\", name: \"md\", px: 4 },\n { token: \"--radius-lg\", name: \"lg\", px: 8 },\n { token: \"--radius-xl\", name: \"xl\", px: 12 },\n { token: \"--radius-2xl\", name: \"2xl\", px: 16 },\n { token: \"--radius-3xl\", name: \"3xl\", px: 24 },\n { token: \"--radius-full\", name: \"full\", px: 9999 },\n]\n\n// ─── Motion ──────────────────────────────────────────────────────────────────\n\nexport const MOTION_DURATIONS: MotionDurationData[] = [\n { token: \"--motion-duration-100\", name: \"100\", ms: 100 },\n { token: \"--motion-duration-150\", name: \"150\", ms: 150 },\n { token: \"--motion-duration-200\", name: \"200\", ms: 200 },\n { token: \"--motion-duration-300\", name: \"300\", ms: 300 },\n { token: \"--motion-duration-500\", name: \"500\", ms: 500 },\n { token: \"--motion-duration-800\", name: \"800\", ms: 800 },\n]\n\nexport const EASINGS: EasingData[] = [\n { token: \"--motion-easing-linear\", name: \"linear\", value: \"linear\" },\n { token: \"--motion-easing-ease-in\", name: \"ease-in\", value: \"cubic-bezier(0.4, 0, 1, 1)\" },\n { token: \"--motion-easing-ease-out\", name: \"ease-out\", value: \"cubic-bezier(0, 0, 0.2, 1)\" },\n { token: \"--motion-easing-ease-in-out\", name: \"ease-in-out\", value: \"cubic-bezier(0.4, 0, 0.2, 1)\" },\n { token: \"--motion-easing-spring\", name: \"spring\", value: \"cubic-bezier(0.34, 1.56, 0.64, 1)\" },\n]\n\n// ─── Accessibility ───────────────────────────────────────────────────────────\n\nexport const CONTRAST_PAIRS: ContrastPairData[] = [\n { fgToken: \"--text-primary\", bgToken: \"--surface-page\", fgLabel: \"text-primary\", bgLabel: \"surface-page\", ratio: 15.4, wcagAA: true, wcagAAA: true },\n { fgToken: \"--text-primary\", bgToken: \"--surface-card\", fgLabel: \"text-primary\", bgLabel: \"surface-card\", ratio: 15.4, wcagAA: true, wcagAAA: true },\n { fgToken: \"--text-secondary\", bgToken: \"--surface-page\", fgLabel: \"text-secondary\", bgLabel: \"surface-page\", ratio: 5.74, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-tertiary\", bgToken: \"--surface-page\", fgLabel: \"text-tertiary\", bgLabel: \"surface-page\", ratio: 4.75, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-link\", bgToken: \"--surface-page\", fgLabel: \"text-link\", bgLabel: \"surface-page\", ratio: 4.62, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-inverse\", bgToken: \"--surface-overlay\", fgLabel: \"text-inverse\", bgLabel: \"surface-overlay\", ratio: 14.7, wcagAA: true, wcagAAA: true },\n { fgToken: \"--text-success\", bgToken: \"--surface-page\", fgLabel: \"text-success\", bgLabel: \"surface-page\", ratio: 4.49, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-error\", bgToken: \"--surface-page\", fgLabel: \"text-error\", bgLabel: \"surface-page\", ratio: 5.25, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-warning\", bgToken: \"--surface-page\", fgLabel: \"text-warning\", bgLabel: \"surface-page\", ratio: 4.01, wcagAA: true, wcagAAA: false },\n { fgToken: \"--text-primary\", bgToken: \"--surface-subtle\", fgLabel: \"text-primary\", bgLabel: \"surface-subtle\", ratio: 14.9, wcagAA: true, wcagAAA: true },\n { fgToken: \"--text-primary\", bgToken: \"--surface-muted\", fgLabel: \"text-primary\", bgLabel: \"surface-muted\", ratio: 13.8, wcagAA: true, wcagAAA: true },\n]\n\n// ─── Icons ───────────────────────────────────────────────────────────────────\n\nexport const ICON_SPECIMENS: IconSpecimenData[] = [\n { name: \"House\", phosphorName: \"House\", usage: \"Home / dashboard\" },\n { name: \"MagnifyingGlass\", phosphorName: \"MagnifyingGlass\", usage: \"Search\" },\n { name: \"Gear\", phosphorName: \"Gear\", usage: \"Settings\" },\n { name: \"User\", phosphorName: \"User\", usage: \"Profile / account\" },\n { name: \"Bell\", phosphorName: \"Bell\", usage: \"Notifications\" },\n { name: \"EnvelopeSimple\", phosphorName: \"EnvelopeSimple\", usage: \"Messages / email\" },\n { name: \"Plus\", phosphorName: \"Plus\", usage: \"Add / create\" },\n { name: \"X\", phosphorName: \"X\", usage: \"Close / dismiss\" },\n { name: \"Check\", phosphorName: \"Check\", usage: \"Confirm / success\" },\n { name: \"Warning\", phosphorName: \"Warning\", usage: \"Warning / caution\" },\n { name: \"Info\", phosphorName: \"Info\", usage: \"Information\" },\n { name: \"ArrowRight\", phosphorName: \"ArrowRight\", usage: \"Navigate / next\" },\n { name: \"CaretDown\", phosphorName: \"CaretDown\", usage: \"Expand / dropdown\" },\n { name: \"DotsThree\", phosphorName: \"DotsThree\", usage: \"More actions\" },\n { name: \"PencilSimple\", phosphorName: \"PencilSimple\", usage: \"Edit\" },\n { name: \"Trash\", phosphorName: \"Trash\", usage: \"Delete\" },\n]\n\n// ─── Icon sizes for the size scale demo ──────────────────────────────────────\n\nexport const ICON_SIZES = [16, 20, 24, 32] as const\n"
3474
3474
  },
3475
3475
  {
3476
3476
  "path": "blocks/design-system-specimen/token-specimens.tsx",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.4.0",
3
- "generated_at": "2026-05-12T03:24:51.364Z",
3
+ "generated_at": "2026-05-12T23:36:26.147Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "category": "specimen",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "CLI for the Visor design system — add components, hooks, and utilities to your project.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "@aws-sdk/client-s3": "^3.1021.0",
47
47
  "@babel/parser": "^7.26.0",
48
- "@loworbitstudio/visor-theme-engine": "^0.5.0",
48
+ "@loworbitstudio/visor-theme-engine": "^0.6.0",
49
49
  "commander": "^13.1.0",
50
50
  "diff": "^8.0.4",
51
51
  "picocolors": "^1.1.1",