@loworbitstudio/visor 1.6.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.4.0",
3
- "generated_at": "2026-06-11T14:53:30.668Z",
3
+ "generated_at": "2026-06-12T03:42:08.191Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "category": "specimen",
@@ -361,6 +361,9 @@
361
361
  "sm",
362
362
  "md",
363
363
  "lg"
364
+ ],
365
+ "case": [
366
+ "sentence"
364
367
  ]
365
368
  },
366
369
  "props": [
@@ -374,22 +377,34 @@
374
377
  "type": "\"sm\" | \"md\" | \"lg\"",
375
378
  "default": "\"md\"",
376
379
  "description": "Controls token-driven padding, font-size, gap, and embedded-icon scaling. `md` (the default) reproduces the original badge sizing exactly — existing call sites are unchanged. `sm` is tighter for dense inline data contexts; `lg` is larger for editorial contexts like page headers and stat-card status pills. Icons inside the badge scale with the size step.\n"
380
+ },
381
+ {
382
+ "name": "case",
383
+ "type": "\"sentence\"",
384
+ "description": "`case=\"sentence\"` opts a sm/md badge OUT of the editorial uppercase + letter-spacing treatment so prose-y labels (e.g. a plan name like \"Enterprise\") read as a label, not a tag. The `lg` size already opts out intrinsically. Omit for the default, theme-driven casing.\n"
385
+ },
386
+ {
387
+ "name": "iconOnly",
388
+ "type": "boolean",
389
+ "default": "false",
390
+ "description": "Render as a circular, fixed-square chip carrying a single centered glyph (e.g. a filled-success check in a roles matrix). Collapses padding/gap and forces a perfect circle. Opt-in; sets `data-icon-only` for styling hooks. Default badges are unaffected.\n"
377
391
  }
378
392
  ],
379
393
  "dependencies": [
380
394
  "class-variance-authority"
381
395
  ],
382
396
  "tokens_used": [
383
- "--badge-text-transform",
384
397
  "--border-default",
385
398
  "--border-focus",
386
399
  "--color-neutral-600",
387
400
  "--color-white",
401
+ "--destructive",
388
402
  "--focus-ring-offset",
389
403
  "--focus-ring-width",
390
404
  "--font-size-sm",
391
405
  "--font-size-xs",
392
406
  "--font-weight-medium",
407
+ "--info",
393
408
  "--interactive-primary-bg",
394
409
  "--interactive-primary-text",
395
410
  "--interactive-secondary-bg",
@@ -400,6 +415,8 @@
400
415
  "--spacing-1",
401
416
  "--spacing-2",
402
417
  "--spacing-3",
418
+ "--success",
419
+ "--surface-card",
403
420
  "--surface-error-default",
404
421
  "--surface-error-subtle",
405
422
  "--surface-info-default",
@@ -416,7 +433,8 @@
416
433
  "--text-primary",
417
434
  "--text-secondary",
418
435
  "--text-success",
419
- "--text-warning"
436
+ "--text-warning",
437
+ "--warning"
420
438
  ],
421
439
  "example": "<Badge variant=\"default\">Active</Badge>\n<Badge variant=\"secondary\">Secondary</Badge>\n<Badge variant=\"outline\">v2.1.0</Badge>\n<Badge variant=\"neutral\">Draft</Badge>\n<Badge variant=\"destructive\">Error</Badge>\n<Badge variant=\"success\">Published</Badge>\n<Badge variant=\"warning\">Pending</Badge>\n<Badge variant=\"info\">Beta</Badge>\n<Badge variant=\"filled-destructive\">Critical</Badge>\n\n{/* Sizes — sm for dense inline data, md (default) inline, lg for headers/pills */}\n<Badge size=\"sm\" variant=\"secondary\">v2.1.0</Badge>\n<Badge size=\"md\" variant=\"success\">Published</Badge>\n<Badge size=\"lg\" variant=\"info\">Beta</Badge>\n"
422
440
  },
@@ -945,6 +963,7 @@
945
963
  "--border-default",
946
964
  "--font-size-sm",
947
965
  "--font-weight-medium",
966
+ "--hairline",
948
967
  "--line-height-tight",
949
968
  "--motion-duration-moderate",
950
969
  "--motion-easing-standard",
@@ -953,8 +972,10 @@
953
972
  "--spacing-2",
954
973
  "--spacing-3",
955
974
  "--spacing-4",
975
+ "--spacing-5",
956
976
  "--surface-card",
957
- "--text-primary"
977
+ "--text-primary",
978
+ "--text-tertiary"
958
979
  ],
959
980
  "example": "<BulkActionBar\n count={selected.length}\n onClear={() => setSelected([])}\n>\n <Button variant=\"outline\" size=\"sm\">Archive</Button>\n <Button variant=\"destructive\" size=\"sm\">Delete</Button>\n</BulkActionBar>\n"
960
981
  },
@@ -1055,7 +1076,9 @@
1055
1076
  "--spacing-3",
1056
1077
  "--spacing-4",
1057
1078
  "--spacing-6",
1079
+ "--surface-card",
1058
1080
  "--surface-muted",
1081
+ "--surface-subtle",
1059
1082
  "--text-primary"
1060
1083
  ],
1061
1084
  "example": "<Button variant=\"default\" size=\"md\">Save changes</Button>\n\n<Button variant=\"destructive\">Delete account</Button>\n\n<Button variant=\"ghost\" size=\"sm\">Cancel</Button>\n\n{/* Gated — no reason (tooltip suppressed) */}\n<Button gated>Delete organization</Button>\n\n{/* Gated with reason — tooltip shown on hover/focus */}\n<TooltipProvider>\n <Button gated gatedReason=\"You can't delete this — you're not an owner\">\n Delete organization\n </Button>\n</TooltipProvider>\n"
@@ -1266,6 +1289,93 @@
1266
1289
  ],
1267
1290
  "example": "<Carousel>\n <CarouselContent>\n <CarouselItem>Slide 1</CarouselItem>\n <CarouselItem>Slide 2</CarouselItem>\n <CarouselItem>Slide 3</CarouselItem>\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n</Carousel>\n"
1268
1291
  },
1292
+ "challenge-card": {
1293
+ "category": "feedback",
1294
+ "description": "Adversarial challenge message card — an AI pushes back on the user's input and the human holds the gate. Distinct from Alert: ChallengeCard is for messages that require an explicit human decision, not passive notices.\n",
1295
+ "when_to_use": [
1296
+ "Surfacing an AI-generated challenge or push-back that the user must actively accept or override",
1297
+ "Presenting an adversarial review result (e.g. \"Is it actually only?\") where a human must decide the next action",
1298
+ "Any context where the system is flagging user input as potentially wrong and a conscious human gate is required before proceeding",
1299
+ "Chat or workflow UIs where the AI acts as a strategic antagonist and the human must confirm their intent"
1300
+ ],
1301
+ "when_not_to_use": [
1302
+ "Use `alert` for passive notices — ChallengeCard is for adversarial prompts requiring an explicit human decision",
1303
+ "Use Banner for persistent, page-level system notices that don't require an immediate response",
1304
+ "Use Toast for transient feedback that auto-dismisses — ChallengeCard is persistent until the user acts"
1305
+ ],
1306
+ "variants": {
1307
+ "action_variant": [
1308
+ "primary",
1309
+ "ghost"
1310
+ ]
1311
+ },
1312
+ "props": [
1313
+ {
1314
+ "name": "icon",
1315
+ "type": "React.ReactNode | null",
1316
+ "description": "Override the default Flag icon in ChallengeCardHeader. Pass null to suppress the icon entirely.\n"
1317
+ },
1318
+ {
1319
+ "name": "variant",
1320
+ "type": "\"primary\" | \"ghost\"",
1321
+ "default": "\"primary\"",
1322
+ "description": "Action button visual style. primary = filled warning-toned; ghost = transparent with border.\n",
1323
+ "component": "ChallengeCardAction"
1324
+ },
1325
+ {
1326
+ "name": "icon",
1327
+ "type": "React.ReactNode | null",
1328
+ "description": "Optional icon for ChallengeCardAction. Primary defaults to a Check icon; ghost has no default icon.\n",
1329
+ "component": "ChallengeCardAction"
1330
+ },
1331
+ {
1332
+ "name": "onClick",
1333
+ "type": "React.MouseEventHandler<HTMLButtonElement>",
1334
+ "description": "Click handler for the action button.",
1335
+ "component": "ChallengeCardAction"
1336
+ },
1337
+ {
1338
+ "name": "children",
1339
+ "type": "React.ReactNode",
1340
+ "description": "Gate label text. Defaults to \"You hold the gate\" when no children are provided.\n",
1341
+ "component": "ChallengeCardGate"
1342
+ }
1343
+ ],
1344
+ "dependencies": [
1345
+ "@phosphor-icons/react",
1346
+ "@loworbitstudio/visor-core"
1347
+ ],
1348
+ "tokens_used": [
1349
+ "--border-default",
1350
+ "--border-warning",
1351
+ "--challenge-card-action-primary-text",
1352
+ "--color-warning-900",
1353
+ "--focus-ring-offset",
1354
+ "--focus-ring-width",
1355
+ "--motion-duration-150",
1356
+ "--motion-easing-default",
1357
+ "--opacity-40",
1358
+ "--opacity-80",
1359
+ "--radius-lg",
1360
+ "--radius-xl",
1361
+ "--spacing-1",
1362
+ "--spacing-1-5",
1363
+ "--spacing-2",
1364
+ "--spacing-2-5",
1365
+ "--spacing-3",
1366
+ "--spacing-3-5",
1367
+ "--spacing-4",
1368
+ "--stroke-width-thin",
1369
+ "--surface-card",
1370
+ "--surface-warning-subtle",
1371
+ "--text-primary",
1372
+ "--text-secondary",
1373
+ "--text-tertiary",
1374
+ "--text-warning",
1375
+ "--warning"
1376
+ ],
1377
+ "example": "<ChallengeCard>\n <ChallengeCardHeader>Is it actually only?</ChallengeCardHeader>\n <ChallengeCardBody>\n The word \"only\" in this claim is absolute — it invites factual rebuttal.\n Do you have data to back it up, or would a sharper qualifier work better?\n </ChallengeCardBody>\n <ChallengeCardActions>\n <ChallengeCardAction variant=\"primary\" onClick={() => {}}>\n Use the sharper version\n </ChallengeCardAction>\n <ChallengeCardAction variant=\"ghost\" onClick={() => {}}>\n I'll rewrite it\n </ChallengeCardAction>\n <ChallengeCardGate />\n </ChallengeCardActions>\n</ChallengeCard>\n"
1378
+ },
1269
1379
  "chart": {
1270
1380
  "category": "data-display",
1271
1381
  "description": "A Recharts wrapper providing themed chart container, tooltip, and legend components.",
@@ -1409,19 +1519,17 @@
1409
1519
  "--border-error",
1410
1520
  "--border-focus",
1411
1521
  "--border-strong",
1412
- "--checkbox-bg",
1413
1522
  "--checkbox-bg-checked",
1414
- "--checkbox-bg-hover",
1415
- "--checkbox-border",
1416
1523
  "--checkbox-border-checked",
1417
- "--checkbox-border-hover",
1418
1524
  "--focus-ring-width",
1525
+ "--hairline-strong",
1419
1526
  "--interactive-primary-bg",
1420
1527
  "--interactive-primary-text",
1421
1528
  "--motion-duration-150",
1422
1529
  "--motion-easing-default",
1423
1530
  "--primary",
1424
- "--radius-sm"
1531
+ "--radius-sm",
1532
+ "--surface-subtle"
1425
1533
  ],
1426
1534
  "example": "<div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.5rem\" }}>\n <Checkbox id=\"terms\" />\n <Label htmlFor=\"terms\">Accept terms and conditions</Label>\n</div>\n"
1427
1535
  },
@@ -1553,6 +1661,12 @@
1553
1661
  "type": "\"accent\" | \"neutral\"",
1554
1662
  "default": "\"accent\"",
1555
1663
  "description": "Selected-state visual treatment. \"accent\" (default) uses accent-tinted bg + accent border + link text. \"neutral\" uses --surface-card bg + neutral border + primary text; the count pill renders as a solid mint pill (--surface-success-default) when selected. Use in editorial/admin contexts where accent bleed is undesirable.\n"
1664
+ },
1665
+ {
1666
+ "name": "trailingIcon",
1667
+ "type": "React.ReactNode",
1668
+ "optional": true,
1669
+ "description": "Muted trailing icon (e.g. a caret) rendered after the label/count for \"filter-as-dropdown\" chips. Orthogonal to count/selectedTreatment. Hidden from assistive tech (aria-hidden). Default render is unchanged when omitted.\n"
1556
1670
  }
1557
1671
  ]
1558
1672
  }
