@loworbitstudio/visor 0.9.1 → 0.10.2

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-14T19:58:07.660Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "changeType": "current",
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
+ import { readFileSync as readFileSync22 } from "fs";
5
+ import { dirname as dirname8, join as join20 } from "path";
6
+ import { fileURLToPath as fileURLToPath3 } from "url";
4
7
  import { Command as Command2 } from "commander";
5
8
 
6
9
  // src/commands/check.ts
@@ -1173,9 +1176,9 @@ function readPackageJson(cwd) {
1173
1176
  return JSON.parse(readFileSync5(pkgPath, "utf-8"));
1174
1177
  }
1175
1178
  function isPackageInstalled(packageName, cwd) {
1176
- const pkg = readPackageJson(cwd);
1177
- if (!pkg) return false;
1178
- return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]);
1179
+ const pkg2 = readPackageJson(cwd);
1180
+ if (!pkg2) return false;
1181
+ return !!(pkg2.dependencies?.[packageName] || pkg2.devDependencies?.[packageName]);
1179
1182
  }
1180
1183
  function hasVisorTokens(cwd) {
1181
1184
  return isPackageInstalled("@loworbitstudio/visor-core", cwd);
@@ -1439,9 +1442,9 @@ function readVisorCliVersion() {
1439
1442
  const segments = new Array(i).fill("..");
1440
1443
  const candidate = join6(here, ...segments, "package.json");
1441
1444
  try {
1442
- const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
1443
- if (pkg.name === "@loworbitstudio/visor" && pkg.version) {
1444
- return pkg.version;
1445
+ const pkg2 = JSON.parse(readFileSync6(candidate, "utf-8"));
1446
+ if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) {
1447
+ return pkg2.version;
1445
1448
  }
1446
1449
  } catch {
1447
1450
  }
@@ -2459,7 +2462,9 @@ function themeApplyCommand(file, cwd, options) {
2459
2462
  };
2460
2463
  switch (options.adapter) {
2461
2464
  case "nextjs":
2462
- css = nextjsAdapter2(adapterInput);
2465
+ css = nextjsAdapter2(adapterInput, {
2466
+ scopePrefix: options.scopePrefix
2467
+ });
2463
2468
  break;
2464
2469
  case "fumadocs":
2465
2470
  css = fumadocsAdapter(adapterInput);
@@ -3177,8 +3182,8 @@ function extractFontHints(targetDir) {
3177
3182
  const pkgPath = join11(targetDir, "package.json");
3178
3183
  if (!existsSync9(pkgPath)) return void 0;
3179
3184
  try {
3180
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
3181
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
3185
+ const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
3186
+ const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
3182
3187
  const fonts2 = [];
3183
3188
  for (const [dep, _] of Object.entries(allDeps)) {
3184
3189
  const family = FONT_PACKAGE_MAP[dep];
@@ -3216,9 +3221,9 @@ function inferThemeName(targetDir) {
3216
3221
  const pkgPath = join11(targetDir, "package.json");
3217
3222
  if (existsSync9(pkgPath)) {
3218
3223
  try {
3219
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
3220
- if (pkg.name) {
3221
- const name = pkg.name.replace(/^@[\w-]+\//, "");
3224
+ const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
3225
+ if (pkg2.name) {
3226
+ const name = pkg2.name.replace(/^@[\w-]+\//, "");
3222
3227
  return `${name}-theme`;
3223
3228
  }
3224
3229
  } catch {
@@ -3448,11 +3453,11 @@ function detectVisorWorkspace(cwd) {
3448
3453
  const pkgPath = join12(current, "package.json");
3449
3454
  if (existsSync10(pkgPath)) {
3450
3455
  try {
3451
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
3452
- const workspaces = pkg.workspaces ?? [];
3456
+ const pkg2 = JSON.parse(readFileSync13(pkgPath, "utf-8"));
3457
+ const workspaces = pkg2.workspaces ?? [];
3453
3458
  const hasCli = workspaces.some((w) => w.includes("packages/cli"));
3454
3459
  const hasEngine = workspaces.some((w) => w.includes("packages/theme-engine"));
3455
- if (pkg.name === "visor" && hasCli && hasEngine) {
3460
+ if (pkg2.name === "visor" && hasCli && hasEngine) {
3456
3461
  return current;
3457
3462
  }
3458
3463
  } catch {
@@ -3840,7 +3845,7 @@ import {
3840
3845
  } from "fs";
3841
3846
  import { join as join15, basename as basename3, resolve as resolve11, sep } from "path";
3842
3847
  import { parse as parseYaml2 } from "yaml";
3843
- import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
3848
+ import { generateThemeData as generateThemeData4, validateFontCoverage, formatFontCoverageError } from "@loworbitstudio/visor-theme-engine";
3844
3849
  import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
3845
3850
  var PRIVATE_THEMES_REPO_URL = "git@github.com:low-orbit-studio/visor-themes-private.git";
3846
3851
  var PRIVATE_THEMES_ENV_VAR = "VISOR_THEMES_PRIVATE_PATH";
@@ -4236,6 +4241,13 @@ function themeSyncCommand(cwd, options) {
4236
4241
  const group = extractGroup(yamlContent) ?? (isCustom ? "Custom" : "Visor");
4237
4242
  const defaultMode = extractDefaultMode(yamlContent);
4238
4243
  const css = docsAdapter3({ primitives: data.primitives, tokens: data.tokens, config: data.config });
4244
+ const coverage = validateFontCoverage(css);
4245
+ if (coverage.errors.length > 0) {
4246
+ for (const e of coverage.errors) {
4247
+ errors.push(formatFontCoverageError(basename3(filePath), e.declaredAt, e.family));
4248
+ }
4249
+ return;
4250
+ }
4239
4251
  const yamlFilename = slugOverride ?? basename3(filePath).replace(/\.visor\.yaml$/, "");
4240
4252
  manifest.push({ slug: slug2, label, group, defaultMode, css, yamlFilename, isCustom });
4241
4253
  };
@@ -4912,8 +4924,8 @@ async function doctorCommand(cwd, options, cliVersion) {
4912
4924
  }
4913
4925
  const pkgJsonPath = path.join(cwd, "package.json");
4914
4926
  try {
4915
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
4916
- const reactVersion = pkg.dependencies?.react ?? pkg.devDependencies?.react ?? "";
4927
+ const pkg2 = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
4928
+ const reactVersion = pkg2.dependencies?.react ?? pkg2.devDependencies?.react ?? "";
4917
4929
  const versionNum = parseFloat(reactVersion.replace(/[^0-9.]/g, ""));
4918
4930
  if (!reactVersion) {
4919
4931
  checks.push({
@@ -5711,8 +5723,12 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
5711
5723
  }
5712
5724
 
5713
5725
  // src/index.ts
5726
+ var __dirname2 = dirname8(fileURLToPath3(import.meta.url));
5727
+ var pkg = JSON.parse(
5728
+ readFileSync22(join20(__dirname2, "..", "package.json"), "utf-8")
5729
+ );
5714
5730
  var program = new Command2();
5715
- program.name("visor").description("CLI for the Visor design system").version("0.3.0");
5731
+ program.name("visor").description("CLI for the Visor design system").version(pkg.version);
5716
5732
  program.addCommand(checkCommand());
5717
5733
  program.command("init").description("Initialize Visor \u2014 with --template nextjs, scaffolds a complete runnable Borealis-native Next.js app in one command").option("--template <name>", "scaffold a complete runnable app (templates: nextjs)").option("--json", "output structured JSON (for AI agents)").action((options) => {
5718
5734
  initCommand(process.cwd(), options);
@@ -5744,6 +5760,9 @@ theme.command("apply").description(
5744
5760
  ).option("--json", "output structured JSON (for AI agents)").option(
5745
5761
  "--adapter <name>",
5746
5762
  "target adapter: nextjs, fumadocs, deck, docs, flutter"
5763
+ ).option(
5764
+ "--scope-prefix <selector>",
5765
+ "(nextjs) CSS selector to wrap all rules under (e.g. 'body.blacklight-theme'); enables body-class theme swap. Default: :root"
5747
5766
  ).option(
5748
5767
  "--package-name <name>",
5749
5768
  "(flutter) Dart package name for generated pubspec.yaml (default: ui)"
@@ -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 heading slot's family and weights when\n * present, falling back to display, then body. Post-VI-355 the engine\n * resolves `--font-heading` from `typography.heading.family` directly;\n * earlier the docs adapter aliased it to `var(--font-sans)` (body),\n * which is why this used to read body first.\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.heading ?? manifest.display ?? manifest.body\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-14T19:58:07.656Z",
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.2",
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.8.0",
49
49
  "commander": "^13.1.0",
50
50
  "diff": "^8.0.4",
51
51
  "picocolors": "^1.1.1",