@duro-app/cli 5.12.0 → 5.13.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.
Files changed (2) hide show
  1. package/package.json +5 -5
  2. package/registry.json +290 -36
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/cli",
3
- "version": "5.12.0",
3
+ "version": "5.13.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Machine-queryable docs for the Duro design system: components, props, recipes, tokens and rules as CLI output, JSON, or an MCP server.",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@modelcontextprotocol/sdk": ">=1.0.0",
48
- "@duro-app/ui": "^5.12.0"
48
+ "@duro-app/ui": "^5.13.0"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@duro-app/ui": {
@@ -60,9 +60,9 @@
60
60
  "@types/node": "^24.0.0",
61
61
  "ts-morph": "^26.0.0",
62
62
  "typescript": "^5.7.0",
63
- "@duro-app/eslint-plugin": "^5.12.0",
64
- "@duro-app/tokens": "^5.12.0",
65
- "@duro-app/ui": "^5.12.0"
63
+ "@duro-app/eslint-plugin": "^5.13.0",
64
+ "@duro-app/ui": "^5.13.0",
65
+ "@duro-app/tokens": "^5.13.0"
66
66
  },
67
67
  "scripts": {
68
68
  "registry": "node scripts/build-registry.mjs",
package/registry.json CHANGED
@@ -190,7 +190,7 @@
190
190
  "Main"
191
191
  ]
192
192
  },
193
- "description": "An application's frame: a navigation rail (a labelled nav, the brand at its top, an optional footer at its foot) beside the main content (main), with an optional Header. Below collapseBelow (sm default, md or lg), measured on the shell's own width, the rail is hidden and one sticky bar (a banner, appBarH tall) holds a Menu button that opens the same navigation in a left Drawer, the brand, and the Header's content when there is one — a shell without a Header still has its navigation on a phone. Above it the Header is a row that scrolls with the page and the rail stays sticky. CSS makes the switch: the server renders one HTML for every width with the brand in both places, and display: none hides the inactive copy, so nothing moves after hydration. A skip link (the first tab stop) focuses Main; an Aside inside Main sticks below the bar. Labels come from props. Compound component — Root is required.",
193
+ "description": "An application's frame: a navigation rail (a labelled nav, the brand at its top, an optional footer at its foot) beside the main content (main), with an optional Header. Below collapseBelow (sm default, md or lg), measured on the shell's own width, the rail is hidden and one sticky bar (a banner, appBarH tall) holds a Menu button that opens the same navigation in a left Drawer, the brand, and the Header's content when there is one — a shell without a Header still has its navigation on a phone. Above it the Header is a row that scrolls with the page and the rail stays sticky. CSS makes the switch: the server renders one HTML for every width with the brand in both places, and display: none hides the inactive copy, so nothing moves after hydration. A skip link (the first tab stop) focuses Main; an Aside inside Main sticks below the bar. Labels come from props. Compound component — Root is required. On React Native it is always the narrow shape: the bar below the status bar, Main under it, and the Rail in a left Drawer; there is no skip link.",
194
194
  "example": "<AppShell.Root\n menuLabel=\"Menu\"\n closeLabel=\"Close navigation\"\n skipLabel=\"Skip to content\"\n brand={<TextLink href=\"/\">Acme</TextLink>}\n collapseBelow=\"md\"\n>\n <AppShell.Rail aria-label=\"Navigation\" footer={<Text variant=\"bodySm\">{user.name}</Text>}>\n {({close}) => (\n <SideNav.Root value={pathname} onValueChange={(v) => { close(); navigate(v) }}>\n <SideNav.Item value=\"/docs\">Docs</SideNav.Item>\n </SideNav.Root>\n )}\n </AppShell.Rail>\n <AppShell.Header>{search}</AppShell.Header>\n <AppShell.Main>{children}</AppShell.Main>\n</AppShell.Root>",
195
195
  "relatedTo": [
196
196
  {
@@ -225,7 +225,7 @@
225
225
  ]
226
226
  },
227
227
  "name": "AppShell",
228
- "native": false,
228
+ "native": true,
229
229
  "parts": {
230
230
  "Header": {
231
231
  "name": "Header",
@@ -2503,7 +2503,7 @@
2503
2503
  "Portal"
2504
2504
  ]
2505
2505
  },
2506
- "description": "Modal sliding panel from a screen edge (right, left, or bottom). Slides in with backdrop overlay. Supports swipe-to-dismiss gestures on mobile, backdrop click dismiss, and Escape key. Swipe yields to an inner Duro ScrollArea: a drag there scrolls it, and dismisses only toward the edge from the scroll start (a native overflow:auto child is not covered — use ScrollArea). Use for secondary workflows, settings panels, or mobile-friendly detail views.",
2506
+ "description": "Modal sliding panel from a screen edge (right, left, or bottom). Slides in with backdrop overlay. Supports swipe-to-dismiss gestures on mobile, backdrop click dismiss, and Escape key. Swipe yields to an inner Duro ScrollArea: a drag there scrolls it, and dismisses only toward the edge from the scroll start (a native overflow:auto child is not covered — use ScrollArea). A bottom drawer with snapPoints (medium and large) is a phone sheet: it rests at its detents, a grabber steps through them on a tap, a drag moves between them and dismisses below the lowest; its Body is the scroll the drag defers to. Header titleAlign=\"center\" centres the title with a text close at the end. Use for secondary workflows, settings panels, or mobile-friendly detail views.",
2507
2507
  "example": "<Drawer.Root anchor=\"right\" open={open} onOpenChange={setOpen}>\n <Drawer.Trigger>\n <Button>Open drawer</Button>\n </Drawer.Trigger>\n <Drawer.Portal size=\"md\">\n <Drawer.Header>\n <Drawer.Title>Settings</Drawer.Title>\n <Drawer.Close />\n </Drawer.Header>\n <Drawer.Body>\n <Stack gap=\"md\">\n <Text>Drawer content goes here.</Text>\n </Stack>\n </Drawer.Body>\n <Drawer.Footer>\n <Button variant=\"secondary\" onClick={() => setOpen(false)}>Done</Button>\n </Drawer.Footer>\n </Drawer.Portal>\n</Drawer.Root>",
2508
2508
  "relatedTo": [
2509
2509
  {
@@ -2531,12 +2531,13 @@
2531
2531
  "whenToUse": [
2532
2532
  "Settings or configuration panels that slide in from the side",
2533
2533
  "Mobile-friendly detail views (bottom sheet pattern)",
2534
+ "Phone pickers and lists that need more room on demand (snapPoints)",
2534
2535
  "Secondary workflows that need more space than a dialog",
2535
2536
  "Filters, navigation drawers, or form panels"
2536
2537
  ]
2537
2538
  },
2538
2539
  "name": "Drawer",
2539
- "native": false,
2540
+ "native": true,
2540
2541
  "parts": {
2541
2542
  "Body": {
2542
2543
  "name": "Body",
@@ -2575,7 +2576,7 @@
2575
2576
  "type": "ReactNode"
2576
2577
  }
2577
2578
  ],
2578
- "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
2579
+ "sourcePath": "packages/ui/src/components/Drawer/parts.tsx"
2579
2580
  },
2580
2581
  "Footer": {
2581
2582
  "name": "Footer",
@@ -2586,7 +2587,7 @@
2586
2587
  "type": "ReactNode"
2587
2588
  }
2588
2589
  ],
2589
- "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
2590
+ "sourcePath": "packages/ui/src/components/Drawer/parts.tsx"
2590
2591
  },
2591
2592
  "Header": {
2592
2593
  "name": "Header",
@@ -2595,9 +2596,16 @@
2595
2596
  "name": "children",
2596
2597
  "required": true,
2597
2598
  "type": "ReactNode"
2599
+ },
2600
+ {
2601
+ "default": "'start'",
2602
+ "description": "'center' puts the Title in the middle and the other children at the end, as a phone sheet does (a text close button on the right). Default: 'start'",
2603
+ "name": "titleAlign",
2604
+ "required": false,
2605
+ "type": "'start' | 'center'"
2598
2606
  }
2599
2607
  ],
2600
- "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
2608
+ "sourcePath": "packages/ui/src/components/Drawer/parts.tsx"
2601
2609
  },
2602
2610
  "Portal": {
2603
2611
  "name": "Portal",
@@ -2617,6 +2625,13 @@
2617
2625
  "md",
2618
2626
  "lg"
2619
2627
  ]
2628
+ },
2629
+ {
2630
+ "default": "DEFAULT_GRABBER_LABELS",
2631
+ "description": "The grabber's accessible names, by what a press does next. Snap sheets only",
2632
+ "name": "grabberLabels",
2633
+ "required": false,
2634
+ "type": "GrabberLabels"
2620
2635
  }
2621
2636
  ],
2622
2637
  "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
@@ -2636,7 +2651,6 @@
2636
2651
  "type": "boolean"
2637
2652
  },
2638
2653
  {
2639
- "default": "false",
2640
2654
  "description": "Default open state (uncontrolled)",
2641
2655
  "name": "defaultOpen",
2642
2656
  "required": false,
@@ -2649,7 +2663,6 @@
2649
2663
  "type": "(open: boolean) => void"
2650
2664
  },