@@ -1572,9 +1686,12 @@
1572
1686
  "--font-size-sm",
1573
1687
  "--font-size-xs",
1574
1688
  "--font-weight-medium",
1689
+ "--font-weight-semibold",
1575
1690
  "--motion-duration-150",
1576
1691
  "--motion-easing-default",
1577
1692
  "--opacity-40",
1693
+ "--primary",
1694
+ "--primary-text",
1578
1695
  "--radius-full",
1579
1696
  "--spacing-1",
1580
1697
  "--spacing-2",
@@ -1584,13 +1701,15 @@
1584
1701
  "--surface-accent-default",
1585
1702
  "--surface-accent-subtle",
1586
1703
  "--surface-card",
1704
+ "--surface-elev",
1587
1705
  "--surface-interactive-hover",
1588
1706
  "--surface-muted",
1589
1707
  "--surface-success-default",
1590
1708
  "--text-inverse",
1591
1709
  "--text-link",
1592
1710
  "--text-primary",
1593
- "--text-secondary"
1711
+ "--text-secondary",
1712
+ "--text-tertiary"
1594
1713
  ],
1595
1714
  "example": "<Chip label=\"React\" />\n<Chip label=\"Featured\" onDeleted={() => removeTag(\"featured\")} />\n<ChoiceChip label=\"Compact\" selected={density === \"compact\"} onPressed={() => setDensity(\"compact\")} />\n<FilterChip label=\"Events\" selected={filters.includes(\"events\")} onPressed={() => toggleFilter(\"events\")} />\n<FilterChip label=\"All\" count={132} onPressed={() => setFilter(\"all\")} />\n<FilterChip label=\"Active\" count={47} countTone=\"primary\" selected onPressed={() => setFilter(\"active\")} />\n<FilterChip label=\"Suspended\" count={8} onPressed={() => setFilter(\"suspended\")} />\n"
1596
1715
  },
@@ -2232,9 +2351,82 @@
2232
2351
  ],
2233
2352
  "example": "<CommandDialog open={open} onOpenChange={setOpen}>\n <CommandInput placeholder=\"Type a command...\" />\n <CommandList>\n <CommandEmpty>No results found.</CommandEmpty>\n <CommandGroup heading=\"Actions\">\n <CommandItem>\n New File\n <CommandShortcut>⌘N</CommandShortcut>\n </CommandItem>\n <CommandItem>Search</CommandItem>\n </CommandGroup>\n </CommandList>\n</CommandDialog>\n"
2234
2353
  },
2354
+ "composer": {
2355
+ "category": "form",
2356
+ "description": "AI-chat composer — a rounded card container with an auto-growing text field, a tools row for icon buttons and status chips, and a circular primary send button.",
2357
+ "when_to_use": [
2358
+ "Building an AI chat or assistant input area where users type multi-line messages",
2359
+ "Any composer-style input that needs contextual tool buttons (attachment, mic, model picker) alongside a submit affordance",
2360
+ "Interfaces where the field must grow with content rather than scroll within a fixed height",
2361
+ "Placing a branded status chip (e.g. \"Claude · key active\") inside the toolbar alongside action buttons"
2362
+ ],
2363
+ "when_not_to_use": [
2364
+ "Plain multi-line text entry without action affordances — use textarea instead",
2365
+ "Single-line search or filter inputs — use search-input or input",
2366
+ "Form fields that need validation, labels, or fieldset grouping — use field + textarea",
2367
+ "Full-page editor surfaces where a document metaphor is more appropriate"
2368
+ ],
2369
+ "variants": {},
2370
+ "props": [
2371
+ {
2372
+ "name": "value",
2373
+ "type": "string",
2374
+ "description": "Controlled field value."
2375
+ },
2376
+ {
2377
+ "name": "onValueChange",
2378
+ "type": "(value: string) => void",
2379
+ "description": "Called when the value changes (controlled mode)."
2380
+ },
2381
+ {
2382
+ "name": "onSubmit",
2383
+ "type": "(value: string) => void",
2384
+ "description": "Called when the user submits (Enter key or send button)."
2385
+ },
2386
+ {
2387
+ "name": "disabled",
2388
+ "type": "boolean",
2389
+ "default": "false",
2390
+ "description": "Disables all interactive children."
2391
+ }
2392
+ ],
2393
+ "dependencies": [
2394
+ "@phosphor-icons/react",
2395
+ "@loworbitstudio/visor-core"
2396
+ ],
2397
+ "tokens_used": [
2398
+ "--border-default",
2399
+ "--border-strong",
2400
+ "--focus-ring-offset",
2401
+ "--focus-ring-width",
2402
+ "--font-size-base",
2403
+ "--line-height-normal",
2404
+ "--motion-duration-150",
2405
+ "--motion-easing-default",
2406
+ "--opacity-40",
2407
+ "--opacity-50",
2408
+ "--primary",
2409
+ "--primary-foreground",
2410
+ "--radius-2xl",
2411
+ "--radius-full",
2412
+ "--radius-sm",
2413
+ "--shadow-md",
2414
+ "--shadow-sm",
2415
+ "--spacing-2",
2416
+ "--spacing-3",
2417
+ "--spacing-4",
2418
+ "--stroke-width-thin",
2419
+ "--surface-card",
2420
+ "--surface-hover",
2421
+ "--text-primary",
2422
+ "--text-secondary",
2423
+ "--text-tertiary"
2424
+ ],
2425
+ "example": "<Composer onSubmit={(v) => console.log(v)}>\n <ComposerField placeholder=\"Ask anything…\" />\n <ComposerToolbar>\n <ComposerToolButton icon={<Paperclip size={16} />} aria-label=\"Attach file\" />\n <ComposerToolButton icon={<Microphone size={16} />} aria-label=\"Voice input\" />\n <Chip size=\"sm\" leadingIcon={<StatusDot tone=\"mint\" />} label=\"Claude · key active\" />\n <ComposerSpacer />\n <ComposerSend />\n </ComposerToolbar>\n</Composer>\n"
2426
+ },
2235
2427
  "confirm-dialog": {
2236
2428
  "category": "admin",
2237
- "description": "Admin confirmation dialog compound wrapping Dialog with severity-driven icon/color/button variant, optional confirm-text gate, and async-aware confirm handler.",
2429
+ "description": "Admin confirmation dialog compound wrapping Dialog with severity-driven icon/color/button variant, an editorial icon-treatment + render-mode API, optional confirm-text gate, and async-aware confirm handler.",
2238
2430
  "when_to_use": [
2239
2431
  "Confirming a destructive action like delete, revoke, or purge",
2240
2432
  "Gating a high-stakes action with a typed confirmation phrase",
@@ -2255,6 +2447,14 @@
2255
2447
  ],
2256
2448
  "severity_deprecated": [
2257
2449
  "danger"
2450
+ ],
2451
+ "iconTreatment": [
2452
+ "inline",
2453
+ "plated"
2454
+ ],
2455
+ "mode": [
2456
+ "dialog",
2457
+ "inline"
2258
2458
  ]
2259
2459
  },
2260
2460
  "props": [
@@ -2280,6 +2480,28 @@
2280
2480
  "default": "warning",
2281
2481
  "description": "Drives icon, tinted icon plate, and confirm button variant. Use \"info\", \"warning\", or \"destructive\". The value \"danger\" is a deprecated alias for \"destructive\" and will be removed in the next major version.\n"
2282
2482
  },
2483
+ {
2484
+ "name": "icon",
2485
+ "type": "React.ReactNode",
2486
+ "description": "Custom severity icon — overrides the built-in icon for the active severity. Sizing/color is handled by the icon container, so pass a bare Phosphor icon (no explicit size).\n"
2487
+ },
2488
+ {
2489
+ "name": "iconTreatment",
2490
+ "type": "'inline' | 'plated'",
2491
+ "description": "Severity-icon treatment. Leave unset for the canonical default — a tinted circular plate stacked above the title. Set \"plated\" for a 40px color-mix severity plate leading the title column, or \"inline\" for a small leading icon next to the title. Default rendering (unset) is unchanged.\n"
2492
+ },
2493
+ {
2494
+ "name": "mode",
2495
+ "type": "'dialog' | 'inline'",
2496
+ "default": "dialog",
2497
+ "description": "Rendering mode. \"dialog\" uses Radix Dialog + Portal + Overlay + auto X-close. \"inline\" renders only the content surface (no portal, no overlay, no X-close) so multiple dialogs can stack inside one shared scrim.\n"
2498
+ },
2499
+ {
2500
+ "name": "cancelVariant",
2501
+ "type": "ButtonProps[\"variant\"]",
2502
+ "default": "outline",
2503
+ "description": "Cancel button variant. Golden editorial screens use \"ghost\"."
2504
+ },
2283
2505
  {
2284
2506
  "name": "trigger",
2285
2507
  "type": "React.ReactNode",
@@ -2342,17 +2564,29 @@
2342
2564
  "@phosphor-icons/react"
2343
2565
  ],
2344
2566
  "tokens_used": [
2567
+ "--accent",
2345
2568
  "--border-default",
2346
2569
  "--border-focus",
2570
+ "--confirm-dialog-plate-icon-size",
2571
+ "--confirm-dialog-plate-size",
2572
+ "--destructive",
2347
2573
  "--focus-ring-offset",
2348
2574
  "--focus-ring-width",
2575
+ "--font-size-2xl",
2576
+ "--font-size-lg",
2349
2577
  "--font-size-sm",
2350
2578
  "--font-weight-medium",
2579
+ "--font-weight-semibold",
2351
2580
  "--line-height-normal",
2352
2581
  "--line-height-relaxed",
2353
2582
  "--motion-duration-150",
2354
2583
  "--motion-easing-default",
2584
+ "--radius-full",
2585
+ "--radius-lg",
2355
2586
  "--radius-md",
2587
+ "--radius-xl",
2588
+ "--shadow-lg",
2589
+ "--shadow-xl",
2356
2590
  "--spacing-1",
2357
2591
  "--spacing-10",
2358
2592
  "--spacing-2",
@@ -2360,6 +2594,8 @@
2360
2594
  "--spacing-4",
2361
2595
  "--spacing-5",
2362
2596
  "--spacing-6",
2597
+ "--spacing-8",
2598
+ "--surface-card",
2363
2599
  "--surface-error-subtle",
2364
2600
  "--surface-info-subtle",
2365
2601
  "--surface-page",
@@ -2368,7 +2604,9 @@
2368
2604
  "--text-info",
2369
2605
  "--text-primary",
2370
2606
  "--text-secondary",
2371
- "--text-warning"
2607
+ "--text-tertiary",
2608
+ "--text-warning",
2609
+ "--warning"
2372
2610
  ],
2373
2611
  "example": "<ConfirmDialog\n trigger={<Button variant=\"destructive\">Delete project</Button>}\n severity=\"destructive\"\n title=\"Delete project?\"\n description=\"This action cannot be undone. All associated data will be permanently removed.\"\n confirmText=\"acme\"\n onConfirm={async () => {\n await deleteProject()\n }}\n/>\n"
2374
2612
  },
@@ -2650,6 +2888,11 @@
2650
2888
  "type": "React.ReactNode",
2651
2889
  "description": "Override for the default EmptyState slot rendered when data is empty."
2652
2890
  },
2891
+ {
2892
+ "name": "loadingSkeletonCell",
2893
+ "type": "(args: { rowIndex: number; colIndex: number; columnId: string }) => React.ReactNode",
2894
+ "description": "Opt-in per-column skeleton shapes for the loading state. When supplied, each skeleton row's cell contents come from this render prop instead of the default uniform full-width bars, letting the caller mirror the real row's silhouette (logo plates, badge pills, two-line id stacks). Returns the cell contents; DataTable still owns the `tr`/`td` structure, key, and `data-slot`. `colIndex` walks the resolved column list (the injected selection column, when present, is index 0). Return `null`/`undefined` to leave a cell empty. Default (prop omitted) is unchanged: every cell renders a full-width Skeleton bar. Pair with the Skeleton shape classes (`shapeLogo`, `shapePill`, `shapeCircle`).\n"
2895
+ },
2653
2896
  {
2654
2897
  "name": "stickyHeader",
2655
2898
  "type": "boolean",
@@ -2660,7 +2903,7 @@
2660
2903
  "name": "density",
2661
2904
  "type": "\"compact\" | \"default\" | \"editorial\"",
2662
2905
  "default": "\"default\"",
2663
- "description": "Vertical row padding step. Maps to a `data-density` attribute on the root, which sets the `--dt-row-py` custom property the table's cells consume. `\"compact\"` = 8px (sub-content density: long lists, narrow viewports). `\"default\"` = 12px (current behaviour; backward-compatible with consumers that don't pass the prop). `\"editorial\"` = 20px (generous; each row reads as a card; high-design admin patterns) — column headers also receive the editorial treatment: uppercase, `--font-size-xs` (~11px), `--text-tertiary` color, and `letter-spacing: 0.08em`. Themes can override per-density values without forking the component by targeting `[data-density=\"…\"]` from their own selector.\n"
2906
+ "description": "Vertical row padding step. Maps to a `data-density` attribute on the root, which sets the `--dt-row-py` custom property the table's cells consume. `\"compact\"` = 8px (sub-content density: long lists, narrow viewports). `\"default\"` = 12px (current behaviour; backward-compatible with consumers that don't pass the prop). `\"editorial\"` = 16px (spacing-4; generous but compact enough for dense admin grids) — column headers also receive the editorial treatment: uppercase, 11px (overridable via `--dt-header-font-size`), `--text-tertiary` color, and `letter-spacing: 0.08em`; the editorial surface treatment (flush container, card/page row mix) is also applied. Themes can override per-density values without forking the component by targeting `[data-density=\"…\"]` from their own selector.\n"
2664
2907
  },
