@loworbitstudio/visor 0.10.0 → 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-12T23:36:26.151Z",
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, validateFontCoverage } 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";
@@ -4239,9 +4244,7 @@ function themeSyncCommand(cwd, options) {
4239
4244
  const coverage = validateFontCoverage(css);
4240
4245
  if (coverage.errors.length > 0) {
4241
4246
  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
- );
4247
+ errors.push(formatFontCoverageError(basename3(filePath), e.declaredAt, e.family));
4245
4248
  }
4246
4249
  return;
4247
4250
  }
@@ -4921,8 +4924,8 @@ async function doctorCommand(cwd, options, cliVersion) {
4921
4924
  }
4922
4925
  const pkgJsonPath = path.join(cwd, "package.json");
4923
4926
  try {
4924
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
4925
- 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 ?? "";
4926
4929
  const versionNum = parseFloat(reactVersion.replace(/[^0-9.]/g, ""));
4927
4930
  if (!reactVersion) {
4928
4931
  checks.push({
@@ -5720,8 +5723,12 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
5720
5723
  }
5721
5724
 
5722
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
+ );
5723
5730
  var program = new Command2();
5724
- 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);
5725
5732
  program.addCommand(checkCommand());
5726
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) => {
5727
5734
  initCommand(process.cwd(), options);
@@ -5753,6 +5760,9 @@ theme.command("apply").description(
5753
5760
  ).option("--json", "output structured JSON (for AI agents)").option(
5754
5761
  "--adapter <name>",
5755
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"
5756
5766
  ).option(
5757
5767
  "--package-name <name>",
5758
5768
  "(flutter) Dart package name for generated pubspec.yaml (default: ui)"
@@ -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\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"
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-12T23:36:26.147Z",
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.10.0",
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.6.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",