2651
2665
  {
2652
- "default": "'right'",
2653
2666
  "description": "Which edge the drawer slides from. Default: 'right'",
2654
2667
  "name": "anchor",
2655
2668
  "required": false,
@@ -2661,21 +2674,18 @@
2661
2674
  ]
2662
2675
  },
2663
2676
  {
2664
- "default": "true",
2665
- "description": "Whether clicking backdrop or pressing Escape closes the drawer. Default: true",
2677
+ "description": "Whether clicking backdrop or pressing Escape (Android's back button on native) closes the drawer. Default: true",
2666
2678
  "name": "dismissable",
2667
2679
  "required": false,
2668
2680
  "type": "boolean"
2669
2681
  },
2670
2682
  {
2671
- "default": "true",
2672
2683
  "description": "Whether swipe gesture dismisses the drawer. Default: true",
2673
2684
  "name": "swipeDismiss",
2674
2685
  "required": false,
2675
2686
  "type": "boolean"
2676
2687
  },
2677
2688
  {
2678
- "default": "'fast'",
2679
2689
  "description": "Motion token for the close animation. Default: 'fast' (150ms)",
2680
2690
  "name": "closeAnimationDuration",
2681
2691
  "required": false,
@@ -2689,6 +2699,22 @@
2689
2699
  "base",
2690
2700
  "slow"
2691
2701
  ]
2702
+ },
2703
+ {
2704
+ "description": "Bottom anchor only: the heights the sheet rests at. 'medium' is half the window, 'large' the window less its top safe-area inset. A drag moves between them and dismisses below the lowest; the grabber steps through them on a tap. Without it, a bottom drawer is as tall as its content.",
2705
+ "name": "snapPoints",
2706
+ "required": false,
2707
+ "type": "readonly SnapPoint[]"
2708
+ },
2709
+ {
2710
+ "description": "The detent the sheet opens at. Default: the lowest of snapPoints",
2711
+ "name": "defaultSnapPoint",
2712
+ "required": false,
2713
+ "type": "SnapPoint",
2714
+ "union": [
2715
+ "medium",
2716
+ "large"
2717
+ ]
2692
2718
  }
2693
2719
  ],
2694
2720
  "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
@@ -2702,7 +2728,7 @@
2702
2728
  "type": "ReactNode"
2703
2729
  }
2704
2730
  ],
2705
- "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
2731
+ "sourcePath": "packages/ui/src/components/Drawer/parts.tsx"
2706
2732
  },
2707
2733
  "Trigger": {
2708
2734
  "name": "Trigger",
@@ -4347,7 +4373,7 @@
4347
4373
  "Item"
4348
4374
  ]
4349
4375
  },
4350
- "description": "Dropdown action menu. Triggers actions (not value selection). Compound component — Root is required (throws without it). Menu.Trigger renders the button itself: give it the label (text, an Icon), never a Button; an icon-only trigger takes aria-label, and variant=\"ghost\" suits toolbars; size=\"small\" matches small Toggles and Buttons (a small ghost trigger is iconButtonSm). The popup is portalled and placed against the trigger (never clipped by a sticky or overflow ancestor), capped at sizes.listMaxH with scrolling, and takes focus on open (WAI-ARIA menu button); Escape closes only the menu, not an enclosing Popover or Dialog.",
4376
+ "description": "Dropdown action menu. Triggers actions (not value selection). Compound component — Root is required (throws without it). Menu.Trigger renders the button itself: give it the label (text, an Icon), never a Button; an icon-only trigger takes aria-label, and variant=\"ghost\" suits toolbars; size=\"small\" matches small Toggles and Buttons (a small ghost trigger is iconButtonSm). The popup is portalled and placed against the trigger (never clipped by a sticky or overflow ancestor), capped at sizes.listMaxH with scrolling, and takes focus on open (WAI-ARIA menu button); Escape closes only the menu, not an enclosing Popover or Dialog. On React Native the popup is placed against the trigger in a modal overlay; a press outside or the Android back button closes it, and an item runs its onClick after the menu has gone (so an item can open a Dialog). Menu.LinkItem takes onNavigate(href, event) on both platforms (ADR-0002); on native, without it or without preventDefault, the link opens with the system. Native has no arrow keys: each item is its own screen-reader stop.",
4351
4377
  "example": "<Menu.Root>\n <Menu.Trigger>Actions</Menu.Trigger>\n <Menu.Popup>\n <Menu.Item onClick={() => console.log('edit')}>Edit</Menu.Item>\n <Menu.Item onClick={() => console.log('duplicate')}>Duplicate</Menu.Item>\n <Menu.Separator />\n <Menu.LinkItem href=\"/settings\">Settings</Menu.LinkItem>\n </Menu.Popup>\n</Menu.Root>",
4352
4378
  "relatedTo": [
4353
4379
  {
@@ -4374,7 +4400,7 @@
4374
4400
  ]
4375
4401
  },
4376
4402
  "name": "Menu",
4377
- "native": false,
4403
+ "native": true,
4378
4404
  "parts": {
4379
4405
  "Item": {
4380
4406
  "name": "Item",
@@ -4400,6 +4426,12 @@
4400
4426
  "required": true,
4401
4427
  "type": "string"
4402
4428
  },
4429
+ {
4430
+ "description": "Called for a plain primary click (ADR-0002): call `event.preventDefault()`, then your router's navigate. Without it the browser follows the link.",
4431
+ "name": "onNavigate",
4432
+ "required": false,
4433
+ "type": "OnNavigate"
4434
+ },
4403
4435
  {
4404
4436
  "name": "children",
4405
4437
  "required": true,
@@ -4964,7 +4996,7 @@
4964
4996
  "Popup"
4965
4997
  ]
4966
4998
  },
4967
- "description": "Non-modal anchored overlay for small interactive content. Compound component — Root is required (throws without it). Anchored to Popover.Trigger, or to a virtual anchor (a () => DOMRect) when there is no Trigger; call reposition() through a ref when the anchor moves. Popover.Trigger takes size=\"small\" beside small Toggles. Popover.Popup raised draws it over another floating surface (floatingRaised, popoverRaised in a modal), never over a modal.",
4999
+ "description": "Non-modal anchored overlay for small interactive content. Compound component — Root is required (throws without it). Anchored to Popover.Trigger, or to a virtual anchor (a () => DOMRect) when there is no Trigger; call reposition() through a ref when the anchor moves. Popover.Trigger takes size=\"small\" beside small Toggles. Popover.Popup raised draws it over another floating surface (floatingRaised, popoverRaised in a modal), never over a modal. On React Native the popup is modal: it is placed against Popover.Trigger in an overlay, a press outside or the Android back button closes it, and screen-reader focus moves in on open and back to the trigger on close; anchor, ignore and raised are web only.",
4968
5000
  "example": "const ref = useRef<PopoverHandle>(null)\nconst anchor = () => new DOMRect(point.x, point.y, 0, 0)\n\n<Popover.Root ref={ref} open={open} onOpenChange={setOpen} anchor={anchor} side=\"bottom\" align=\"start\">\n <Popover.Popup label=\"Edit label\">\n <Field.Root>\n <Field.Label>Label</Field.Label>\n <Input value={label} onChange={(e) => setLabel(e.target.value)} />\n </Field.Root>\n <Popover.Close>Done</Popover.Close>\n </Popover.Popup>\n</Popover.Root>\n// after the anchor moves: ref.current?.reposition()",
4969
5001
  "relatedTo": [
4970
5002
  {
@@ -5018,7 +5050,7 @@
5018
5050
  ]
5019
5051
  },
5020
5052
  "name": "Popover",
5021
- "native": false,
5053
+ "native": true,
5022
5054
  "parts": {
5023
5055
  "Close": {
5024
5056
  "name": "Close",
@@ -5613,7 +5645,7 @@
5613
5645
  "Item"
5614
5646
  ]
5615
5647
  },
5616
- "description": "Vertical side navigation. `Section` is a labelled block that is always open — the default for primary navigation. `Group` is the same block behind a chevron (a disclosure) — reach for it only for a rare/advanced region, an unbounded data-driven list, or a rail long enough that flat stops reading as an overview; mixing the two in one rail is the healthy shape. `Item`s take an optional `icon` and show an active left-marker. Supports controlled and uncontrolled active item. Compound component — Root is required.",
5648
+ "description": "Vertical side navigation. `Section` is a labelled block that is always open — the default for primary navigation. `Group` is the same block behind a chevron (a disclosure) — reach for it only for a rare/advanced region, an unbounded data-driven list, or a rail long enough that flat stops reading as an overview; mixing the two in one rail is the healthy shape. `Item`s take an optional `icon` and show an active left-marker. Supports controlled and uncontrolled active item. Compound component — Root is required. On React Native the same parts render, and a Group knows whether it holds the active item from its Items, not from the DOM.",
5617
5649
  "example": "<SideNav.Root defaultValue=\"identities\">\n {/* Default: always-open sections. Nothing is a click away from being seen. */}\n <SideNav.Section label=\"People & access\">\n <SideNav.Item value=\"identities\" icon={<Icon name=\"users\" size=\"md\" />}>Identities</SideNav.Item>\n <SideNav.Item value=\"grants\" icon={<Icon name=\"key\" size=\"md\" />}>Grants</SideNav.Item>\n </SideNav.Section>\n {/* Disclosure, earned: rarely visited, so it starts collapsed. */}\n <SideNav.Group label=\"Advanced\" defaultExpanded={false}>\n <SideNav.Item value=\"plugins\" icon={<Icon name=\"plug\" size=\"md\" />}>Plugins</SideNav.Item>\n </SideNav.Group>\n</SideNav.Root>",