2665
2908
  {
2666
2909
  "name": "rowTone",
@@ -2682,6 +2925,12 @@
2682
2925
  "--border-default",
2683
2926
  "--border-strong",
2684
2927
  "--data-table-sort-bar-radius",
2928
+ "--dt-cell-px",
2929
+ "--dt-container-radius",
2930
+ "--dt-container-shadow",
2931
+ "--dt-header-bg",
2932
+ "--dt-header-font-size",
2933
+ "--dt-row-bg",
2685
2934
  "--dt-row-py",
2686
2935
  "--focus-ring-color",
2687
2936
  "--focus-ring-offset",
@@ -2708,6 +2957,7 @@
2708
2957
  "--surface-info-subtle",
2709
2958
  "--surface-interactive-active",
2710
2959
  "--surface-interactive-default",
2960
+ "--surface-page",
2711
2961
  "--surface-selected",
2712
2962
  "--surface-subtle",
2713
2963
  "--surface-success-subtle",
@@ -2921,6 +3171,10 @@
2921
3171
  "name": "DialogHeader",
2922
3172
  "description": "Container for title and description at the top."
2923
3173
  },
3174
+ {
3175
+ "name": "DialogFooter",
3176
+ "description": "Right-aligned action row with a hairline top border, for dialog buttons."
3177
+ },
2924
3178
  {
2925
3179
  "name": "DialogTitle",
2926
3180
  "description": "Accessible dialog heading."
@@ -2948,11 +3202,16 @@
2948
3202
  ],
2949
3203
  "tokens_used": [
2950
3204
  "--border-focus",
3205
+ "--border-subtle",
2951
3206
  "--focus-ring-offset",
2952
3207
  "--focus-ring-width",
3208
+ "--font-size-2xl",
2953
3209
  "--font-size-lg",
2954
3210
  "--font-size-sm",
3211
+ "--font-weight-bold",
2955
3212
  "--font-weight-semibold",
3213
+ "--hairline",
3214
+ "--hairline-width",
2956
3215
  "--motion-duration-150",
2957
3216
  "--motion-duration-normal",
2958
3217
  "--motion-easing-default",
@@ -2961,13 +3220,20 @@
2961
3220
  "--overlay-bg",
2962
3221
  "--radius-lg",
2963
3222
  "--radius-sm",
3223
+ "--radius-xl",
3224
+ "--shadow-lg",
2964
3225
  "--shadow-xl",
2965
3226
  "--spacing-1",
3227
+ "--spacing-2",
3228
+ "--spacing-3",
2966
3229
  "--spacing-4",
2967
3230
  "--spacing-6",
3231
+ "--spacing-8",
3232
+ "--surface-card",
2968
3233
  "--surface-page",
2969
3234
  "--text-primary",
2970
- "--text-secondary"
3235
+ "--text-secondary",
3236
+ "--text-tertiary"
2971
3237
  ],
2972
3238
  "example": "<Dialog>\n <DialogTrigger asChild>\n <Button>Edit profile</Button>\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Edit profile</DialogTitle>\n <DialogDescription>Make changes to your profile here.</DialogDescription>\n </DialogHeader>\n {/* form content */}\n </DialogContent>\n</Dialog>\n"
2973
3239
  },
@@ -3074,27 +3340,37 @@
3074
3340
  ],
3075
3341
  "tokens_used": [
3076
3342
  "--border-default",
3343
+ "--border-subtle",
3344
+ "--destructive",
3077
3345
  "--font-size-sm",
3078
3346
  "--font-size-xs",
3347
+ "--hairline",
3079
3348
  "--motion-duration-fast",
3080
3349
  "--motion-easing-default",
3081
3350
  "--motion-easing-enter",
3082
3351
  "--motion-easing-exit",
3083
3352
  "--radius-lg",
3084
3353
  "--radius-md",
3354
+ "--radius-sm",
3085
3355
  "--shadow-lg",
3086
3356
  "--shadow-xl",
3087
3357
  "--spacing-1",
3088
3358
  "--spacing-2",
3089
3359
  "--spacing-3",
3090
3360
  "--spacing-8",
3361
+ "--stroke-width-thin",
3091
3362
  "--surface-card",
3363
+ "--surface-elev",
3092
3364
  "--surface-error-subtle",
3093
3365
  "--surface-interactive-hover",
3366
+ "--surface-muted",
3094
3367
  "--surface-popover",
3368
+ "--surface-subtle",
3095
3369
  "--text-error",
3370
+ "--text-muted",
3096
3371
  "--text-primary",
3097
- "--text-secondary"
3372
+ "--text-secondary",
3373
+ "--text-tertiary"
3098
3374
  ],
3099
3375
  "example": "<DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\">Actions</Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuItem>Edit</DropdownMenuItem>\n <DropdownMenuItem>Duplicate</DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem variant=\"destructive\">Delete</DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>\n"
3100
3376
  },
@@ -3154,6 +3430,10 @@
3154
3430
  "tone": [
3155
3431
  "default",
3156
3432
  "subtle"
3433
+ ],
3434
+ "variant": [
3435
+ "default",
3436
+ "editorial"
3157
3437
  ]
3158
3438
  },
3159
3439
  "props": [
@@ -3195,6 +3475,12 @@
3195
3475
  "default": "default",
3196
3476
  "description": "Default uses a dashed border on a muted surface. Subtle is borderless."
3197
3477
  },
3478
+ {
3479
+ "name": "variant",
3480
+ "type": "'default' | 'editorial'",
3481
+ "default": "default",
3482
+ "description": "Surface treatment. Default is the base placeholder (dashed/borderless per tone). Editorial is an opt-in filled card on --surface-card with a 64px circular icon chip and a larger heading; it overrides the tone surface and the size icon/heading scale.\n"
3483
+ },
3198
3484
  {
3199
3485
  "name": "headingAs",
3200
3486
  "type": "'h2' | 'h3' | 'h4'",
@@ -3219,16 +3505,20 @@
3219
3505
  "--letter-spacing-tight",
3220
3506
  "--line-height-relaxed",
3221
3507
  "--line-height-tight",
3508
+ "--radius-full",
3222
3509
  "--radius-lg",
3223
3510
  "--spacing-1",
3224
3511
  "--spacing-12",
3512
+ "--spacing-16",
3225
3513
  "--spacing-2",
3226
3514
  "--spacing-3",
3227
3515
  "--spacing-4",
3228
3516
  "--spacing-5",
3229
3517
  "--spacing-6",
3230
3518
  "--spacing-8",
3519
+ "--surface-card",
3231
3520
  "--surface-muted",
3521
+ "--surface-subtle",
3232
3522
  "--text-primary",
3233
3523
  "--text-secondary",
3234
3524
  "--text-tertiary"
@@ -3273,7 +3563,7 @@
3273
3563
  },
3274
3564
  {
3275
3565
  "name": "FieldError",
3276
- "description": "Error message display with role=\"alert\". Accepts an errors array or children."
3566
+ "description": "Error message display with role=\"alert\". Accepts an errors array or children. Optional `icon` prop renders an aria-hidden leading glyph and switches to a centered icon + message row; without it the error renders as plain inline text (default, unchanged). Typography follows the density axis: place data-density=\"editorial\" on any ancestor to activate the 13px/11px discrete ramp and tertiary color treatment.\n"
3277
3567
  }
3278
3568
  ],
3279
3569
  "dependencies": [
@@ -3289,7 +3579,8 @@
3289
3579
  "--spacing-4",
3290
3580
  "--text-error",
3291
3581
  "--text-primary",
3292
- "--text-secondary"
3582
+ "--text-secondary",
3583
+ "--text-tertiary"
3293
3584
  ],
3294
3585
  "example": "<Field>\n <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n <FieldDescription>We will never share your email.</FieldDescription>\n <Input id=\"email\" type=\"email\" />\n <FieldError errors={[{ message: \"Email is required\" }]} />\n</Field>\n"
3295
3586
  },
@@ -4187,6 +4478,7 @@
4187
4478
  "--border-error",
4188
4479
  "--border-focus",
4189
4480
  "--border-strong",
4481
+ "--field-control-bg",
4190
4482
  "--focus-ring-offset",
4191
4483
  "--focus-ring-width",
4192
4484
  "--font-size-base",
@@ -4194,8 +4486,17 @@
4194
4486
  "--input-bg",
4195
4487
  "--input-bg-invalid",
4196
4488
  "--input-border",
4489
+ "--input-height-lg",
4490
+ "--input-height-md",
4491
+ "--input-padding-lg",
4492
+ "--input-padding-md",
4493
+ "--input-padding-sm",
4494
+ "--input-placeholder-color",
4495
+ "--input-radius-lg",
4496
+ "--input-radius-md",
4197
4497
  "--motion-duration-150",
4198
4498
  "--motion-easing-default",
4499
+ "--radius-md",
4199
4500
  "--radius-sm",
4200
4501
  "--spacing-1",
4201
4502
  "--spacing-3",
@@ -4203,8 +4504,10 @@
4203
4504
  "--spacing-4",
4204
4505
  "--spacing-4_5",
4205
4506
  "--spacing-5",
4507
+ "--surface-card",
4206
4508
  "--surface-error-subtle",
4207
4509
  "--surface-interactive-default",
4510
+ "--surface-subtle",
4208
4511
  "--text-primary",
4209
4512
  "--text-secondary"
4210
4513
  ],
@@ -4544,10 +4847,11 @@
4544
4847
  },
4545
4848
  "matrix-table": {
4546
4849
  "category": "admin",
4547
- "description": "A fixed assignment grid component for members×roles patterns. Sticky-left identity column, centered boolean cells (22px filled-success check when active / muted empty when not), and multi-line centered column headers (label over count) that opt out of the uppercase header treatment. No list machinery — no selection column, no sort buttons, no pagination footer.",
4850
+ "description": "A fixed members×roles assignment grid. Sticky-left identity column with an uppercase, tracked \"MEMBER\" eyebrow header; centered boolean cells rendered as a 22px filled-success disc with a check glyph (active) or a muted dash (not assigned); optional string cells render as plain text; multi-line centered role-column headers (label over count) that opt out of the uppercase header treatment; an editorial cell rhythm (spacing-5 horizontal) and hairline borders driven by the shared --dt-* table hooks (--dt-container-radius/-shadow, --dt-header-bg, --dt-row-bg, --hairline). Participates in the density axis — placing an ancestor with data-density=\"editorial\" activates the editorial surface treatment (flush container, page/card row mix). No list machinery — no selection column, no sort buttons, no pagination footer.",
4548
4851
  "when_to_use": [
4549
4852
  "Displaying a members × roles assignment matrix in organization settings",
4550
- "Any fixed grid where rows are identities (users, items) and columns are boolean attributes (permissions, roles, features)",
4853
+ "A plans × features monetization pricing matrix (string + boolean cells)",
4854
+ "Any fixed grid where rows are identities (users, plans) and columns are attributes (permissions, roles, features), rendered as a sibling of the editorial data-table",
4551
4855
  "When the shape is a matrix, not a sortable/pageable list — where DataTable's list machinery would fight the layout"
4552
4856
  ],
4553
4857
  "when_not_to_use": [
@@ -4561,19 +4865,24 @@
4561
4865
  "name": "columns",
4562
4866
  "type": "MatrixColumn[]",
4563
4867
  "required": true,
4564
- "description": "Column definitions. Each column has an `id`, `label`, and optional `count` sub-label rendered below the label in the header."
4868
+ "description": "Column (role/feature) definitions. Each column carries a `key` (stable identifier indexing the per-row cell map; `id` is accepted as a compatible alias), a `label`, an optional `count` sub-label rendered below the label, and an optional `description` shown as a tooltip when hovering an active cell in that column.\n"
4565
4869
  },
4566
4870
  {
4567
4871
  "name": "rows",
4568
4872
  "type": "MatrixRow<TIdentity>[]",
4569
4873
  "required": true,
4570
- "description": "Row data. Each row has an `id`, an `identity` value passed to `renderIdentity`, an `activeColumns` set/array of column ids that should show the active check indicator, and an optional `cells` map of per-column values. A `cells` entry (`string | boolean`) overrides `activeColumns` for that column — `true`/`false` render the boolean indicator, a `string` renders as plain text in the standard cell style."
4874
+ "description": "Row data. Each row has an `id` (React key), an optional `identity` payload passed through to `renderIdentity` via the row, and its truth values. Truth is supplied via `cells` — a map keyed by column key whose values are `string | boolean` (`true`/`false` render the boolean indicator, a `string` renders as plain text). `activeColumns` (a Set/array of column keys) is accepted as a compatible alias for the boolean half of `cells`; a `cells` entry takes precedence for the same column.\n"
4571
4875
  },
4572
4876
  {
4573
4877
  "name": "renderIdentity",
4574
- "type": "(identity: TIdentity) => React.ReactNode",
4878
+ "type": "(row: MatrixRow<TIdentity>) => React.ReactNode",
4575
4879
  "required": true,
4576
- "description": "Render slot for the sticky-left identity cell. Receives the row's `identity` valuetypically renders a name, avatar+name, or any identity compound."
4880
+ "description": "Render slot for the sticky-left identity cell. Receives the whole row (blessed shape)read `row.identity` for structured identity data."
4881
+ },
4882
+ {
4883
+ "name": "identityLabel",
4884
+ "type": "React.ReactNode",
4885
+ "description": "Header label for the sticky identity column. Defaults to \"Member\" (rendered with the uppercase, tracked eyebrow treatment)."
4577
4886
  },
4578
4887
  {
4579
4888
  "name": "aria-label",
@@ -4586,28 +4895,39 @@
4586
4895
  "@loworbitstudio/visor-core"
4587
4896
  ],
4588
4897
  "tokens_used": [
4589
- "--border-default",
4898
+ "--dt-container-radius",
4899
+ "--dt-container-shadow",
4900
+ "--dt-header-bg",
4901
+ "--dt-row-bg",
4902
+ "--font-size-base",
4590
4903
  "--font-size-sm",
4591
4904
  "--font-weight-medium",
4592
- "--font-weight-normal",
4593
- "--line-height-tight",
4594
- "--motion-duration-fast",
4595
- "--motion-easing-standard",
4905
+ "--font-weight-semibold",
4906
+ "--hairline",
4907
+ "--hairline-width",
4908
+ "--motion-duration-100",
4909
+ "--motion-easing-default",
4910
+ "--primary-text",
4596
4911
  "--radius-full",
4597
4912
  "--radius-lg",
4913
+ "--radius-sm",
4914
+ "--shadow-md",
4598
4915
  "--shadow-sm",
4599
4916
  "--spacing-1",
4600
4917
  "--spacing-2",
4601
4918
  "--spacing-3",
4602
- "--stroke-width-thin",
4919
+ "--spacing-4",
4920
+ "--spacing-5",
4921
+ "--success",
4603
4922
  "--surface-card",
4604
- "--surface-muted",
4605
- "--surface-success-default",
4606
- "--text-inverse",
4923
+ "--surface-elev",
4924
+ "--surface-page",
4925
+ "--surface-subtle",
4926
+ "--text-muted",
4607
4927
  "--text-primary",
4608
4928
  "--text-tertiary"
4609
4929
  ],
4610
- "example": "const columns: MatrixColumn[] = [\n { id: 'admin', label: 'Admin', count: 3 },\n { id: 'editor', label: 'Editor', count: 5 },\n { id: 'viewer', label: 'Viewer', count: 12 },\n]\n\nconst rows: MatrixRow<{ name: string; email: string }>[] = [\n { id: '1', identity: { name: 'Ada Lovelace', email: 'ada@example.com' }, activeColumns: new Set(['admin', 'editor']) },\n { id: '2', identity: { name: 'Grace Hopper', email: 'grace@example.com' }, activeColumns: ['viewer'] },\n // Mixed string + boolean cells via `cells` (string renders as plain text):\n { id: '3', identity: { name: 'Storage', email: '' }, activeColumns: [], cells: { admin: '50GB', editor: true, viewer: false } },\n]\n\n<MatrixTable\n columns={columns}\n rows={rows}\n renderIdentity={(identity) => <span>{identity.name}</span>}\n aria-label=\"Member role assignments\"\n/>\n"
4930
+ "example": "const columns: MatrixColumn[] = [\n { key: 'admin', label: 'Admin', count: 3 },\n { key: 'editor', label: 'Editor', count: 5, description: 'Can edit content' },\n { key: 'viewer', label: 'Viewer', count: 12 },\n]\n\nconst rows: MatrixRow<{ name: string; email: string }>[] = [\n { id: '1', identity: { name: 'Ada Lovelace', email: 'ada@example.com' }, cells: { admin: true, editor: true } },\n { id: '2', identity: { name: 'Grace Hopper', email: 'grace@example.com' }, cells: { viewer: true } },\n // Mixed string + boolean cells (string renders as plain text):\n { id: '3', identity: { name: 'Storage', email: '' }, cells: { admin: '50GB', editor: true, viewer: false } },\n // Compatible alias: activeColumns instead of a boolean cells map:\n { id: '4', identity: { name: 'Legacy', email: '' }, activeColumns: ['admin'] },\n]\n\n<MatrixTable\n columns={columns}\n rows={rows}\n renderIdentity={(row) => <span>{row.identity?.name}</span>}\n identityLabel=\"Member\"\n aria-label=\"Member role assignments\"\n/>\n"
4611
4931
  },
4612
4932
  "menubar": {
4613
4933
  "category": "overlay",
@@ -5188,6 +5508,11 @@
5188
5508
  },
5189
5509
  {
5190
5510
  "name": "leading",
5511
+ "type": "React.ReactNode",
5512
+ "description": "Optional media/identity node rendered to the LEFT of the title block inside the title row (typically an Avatar or identity plate). When present the row forms an identity lockup and the slot top-aligns against the title/description stack (VI-539). Omit to keep the default text|actions layout, so existing call sites render unchanged.\n"
5513
+ },
5514
+ {
5515
+ "name": "titleLeading",
5191
5516
  "type": "string | number",
5192
5517
  "description": "Title line-height override, forwarded as an inline `--page-header-title-leading` custom property (a unitless number like `1.05` or any CSS length). When omitted the title keeps its default line-height (tight for the standard title, `1` for the marquee scale), so existing call sites render unchanged.\n"
5193
5518
  }
@@ -5209,6 +5534,7 @@
5209
5534
  "--letter-spacing-wide",
5210
5535
  "--line-height-relaxed",
5211
5536
  "--line-height-tight",
5537
+ "--page-header-leading-gap",
5212
5538
  "--page-header-title-family",
5213
5539
  "--page-header-title-leading",
5214
5540
  "--page-header-title-size",
@@ -5720,6 +6046,10 @@
5720
6046
  "Linear capacity bars (use Progress with size=\"thin\")"
5721
6047
  ],
5722
6048
  "variants": {
6049
+ "variant": [
6050
+ "ring",
6051
+ "solid"
6052
+ ],
5723
6053
  "size": [
5724
6054
  "sm",
5725
6055
  "md",
@@ -5751,6 +6081,12 @@
5751
6081
  "default": "100",
5752
6082
  "description": "Maximum value the score can reach."
5753
6083
  },
6084
+ {
6085
+ "name": "variant",
6086
+ "type": "\"ring\" | \"solid\"",
6087
+ "default": "\"ring\"",
6088
+ "description": "Display treatment. \"ring\" draws the SVG track + progress arc (default). \"solid\" renders a flat filled tinted disc with no ring — the editorial health-score look."
6089
+ },
5754
6090
  {
5755
6091
  "name": "size",
5756
6092
  "type": "\"sm\" | \"md\" | \"lg\"",
@@ -5787,11 +6123,13 @@
5787
6123
  ],
5788
6124
  "tokens_used": [
5789
6125
  "--border-default",
6126
+ "--destructive",
5790
6127
  "--font-size-base",
5791
6128
  "--font-size-sm",
5792
6129
  "--font-size-xs",
5793
6130
  "--font-weight-regular",
5794
6131
  "--font-weight-semibold",
6132
+ "--info",
5795
6133
  "--line-height-tight",
5796
6134
  "--motion-duration-150",
5797
6135
  "--motion-duration-500",
@@ -5799,11 +6137,14 @@
5799
6137
  "--radius-full",
5800
6138
  "--score-indicator-center-bg",
5801
6139
  "--score-indicator-icon-color",
6140
+ "--score-indicator-solid-bg",
6141
+ "--score-indicator-solid-value-color",
5802
6142
  "--score-indicator-stroke-color",
5803
6143
  "--score-indicator-track-color",
5804
6144
  "--score-indicator-value-color",
5805
6145
  "--spacing-1",
5806
6146
  "--spacing-2",
6147
+ "--success",
5807
6148
  "--surface-card",
5808
6149
  "--surface-error-subtle",
5809
6150
  "--surface-info-subtle",
@@ -5814,7 +6155,8 @@
5814
6155
  "--text-primary",
5815
6156
  "--text-success",
5816
6157
  "--text-tertiary",
5817
- "--text-warning"
6158
+ "--text-warning",
6159
+ "--warning"
5818
6160
  ],
5819
6161
  "example": "<ScoreIndicator value={98} max={100} />\n<ScoreIndicator value={42} max={100} size=\"lg\" />\n<ScoreIndicator value={86} max={100} tone=\"success\" denominator=\"below\" />\n"
5820
6162
  },
@@ -5948,36 +6290,116 @@
5948
6290
  ],
5949
6291
  "example": "<SectionHeader title=\"Tonight's events\" meta=\"2 events\" />\n<SectionHeader title=\"Activity\" meta=\"last hour\" />\n<SectionHeader title=\"Top promoters · 30d\" />\n"
5950
6292
  },