5618
5650
  "relatedTo": [
5619
5651
  {
@@ -5646,7 +5678,7 @@
5646
5678
  ]
5647
5679
  },
5648
5680
  "name": "SideNav",
5649
- "native": false,
5681
+ "native": true,
5650
5682
  "parts": {
5651
5683
  "Group": {
5652
5684
  "name": "Group",
@@ -5787,6 +5819,130 @@
5787
5819
  ],
5788
5820
  "sourcePath": "packages/ui/src/components/Spinner/Spinner.tsx"
5789
5821
  },
5822
+ "SplitPane": {
5823
+ "importPath": "@duro-app/ui",
5824
+ "kind": "compound",
5825
+ "meta": {
5826
+ "anatomy": {
5827
+ "optional": [
5828
+ "Handle"
5829
+ ],
5830
+ "required": [
5831
+ "Root",
5832
+ "Pane"
5833
+ ]
5834
+ },
5835
+ "description": "Two panes side by side with a resizable separator: the first Pane is sized by the Handle (pointer drag, arrow keys, Shift for larger steps, Home and End), the second takes the rest. Min, max and starting widths are size tokens. Below collapseBelow, measured on its own width, the panes stack in one column and the handle goes; on native they always stack. The Handle is a hairline that thickens to the accent on hover, keyboard focus or drag.",
5836
+ "example": "<SplitPane.Root aria-label=\"Resize the session list\" minSize=\"sidebarW\" maxSize=\"panelLg\">\n <SplitPane.Pane>\n <List.Root aria-label=\"Sessions\">…</List.Root>\n </SplitPane.Pane>\n <SplitPane.Handle />\n <SplitPane.Pane>\n <Panel.Root>…</Panel.Root>\n </SplitPane.Pane>\n</SplitPane.Root>",
5837
+ "relatedTo": [
5838
+ {
5839
+ "component": "Grid",
5840
+ "kind": "contrast",
5841
+ "relationship": "Grid layout=\"split\" is a fixed split; SplitPane lets people resize it"
5842
+ },
5843
+ {
5844
+ "component": "DetailPanel",
5845
+ "kind": "contrast",
5846
+ "relationship": "DetailPanel overlays the list; SplitPane keeps both side by side"
5847
+ }
5848
+ ],
5849
+ "whenNotToUse": [
5850
+ "A fixed list/detail layout nobody resizes — use Grid layout=\"split\"",
5851
+ "Navigation beside content — use AppShell or the page-with-sidenav recipe",
5852
+ "A short-lived inspection over a list — use DetailPanel"
5853
+ ],
5854
+ "whenToUse": [
5855
+ "A list beside its detail where people need more room on either side (a session list and its transcript)",
5856
+ "An editor beside its preview"
5857
+ ]
5858
+ },
5859
+ "name": "SplitPane",
5860
+ "native": true,
5861
+ "parts": {
5862
+ "Handle": {
5863
+ "name": "Handle",
5864
+ "props": [],
5865
+ "sourcePath": "packages/ui/src/components/SplitPane/SplitPane.tsx"
5866
+ },
5867
+ "Pane": {
5868
+ "name": "Pane",
5869
+ "props": [
5870
+ {
5871
+ "name": "children",
5872
+ "required": true,
5873
+ "type": "ReactNode"
5874
+ }
5875
+ ],
5876
+ "sourcePath": "packages/ui/src/components/SplitPane/SplitPane.tsx"
5877
+ },
5878
+ "Root": {
5879
+ "name": "Root",
5880
+ "props": [
5881
+ {
5882
+ "name": "children",
5883
+ "required": true,
5884
+ "type": "ReactNode"
5885
+ },
5886
+ {
5887
+ "default": "'sidebarW'",
5888
+ "description": "The primary (first) pane's starting width, a size token. Default: `sidebarW`",
5889
+ "name": "defaultSize",
5890
+ "required": false,
5891
+ "type": "SizeToken"
5892
+ },
5893
+ {
5894
+ "description": "Controlled width of the primary pane, in px (from `onSizeChange`)",
5895
+ "name": "size",
5896
+ "required": false,
5897
+ "type": "number"
5898
+ },
5899
+ {
5900
+ "description": "Called with the primary pane's new width while the handle moves",
5901
+ "name": "onSizeChange",
5902
+ "required": false,
5903
+ "type": "(px: number) => void"
5904
+ },
5905
+ {
5906
+ "default": "'sidebarW'",
5907
+ "description": "The primary pane's narrowest width, a size token. Default: `sidebarW`",
5908
+ "name": "minSize",
5909
+ "required": false,
5910
+ "type": "SizeToken"
5911
+ },
5912
+ {
5913
+ "default": "'panelLg'",
5914
+ "description": "The primary pane's widest width, a size token. Default: `panelLg`",
5915
+ "name": "maxSize",
5916
+ "required": false,
5917
+ "type": "SizeToken"
5918
+ },
5919
+ {
5920
+ "default": "'sm'",
5921
+ "description": "Below this breakpoint, measured on the split's own width, the panes stack. Default: `sm`",
5922
+ "name": "collapseBelow",
5923
+ "required": false,
5924
+ "type": "Breakpoint",
5925
+ "union": [
5926
+ "xs",
5927
+ "sm",
5928
+ "md",
5929
+ "lg",
5930
+ "xl"
5931
+ ]
5932
+ },
5933
+ {
5934
+ "description": "The handle's accessible name (\"Resize list\").",
5935
+ "name": "aria-label",
5936
+ "required": true,
5937
+ "type": "string"
5938
+ }
5939
+ ],
5940
+ "sourcePath": "packages/ui/src/components/SplitPane/SplitPane.tsx"
5941
+ }
5942
+ },
5943
+ "props": null,
5944
+ "sourcePath": "packages/ui/src/components/SplitPane/SplitPane.tsx"
5945
+ },
5790
5946
  "Stack": {
5791
5947
  "importPath": "@duro-app/ui",
5792
5948
  "kind": "component",
@@ -6680,7 +6836,7 @@
6680
6836
  "Panel"
6681
6837
  ]
6682
6838
  },
6683
- "description": "Tabbed interface with keyboard navigation. Supports horizontal and vertical orientation. Compound component — Root is required.",
6839
+ "description": "Tabbed interface with keyboard navigation. Supports horizontal and vertical orientation. Compound component — Root is required. On React Native the indicator follows each tab layout; there is no arrow-key navigation there, each tab is its own screen-reader stop.",
6684
6840
  "example": "<Tabs.Root defaultValue=\"general\">\n <Tabs.List>\n <Tabs.Tab value=\"general\">General</Tabs.Tab>\n <Tabs.Tab value=\"security\">Security</Tabs.Tab>\n <Tabs.Tab value=\"billing\" disabled>Billing</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panel value=\"general\">General settings content</Tabs.Panel>\n <Tabs.Panel value=\"security\">Security settings content</Tabs.Panel>\n <Tabs.Panel value=\"billing\">Billing content</Tabs.Panel>\n</Tabs.Root>",
6685
6841
  "relatedTo": [
6686
6842
  {
@@ -6705,7 +6861,7 @@
6705
6861
  ]
6706
6862
  },
6707
6863
  "name": "Tabs",