5951
- "section-nav": {
5952
- "category": "navigation",
5953
- "description": "A link/anchor-based section sub-navigation strip a leading Phosphor icon + label + optional trailing count pill per item, with a static 2px primary underline on the active item. Items navigate via href (use `asChild` with `next/link` for client-side routing). Distinct from Tabs — no button triggers, no content panels, no animated indicator.",
6293
+ "section-intro": {
6294
+ "category": "general",
6295
+ "description": "Marketing section opener with a mono uppercase eyebrow, display-font heading, and optional lede paragraph. Eyebrow color tracks a live-rewritten CSS custom property for brand-accent binding.",
5954
6296
  "when_to_use": [
5955
- "A sub-navigation strip across the top of a detail view whose tabs are real routes (e.g. Organization → Detail / Roles / Invites)",
5956
- "When each section is its own URL and you want client-side navigation via next/link rather than in-page panel switching",
5957
- "When sections carry a count (member count, pending invites) you want to surface in a trailing pill"
6297
+ "Opening a marketing section with an eyebrow + display heading pattern",
6298
+ "Replacing hand-rolled eyebrow/heading combos that repeat across homepage sections",
6299
+ "Binding the eyebrow color to a keyed brand accent that changes at runtime",
6300
+ "Using left or center alignment for section intros on marketing pages"
5958
6301
  ],
5959
6302
  "when_not_to_use": [
5960
- "In-page panel switching where the content lives on the same route (use Tabs)",
5961
- "Top-level site navigation with brand + actions (use Navbar)",
5962
- "Paginated record lists (use Pagination)"
6303
+ "Admin page headers with actions and breadcrumbs (use PageHeader)",
6304
+ "In-page content section dividers with meta labels (use SectionHeader)",
6305
+ "Plain typographic headings inside body copy (use Heading)",
6306
+ "Sections that require icon, badge, or button inside the heading area"
5963
6307
  ],
5964
- "variants": {},
6308
+ "variants": {
6309
+ "align": [
6310
+ "left",
6311
+ "center"
6312
+ ]
6313
+ },
5965
6314
  "props": [
5966
6315
  {
5967
- "name": "label",
6316
+ "name": "eyebrow",
6317
+ "type": "React.ReactNode",
6318
+ "description": "Short mono uppercase label rendered above the heading. Reads color from --section-intro-eyebrow-color."
6319
+ },
6320
+ {
6321
+ "name": "heading",
5968
6322
  "type": "React.ReactNode",
5969
6323
  "required": true,
5970
- "description": "The item's visible label text. (SectionNavItem)"
6324
+ "description": "Display-font heading the main marketing statement."
5971
6325
  },
5972
6326
  {
5973
- "name": "href",
6327
+ "name": "lede",
6328
+ "type": "React.ReactNode",
6329
+ "description": "Optional supporting paragraph rendered beneath the heading."
6330
+ },
6331
+ {
6332
+ "name": "align",
6333
+ "type": "'left' | 'center'",
6334
+ "default": "left",
6335
+ "description": "Text alignment for all three slots."
6336
+ },
6337
+ {
6338
+ "name": "headingAs",
6339
+ "type": "'h1' | 'h2' | 'h3'",
6340
+ "default": "h2",
6341
+ "description": "Heading level for the heading slot."
6342
+ },
6343
+ {
6344
+ "name": "as",
6345
+ "type": "'header' | 'div' | 'section'",
6346
+ "default": "header",
6347
+ "description": "Root element tag."
6348
+ }
6349
+ ],
6350
+ "dependencies": [],
6351
+ "tokens_used": [
6352
+ "--accent-default",
6353
+ "--font-display",
6354
+ "--font-family-heading",
6355
+ "--font-mono",
6356
+ "--font-size-base",
6357
+ "--font-size-xs",
6358
+ "--font-weight-light",
6359
+ "--font-weight-medium",
6360
+ "--interactive-primary-bg",
6361
+ "--letter-spacing-tight",
6362
+ "--letter-spacing-widest",
6363
+ "--line-height-none",
6364
+ "--line-height-relaxed",
6365
+ "--line-height-tight",
6366
+ "--section-intro-eyebrow-color",
6367
+ "--spacing-4",
6368
+ "--text-primary",
6369
+ "--text-secondary"
6370
+ ],
6371
+ "example": "<SectionIntro\n eyebrow=\"On the map\"\n heading=\"Built for the world's best venues\"\n lede=\"One platform. Every door.\"\n/>\n"
6372
+ },
6373
+ "section-nav": {
6374
+ "category": "navigation",
6375
+ "description": "A link/anchor-based section sub-navigation strip — a leading Phosphor icon + label + optional trailing count pill per item, with a static 2px primary underline on the active item. Items navigate via href (use `asChild` with `next/link` for client-side routing). Distinct from Tabs — no button triggers, no content panels, no animated indicator.",
6376
+ "when_to_use": [
6377
+ "A sub-navigation strip across the top of a detail view whose tabs are real routes (e.g. Organization → Detail / Roles / Invites)",
6378
+ "When each section is its own URL and you want client-side navigation via next/link rather than in-page panel switching",
6379
+ "When sections carry a count (member count, pending invites) you want to surface in a trailing pill"
6380
+ ],
6381
+ "when_not_to_use": [
6382
+ "In-page panel switching where the content lives on the same route (use Tabs)",
6383
+ "Top-level site navigation with brand + actions (use Navbar)",
6384
+ "Paginated record lists (use Pagination)"
6385
+ ],
6386
+ "variants": {},
6387
+ "props": [
6388
+ {
6389
+ "name": "label",
6390
+ "type": "React.ReactNode",
6391
+ "required": true,
6392
+ "description": "The item's visible label text. (SectionNavItem)"
6393
+ },
6394
+ {
6395
+ "name": "href",
5974
6396
  "type": "string",
5975
6397
  "description": "Destination URL. Applied directly when not using `asChild`. (SectionNavItem)"
5976
6398
  },
5977
6399
  {
5978
6400
  "name": "icon",
5979
- "type": "Icon",
5980
- "description": "Leading Phosphor icon component (e.g. `UsersIcon`). (SectionNavItem)"
6401
+ "type": "Icon | React.ReactNode",
6402
+ "description": "Leading icon, accepted in two forms — (1) a Phosphor icon component (`icon={UsersIcon}`), rendered as `<Icon className=... weight=\"regular\" />`; or (2) a rendered element (`icon={<Users size={16} weight=\"bold\" />}`), rendered as-is so its own size/weight/props are preserved, wrapped in the icon slot span. Branches on `React.isValidElement`. (SectionNavItem)\n"
5981
6403
  },
5982
6404
  {
5983
6405
  "name": "count",
@@ -6008,9 +6430,8 @@
6008
6430
  "--focus-ring-offset",
6009
6431
  "--focus-ring-width",
6010
6432
  "--font-size-sm",
6011
- "--font-size-xs",
6012
6433
  "--font-weight-medium",
6013
- "--line-height-tight",
6434
+ "--hairline",
6014
6435
  "--motion-duration-fast",
6015
6436
  "--motion-easing-standard",
6016
6437
  "--primary",
@@ -6019,15 +6440,80 @@
6019
6440
  "--spacing-1",
6020
6441
  "--spacing-2",
6021
6442
  "--spacing-3",
6443
+ "--spacing-4",
6022
6444
  "--stroke-width-thin",
6023
- "--surface-accent-subtle",
6024
- "--surface-muted",
6025
- "--text-link",
6445
+ "--surface-subtle",
6026
6446
  "--text-primary",
6027
- "--text-secondary"
6447
+ "--text-tertiary"
6028
6448
  ],
6029
6449
  "example": "import Link from 'next/link'\nimport { UsersIcon, ShieldIcon, EnvelopeIcon } from '@phosphor-icons/react'\nimport { SectionNav, SectionNavItem } from '@/components/ui/section-nav/section-nav'\n\n<SectionNav aria-label=\"Organization sections\">\n <SectionNavItem asChild isActive icon={UsersIcon} label=\"Detail\">\n <Link href=\"/org/detail\" />\n </SectionNavItem>\n <SectionNavItem asChild icon={ShieldIcon} label=\"Roles\" count={4}>\n <Link href=\"/org/roles\" />\n </SectionNavItem>\n <SectionNavItem asChild icon={EnvelopeIcon} label=\"Invites\" count={2}>\n <Link href=\"/org/invites\" />\n </SectionNavItem>\n</SectionNav>\n"
6030
6450
  },
6451
+ "segmented-progress": {
6452
+ "category": "data-display",
6453
+ "description": "Discrete per-step progress meter — N equal pill segments in a row, each individually expressing done, current, or pending state. Designed for multi-step flows where individual step completion matters, not just an overall percentage.",
6454
+ "when_to_use": [
6455
+ "Multi-step onboarding or wizard flows where each discrete step needs individual visual state",
6456
+ "Brand elicitation, survey, or questionnaire progress where step-level completion is meaningful",
6457
+ "Short sequence indicators (≤12 steps) where a pill-per-step treatment reads clearly",
6458
+ "Inline progress in cards or compact surfaces where a stepper is too heavy"
6459
+ ],
6460
+ "when_not_to_use": [
6461
+ "Use `progress` for continuous values like upload percentage or loading progress",
6462
+ "Use `stepper` when step labels or click-to-navigate behavior is needed",
6463
+ "Sequences longer than ~15 segments (pills become too narrow to read)"
6464
+ ],
6465
+ "variants": {
6466
+ "size": [
6467
+ "sm",
6468
+ "md"
6469
+ ]
6470
+ },
6471
+ "props": [
6472
+ {
6473
+ "name": "total",
6474
+ "type": "number",
6475
+ "required": true,
6476
+ "description": "Total number of segments to render."
6477
+ },
6478
+ {
6479
+ "name": "value",
6480
+ "type": "number",
6481
+ "required": true,
6482
+ "description": "Count of completed segments. Indices 0..value-1 render as done."
6483
+ },
6484
+ {
6485
+ "name": "current",
6486
+ "type": "number",
6487
+ "description": "Optional 0-based index of the in-progress segment. Renders with a primary→muted gradient. Typically equals `value`."
6488
+ },
6489
+ {
6490
+ "name": "size",
6491
+ "type": "\"sm\" | \"md\"",
6492
+ "default": "\"sm\"",
6493
+ "description": "Segment height. sm=6px (prototype height), md=8px."
6494
+ },
6495
+ {
6496
+ "name": "aria-label",
6497
+ "type": "string",
6498
+ "required": true,
6499
+ "description": "Accessible name for the progressbar. Required — describe the current step context (e.g. \"Step 3 of 10\")."
6500
+ }
6501
+ ],
6502
+ "dependencies": [
6503
+ "class-variance-authority",
6504
+ "@loworbitstudio/visor-core"
6505
+ ],
6506
+ "tokens_used": [
6507
+ "--interactive-primary-bg",
6508
+ "--motion-duration-150",
6509
+ "--motion-easing-default",
6510
+ "--primary",
6511
+ "--radius-full",
6512
+ "--spacing-1",
6513
+ "--surface-muted"
6514
+ ],
6515
+ "example": "<SegmentedProgress total={10} value={2} current={2} aria-label=\"Step 2 of 10\" />\n<SegmentedProgress total={6} value={3} aria-label=\"Step 3 of 6\" size=\"md\" />\n"
6516
+ },
6031
6517
  "select": {
6032
6518
  "category": "form",
6033
6519
  "description": "An accessible select component built on Radix UI Select with all sub-components.",
@@ -6070,7 +6556,7 @@
6070
6556
  "sub_components": [
6071
6557
  {
6072
6558
  "name": "SelectTrigger",
6073
- "description": "The button that opens the select dropdown. Accepts a size prop (\"sm\" | \"md\" | \"lg\")."
6559
+ "description": "The button that opens the select dropdown. Accepts a size prop (\"sm\" | \"md\" | \"lg\") and a variant prop (\"default\" | \"borderless\"); borderless drops the border and sits on --field-control-bg to match a borderless Input in an in-dialog form."
6074
6560
  },
6075
6561
  {
6076
6562
  "name": "SelectValue",
@@ -6106,6 +6592,7 @@
6106
6592
  "--border-error",
6107
6593
  "--border-focus",
6108
6594
  "--border-strong",
6595
+ "--field-control-bg",
6109
6596
  "--field-menu-bg",
6110
6597
  "--focus-ring-width",
6111
6598
  "--font-size-base",
@@ -6134,6 +6621,7 @@
6134
6621
  "--surface-interactive-default",
6135
6622
  "--surface-interactive-hover",
6136
6623
  "--surface-popover",
6624
+ "--surface-subtle",
6137
6625
  "--text-primary",
6138
6626
  "--text-secondary"
6139
6627
  ],
@@ -6472,16 +6960,23 @@
6472
6960
  {
6473
6961
  "name": "className",
6474
6962
  "type": "string",
6475
- "description": "Control dimensions and shape (e.g., \"w-full h-4 rounded\")"
6963
+ "description": "Control dimensions and shape (e.g., \"w-full h-4 rounded\"). Opt into a preset silhouette by applying a shape modifier class from the module: shapeLogo (~104x24), shapePill (~64x18), or shapeCircle (square, fully rounded). These layer on top of the base .skeleton class.\n"
6476
6964
  }
6477
6965
  ],
6478
6966
  "dependencies": [],
6479
6967
  "tokens_used": [
6480
6968
  "--_shimmer-duration",
6481
6969
  "--motion-easing-linear",
6970
+ "--radius-full",
6482
6971
  "--radius-md",
6972
+ "--radius-sm",
6483
6973
  "--skeleton-from",
6484
- "--skeleton-to"
6974
+ "--skeleton-logo-height",
6975
+ "--skeleton-logo-width",
6976
+ "--skeleton-pill-height",
6977
+ "--skeleton-pill-width",
6978
+ "--skeleton-to",
6979
+ "--text-primary"
6485
6980
  ],
6486
6981
  "example": "<Skeleton className=\"h-4 w-[200px]\" />\n<Skeleton className=\"h-12 w-12 rounded-full\" />\n"
6487
6982
  },
@@ -6708,6 +7203,116 @@
6708
7203
  ],
6709
7204
  "example": "<Sparkline values={[12, 18, 22, 19, 27, 36, 41, 48, 54, 62, 71, 84]} />\n<Sparkline values={[12, 18, 22, 19, 27, 36, 41, 48, 54, 62, 71, 84]} width={120} height={32} />\n<Sparkline values={[12, 18, 22, 19, 27, 36, 41, 48, 54, 62, 71, 84]} color=\"var(--text-success)\" />\n<Sparkline values={[12, 18, 22, 19, 27, 36, 41, 48, 54, 62, 71, 84]} fluid />\n<Sparkline values={[12, 18, 22, 19, 27, 36, 41, 48, 54, 62, 71, 84]} aria-label=\"RSVPs over the last 12 weeks\" />\n"
6710
7205
  },
7206
+ "specimen-card": {
7207
+ "category": "specimen",
7208
+ "description": "Labeled frame that pairs a context token + \"feel\" descriptor with arbitrary live component children — proving how a brand voice/tone renders on real components.",
7209
+ "when_to_use": [
7210
+ "Displaying tone-by-context previews in a brand workbench grid (e.g. \"error\" + \"warm, accountable\" above a real Alert component)",
7211
+ "Building brand-voice proof blocks that show live components annotated with their voice/tone attribution"
7212
+ ],
7213
+ "when_not_to_use": [
7214
+ "General-purpose card containers (use Card) — SpecimenCard is specifically a labeled component-specimen frame",
7215
+ "Production UI layouts where the context/feel labeling would be confusing to end users"
7216
+ ],
7217
+ "variants": {
7218
+ "footer": [
7219
+ "with-footer",
7220
+ "without-footer"
7221
+ ]
7222
+ },
7223
+ "props": [
7224
+ {
7225
+ "name": "context",
7226
+ "type": "string",
7227
+ "required": true,
7228
+ "description": "Context token label rendered uppercase (e.g. \"error\", \"onboarding\")."
7229
+ },
7230
+ {
7231
+ "name": "feel",
7232
+ "type": "string",
7233
+ "description": "Italic feel descriptor rendered at the end of the label row (e.g. \"warm, accountable\")."
7234
+ }
7235
+ ],
7236
+ "dependencies": [
7237
+ "@loworbitstudio/visor-core"
7238
+ ],
7239
+ "tokens_used": [
7240
+ "--hairline",
7241
+ "--radius-lg",
7242
+ "--shadow-sm",
7243
+ "--spacing-1",
7244
+ "--spacing-1-5",
7245
+ "--spacing-2",
7246
+ "--spacing-3",
7247
+ "--stroke-width-thin",
7248
+ "--surface-card",
7249
+ "--text-secondary",
7250
+ "--text-tertiary"
7251
+ ],
7252
+ "example": "<SpecimenCard context=\"error\" feel=\"warm, accountable\">\n <Alert variant=\"destructive\">Something went wrong — we've got it.</Alert>\n</SpecimenCard>\n\n<SpecimenCard context=\"onboarding\">\n <Button>Get started</Button>\n <Button variant=\"outline\">Learn more</Button>\n <SpecimenCardFooter>voice — precise · warm · direct</SpecimenCardFooter>\n</SpecimenCard>\n"
7253
+ },
7254
+ "spinner": {
7255
+ "category": "data-display",
7256
+ "description": "Inline loading spinner — a rotating border ring with a subtle track and a tone-colored leading edge. Three sizes (xs/sm/md), two tones (default/primary), and full reduced-motion support.",
7257
+ "when_to_use": [
7258
+ "Indicating an async operation is in progress within a button, form field, or inline context",
7259
+ "Replacing text or an icon while content is loading (inline, non-blocking)",
7260
+ "Decorative loading affordance inside a card or panel where a full-page skeleton would be too heavy"
7261
+ ],
7262
+ "when_not_to_use": [
7263
+ "Full-page or full-section loading states (use Skeleton instead)",
7264
+ "Progress that can be measured and communicated as a percentage (use Progress)",
7265
+ "Indicating indeterminate progress for a long-running task visible to the user (use a labeled progress bar)"
7266
+ ],
7267
+ "variants": {
7268
+ "size": [
7269
+ "xs",
7270
+ "sm",
7271
+ "md"
7272
+ ],
7273
+ "tone": [
7274
+ "default",
7275
+ "primary"
7276
+ ]
7277
+ },
7278
+ "props": [
7279
+ {
7280
+ "name": "size",
7281
+ "type": "\"xs\" | \"sm\" | \"md\"",
7282
+ "default": "\"md\"",
7283
+ "description": "Ring size. xs=12px, sm=16px, md=24px."
7284
+ },
7285
+ {
7286
+ "name": "tone",
7287
+ "type": "\"default\" | \"primary\"",
7288
+ "default": "\"default\"",
7289
+ "description": "Color treatment for the leading edge. default=text-tertiary, primary=primary brand token."
7290
+ },
7291
+ {
7292
+ "name": "label",
7293
+ "type": "string",
7294
+ "description": "Accessible label. When provided, renders role=\"status\" with visually-hidden text. When omitted, aria-hidden=\"true\" (decorative)."
7295
+ }
7296
+ ],
7297
+ "dependencies": [
7298
+ "class-variance-authority",
7299
+ "@loworbitstudio/visor-core"
7300
+ ],
7301
+ "tokens_used": [
7302
+ "--border-default",
7303
+ "--motion-duration-1500",
7304
+ "--motion-easing-linear",
7305
+ "--primary",
7306
+ "--radius-full",
7307
+ "--spinner-edge-color",
7308
+ "--spinner-track-color",
7309
+ "--stroke-width-medium",
7310
+ "--stroke-width-regular",
7311
+ "--stroke-width-thin",
7312
+ "--text-tertiary"
7313
+ ],
7314
+ "example": "<Spinner />\n<Spinner size=\"xs\" tone=\"primary\" />\n<Spinner size=\"sm\" label=\"Loading results…\" />\n"
7315
+ },
6711
7316
  "stack": {
6712
7317
  "category": "layout",
6713
7318
  "description": "Vertical flex container with token-driven gap, alignment, and `as` polymorphism.",
@@ -7200,7 +7805,7 @@
7200
7805
  },