6708
- "native": false,
6864
+ "native": true,
6709
6865
  "parts": {
6710
6866
  "List": {
6711
6867
  "name": "List",
@@ -6796,7 +6952,7 @@
6796
6952
  "importPath": "@duro-app/ui",
6797
6953
  "kind": "component",
6798
6954
  "meta": {
6799
- "description": "Interactive tag/chip with optional remove button. Works standalone or inside TagGroup for collection management. Visually similar to Badge but supports interaction.",
6955
+ "description": "Interactive tag/chip with optional remove button. Works standalone or inside TagGroup for collection management. Visually similar to Badge but supports interaction. On React Native a Tag stands alone (TagGroup is web only): the label and, when removable, a remove button named from a string label.",
6800
6956
  "example": "<Tag removable onRemove={() => console.log('removed')}>\n user@example.com\n</Tag>",
6801
6957
  "relatedTo": [
6802
6958
  {
@@ -6821,7 +6977,7 @@
6821
6977
  ]
6822
6978
  },
6823
6979
  "name": "Tag",
6824
- "native": false,
6980
+ "native": true,
6825
6981
  "props": [
6826
6982
  {
6827
6983
  "description": "Value identifier — required when used inside TagGroup",
@@ -7621,7 +7777,7 @@
7621
7777
  "importPath": "@duro-app/ui",
7622
7778
  "kind": "provider",
7623
7779
  "meta": {
7624
- "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it).",
7780
+ "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it). On React Native the same API draws through the portal host above every open overlay and the keyboard, as a card with a status-coloured edge, its action and its close in one row; a sideways swipe dismisses, a press holds the countdown, and so does an active screen reader (WCAG 2.2.1).",
7625
7781
  "example": "function Example() {\n const {toast} = useToast()\n return (\n <Button onClick={() => toast({variant: 'success', message: 'Grant revoked', action: {label: 'Undo', onClick: restore}})}>\n Revoke\n </Button>\n )\n}",
7626
7782
  "relatedTo": [
7627
7783
  {
@@ -7648,7 +7804,7 @@
7648
7804
  ]
7649
7805
  },
7650
7806
  "name": "ToastProvider",
7651
- "native": false,
7807
+ "native": true,
7652
7808
  "props": [
7653
7809
  {
7654
7810
  "default": "'Dismiss'",
@@ -7954,7 +8110,7 @@
7954
8110
  "Trigger"
7955
8111
  ]
7956
8112
  },
7957
- "description": "Hover/focus tooltip that shows supplementary content. Compound component — Root is required (throws without it).",
8113
+ "description": "Hover/focus tooltip that shows supplementary content. Compound component — Root is required (throws without it). On touch (React Native) no bubble is drawn: a string content becomes the accessibility hint of the Duro trigger inside (Button, Toggle, Menu.Trigger, Popover.Trigger), read after its name. Keep tooltip text non-essential, and keep aria-label on icon-only triggers.",
7958
8114
  "example": "<Tooltip.Root content=\"Delete this item permanently\" placement=\"top\">\n <Tooltip.Trigger>\n <Button variant=\"danger\">Delete</Button>\n </Tooltip.Trigger>\n</Tooltip.Root>",
7959
8115
  "whenNotToUse": [
7960
8116
  "Critical information that must always be visible — use Text or Callout",
@@ -7966,7 +8122,7 @@
7966
8122
  ]
7967
8123
  },
7968
8124
  "name": "Tooltip",
7969
- "native": false,
8125
+ "native": true,
7970
8126
  "parts": {
7971
8127
  "Root": {
7972
8128
  "name": "Root",
@@ -8021,7 +8177,7 @@
8021
8177
  "Item"
8022
8178
  ]
8023
8179
  },
8024
- "description": "Hierarchy of items with single selection and expandable branches (the WAI-ARIA tree pattern): one tab stop, arrow keys, Home/End, typeahead. An Item with href renders its row as a real link (cmd-click opens a tab) outside the tab order; Enter/Space follows it, through onNavigate(href, event) when given. Compound component — Root is required.",
8180
+ "description": "Hierarchy of items with single selection and expandable branches (the WAI-ARIA tree pattern): one tab stop, arrow keys, Home/End, typeahead. An Item with href renders its row as a real link (cmd-click opens a tab) outside the tab order; Enter/Space follows it, through onNavigate(href, event) when given. Compound component — Root is required. On React Native selection, expansion and aria-level are the same; the keyboard model (arrows, Home/End, typeahead) is web only.",
8025
8181
  "example": "<Tree.Root aria-label=\"Blocs\" defaultExpanded={['energy']} onValueChange={setSelected}>\n <Tree.Item value=\"energy\" label=\"Core business — Energy\">\n <Tree.Item value=\"generation\" label=\"Generation\" meta=\"4 apps\" />\n <Tree.Item value=\"network\" label=\"Network operations\">\n <Tree.Item value=\"scada\" label=\"SCADA supervision\" />\n </Tree.Item>\n </Tree.Item>\n <Tree.Item value=\"finance\" label=\"Finance & steering\" />\n</Tree.Root>",
8026
8182
  "relatedTo": [
8027
8183
  {
@@ -8047,7 +8203,7 @@
8047
8203
  ]
8048
8204
  },
8049
8205
  "name": "Tree",
8050
- "native": false,
8206
+ "native": true,
8051
8207
  "parts": {
8052
8208
  "Item": {
8053
8209
  "name": "Item",
@@ -8157,6 +8313,92 @@
8157
8313
  "props": null,
8158
8314
  "sourcePath": "packages/ui/src/components/Tree/Tree.tsx"
8159
8315
  },
8316
+ "VirtualList": {
8317
+ "importPath": "@duro-app/ui",
8318
+ "kind": "component",
8319
+ "meta": {
8320
+ "anatomy": {
8321
+ "optional": [],
8322
+ "required": []
8323
+ },
8324
+ "description": "A windowed list on @tanstack/react-virtual: only the rows in view (plus overscan) are mounted, and rows are measured so they may differ in height. role=\"list\" with listitem rows carrying aria-setsize and aria-posinset. anchor=\"end\" opens at the last item and stays pinned to the bottom while new items arrive and the reader is at the bottom (a transcript, a log). It fills its parent, or stops at maxHeight. Web only: on native, host FlashList or Legend List behind your own .native.tsx leaf.",
8325
+ "example": "<VirtualList\n aria-label=\"Transcript\"\n items={messages}\n getKey={(m) => m.id}\n estimateSize=\"controlLg\"\n anchor=\"end\"\n renderItem={(m) => <Text>{m.text}</Text>}\n/>",
8326
+ "relatedTo": [
8327
+ {
8328
+ "component": "VirtualTable",
8329
+ "kind": "contrast",
8330
+ "relationship": "VirtualTable windows table rows with columns; VirtualList windows any row"
8331
+ },
8332
+ {
8333
+ "component": "List",
8334
+ "kind": "composition",
8335
+ "relationship": "Render a List.Item-like row in renderItem for the list look"
8336
+ }
8337
+ ],
8338
+ "whenNotToUse": [
8339
+ "Tabular data with columns and sorting — use VirtualTable",
8340
+ "A few dozen rows — use List; windowing only adds cost there",
8341
+ "Selectable options in a picker — use Listbox"
8342
+ ],
8343
+ "whenToUse": [
8344
+ "Hundreds to tens of thousands of rows: sessions, a chat transcript, log lines",
8345
+ "A list that grows at the bottom while the reader follows it (anchor=\"end\")"
8346
+ ]
8347
+ },
8348
+ "name": "VirtualList",
8349
+ "native": false,
8350
+ "props": [
8351
+ {
8352
+ "name": "items",
8353
+ "required": true,
8354
+ "type": "readonly T[]"
8355
+ },
8356
+ {
8357
+ "description": "Each row's starting height estimate, a size token; rows are then measured.",
8358
+ "name": "estimateSize",
8359
+ "required": true,
8360
+ "type": "SizeToken"
8361
+ },
8362
+ {
8363
+ "name": "renderItem",
8364
+ "required": true,
8365
+ "type": "(item: T, index: number) => ReactNode"
8366
+ },
8367
+ {
8368
+ "description": "A stable key per item (an id), so rows keep their measurement as items change.",
8369
+ "name": "getKey",
8370
+ "required": true,
8371
+ "type": "(item: T, index: number) => string | number"
8372
+ },
8373
+ {
8374
+ "default": "5",
8375
+ "description": "Rows rendered beyond each edge of the view. Default 5",
8376
+ "name": "overscan",
8377
+ "required": false,
8378
+ "type": "number"
8379
+ },
8380
+ {
8381
+ "default": "'start'",
8382
+ "description": "`end` starts at the last item and stays pinned to the bottom while new items arrive, as long as the reader is at the bottom (a transcript, a log). Default `start`.",
8383
+ "name": "anchor",
8384
+ "required": false,
8385
+ "type": "'start' | 'end'"
8386
+ },
8387
+ {
8388
+ "description": "Cap the list's height; without it, it fills its parent.",
8389
+ "name": "maxHeight",
8390
+ "required": false,
8391
+ "type": "Length"
8392
+ },
8393
+ {
8394
+ "description": "The list's accessible name.",
8395
+ "name": "aria-label",
8396
+ "required": true,
8397
+ "type": "string"
8398
+ }
8399
+ ],
8400
+ "sourcePath": "packages/ui/src/components/VirtualList/VirtualList.tsx"
8401
+ },
8160
8402
  "VirtualTable": {
8161
8403
  "importPath": "@duro-app/ui/table",
8162
8404
  "kind": "component",
@@ -8543,7 +8785,7 @@
8543
8785
  "importPath": "@duro-app/ui",
8544
8786
  "kind": "hook",
8545
8787
  "meta": {
8546
- "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it).",
8788
+ "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it). On React Native the same API draws through the portal host above every open overlay and the keyboard, as a card with a status-coloured edge, its action and its close in one row; a sideways swipe dismisses, a press holds the countdown, and so does an active screen reader (WCAG 2.2.1).",
8547
8789
  "example": "function Example() {\n const {toast} = useToast()\n return (\n <Button onClick={() => toast({variant: 'success', message: 'Grant revoked', action: {label: 'Undo', onClick: restore}})}>\n Revoke\n </Button>\n )\n}",
8548
8790
  "relatedTo": [
8549
8791
  {
@@ -8570,7 +8812,7 @@
8570
8812
  ]
8571
8813
  },
8572
8814
  "name": "useToast",
8573
- "native": false,
8815
+ "native": true,
8574
8816
  "props": [],
8575
8817
  "sourcePath": "packages/ui/src/components/Toast/ToastProvider.tsx"
8576
8818
  }
@@ -8963,7 +9205,7 @@
8963
9205
  "split-pane": {
8964
9206
  "export": "SplitPaneRecipe",
8965
9207
  "meta": {
8966
- "description": "List/detail split: a selectable List beside a Panel showing the selection, on Grid layout=\"split\" (list ≥ 240px, one column when the board is narrower than sm).",
9208
+ "description": "List/detail split: a selectable List beside a Panel showing the selection, in a SplitPane (the list starts at sidebarW, resizes up to panelLg with the handle, and the panes stack when the split is narrower than sm).",
8967
9209
  "example": "<SplitPaneRecipe />",
8968
9210
  "whenNotToUse": [
8969
9211
  "The detail is a short-lived inspection — use DetailPanel over the list instead",
@@ -8976,18 +9218,18 @@
8976
9218
  },
8977
9219
  "name": "split-pane",
8978
9220
  "peerDeps": [],
8979
- "source": "import {useState} from 'react'\nimport {Grid, List, Panel, Stack, Inline, Heading, Text, Badge, Button, EmptyState} from '@duro-app/ui'\n\nconst ITEMS = [\n {id: 'grafana', name: 'Grafana', owner: 'platform', status: 'active'},\n {id: 'vault', name: 'Vault', owner: 'security', status: 'active'},\n {id: 'legacy-crm', name: 'Legacy CRM', owner: 'sales', status: 'retiring'},\n] as const\n\nexport function SplitPaneRecipe() {\n const [selected, setSelected] = useState<string | null>(ITEMS[0].id)\n const item = ITEMS.find((candidate) => candidate.id === selected)\n\n return (\n <Grid layout=\"split\" gap=\"lg\">\n <List.Root selectionMode=\"single\" aria-label=\"Applications\">\n {ITEMS.map((candidate) => (\n <List.Item\n key={candidate.id}\n selected={candidate.id === selected}\n onClick={() => setSelected(candidate.id)}\n >\n <List.Content>\n <List.Text>{candidate.name}</List.Text>\n <List.Description>Owned by {candidate.owner}</List.Description>\n </List.Content>\n <List.Actions>\n <Badge variant={candidate.status === 'active' ? 'success' : 'warning'}>\n {candidate.status}\n </Badge>\n </List.Actions>\n </List.Item>\n ))}\n <List.Empty>No applications.</List.Empty>\n </List.Root>\n\n <Panel.Root bordered>\n {item ? (\n <>\n <Panel.Header>\n <Heading level={2}>{item.name}</Heading>\n <Inline gap=\"sm\">\n <Button variant=\"secondary\" size=\"small\">\n Edit\n </Button>\n <Button variant=\"danger\" size=\"small\">\n Retire\n </Button>\n </Inline>\n </Panel.Header>\n <Panel.Body>\n <Stack gap=\"md\">\n <Text>\n Owner: <Text weight=\"medium\">{item.owner}</Text>\n </Text>\n <Text color=\"muted\">\n Everything about the selection lives here: fields, history, related records.\n </Text>\n </Stack>\n </Panel.Body>\n </>\n ) : (\n <Panel.Body>\n <EmptyState message=\"Select an application to see its details.\" />\n </Panel.Body>\n )}\n </Panel.Root>\n </Grid>\n )\n}\n",
9221
+ "source": "import {useState} from 'react'\nimport {SplitPane, List, Panel, Stack, Inline, Heading, Text, Badge, Button, EmptyState} from '@duro-app/ui'\n\nconst ITEMS = [\n {id: 'grafana', name: 'Grafana', owner: 'platform', status: 'active'},\n {id: 'vault', name: 'Vault', owner: 'security', status: 'active'},\n {id: 'legacy-crm', name: 'Legacy CRM', owner: 'sales', status: 'retiring'},\n] as const\n\nexport function SplitPaneRecipe() {\n const [selected, setSelected] = useState<string | null>(ITEMS[0].id)\n const item = ITEMS.find((candidate) => candidate.id === selected)\n\n return (\n <SplitPane.Root aria-label=\"Resize the application list\" minSize=\"sidebarW\" maxSize=\"panelLg\">\n <SplitPane.Pane>\n <List.Root selectionMode=\"single\" aria-label=\"Applications\">\n {ITEMS.map((candidate) => (\n <List.Item\n key={candidate.id}\n selected={candidate.id === selected}\n onClick={() => setSelected(candidate.id)}\n >\n <List.Content>\n <List.Text>{candidate.name}</List.Text>\n <List.Description>Owned by {candidate.owner}</List.Description>\n </List.Content>\n <List.Actions>\n <Badge variant={candidate.status === 'active' ? 'success' : 'warning'}>\n {candidate.status}\n </Badge>\n </List.Actions>\n </List.Item>\n ))}\n <List.Empty>No applications.</List.Empty>\n </List.Root>\n </SplitPane.Pane>\n <SplitPane.Handle />\n <SplitPane.Pane>\n <Panel.Root bordered>\n {item ? (\n <>\n <Panel.Header>\n <Heading level={2}>{item.name}</Heading>\n <Inline gap=\"sm\">\n <Button variant=\"secondary\" size=\"small\">\n Edit\n </Button>\n <Button variant=\"danger\" size=\"small\">\n Retire\n </Button>\n </Inline>\n </Panel.Header>\n <Panel.Body>\n <Stack gap=\"md\">\n <Text>\n Owner: <Text weight=\"medium\">{item.owner}</Text>\n </Text>\n <Text color=\"muted\">\n Everything about the selection lives here: fields, history, related records.\n </Text>\n </Stack>\n </Panel.Body>\n </>\n ) : (\n <Panel.Body>\n <EmptyState message=\"Select an application to see its details.\" />\n </Panel.Body>\n )}\n </Panel.Root>\n </SplitPane.Pane>\n </SplitPane.Root>\n )\n}\n",
8980
9222
  "sourcePath": "docs/ai/recipes/split-pane.recipe.tsx",
8981
9223
  "title": "Split Pane",
8982
9224
  "usesComponents": [
8983
9225
  "Badge",
8984
9226
  "Button",
8985
9227
  "EmptyState",
8986
- "Grid",
8987
9228
  "Heading",
8988
9229
  "Inline",
8989
9230
  "List",
8990
9231
  "Panel",
9232
+ "SplitPane",
8991
9233
  "Stack",
8992
9234
  "Text"
8993
9235
  ]
@@ -10089,6 +10331,14 @@
10089
10331
  "key": "barH",
10090
10332
  "value": "4px"
10091
10333
  },
10334
+ {
10335
+ "key": "sheetGrabberW",
10336
+ "value": "40px"
10337
+ },
10338
+ {
10339
+ "key": "sheetGrabberH",
10340
+ "value": "4px"
10341
+ },
10092
10342
  {
10093
10343
  "key": "readoutW",
10094
10344
  "value": "40px"
@@ -10278,7 +10528,7 @@
10278
10528
  "importPath": "@duro-app/tokens/tokens/typography.css"
10279
10529
  }
10280
10530
  },
10281
- "mockupCss": "/*\n * GENERATED by scripts/generate-mockup-css.mjs — do not edit.\n *\n * The design tokens as resolved values, for HTML that loads no stylesheet\n * (mockup artboards, static previews). Same names as vars.css; where vars.css\n * follows the live theme, this file carries every theme and switches on\n * <html data-theme=\"light|high-contrast\">.\n *\n * Inside the component library and the apps, never use this: go through\n * css.create() and the token imports, or vars.css from plain CSS.\n */\n:root {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #6aaffc;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #0f0f0f;\n --duro-color-bg-card: #1a1a1a;\n --duro-color-bg-card-hover: #242424;\n --duro-color-border: #333333;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #f5f5f5;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.1);\n --duro-color-error-border: rgba(248, 113, 113, 0.3);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #c084fc;\n --duro-color-highlight-bg: rgba(192, 132, 252, 0.1);\n --duro-color-highlight-border: rgba(192, 132, 252, 0.3);\n --duro-color-highlight-text: #d8b4fe;\n --duro-color-info: #6aaffc;\n --duro-color-info-bg: rgba(106, 175, 252, 0.1);\n --duro-color-info-border: rgba(106, 175, 252, 0.3);\n --duro-color-info-text: #93c5fd;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #1a1a1a;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #22c55e;\n --duro-color-success-bg: rgba(34, 197, 94, 0.1);\n --duro-color-success-border: rgba(34, 197, 94, 0.3);\n --duro-color-success-text: #86efac;\n --duro-color-text: #e5e5e5;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fbbf24;\n --duro-color-warning-bg: rgba(251, 191, 36, 0.1);\n --duro-color-warning-border: rgba(251, 191, 36, 0.3);\n --duro-color-warning-text: #fde68a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #6aaffc;\n --duro-shadow-drop-ready: inset 0 0 0 1px #333333;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);\n /* spacing */\n --duro-spacing-lg: 24px;\n --duro-spacing-md: 16px;\n --duro-spacing-ms: 12px;\n --duro-spacing-sm: 8px;\n --duro-spacing-xl: 32px;\n --duro-spacing-xs: 4px;\n --duro-spacing-xxl: 48px;\n --duro-spacing-xxxl: 64px;\n /* microSpacing */\n --duro-micro-spacing-px1: 1px;\n --duro-micro-spacing-px2: 2px;\n --duro-micro-spacing-px3: 3px;\n --duro-micro-spacing-px5: 5px;\n --duro-micro-spacing-px6: 6px;\n /* radii */\n --duro-radius-full: 9999px;\n --duro-radius-lg: 16px;\n --duro-radius-md: 12px;\n --duro-radius-px6: 6px;\n --duro-radius-sm: 8px;\n --duro-radius-xs: 4px;\n /* sizes */\n --duro-size-app-bar-h: 61px;\n --duro-size-aside-w: 320px;\n --duro-size-bar-h: 4px;\n --duro-size-canvas-min-h: 480px;\n --duro-size-check-mark-h: 9px;\n --duro-size-check-mark-w: 5px;\n --duro-size-chip: 24px;\n --duro-size-color-area-h: 150px;\n --duro-size-color-picker-w: 196px;\n --duro-size-color-preview-h: 22px;\n --duro-size-color-swatch: 20px;\n --duro-size-color-track-h: 12px;\n --duro-size-control-lg: 40px;\n --duro-size-control-md: 39px;\n --duro-size-control-sm: 28px;\n --duro-size-day-header-h: 46px;\n --duro-size-device-bar-w: 96px;\n --duro-size-dialog-lg: 680px;\n --duro-size-dialog-md: 520px;\n --duro-size-dialog-sm: 400px;\n --duro-size-divider: 1px;\n --duro-size-drag-thumb-h: 99px;\n --duro-size-drag-thumb-w: 132px;\n --duro-size-drop-zone-min-h: 128px;\n --duro-size-edge-fade: 32px;\n --duro-size-editor-min-h: 160px;\n --duro-size-embed-w: 550px;\n --duro-size-field-min-w: 160px;\n --duro-size-field-min-w-sm: 80px;\n --duro-size-glyph-clamp-max: 160px;\n --duro-size-glyph-md: 16px;\n --duro-size-glyph-sm: 12px;\n --duro-size-glyph-xs: 10px;\n --duro-size-grid-col-md: 280px;\n --duro-size-grid-col-sm: 240px;\n --duro-size-grid-col-xs: 200px;\n --duro-size-handle: 16px;\n --duro-size-icon-button: 32px;\n --duro-size-icon-button-sm: 28px;\n --duro-size-icon-lg: 24px;\n --duro-size-icon-md: 18px;\n --duro-size-icon-sm: 16px;\n --duro-size-icon-xl: 36px;\n --duro-size-icon-xxl: 48px;\n --duro-size-indicator: 18px;\n --duro-size-indicator-dot: 8px;\n --duro-size-label-min-w: 120px;\n --duro-size-list-max-h: 280px;\n --duro-size-list-max-h-sm: 200px;\n --duro-size-media-tile-min-w: 108px;\n --duro-size-meter-h: 6px;\n --duro-size-nav-marker-h: 18px;\n --duro-size-nav-marker-w: 3px;\n --duro-size-page-lg: 1200px;\n --duro-size-page-md: 800px;\n --duro-size-page-sm: 600px;\n --duro-size-page-xl: 1440px;\n --duro-size-page-xs: 480px;\n --duro-size-palette-min-w: 96px;\n --duro-size-panel-lg: 640px;\n --duro-size-panel-md: 480px;\n --duro-size-panel-sm: 360px;\n --duro-size-placeholder-min-h: 80px;\n --duro-size-popover-w: 320px;\n --duro-size-popover-w-sm: 240px;\n --duro-size-popup-max-w: 280px;\n --duro-size-popup-min-w: 160px;\n --duro-size-preview-max-h: 320px;\n --duro-size-readout-w: 40px;\n --duro-size-scrollbar: 8px;\n --duro-size-sidebar-w: 240px;\n --duro-size-skeleton-chip-w: 96px;\n --duro-size-slider-w: 96px;\n --duro-size-spinner-lg: 40px;\n --duro-size-spinner-md: 24px;\n --duro-size-spinner-sm: 16px;\n --duro-size-swatch-h: 34px;\n --duro-size-swatch-w: 44px;\n --duro-size-switch-thumb: 16px;\n --duro-size-switch-track-h: 20px;\n --duro-size-switch-track-w: 36px;\n --duro-size-tab-indicator: 2px;\n --duro-size-thumb-sm: 40px;\n --duro-size-time-gutter-w: 64px;\n --duro-size-timeline-label-w: 180px;\n --duro-size-time-readout-w: 74px;\n --duro-size-toast-max-w: 440px;\n --duro-size-toolbar-h: 36px;\n --duro-size-touch-target: 44px;\n /* borders */\n --duro-border-accent: 3px;\n --duro-border-focus-offset: 2px;\n --duro-border-focus-offset-sm: 1px;\n --duro-border-focus-ring: 2px;\n --duro-border-hairline: 1px;\n --duro-border-strong: 2px;\n /* layers */\n --duro-layer-floating: 50;\n --duro-layer-floating-raised: 60;\n --duro-layer-modal: 1001;\n --duro-layer-modal-raised: 1002;\n --duro-layer-overlay: 1000;\n --duro-layer-popover: 1040;\n --duro-layer-popover-raised: 1041;\n --duro-layer-popup: 1050;\n --duro-layer-popup-backdrop: 1049;\n --duro-layer-portal: 1100;\n --duro-layer-raised: 1;\n --duro-layer-toast: 1060;\n /* effects */\n --duro-effect-overlay-blur: blur(2px);\n --duro-effect-surface-blur: blur(6px);\n /* layoutSpacing */\n --duro-layout-spacing-container-lg: 32px;\n --duro-layout-spacing-container-md: 24px;\n --duro-layout-spacing-container-sm: 16px;\n --duro-layout-spacing-inline-lg: 24px;\n --duro-layout-spacing-inline-md: 16px;\n --duro-layout-spacing-inline-sm: 8px;\n --duro-layout-spacing-inline-xs: 4px;\n --duro-layout-spacing-stack-lg: 24px;\n --duro-layout-spacing-stack-md: 16px;\n --duro-layout-spacing-stack-sm: 8px;\n --duro-layout-spacing-stack-xl: 48px;\n --duro-layout-spacing-stack-xs: 4px;\n /* typography */\n --duro-typography-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n --duro-typography-font-family-mono: \"SF Mono\", \"Fira Code\", \"Fira Mono\", \"Roboto Mono\", \"Courier New\", monospace;\n --duro-typography-font-size-heading: 1.5rem;\n --duro-typography-font-size-lg: 1.125rem;\n --duro-typography-font-size-md: 1rem;\n --duro-typography-font-size-sm: 0.875rem;\n --duro-typography-font-size-xl: 1.25rem;\n --duro-typography-font-size-xs: 0.75rem;\n --duro-typography-font-weight-bold: 700;\n --duro-typography-font-weight-medium: 500;\n --duro-typography-font-weight-normal: 400;\n --duro-typography-font-weight-semibold: 600;\n --duro-typography-line-height: 1.5;\n /* typeScale */\n --duro-type-scale-display-lg: clamp(3.5rem, 2rem + 3.5vw, 4.5rem);\n --duro-type-scale-display-md: clamp(2.75rem, 1.75rem + 2.5vw, 3.75rem);\n --duro-type-scale-display-sm: clamp(2.25rem, 1.5rem + 2vw, 3rem);\n --duro-type-scale-font-size1: 0.75rem;\n --duro-type-scale-font-size2: 0.8125rem;\n --duro-type-scale-font-size3: 0.875rem;\n --duro-type-scale-font-size4: 1rem;\n --duro-type-scale-font-size5: 1.125rem;\n --duro-type-scale-font-size6: 1.25rem;\n --duro-type-scale-font-size7: 1.5rem;\n --duro-type-scale-font-size8: 1.875rem;\n --duro-type-scale-font-size9: 2.25rem;\n --duro-type-scale-letter-spacing-normal: 0;\n --duro-type-scale-letter-spacing-tight: -0.02em;\n --duro-type-scale-letter-spacing-wide: 0.04em;\n --duro-type-scale-line-height1: 1rem;\n --duro-type-scale-line-height2: 1.25rem;\n --duro-type-scale-line-height3: 1.25rem;\n --duro-type-scale-line-height4: 1.5rem;\n --duro-type-scale-line-height5: 1.5rem;\n --duro-type-scale-line-height6: 1.75rem;\n --duro-type-scale-line-height7: 2rem;\n --duro-type-scale-line-height8: 2.25rem;\n --duro-type-scale-line-height9: 2.75rem;\n /* duration */\n --duro-duration-base: 200ms;\n --duro-duration-brisk: 160ms;\n --duro-duration-fast: 150ms;\n --duro-duration-instant: 0ms;\n --duro-duration-minimal: 1ms;\n --duro-duration-quick: 120ms;\n --duro-duration-slow: 280ms;\n /* easing */\n --duro-easing-ease-in: cubic-bezier(0.72, 0, 0.68, 0.28);\n --duro-easing-ease-out: cubic-bezier(0.32, 0.72, 0, 1);\n --duro-easing-linear: linear;\n --duro-easing-standard: ease;\n /* breakpoints */\n --duro-breakpoint-lg: 1024px;\n --duro-breakpoint-md: 768px;\n --duro-breakpoint-sm: 640px;\n --duro-breakpoint-xl: 1280px;\n --duro-breakpoint-xs: 480px;\n}\n\n:root[data-theme=\"light\"] {\n color-scheme: light;\n /* colors */\n --duro-color-accent: #1e40af;\n --duro-color-accent-contrast: #ffffff;\n --duro-color-accent-hover: #1a3799;\n --duro-color-bg: #ffffff;\n --duro-color-bg-card: #f5f5f5;\n --duro-color-bg-card-hover: #ebebeb;\n --duro-color-border: #d4d4d4;\n --duro-color-contrast-border: rgba(255, 255, 255, 0.33);\n --duro-color-contrast-surface: #1a1a1a;\n --duro-color-error: #991b1b;\n --duro-color-error-bg: rgba(153, 27, 27, 0.08);\n --duro-color-error-border: rgba(153, 27, 27, 0.3);\n --duro-color-error-contrast: #ffffff;\n --duro-color-error-hover: #7f1d1d;\n --duro-color-error-text: #7f1d1d;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #6b21a8;\n --duro-color-highlight-bg: rgba(107, 33, 168, 0.08);\n --duro-color-highlight-border: rgba(107, 33, 168, 0.3);\n --duro-color-highlight-text: #581c87;\n --duro-color-info: #1e40af;\n --duro-color-info-bg: rgba(30, 64, 175, 0.08);\n --duro-color-info-border: rgba(30, 64, 175, 0.3);\n --duro-color-info-text: #1e40af;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #e5e5e5;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #166534;\n --duro-color-success-bg: rgba(22, 101, 52, 0.08);\n --duro-color-success-border: rgba(22, 101, 52, 0.3);\n --duro-color-success-text: #14532d;\n --duro-color-text: #1a1a1a;\n --duro-color-text-muted: #4a4a4a;\n --duro-color-warning: #92400e;\n --duro-color-warning-bg: rgba(146, 64, 14, 0.08);\n --duro-color-warning-border: rgba(146, 64, 14, 0.3);\n --duro-color-warning-text: #78350f;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #1e40af;\n --duro-shadow-drop-ready: inset 0 0 0 1px #d4d4d4;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.08);\n}\n\n:root[data-theme=\"high-contrast\"] {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #60a5fa;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #000000;\n --duro-color-bg-card: #111111;\n --duro-color-bg-card-hover: #1a1a1a;\n --duro-color-border: #555555;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #ffffff;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.15);\n --duro-color-error-border: rgba(248, 113, 113, 0.5);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #d8b4fe;\n --duro-color-highlight-bg: rgba(216, 180, 254, 0.15);\n --duro-color-highlight-border: rgba(216, 180, 254, 0.5);\n --duro-color-highlight-text: #e9d5ff;\n --duro-color-info: #60a5fa;\n --duro-color-info-bg: rgba(96, 165, 250, 0.15);\n --duro-color-info-border: rgba(96, 165, 250, 0.5);\n --duro-color-info-text: #bfdbfe;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #000000;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #4ade80;\n --duro-color-success-bg: rgba(74, 222, 128, 0.15);\n --duro-color-success-border: rgba(74, 222, 128, 0.5);\n --duro-color-success-text: #86efac;\n --duro-color-text: #ffffff;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fcd34d;\n --duro-color-warning-bg: rgba(252, 211, 77, 0.15);\n --duro-color-warning-border: rgba(252, 211, 77, 0.5);\n --duro-color-warning-text: #fef08a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #60a5fa;\n --duro-shadow-drop-ready: inset 0 0 0 1px #555555;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.8);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.7);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.6);\n}\n"
10531
+ "mockupCss": "/*\n * GENERATED by scripts/generate-mockup-css.mjs — do not edit.\n *\n * The design tokens as resolved values, for HTML that loads no stylesheet\n * (mockup artboards, static previews). Same names as vars.css; where vars.css\n * follows the live theme, this file carries every theme and switches on\n * <html data-theme=\"light|high-contrast\">.\n *\n * Inside the component library and the apps, never use this: go through\n * css.create() and the token imports, or vars.css from plain CSS.\n */\n:root {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #6aaffc;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #0f0f0f;\n --duro-color-bg-card: #1a1a1a;\n --duro-color-bg-card-hover: #242424;\n --duro-color-border: #333333;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #f5f5f5;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.1);\n --duro-color-error-border: rgba(248, 113, 113, 0.3);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #c084fc;\n --duro-color-highlight-bg: rgba(192, 132, 252, 0.1);\n --duro-color-highlight-border: rgba(192, 132, 252, 0.3);\n --duro-color-highlight-text: #d8b4fe;\n --duro-color-info: #6aaffc;\n --duro-color-info-bg: rgba(106, 175, 252, 0.1);\n --duro-color-info-border: rgba(106, 175, 252, 0.3);\n --duro-color-info-text: #93c5fd;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #1a1a1a;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #22c55e;\n --duro-color-success-bg: rgba(34, 197, 94, 0.1);\n --duro-color-success-border: rgba(34, 197, 94, 0.3);\n --duro-color-success-text: #86efac;\n --duro-color-text: #e5e5e5;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fbbf24;\n --duro-color-warning-bg: rgba(251, 191, 36, 0.1);\n --duro-color-warning-border: rgba(251, 191, 36, 0.3);\n --duro-color-warning-text: #fde68a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #6aaffc;\n --duro-shadow-drop-ready: inset 0 0 0 1px #333333;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);\n /* spacing */\n --duro-spacing-lg: 24px;\n --duro-spacing-md: 16px;\n --duro-spacing-ms: 12px;\n --duro-spacing-sm: 8px;\n --duro-spacing-xl: 32px;\n --duro-spacing-xs: 4px;\n --duro-spacing-xxl: 48px;\n --duro-spacing-xxxl: 64px;\n /* microSpacing */\n --duro-micro-spacing-px1: 1px;\n --duro-micro-spacing-px2: 2px;\n --duro-micro-spacing-px3: 3px;\n --duro-micro-spacing-px5: 5px;\n --duro-micro-spacing-px6: 6px;\n /* radii */\n --duro-radius-full: 9999px;\n --duro-radius-lg: 16px;\n --duro-radius-md: 12px;\n --duro-radius-px6: 6px;\n --duro-radius-sm: 8px;\n --duro-radius-xs: 4px;\n /* sizes */\n --duro-size-app-bar-h: 61px;\n --duro-size-aside-w: 320px;\n --duro-size-bar-h: 4px;\n --duro-size-canvas-min-h: 480px;\n --duro-size-check-mark-h: 9px;\n --duro-size-check-mark-w: 5px;\n --duro-size-chip: 24px;\n --duro-size-color-area-h: 150px;\n --duro-size-color-picker-w: 196px;\n --duro-size-color-preview-h: 22px;\n --duro-size-color-swatch: 20px;\n --duro-size-color-track-h: 12px;\n --duro-size-control-lg: 40px;\n --duro-size-control-md: 39px;\n --duro-size-control-sm: 28px;\n --duro-size-day-header-h: 46px;\n --duro-size-device-bar-w: 96px;\n --duro-size-dialog-lg: 680px;\n --duro-size-dialog-md: 520px;\n --duro-size-dialog-sm: 400px;\n --duro-size-divider: 1px;\n --duro-size-drag-thumb-h: 99px;\n --duro-size-drag-thumb-w: 132px;\n --duro-size-drop-zone-min-h: 128px;\n --duro-size-edge-fade: 32px;\n --duro-size-editor-min-h: 160px;\n --duro-size-embed-w: 550px;\n --duro-size-field-min-w: 160px;\n --duro-size-field-min-w-sm: 80px;\n --duro-size-glyph-clamp-max: 160px;\n --duro-size-glyph-md: 16px;\n --duro-size-glyph-sm: 12px;\n --duro-size-glyph-xs: 10px;\n --duro-size-grid-col-md: 280px;\n --duro-size-grid-col-sm: 240px;\n --duro-size-grid-col-xs: 200px;\n --duro-size-handle: 16px;\n --duro-size-icon-button: 32px;\n --duro-size-icon-button-sm: 28px;\n --duro-size-icon-lg: 24px;\n --duro-size-icon-md: 18px;\n --duro-size-icon-sm: 16px;\n --duro-size-icon-xl: 36px;\n --duro-size-icon-xxl: 48px;\n --duro-size-indicator: 18px;\n --duro-size-indicator-dot: 8px;\n --duro-size-label-min-w: 120px;\n --duro-size-list-max-h: 280px;\n --duro-size-list-max-h-sm: 200px;\n --duro-size-media-tile-min-w: 108px;\n --duro-size-meter-h: 6px;\n --duro-size-nav-marker-h: 18px;\n --duro-size-nav-marker-w: 3px;\n --duro-size-page-lg: 1200px;\n --duro-size-page-md: 800px;\n --duro-size-page-sm: 600px;\n --duro-size-page-xl: 1440px;\n --duro-size-page-xs: 480px;\n --duro-size-palette-min-w: 96px;\n --duro-size-panel-lg: 640px;\n --duro-size-panel-md: 480px;\n --duro-size-panel-sm: 360px;\n --duro-size-placeholder-min-h: 80px;\n --duro-size-popover-w: 320px;\n --duro-size-popover-w-sm: 240px;\n --duro-size-popup-max-w: 280px;\n --duro-size-popup-min-w: 160px;\n --duro-size-preview-max-h: 320px;\n --duro-size-readout-w: 40px;\n --duro-size-scrollbar: 8px;\n --duro-size-sheet-grabber-h: 4px;\n --duro-size-sheet-grabber-w: 40px;\n --duro-size-sidebar-w: 240px;\n --duro-size-skeleton-chip-w: 96px;\n --duro-size-slider-w: 96px;\n --duro-size-spinner-lg: 40px;\n --duro-size-spinner-md: 24px;\n --duro-size-spinner-sm: 16px;\n --duro-size-swatch-h: 34px;\n --duro-size-swatch-w: 44px;\n --duro-size-switch-thumb: 16px;\n --duro-size-switch-track-h: 20px;\n --duro-size-switch-track-w: 36px;\n --duro-size-tab-indicator: 2px;\n --duro-size-thumb-sm: 40px;\n --duro-size-time-gutter-w: 64px;\n --duro-size-timeline-label-w: 180px;\n --duro-size-time-readout-w: 74px;\n --duro-size-toast-max-w: 440px;\n --duro-size-toolbar-h: 36px;\n --duro-size-touch-target: 44px;\n /* borders */\n --duro-border-accent: 3px;\n --duro-border-focus-offset: 2px;\n --duro-border-focus-offset-sm: 1px;\n --duro-border-focus-ring: 2px;\n --duro-border-hairline: 1px;\n --duro-border-strong: 2px;\n /* layers */\n --duro-layer-floating: 50;\n --duro-layer-floating-raised: 60;\n --duro-layer-modal: 1001;\n --duro-layer-modal-raised: 1002;\n --duro-layer-overlay: 1000;\n --duro-layer-popover: 1040;\n --duro-layer-popover-raised: 1041;\n --duro-layer-popup: 1050;\n --duro-layer-popup-backdrop: 1049;\n --duro-layer-portal: 1100;\n --duro-layer-raised: 1;\n --duro-layer-toast: 1060;\n /* effects */\n --duro-effect-overlay-blur: blur(2px);\n --duro-effect-surface-blur: blur(6px);\n /* layoutSpacing */\n --duro-layout-spacing-container-lg: 32px;\n --duro-layout-spacing-container-md: 24px;\n --duro-layout-spacing-container-sm: 16px;\n --duro-layout-spacing-inline-lg: 24px;\n --duro-layout-spacing-inline-md: 16px;\n --duro-layout-spacing-inline-sm: 8px;\n --duro-layout-spacing-inline-xs: 4px;\n --duro-layout-spacing-stack-lg: 24px;\n --duro-layout-spacing-stack-md: 16px;\n --duro-layout-spacing-stack-sm: 8px;\n --duro-layout-spacing-stack-xl: 48px;\n --duro-layout-spacing-stack-xs: 4px;\n /* typography */\n --duro-typography-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n --duro-typography-font-family-mono: \"SF Mono\", \"Fira Code\", \"Fira Mono\", \"Roboto Mono\", \"Courier New\", monospace;\n --duro-typography-font-size-heading: 1.5rem;\n --duro-typography-font-size-lg: 1.125rem;\n --duro-typography-font-size-md: 1rem;\n --duro-typography-font-size-sm: 0.875rem;\n --duro-typography-font-size-xl: 1.25rem;\n --duro-typography-font-size-xs: 0.75rem;\n --duro-typography-font-weight-bold: 700;\n --duro-typography-font-weight-medium: 500;\n --duro-typography-font-weight-normal: 400;\n --duro-typography-font-weight-semibold: 600;\n --duro-typography-line-height: 1.5;\n /* typeScale */\n --duro-type-scale-display-lg: clamp(3.5rem, 2rem + 3.5vw, 4.5rem);\n --duro-type-scale-display-md: clamp(2.75rem, 1.75rem + 2.5vw, 3.75rem);\n --duro-type-scale-display-sm: clamp(2.25rem, 1.5rem + 2vw, 3rem);\n --duro-type-scale-font-size1: 0.75rem;\n --duro-type-scale-font-size2: 0.8125rem;\n --duro-type-scale-font-size3: 0.875rem;\n --duro-type-scale-font-size4: 1rem;\n --duro-type-scale-font-size5: 1.125rem;\n --duro-type-scale-font-size6: 1.25rem;\n --duro-type-scale-font-size7: 1.5rem;\n --duro-type-scale-font-size8: 1.875rem;\n --duro-type-scale-font-size9: 2.25rem;\n --duro-type-scale-letter-spacing-normal: 0;\n --duro-type-scale-letter-spacing-tight: -0.02em;\n --duro-type-scale-letter-spacing-wide: 0.04em;\n --duro-type-scale-line-height1: 1rem;\n --duro-type-scale-line-height2: 1.25rem;\n --duro-type-scale-line-height3: 1.25rem;\n --duro-type-scale-line-height4: 1.5rem;\n --duro-type-scale-line-height5: 1.5rem;\n --duro-type-scale-line-height6: 1.75rem;\n --duro-type-scale-line-height7: 2rem;\n --duro-type-scale-line-height8: 2.25rem;\n --duro-type-scale-line-height9: 2.75rem;\n /* duration */\n --duro-duration-base: 200ms;\n --duro-duration-brisk: 160ms;\n --duro-duration-fast: 150ms;\n --duro-duration-instant: 0ms;\n --duro-duration-minimal: 1ms;\n --duro-duration-quick: 120ms;\n --duro-duration-slow: 280ms;\n /* easing */\n --duro-easing-ease-in: cubic-bezier(0.72, 0, 0.68, 0.28);\n --duro-easing-ease-out: cubic-bezier(0.32, 0.72, 0, 1);\n --duro-easing-linear: linear;\n --duro-easing-standard: ease;\n /* breakpoints */\n --duro-breakpoint-lg: 1024px;\n --duro-breakpoint-md: 768px;\n --duro-breakpoint-sm: 640px;\n --duro-breakpoint-xl: 1280px;\n --duro-breakpoint-xs: 480px;\n}\n\n:root[data-theme=\"light\"] {\n color-scheme: light;\n /* colors */\n --duro-color-accent: #1e40af;\n --duro-color-accent-contrast: #ffffff;\n --duro-color-accent-hover: #1a3799;\n --duro-color-bg: #ffffff;\n --duro-color-bg-card: #f5f5f5;\n --duro-color-bg-card-hover: #ebebeb;\n --duro-color-border: #d4d4d4;\n --duro-color-contrast-border: rgba(255, 255, 255, 0.33);\n --duro-color-contrast-surface: #1a1a1a;\n --duro-color-error: #991b1b;\n --duro-color-error-bg: rgba(153, 27, 27, 0.08);\n --duro-color-error-border: rgba(153, 27, 27, 0.3);\n --duro-color-error-contrast: #ffffff;\n --duro-color-error-hover: #7f1d1d;\n --duro-color-error-text: #7f1d1d;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #6b21a8;\n --duro-color-highlight-bg: rgba(107, 33, 168, 0.08);\n --duro-color-highlight-border: rgba(107, 33, 168, 0.3);\n --duro-color-highlight-text: #581c87;\n --duro-color-info: #1e40af;\n --duro-color-info-bg: rgba(30, 64, 175, 0.08);\n --duro-color-info-border: rgba(30, 64, 175, 0.3);\n --duro-color-info-text: #1e40af;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #e5e5e5;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #166534;\n --duro-color-success-bg: rgba(22, 101, 52, 0.08);\n --duro-color-success-border: rgba(22, 101, 52, 0.3);\n --duro-color-success-text: #14532d;\n --duro-color-text: #1a1a1a;\n --duro-color-text-muted: #4a4a4a;\n --duro-color-warning: #92400e;\n --duro-color-warning-bg: rgba(146, 64, 14, 0.08);\n --duro-color-warning-border: rgba(146, 64, 14, 0.3);\n --duro-color-warning-text: #78350f;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #1e40af;\n --duro-shadow-drop-ready: inset 0 0 0 1px #d4d4d4;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.08);\n}\n\n:root[data-theme=\"high-contrast\"] {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #60a5fa;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #000000;\n --duro-color-bg-card: #111111;\n --duro-color-bg-card-hover: #1a1a1a;\n --duro-color-border: #555555;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #ffffff;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.15);\n --duro-color-error-border: rgba(248, 113, 113, 0.5);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #d8b4fe;\n --duro-color-highlight-bg: rgba(216, 180, 254, 0.15);\n --duro-color-highlight-border: rgba(216, 180, 254, 0.5);\n --duro-color-highlight-text: #e9d5ff;\n --duro-color-info: #60a5fa;\n --duro-color-info-bg: rgba(96, 165, 250, 0.15);\n --duro-color-info-border: rgba(96, 165, 250, 0.5);\n --duro-color-info-text: #bfdbfe;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #000000;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #4ade80;\n --duro-color-success-bg: rgba(74, 222, 128, 0.15);\n --duro-color-success-border: rgba(74, 222, 128, 0.5);\n --duro-color-success-text: #86efac;\n --duro-color-text: #ffffff;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fcd34d;\n --duro-color-warning-bg: rgba(252, 211, 77, 0.15);\n --duro-color-warning-border: rgba(252, 211, 77, 0.5);\n --duro-color-warning-text: #fef08a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #60a5fa;\n --duro-shadow-drop-ready: inset 0 0 0 1px #555555;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.8);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.7);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.6);\n}\n"
10282
10532
  },
10283
10533
  "unions": {
10284
10534
  "AlertVariant": [
@@ -10529,6 +10779,10 @@
10529
10779
  "dropReady",
10530
10780
  "dropOver"
10531
10781
  ],
10782
+ "SnapPoint": [
10783
+ "medium",
10784
+ "large"
10785
+ ],
10532
10786
  "SpacingKey": [
10533
10787
  "xs",
10534
10788
  "sm",