7201
7806
  "stepper": {
7202
7807
  "category": "navigation",
7203
- "description": "A multi-step progress indicator with horizontal and vertical orientations and auto-derived step status.",
7808
+ "description": "A multi-step progress indicator with horizontal and vertical orientations, auto-derived step status, and a locked status for non-interactive steps.",
7204
7809
  "when_to_use": [
7205
7810
  "Multi-step forms or wizards showing progress",
7206
7811
  "Onboarding flows with sequential steps",
@@ -7213,6 +7818,18 @@
7213
7818
  "Timelines of past events (use Timeline)",
7214
7819
  "Pagination through pages of content (use Pagination)"
7215
7820
  ],
7821
+ "variants": [
7822
+ {
7823
+ "name": "status",
7824
+ "values": [
7825
+ "complete",
7826
+ "active",
7827
+ "upcoming",
7828
+ "locked"
7829
+ ],
7830
+ "description": "Per-item status override. \"complete\", \"active\", and \"upcoming\" can be auto-derived from activeStep; \"locked\" must always be set explicitly and is never auto-derived. A locked item renders a lock glyph in its trigger circle and its trigger is non-interactive (aria-disabled, removed from tab order).\n"
7831
+ }
7832
+ ],
7216
7833
  "props": [
7217
7834
  {
7218
7835
  "name": "activeStep",
@@ -7233,15 +7850,15 @@
7233
7850
  },
7234
7851
  {
7235
7852
  "name": "StepperItem",
7236
- "description": "Container for a single step with auto-derived status from context."
7853
+ "description": "Container for a single step with auto-derived status from context. Pass status=\"locked\" to override."
7237
7854
  },
7238
7855
  {
7239
7856
  "name": "StepperTrigger",
7240
- "description": "Clickable step indicator circle with status-based styling and check icon."
7857
+ "description": "Clickable step indicator circle with status-based styling; check icon for complete, lock glyph for locked."
7241
7858
  },
7242
7859
  {
7243
7860
  "name": "StepperTitle",
7244
- "description": "Step label text."
7861
+ "description": "Step label text. Rendered in secondary color when the parent item is locked."
7245
7862
  },
7246
7863
  {
7247
7864
  "name": "StepperDescription",
@@ -7264,12 +7881,14 @@
7264
7881
  "--font-size-sm",
7265
7882
  "--font-size-xs",
7266
7883
  "--font-weight-medium",
7884
+ "--font-weight-normal",
7267
7885
  "--interactive-primary-bg",
7268
7886
  "--interactive-primary-text",
7269
7887
  "--line-height-normal",
7270
7888
  "--line-height-snug",
7271
7889
  "--motion-duration-200",
7272
7890
  "--motion-easing-default",
7891
+ "--opacity-60",
7273
7892
  "--primary",
7274
7893
  "--radius-full",
7275
7894
  "--spacing-1",
@@ -7282,6 +7901,97 @@
7282
7901
  ],
7283
7902
  "example": "<Stepper activeStep={1}>\n <StepperItem step={0}>\n <StepperTrigger step={0} />\n <StepperTitle>Account</StepperTitle>\n </StepperItem>\n <StepperSeparator complete />\n <StepperItem step={1}>\n <StepperTrigger step={1} />\n <StepperTitle>Profile</StepperTitle>\n </StepperItem>\n <StepperSeparator />\n <StepperItem step={2}>\n <StepperTrigger step={2} />\n <StepperTitle>Review</StepperTitle>\n </StepperItem>\n</Stepper>\n"
7284
7903
  },
7904
+ "structured-prompt": {
7905
+ "category": "form",
7906
+ "description": "Inline \"mad-lib\" fill-in-the-blank card for structured elicitation flows. A card with an eyebrow header, flowing prose at tall line-height, and inline slot chips (filled or empty) embedded in the prose, plus a hint footer.",
7907
+ "when_to_use": [
7908
+ "Guiding users through structured sentence completion (e.g. \"For [audience], we are the only [category] that [differentiator]\")",
7909
+ "Brand elicitation or onliness-statement workflows where each bracket is a fillable slot",
7910
+ "AI-assisted prompt templates where the model pre-fills slots and the user confirms or edits them",
7911
+ "Any pattern that mixes fixed prose with discrete editable inputs inline in a sentence"
7912
+ ],
7913
+ "when_not_to_use": [
7914
+ "Free-form text entry where sentence structure is not fixed (use Textarea or Input)",
7915
+ "Long multi-field forms (use Field + Fieldset)",
7916
+ "Structured data entry with labels and descriptions (use KeyValueList or a form layout)"
7917
+ ],
7918
+ "variants": {
7919
+ "slot_state": [
7920
+ "filled",
7921
+ "empty"
7922
+ ]
7923
+ },
7924
+ "props": [
7925
+ {
7926
+ "name": "className",
7927
+ "type": "string",
7928
+ "description": "Additional CSS classes for the root card element."
7929
+ },
7930
+ {
7931
+ "name": "icon",
7932
+ "type": "ReactNode",
7933
+ "description": "(StructuredPromptHeader) Phosphor icon rendered before the eyebrow label."
7934
+ },
7935
+ {
7936
+ "name": "filled",
7937
+ "type": "boolean",
7938
+ "default": "false",
7939
+ "description": "(StructuredPromptSlot) When true, renders the primary-soft filled chip. When false, renders the dashed empty chip."
7940
+ },
7941
+ {
7942
+ "name": "onClick",
7943
+ "type": "() => void",
7944
+ "description": "(StructuredPromptSlot) When provided, the slot renders as a focusable <button>. Without onClick it renders as a <span>."
7945
+ }
7946
+ ],
7947
+ "sub_components": [
7948
+ {
7949
+ "name": "StructuredPromptHeader",
7950
+ "description": "Eyebrow header bar with optional Phosphor icon and uppercase label. Renders a hairline bottom border separating it from the body."
7951
+ },
7952
+ {
7953
+ "name": "StructuredPromptBody",
7954
+ "description": "Prose container at tall line-height (2.05) so inline slot chips breathe within the sentence flow."
7955
+ },
7956
+ {
7957
+ "name": "StructuredPromptSlot",
7958
+ "description": "Inline chip embedded in prose. filled=true renders a primary-soft chip; false/absent renders a dashed empty chip. Renders a <button> when onClick is provided (interactive), otherwise a <span> (display-only)."
7959
+ },
7960
+ {
7961
+ "name": "StructuredPromptHint",
7962
+ "description": "Footer hint text at small tertiary size, rendered below the body."
7963
+ }
7964
+ ],
7965
+ "dependencies": [
7966
+ "@loworbitstudio/visor-core"
7967
+ ],
7968
+ "tokens_used": [
7969
+ "--border-default",
7970
+ "--border-focus",
7971
+ "--focus-ring-offset",
7972
+ "--focus-ring-width",
7973
+ "--font-weight-medium",
7974
+ "--font-weight-semibold",
7975
+ "--hairline",
7976
+ "--radius-md",
7977
+ "--radius-xl",
7978
+ "--shadow-sm",
7979
+ "--spacing-0-5",
7980
+ "--spacing-2",
7981
+ "--spacing-2-5",
7982
+ "--spacing-3",
7983
+ "--spacing-3-5",
7984
+ "--stroke-width-thin",
7985
+ "--surface-accent-default",
7986
+ "--surface-accent-subtle",
7987
+ "--surface-card",
7988
+ "--surface-subtle",
7989
+ "--text-link",
7990
+ "--text-primary",
7991
+ "--text-tertiary"
7992
+ ],
7993
+ "example": "<StructuredPrompt>\n <StructuredPromptHeader icon={<ListBullets weight=\"stroke\" />}>\n ONLINESS · THE SPEARHEAD\n </StructuredPromptHeader>\n <StructuredPromptBody>\n For <StructuredPromptSlot filled>design-led product teams</StructuredPromptSlot>,\n Visor is the only <StructuredPromptSlot filled>component system</StructuredPromptSlot>\n that <StructuredPromptSlot>derives every surface from one brand record</StructuredPromptSlot>.\n </StructuredPromptBody>\n <StructuredPromptHint>Click any slot to edit</StructuredPromptHint>\n</StructuredPrompt>\n"
7994
+ },
7285
7995
  "surface-row": {
7286
7996
  "category": "specimen",
7287
7997
  "description": "Surface background variant display showing a surface token with its name and token reference.",
@@ -7496,15 +8206,22 @@
7496
8206
  "dependencies": [],
7497
8207
  "tokens_used": [
7498
8208
  "--border-default",
8209
+ "--dt-container-radius",
8210
+ "--dt-container-shadow",
8211
+ "--dt-header-bg",
8212
+ "--dt-row-bg",
7499
8213
  "--font-size-sm",
7500
8214
  "--font-weight-medium",
8215
+ "--hairline",
7501
8216
  "--motion-duration-150",
7502
8217
  "--motion-easing-default",
7503
8218
  "--radius-lg",
7504
8219
  "--shadow-sm",
7505
8220
  "--spacing-3",
7506
8221
  "--spacing-4",
8222
+ "--surface-card",
7507
8223
  "--surface-muted",
8224
+ "--surface-page",
7508
8225
  "--text-primary",
7509
8226
  "--text-secondary"
7510
8227
  ],
@@ -7593,18 +8310,20 @@
7593
8310
  "--font-size-sm",
7594
8311
  "--font-size-xs",
7595
8312
  "--font-weight-medium",
8313
+ "--hairline",
7596
8314
  "--motion-duration-150",
7597
8315
  "--motion-duration-normal",
7598
8316
  "--motion-easing-default",
8317
+ "--primary",
7599
8318
  "--radius-full",
7600
8319
  "--radius-md",
7601
8320
  "--shadow-sm",
7602
8321
  "--spacing-1",
7603
8322
  "--spacing-2",
8323
+ "--spacing-3",
7604
8324
  "--surface-accent-subtle",
7605
8325
  "--surface-muted",
7606
8326
  "--surface-page",
7607
- "--tabs-indicator-color",
7608
8327
  "--text-link",
7609
8328
  "--text-primary",
7610
8329
  "--text-secondary"
@@ -8887,6 +9606,235 @@
8887
9606
  "tokens_used": [],
8888
9607
  "example": "<TOCSlide\n sections={[\n {\n section: \"Strategy\",\n items: [\n { id: \"s-vision\", title: \"Vision\" },\n { id: \"s-goals\", title: \"Goals\" },\n ],\n },\n ]}\n/>\n"
8889
9608
  },
9609
+ "ambient-glow": {
9610
+ "category": "visual-elements",
9611
+ "description": "Absolutely-positioned drifting radial glow with live CSS-var-driven color. Decorative only — aria-hidden, pointer-events-none. Ported from the Blacklight marketing depth system (BL-326).",
9612
+ "when_to_use": [
9613
+ "Atmospheric background lighting behind hero sections or focal objects",
9614
+ "Live color-reactive glows that track a dynamically rewritten CSS custom property (e.g. hero crossfade accent)",
9615
+ "Marketing and landing pages that need depth via subtle light fields",
9616
+ "Pro/premium surface accents (gold variant)"
9617
+ ],
9618
+ "when_not_to_use": [
9619
+ "Functional UI elements — AmbientGlow is purely decorative",
9620
+ "When the user prefers reduced motion and you need a visible element (the glow becomes static but stays visible)",
9621
+ {
9622
+ "Inside overflow": "hidden containers without adjusting inset values"
9623
+ }
9624
+ ],
9625
+ "props": [
9626
+ {
9627
+ "name": "variant",
9628
+ "type": "'keyed' | 'gold'",
9629
+ "default": "keyed",
9630
+ "description": "keyed — color-mix from --glow-color (live-rewritable); gold — static warm gold at 7% opacity"
9631
+ },
9632
+ {
9633
+ "name": "glowColor",
9634
+ "type": "string",
9635
+ "description": "Static glow color override — sets --glow-color as an inline custom property. For live runtime rewrites use a parent-element CSS variable instead."
9636
+ },
9637
+ {
9638
+ "name": "className",
9639
+ "type": "string",
9640
+ "description": "Additional class names for positioning and sizing (position/inset/width/height)."
9641
+ },
9642
+ {
9643
+ "name": "style",
9644
+ "type": "React.CSSProperties",
9645
+ "description": "Inline styles, typically inset/width/height. --glow-color can also be set here."
9646
+ }
9647
+ ],
9648
+ "dependencies": [
9649
+ "class-variance-authority",
9650
+ "@loworbitstudio/visor-core"
9651
+ ],
9652
+ "tokens_used": [
9653
+ "--accent",
9654
+ "--glow-color",
9655
+ "--interactive-primary-bg"
9656
+ ],
9657
+ "example": "<div style={{ position: 'relative', width: 400, height: 300 }}>\n <AmbientGlow style={{ inset: 0 }} />\n <p style={{ position: 'relative' }}>Content above the glow</p>\n</div>\n"
9658
+ },
9659
+ "browser-frame": {
9660
+ "category": "visual-elements",
9661
+ "description": "Browser-chrome mockup frame with traffic-light dots and a URL pill. Wraps arbitrary content for marketing and landing surfaces. Elevation-free — compose with .lit / .lit-soft / .lit-strong for depth.",
9662
+ "when_to_use": [
9663
+ "Showing a product screenshot or live embed inside a browser mockup on a marketing or landing page",
9664
+ "EPK-style artist/product demos where the \"in-browser\" framing provides context",
9665
+ "Any dark surface that needs a browser-chrome visual metaphor without hand-rolling the chrome bar"
9666
+ ],
9667
+ "when_not_to_use": [
9668
+ "Real browser chrome or app shells — this is a decorative mockup, not a functional navigation component",
9669
+ "Light-mode surfaces (the default deep-dark background will clash; override --browser-frame-bg to adapt)",
9670
+ "Nested inside another BrowserFrame (a browser inside a browser is confusing)"
9671
+ ],
9672
+ "props": [
9673
+ {
9674
+ "name": "url",
9675
+ "type": "string",
9676
+ "required": true,
9677
+ "description": "Display URL shown in the chrome bar, e.g. \"sharlese.epk.pro\"."
9678
+ },
9679
+ {
9680
+ "name": "href",
9681
+ "type": "string",
9682
+ "required": false,
9683
+ "description": "Optional real link target. When set, the URL pill becomes a clickable <a> with a focus ring."
9684
+ },
9685
+ {
9686
+ "name": "children",
9687
+ "type": "React.ReactNode",
9688
+ "required": true,
9689
+ "description": "Content rendered inside the frame below the chrome bar."
9690
+ },
9691
+ {
9692
+ "name": "className",
9693
+ "type": "string",
9694
+ "required": false,
9695
+ "description": "Additional class name applied to the outer wrapper (e.g. to set --lit-color or --browser-frame-focus-color)."
9696
+ }
9697
+ ],
9698
+ "dependencies": [
9699
+ "@loworbitstudio/visor-core",
9700
+ "@phosphor-icons/react"
9701
+ ],
9702
+ "tokens_used": [
9703
+ "--accent",
9704
+ "--browser-frame-bg",
9705
+ "--browser-frame-focus-color",
9706
+ "--focus-ring-offset",
9707
+ "--focus-ring-width",
9708
+ "--font-weight-light",
9709
+ "--hairline",
9710
+ "--hairline-strong",
9711
+ "--motion-duration-fast",
9712
+ "--motion-easing-standard",
9713
+ "--opacity-10",
9714
+ "--opacity-12",
9715
+ "--opacity-5",
9716
+ "--opacity-60",
9717
+ "--opacity-80",
9718
+ "--radius-md",
9719
+ "--radius-xl",
9720
+ "--spacing-1",
9721
+ "--spacing-10",
9722
+ "--spacing-3",
9723
+ "--spacing-3_5",
9724
+ "--stroke-width-thin"
9725
+ ],
9726
+ "example": "{/* Static display-only */}\n<BrowserFrame url=\"myproduct.example.com\">\n <img src=\"/screenshot.png\" alt=\"Product screenshot\" />\n</BrowserFrame>\n\n{/* Clickable URL pill */}\n<BrowserFrame url=\"sharlese.epk.pro\" href=\"https://sharlese.epk.pro\">\n <div style={{ padding: '24px' }}>Artist EPK content</div>\n</BrowserFrame>\n\n{/* With lit elevation from visor-core/utilities */}\n<BrowserFrame\n url=\"sharlese.epk.pro\"\n href=\"https://sharlese.epk.pro\"\n className=\"lit\"\n style={{ '--browser-frame-focus-color': '#c8ff00' }}\n>\n <div style={{ padding: '24px' }}>Artist EPK content</div>\n</BrowserFrame>\n"
9727
+ },
9728
+ "card-lift": {
9729
+ "category": "visual-elements",
9730
+ "description": "CSS-only hover-lift wrapper — translateY(-4px) + deep shadow + live-keyed color-mix halo on hover. Port of .bl-card-lift from blacklight-website (BL-326).",
9731
+ "when_to_use": [
9732
+ "Adding hover-lift affordance to cards, tiles, or any clickable surface",
9733
+ "Marketing pages and showcase walls where surfaces should \"catch the light\" on hover",
9734
+ "Keying the halo color to a live CSS variable (e.g. artist accent color) that changes at runtime"
9735
+ ],
9736
+ "when_not_to_use": [
9737
+ "Standard admin/app UI where motion-heavy hover effects are distracting",
9738
+ "When you need the entire hover interaction (border, background, text) — CardLift is a motion-only layer",
9739
+ "Contexts where `prefers-reduced-motion` users will miss the affordance (pair with a visible focus style on the inner element)"
9740
+ ],
9741
+ "props": [
9742
+ {
9743
+ "name": "liftColor",
9744
+ "type": "string",
9745
+ "default": "var(--accent, #6366f1)",
9746
+ "description": "CSS color for the halo on hover. Accepts any CSS color or a `var()` reference. Written to `--lift-color` inline; the halo tracks live rewrites at paint time.\n"
9747
+ },
9748
+ {
9749
+ "name": "children",
9750
+ "type": "React.ReactNode",
9751
+ "description": "Content to wrap (typically a Card or BentoGrid tile)."
9752
+ },
9753
+ {
9754
+ "name": "className",
9755
+ "type": "string",
9756
+ "description": "Additional CSS class names to merge onto the wrapper."
9757
+ },
9758
+ {
9759
+ "name": "style",
9760
+ "type": "React.CSSProperties",
9761
+ "description": "Inline styles merged onto the wrapper (after `--lift-color`)."
9762
+ }
9763
+ ],
9764
+ "dependencies": [
9765
+ "@loworbitstudio/visor-core"
9766
+ ],
9767
+ "tokens_used": [
9768
+ "--accent",
9769
+ "--lift-color",
9770
+ "--shadow-"
9771
+ ],
9772
+ "example": "<CardLift liftColor=\"var(--accent)\">\n <Card>\n <CardContent>Hover me</CardContent>\n </Card>\n</CardLift>\n"
9773
+ },
9774
+ "grain-overlay": {
9775
+ "category": "visual-elements",
9776
+ "description": "Fixed, full-viewport film-grain noise layer. Ported from Blacklight's bl-grain depth-system primitive (BL-326). Decorative only — pointer-events-none, aria-hidden.",
9777
+ "when_to_use": [
9778
+ "Dark marketing surfaces where large near-black fills read as flat and digital",
9779
+ "Hero sections or landing pages with a cinema/atmospheric aesthetic",
9780
+ "Any surface using the Blacklight or similar dark depth-system look"
9781
+ ],
9782
+ "when_not_to_use": [
9783
+ "Light-mode surfaces (grain is near-invisible and adds nothing)",
9784
+ "Pages where every byte of bundle size counts (the SVG data URI is ~250 bytes — fine in practice)",
9785
+ "Animated grain effects (this is intentionally static; animated grain repaints the full viewport every frame)"
9786
+ ],
9787
+ "props": [
9788
+ {
9789
+ "name": "opacity",
9790
+ "type": "number",
9791
+ "default": "0.035",
9792
+ "description": "Grain opacity (0–1). Default matches the Blacklight bl-grain reference."
9793
+ },
9794
+ {
9795
+ "name": "zIndex",
9796
+ "type": "number",
9797
+ "default": "30",
9798
+ "description": "z-index of the overlay layer. Default 30 sits above page sections but below modals (z-100)."
9799
+ }
9800
+ ],
9801
+ "dependencies": [
9802
+ "@loworbitstudio/visor-core"
9803
+ ],
9804
+ "tokens_used": [],
9805
+ "example": "{/* Drop at the top of your dark page layout — renders nothing visible on its own */}\n<GrainOverlay />\n\n{/* Custom opacity for a more prominent grain */}\n<GrainOverlay opacity={0.06} />\n"
9806
+ },
9807
+ "hero-glow": {
9808
+ "category": "visual-elements",
9809
+ "description": "Breathing radial glow band for hero media. Color is driven by a live CSS custom property (--glow-color) so the caller can rewrite it every rAF frame without triggering a React re-render.",
9810
+ "when_to_use": [
9811
+ "Behind hero images or carousels to cast a color-keyed ambient glow",
9812
+ "When the glow color must track a live value (e.g. current artist color, active palette)",
9813
+ "Marketing hero sections that need atmospheric depth without a heavy shader"
9814
+ ],
9815
+ "when_not_to_use": [
9816
+ "As a layout element — HeroGlow is purely decorative (aria-hidden, pointer-events none)",
9817
+ {
9818
+ "When the parent is not position": "relative (the negative inset will be clipped or invisible)"
9819
+ },
9820
+ "When a static, non-breathing glow is preferred (use a box-shadow or plain gradient instead)"
9821
+ ],
9822
+ "props": [
9823
+ {
9824
+ "name": "glowColor",
9825
+ "type": "string",
9826
+ "description": "CSS color value applied as --glow-color. Accepts any CSS color (hex, oklch, rgb, custom property reference). When omitted, --glow-color must be set by an ancestor for the glow to appear; the fallback is transparent.\n",
9827
+ "default": "undefined"
9828
+ }
9829
+ ],
9830
+ "dependencies": [
9831
+ "@loworbitstudio/visor-core"
9832
+ ],
9833
+ "tokens_used": [
9834
+ "--glow-color"
9835
+ ],
9836
+ "example": "{/* Static color */}\n<div style={{ position: 'relative', height: 400 }}>\n <HeroGlow glowColor=\"oklch(70% 0.3 145)\" />\n <img src=\"hero.jpg\" alt=\"Hero\" />\n</div>\n\n{/* Live rAF-driven color (no React re-render) */}\n<div\n ref={(el) => {\n if (!el) return;\n const glow = el.querySelector('[data-slot=\"hero-glow\"]');\n let frame: number;\n const tick = () => {\n glow?.style.setProperty('--glow-color', computeArtistColor());\n frame = requestAnimationFrame(tick);\n };\n frame = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(frame);\n }}\n style={{ position: 'relative' }}\n>\n <HeroGlow />\n <img src=\"hero.jpg\" alt=\"Hero\" />\n</div>\n"
9837
+ },
8890
9838
  "sphere": {
8891
9839
  "category": "visual-elements",
8892
9840
  "description": "GPU-accelerated particle sphere visualization with 256K particles, custom GLSL shaders, 6 geometry modes, 5 color schemes, and think-mode animation effects.",
@@ -8977,6 +9925,50 @@
8977
9925
  ],
8978
9926
  "tokens_used": [],
8979
9927
  "example": "<Sphere\n mode=\"sphere\"\n colorScheme=\"solar\"\n speed={1}\n style={{ width: \"100%\", height: \"400px\" }}\n/>\n"
9928
+ },
9929
+ "vignette": {
9930
+ "category": "visual-elements",
9931
+ "description": "Fixed, full-viewport radial vignette layer. Decorative atmosphere atom — pointer-events-none, aria-hidden, static. Ported from Blacklight bl-vignette (BL-326).",
9932
+ "when_to_use": [
9933
+ "Adding cinematic depth to dark-background hero sections",
9934
+ "Creating stage-lighting atmosphere on full-viewport canvases",
9935
+ "Softening hard edges on near-black page backgrounds"
9936
+ ],
9937
+ "when_not_to_use": [
9938
+ "Light-mode pages (the dark falloff color will be jarring)",
9939
+ "Content-heavy pages where the dark vignette obscures readable text at the edges",
9940
+ "When you need a scrolling (non-fixed) overlay — Vignette is always fixed"
9941
+ ],
9942
+ "props": [
9943
+ {
9944
+ "name": "zIndex",
9945
+ "type": "number",
9946
+ "default": 20,
9947
+ "description": "CSS z-index for the vignette layer"
9948
+ },
9949
+ {
9950
+ "name": "className",
9951
+ "type": "string",
9952
+ "description": "Additional class names"
9953
+ },
9954
+ {
9955
+ "name": "style",
9956
+ "type": "React.CSSProperties",
9957
+ "description": "Inline styles; use CSS custom properties to tune the gradient: --vignette-transparent-stop (52%), --vignette-color-stop (100%), --vignette-color (rgba(0,0,6,0.5)), --vignette-size-x (120%), --vignette-size-y (90%), --vignette-position (50% 38%)\n"
9958
+ }
9959
+ ],
9960
+ "dependencies": [
9961
+ "@loworbitstudio/visor-core"
9962
+ ],
9963
+ "tokens_used": [
9964
+ "--vignette-color",
9965
+ "--vignette-color-stop",
9966
+ "--vignette-position",
9967
+ "--vignette-size-x",
9968
+ "--vignette-size-y",
9969
+ "--vignette-transparent-stop"
9970
+ ],
9971
+ "example": "<div style={{ position: 'relative', height: '400px', background: '#020214' }}>\n <Vignette />\n</div>\n"
8980
9972
  }
8981
9973
  },
8982
9974
  "blocks": {
@@ -10188,6 +11180,7 @@
10188
11180
  "opacity-bar",
10189
11181
  "radius-scale",
10190
11182
  "spacing-scale",
11183
+ "specimen-card",
10191
11184
  "surface-row",
10192
11185
  "surface-scale-stack",
10193
11186
  "type-scale-stack",
@@ -10203,7 +11196,9 @@
10203
11196
  "key-value-list",
10204
11197
  "marquee",
10205
11198
  "name-roster",
11199
+ "segmented-progress",
10206
11200
  "sparkline",
11201
+ "spinner",
10207
11202
  "status-dot",
10208
11203
  "table",
10209
11204
  "timeline"
@@ -10227,6 +11222,7 @@
10227
11222
  "feedback": [
10228
11223
  "alert",
10229
11224
  "banner",
11225
+ "challenge-card",
10230
11226
  "popover",
10231
11227
  "progress",
10232
11228
  "skeleton",
@@ -10263,6 +11259,7 @@
10263
11259
  "chip",
10264
11260
  "color-picker",
10265
11261
  "combobox",
11262
+ "composer",
10266
11263
  "date-picker",
10267
11264
  "date-range-picker",
10268
11265
  "field",
@@ -10280,6 +11277,7 @@
10280
11277
  "select",
10281
11278
  "slider",
10282
11279
  "slider-control",
11280
+ "structured-prompt",
10283
11281
  "switch",
10284
11282
  "tag-input",
10285
11283
  "textarea",
@@ -10304,13 +11302,20 @@
10304
11302
  "menubar",
10305
11303
  "sheet"
10306
11304
  ],
10307
- "visual-elements": [
10308
- "sphere",
10309
- "station-spectrum"
10310
- ],
10311
11305
  "general": [
11306
+ "section-intro",
10312
11307
  "theme-switcher"
10313
11308
  ],
11309
+ "visual-elements": [
11310
+ "ambient-glow",
11311
+ "browser-frame",
11312
+ "card-lift",
11313
+ "grain-overlay",
11314
+ "hero-glow",
11315
+ "sphere",
11316
+ "station-spectrum",
11317
+ "vignette"
11318
+ ],
10314
11319
  "deck": [
10315
11320
  "deck-card-grid",
10316
11321
  "deck-carousel-gallery",
@@ -10375,7 +11380,12 @@
10375
11380
  "components": [
10376
11381
  "*"
10377
11382
  ]
10378
- }
11383
+ },
11384
+ "proof": [
11385
+ "Switch theme or mode and the entire surface re-resolves — nothing is pinned to a hard-coded value.",
11386
+ "Every component reads tokens, never literals: --primary flows to semantic, then adaptive, then the rendered pixel.",
11387
+ "Change one .visor.yaml file and the whole system follows — the file is the single source the cascade derives from."
11388
+ ]
10379
11389
  },
10380
11390
  {
10381
11391
  "id": "openness",
@@ -10386,7 +11396,12 @@
10386
11396
  "cli",
10387
11397
  "component-metadata"
10388
11398
  ]
10389
- }
11399
+ },
11400
+ "proof": [
11401
+ "An agent can discover, select, and compose a component from structured data alone — the manifest, when_to_use metadata, and an agent-first CLI.",
11402
+ "The source is open and free to take; the same file an engineer reads is the one an agent queries.",
11403
+ "Even brand strategy ships as readable data — this record — not a locked PDF."
11404
+ ]
10390
11405
  },
10391
11406
  {
10392
11407
  "id": "ownership",
@@ -10395,7 +11410,12 @@
10395
11410
  "components": [
10396
11411
  "*"
10397
11412
  ]
10398
- }
11413
+ },
11414
+ "proof": [
11415
+ "npx visor add button copies real source into your project — yours to edit, with nothing to eject from.",
11416
+ "Tokens still update via npm update, so you keep design consistency without surrendering control.",
11417
+ "Copy-and-own is the starting state, not an escape hatch — there's no wrapper to fight."
11418
+ ]
10399
11419
  }
10400
11420
  ],
10401
11421
  "voice": {
@@ -10483,7 +11503,64 @@
10483
11503
  "essence",
10484
11504
  "pillars"
10485
11505
  ],
10486
- "visibility": "public"
11506
+ "visibility": "public",
11507
+ "messaging": {
11508
+ "roof": "Design intent as data — all the way up to brand."
11509
+ },
11510
+ "taglines": [
11511
+ "Your entire brand system, created and encoded in one file."
11512
+ ],
11513
+ "boilerplate": {
11514
+ "short": "Visor is an open design system that compiles a complete brand — visual and verbal — from one portable file, legible to humans and agents alike. Components are yours to copy and own; shared tokens keep every layer coherent.",
11515
+ "long": "Visor is Low Orbit Studio's open design system, built on one idea: design intent should live as data — typed, portable, and machine-readable — from a single color token all the way up to a brand's voice. Components are copy-and-own, so `npx visor add` drops real source into your project for you to edit, while shared tokens keep design consistent across every app through `npm update`. A theme is a complete design system carried in one `.visor.yaml` file; change the file and the whole surface re-resolves, light to dark, one brand to another. And because that same file reads cleanly to people and agents alike, Visor is as legible to the engineer editing it as to the agent composing against it — coherent, open, and yours."
11516
+ },
11517
+ "colorUsage": {
11518
+ "pairings": [
11519
+ {
11520
+ "use": "--primary",
11521
+ "with": "--surface-card / --surface-base",
11522
+ "rule": "Primary is the one emphatic action per view — reserve it for the single most important action and let everything else recede to surfaces and text tokens."
11523
+ },
11524
+ {
11525
+ "use": "--accent",
11526
+ "with": "--primary",
11527
+ "rule": "Accent is a supporting highlight, never a second primary. Keep accent and primary visibly distinct — Visor flags them when they're near-twins, because a theme reads flat when they sit too close."
11528
+ },
11529
+ {
11530
+ "use": "--text-primary / --text-secondary",
11531
+ "with": "--surface-card / --surface-base",
11532
+ "rule": "Text always uses the semantic text tokens against a surface token, never raw hex, so contrast tracks the active theme instead of being pinned."
11533
+ },
11534
+ {
11535
+ "use": "--destructive",
11536
+ "with": "--surface-card",
11537
+ "rule": "Destructive is reserved for irreversible or error states; it is never a decorative or emphasis color."
11538
+ },
11539
+ {
11540
+ "use": "fallback neutral (Gray)",
11541
+ "with": "any un-themed surface",
11542
+ "rule": "Fallbacks use Gray, not Slate, so an un-themed component lands on a neutral that fits the palette instead of clashing."
11543
+ }
11544
+ ]
11545
+ },
11546
+ "accessibility": {
11547
+ "standard": "WCAG 2.1 AA",
11548
+ "contrast": [
11549
+ {
11550
+ "context": "Body text and other normal-size text",
11551
+ "ratio": "4.5:1"
11552
+ },
11553
+ {
11554
+ "context": "Large text (≥ 24px, or ≥ 18.66px bold)",
11555
+ "ratio": "3:1"
11556
+ },
11557
+ {
11558
+ "context": "Non-text UI — icons, focus rings, control boundaries",
11559
+ "ratio": "3:1"
11560
+ }
11561
+ ],
11562
+ "intent": "Every stock Visor theme is meant to clear WCAG 2.1 AA against these targets. The theme validator surfaces pairings that fall short as a candid, non-blocking warning — 'this theme fails AA on small text' — so the author can bump the contrast or keep the warning if it's intentional."
11563
+ }
10487
11564
  },
10488
11565
  "tokens": {
10489
11566
  "primitives": [