@digithings-ai/ui 0.0.0-stage → 0.1.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 (424) hide show
  1. package/README.md +48 -2
  2. package/package.json +143 -4
  3. package/src/components/NavShell.tsx +776 -0
  4. package/src/components/SocialRow.tsx +87 -0
  5. package/src/components/Spinner.tsx +27 -0
  6. package/src/components/StackLogo.tsx +40 -0
  7. package/src/components/Terminal.tsx +110 -0
  8. package/src/components/TerminalManifest.tsx +212 -0
  9. package/src/components/ThemeProvider.tsx +271 -0
  10. package/src/components/account/AuthCard.tsx +309 -0
  11. package/src/components/assistant-ui/elements/mermaid-diagram.aui.tsx +63 -0
  12. package/src/components/assistant-ui/elements/mermaid-diagram.tsx +340 -0
  13. package/src/components/brand/BrandWordmark.tsx +32 -0
  14. package/src/components/changelog/ReleaseRail.tsx +81 -0
  15. package/src/components/changelog/index.ts +1 -0
  16. package/src/components/chat/ChatCodeBlock.tsx +67 -0
  17. package/src/components/chat/ChatMarkdown.tsx +66 -0
  18. package/src/components/chat/ChatMarkdownSource.tsx +203 -0
  19. package/src/components/chat/ChatMermaidBlock.tsx +202 -0
  20. package/src/components/chat/ChatMessage.tsx +77 -0
  21. package/src/components/chat/ChatPlayback.tsx +422 -0
  22. package/src/components/chat/ChatStreamCursor.tsx +25 -0
  23. package/src/components/chat/ChatThinking.tsx +113 -0
  24. package/src/components/chat/ChatToolCall.tsx +190 -0
  25. package/src/components/chat/ChatTranscript.tsx +60 -0
  26. package/src/components/chat/ChatWidgetFrame.tsx +107 -0
  27. package/src/components/chat/DigichatBootLoader.tsx +683 -0
  28. package/src/components/chat/DigichatLauncher.tsx +361 -0
  29. package/src/components/chat/DigichatThread.tsx +118 -0
  30. package/src/components/chat/DigichatThreadList.tsx +54 -0
  31. package/src/components/chat/DigichatWordmark.tsx +24 -0
  32. package/src/components/chat/DotMatrix.tsx +650 -0
  33. package/src/components/chat/boot-lab.tsx +766 -0
  34. package/src/components/chat/boot-signal.ts +144 -0
  35. package/src/components/chat/chat-surfaces.ts +52 -0
  36. package/src/components/chat/connection-status.ts +40 -0
  37. package/src/components/chat/digichat-wordmark.ts +355 -0
  38. package/src/components/chat/disclosure-chevron.tsx +49 -0
  39. package/src/components/chat/followable-href.ts +9 -0
  40. package/src/components/chat/gallery-thread/action-icons.tsx +25 -0
  41. package/src/components/chat/gallery-thread/attachment.aui.tsx +262 -0
  42. package/src/components/chat/gallery-thread/block-caret.tsx +439 -0
  43. package/src/components/chat/gallery-thread/cn.ts +1 -0
  44. package/src/components/chat/gallery-thread/composer-keyboard.ts +106 -0
  45. package/src/components/chat/gallery-thread/composer-trigger-popover.aui.tsx +235 -0
  46. package/src/components/chat/gallery-thread/css.d.ts +6 -0
  47. package/src/components/chat/gallery-thread/file.tsx +238 -0
  48. package/src/components/chat/gallery-thread/follow-up-suggestions.aui.tsx +92 -0
  49. package/src/components/chat/gallery-thread/format-json-dump.ts +112 -0
  50. package/src/components/chat/gallery-thread/hooks/use-attachment-src.ts +46 -0
  51. package/src/components/chat/gallery-thread/hooks/use-copy-to-clipboard.ts +9 -0
  52. package/src/components/chat/gallery-thread/image.tsx +519 -0
  53. package/src/components/chat/gallery-thread/markdown-text.tsx +278 -0
  54. package/src/components/chat/gallery-thread/message-error.aui.tsx +105 -0
  55. package/src/components/chat/gallery-thread/message-timing.aui.tsx +143 -0
  56. package/src/components/chat/gallery-thread/private-work-step.tsx +188 -0
  57. package/src/components/chat/gallery-thread/reasoning.tsx +332 -0
  58. package/src/components/chat/gallery-thread/scroll-mode.ts +174 -0
  59. package/src/components/chat/gallery-thread/source.tsx +76 -0
  60. package/src/components/chat/gallery-thread/thread.aui.tsx +1619 -0
  61. package/src/components/chat/gallery-thread/tooltip-icon-button.tsx +53 -0
  62. package/src/components/chat/gallery-thread/typed-welcome-copy.tsx +18 -0
  63. package/src/components/chat/gallery-thread/ui/tooltip.tsx +22 -0
  64. package/src/components/chat/gallery-thread/work-chain-summary.tsx +48 -0
  65. package/src/components/chat/gallery-thread/work-chain.tsx +110 -0
  66. package/src/components/chat/gallery-thread/work-status.ts +66 -0
  67. package/src/components/chat/overlay-scrollbar.tsx +86 -0
  68. package/src/components/chat/sanitize-mermaid-svg.ts +38 -0
  69. package/src/components/chat/skins/SOURCE.md +36 -0
  70. package/src/components/chat/skins/base/attachment.aui.tsx +287 -0
  71. package/src/components/chat/skins/base/aui-default/README.md +45 -0
  72. package/src/components/chat/skins/base/aui-default/collapsible-root.tsx +19 -0
  73. package/src/components/chat/skins/base/aui-default/collapsible.tsx +21 -0
  74. package/src/components/chat/skins/base/aui-default/reasoning.aui.tsx +120 -0
  75. package/src/components/chat/skins/base/aui-default/reasoning.tsx +331 -0
  76. package/src/components/chat/skins/base/aui-default/surfaces.ts +7 -0
  77. package/src/components/chat/skins/base/aui-default/tool-fallback.aui.tsx +772 -0
  78. package/src/components/chat/skins/base/aui-default/tool-group.aui.tsx +241 -0
  79. package/src/components/chat/skins/base/docs-model-options.ts +6 -0
  80. package/src/components/chat/skins/base/elements/assistant-trace.aui.tsx +169 -0
  81. package/src/components/chat/skins/base/elements/composer-trigger-popover.aui.tsx +271 -0
  82. package/src/components/chat/skins/base/elements/directive-text.aui.tsx +29 -0
  83. package/src/components/chat/skins/base/elements/directive-text.tsx +76 -0
  84. package/src/components/chat/skins/base/elements/message-timing.aui.tsx +104 -0
  85. package/src/components/chat/skins/base/elements/model-selector.aui.tsx +140 -0
  86. package/src/components/chat/skins/base/elements/model-selector.tsx +720 -0
  87. package/src/components/chat/skins/base/elements/quote.aui.tsx +290 -0
  88. package/src/components/chat/skins/base/elements/work-chain.tsx +100 -0
  89. package/src/components/chat/skins/base/markdown-text.tsx +294 -0
  90. package/src/components/chat/skins/base/model.ts +1 -0
  91. package/src/components/chat/skins/base/thread.tsx +1050 -0
  92. package/src/components/chat/skins/base/tooltip-icon-button.tsx +56 -0
  93. package/src/components/chat/skins/base/ui/avatar.tsx +115 -0
  94. package/src/components/chat/skins/base/ui/badge.tsx +51 -0
  95. package/src/components/chat/skins/base/ui/button.tsx +67 -0
  96. package/src/components/chat/skins/base/ui/command.tsx +185 -0
  97. package/src/components/chat/skins/base/ui/dialog.tsx +168 -0
  98. package/src/components/chat/skins/base/ui/dot-matrix.tsx +267 -0
  99. package/src/components/chat/skins/base/ui/popover.tsx +97 -0
  100. package/src/components/chat/skins/base/ui/skeleton.tsx +20 -0
  101. package/src/components/chat/skins/base/ui/textarea.tsx +24 -0
  102. package/src/components/chat/skins/base/ui/tooltip.tsx +73 -0
  103. package/src/components/chat/skins/base-assistant-ui/config-provider.tsx +90 -0
  104. package/src/components/chat/skins/base-assistant-ui/defaults.ts +419 -0
  105. package/src/components/chat/skins/base-assistant-ui/index.tsx +44 -0
  106. package/src/components/chat/skins/chatgpt.tsx +448 -0
  107. package/src/components/chat/skins/claude.tsx +380 -0
  108. package/src/components/chat/skins/digichat-composer-toolbar.tsx +151 -0
  109. package/src/components/chat/skins/digichat-context-meter-shares.ts +158 -0
  110. package/src/components/chat/skins/digichat-context-meter.tsx +243 -0
  111. package/src/components/chat/skins/digichat-meter-tone.ts +26 -0
  112. package/src/components/chat/skins/digichat-usage-meter.tsx +103 -0
  113. package/src/components/chat/skins/digichat.tsx +144 -0
  114. package/src/components/chat/skins/expo-react-native.tsx +29 -0
  115. package/src/components/chat/skins/gemini.tsx +322 -0
  116. package/src/components/chat/skins/grok-icon.tsx +34 -0
  117. package/src/components/chat/skins/grok.tsx +369 -0
  118. package/src/components/chat/skins/index.ts +46 -0
  119. package/src/components/chat/skins/perplexity.tsx +462 -0
  120. package/src/components/chat/skins/product-page-assistant/assistant-modal.tsx +73 -0
  121. package/src/components/chat/skins/product-page-assistant/index.tsx +19 -0
  122. package/src/components/chat/skins/product-page-assistant/product-dashboard.tsx +164 -0
  123. package/src/components/chat/skins/react-ink.tsx +97 -0
  124. package/src/components/chat/skins/thread-skin-host-contract.ts +102 -0
  125. package/src/components/chat/skins/thread-skin-view.tsx +156 -0
  126. package/src/components/chat/skins/thread-skins.ts +143 -0
  127. package/src/components/chat/skins/tooltip-icon-button.tsx +35 -0
  128. package/src/components/chat/skins/ui/dropdown-menu.tsx +272 -0
  129. package/src/components/chat/skins/webpage-assistant/assistant-config.ts +238 -0
  130. package/src/components/chat/skins/webpage-assistant/assistant-modal.tsx +78 -0
  131. package/src/components/chat/skins/webpage-assistant/config-provider.tsx +132 -0
  132. package/src/components/chat/skins/webpage-assistant/docs-app.tsx +106 -0
  133. package/src/components/chat/skins/webpage-assistant/docs-article.tsx +109 -0
  134. package/src/components/chat/skins/webpage-assistant/docs-assistant.tsx +73 -0
  135. package/src/components/chat/skins/webpage-assistant/docs-nav.tsx +81 -0
  136. package/src/components/chat/skins/webpage-assistant/host-ui.ts +79 -0
  137. package/src/components/chat/skins/webpage-assistant/index.tsx +12 -0
  138. package/src/components/chat/skins/webpage-assistant/markdown-page.tsx +11 -0
  139. package/src/components/chat/skins/webpage-assistant/pages.ts +9 -0
  140. package/src/components/chat/skins/webpage-assistant/types.ts +178 -0
  141. package/src/components/chat/skins/webpage-assistant/use-mobile.ts +19 -0
  142. package/src/components/chat/stock/SOURCE.md +90 -0
  143. package/src/components/chat/stock/artifacts-panel.tsx +305 -0
  144. package/src/components/chat/stock/attachment.aui.tsx +280 -0
  145. package/src/components/chat/stock/chat-notice.tsx +143 -0
  146. package/src/components/chat/stock/chat-private-mode.tsx +64 -0
  147. package/src/components/chat/stock/deploy-ui-context.tsx +89 -0
  148. package/src/components/chat/stock/embed-chat-prefs.tsx +471 -0
  149. package/src/components/chat/stock/file.tsx +265 -0
  150. package/src/components/chat/stock/follow-up-suggestions.aui.tsx +83 -0
  151. package/src/components/chat/stock/image.tsx +531 -0
  152. package/src/components/chat/stock/index.ts +129 -0
  153. package/src/components/chat/stock/json-view.tsx +140 -0
  154. package/src/components/chat/stock/markdown-text.tsx +319 -0
  155. package/src/components/chat/stock/message-error.aui.tsx +96 -0
  156. package/src/components/chat/stock/message-export-menu.tsx +156 -0
  157. package/src/components/chat/stock/palette-row-preview.tsx +26 -0
  158. package/src/components/chat/stock/reasoning.aui.tsx +120 -0
  159. package/src/components/chat/stock/reasoning.tsx +335 -0
  160. package/src/components/chat/stock/skin-chrome.tsx +81 -0
  161. package/src/components/chat/stock/skin-credit.tsx +63 -0
  162. package/src/components/chat/stock/skin-palette-bridge.tsx +235 -0
  163. package/src/components/chat/stock/skin-palette-context.ts +16 -0
  164. package/src/components/chat/stock/skin-runtime.tsx +52 -0
  165. package/src/components/chat/stock/skin-slash-palette.tsx +393 -0
  166. package/src/components/chat/stock/skin-types.ts +217 -0
  167. package/src/components/chat/stock/stock-chat-prefs-host.tsx +501 -0
  168. package/src/components/chat/stock/stock-send-gate.tsx +94 -0
  169. package/src/components/chat/stock/stock-shape.tsx +39 -0
  170. package/src/components/chat/stock/thread.aui.tsx +645 -0
  171. package/src/components/chat/stock/tool-display.ts +6 -0
  172. package/src/components/chat/stock/tool-fallback.aui.tsx +866 -0
  173. package/src/components/chat/stock/tool-group.aui.tsx +244 -0
  174. package/src/components/chat/stock/tool-surface.ts +120 -0
  175. package/src/components/chat/stock/tooltip-icon-button.tsx +49 -0
  176. package/src/components/chat/stock/ui/avatar.tsx +108 -0
  177. package/src/components/chat/stock/ui/button.tsx +81 -0
  178. package/src/components/chat/stock/ui/collapsible.tsx +21 -0
  179. package/src/components/chat/stock/ui/dialog.tsx +163 -0
  180. package/src/components/chat/stock/ui/skeleton.tsx +13 -0
  181. package/src/components/chat/stock/ui/textarea.tsx +20 -0
  182. package/src/components/chat/stock/ui/tooltip.tsx +68 -0
  183. package/src/components/chat/stock/use-attachment-src.ts +42 -0
  184. package/src/components/chat/stock/use-copy-to-clipboard.ts +29 -0
  185. package/src/components/chat/strip-model-boilerplate.ts +42 -0
  186. package/src/components/chat/suggestion-cycle.tsx +230 -0
  187. package/src/components/chat/suggestion-scroll-row.tsx +81 -0
  188. package/src/components/chat/trace-grouping/chain-group-by.ts +44 -0
  189. package/src/components/chat/trace-grouping/chain-state.ts +141 -0
  190. package/src/components/chat/trace-grouping/index.ts +32 -0
  191. package/src/components/chat/trace-grouping/tool-row.ts +74 -0
  192. package/src/components/chat/trace-grouping/use-deploy-work-disclosure.ts +35 -0
  193. package/src/components/chat/trace-grouping/use-work-chain.ts +103 -0
  194. package/src/components/chat/trace-i18n/core.ts +48 -0
  195. package/src/components/chat/trace-i18n/index.tsx +23 -0
  196. package/src/components/chat/trace-i18n/locales/en.ts +24 -0
  197. package/src/components/chat/trace-i18n/locales/fr.ts +28 -0
  198. package/src/components/chat/trace-i18n/types.ts +35 -0
  199. package/src/components/chat/trace-spacing.ts +249 -0
  200. package/src/components/chat/transcript/activity-view.ts +375 -0
  201. package/src/components/chat/transcript/chat-export.ts +133 -0
  202. package/src/components/chat/transcript/index.ts +10 -0
  203. package/src/components/chat/transcript/save-file.ts +209 -0
  204. package/src/components/chat/transcript/transcript-markdown.ts +428 -0
  205. package/src/components/chat/transcript/types.ts +81 -0
  206. package/src/components/chat/turn-outcome.ts +52 -0
  207. package/src/components/chat/use-warmup-phase.ts +18 -0
  208. package/src/components/chat/work-status-lead.tsx +49 -0
  209. package/src/components/chrome.tsx +264 -0
  210. package/src/components/command-palette/CommandPalette.tsx +404 -0
  211. package/src/components/command-palette/index.ts +6 -0
  212. package/src/components/contact/ContactMailto.tsx +112 -0
  213. package/src/components/contact/index.ts +5 -0
  214. package/src/components/conviction/ConvictionMeter.tsx +40 -0
  215. package/src/components/conviction/SignedConvictionBadge.tsx +26 -0
  216. package/src/components/conviction/index.ts +7 -0
  217. package/src/components/data-layout/BentoGrid.tsx +52 -0
  218. package/src/components/data-layout/CardRail.tsx +150 -0
  219. package/src/components/data-layout/DotMatrixStat.tsx +65 -0
  220. package/src/components/data-layout/FeatureCell.tsx +80 -0
  221. package/src/components/data-layout/MediaFrame.tsx +125 -0
  222. package/src/components/data-layout/ModuleGrid.tsx +735 -0
  223. package/src/components/data-layout/Odometer.tsx +149 -0
  224. package/src/components/data-layout/ProductFrame.tsx +79 -0
  225. package/src/components/data-layout/TestimonialWall.tsx +139 -0
  226. package/src/components/data-layout/index.ts +19 -0
  227. package/src/components/data-layout/treemap.ts +368 -0
  228. package/src/components/deck/DeckStack.tsx +243 -0
  229. package/src/components/desk/DataTable.tsx +373 -0
  230. package/src/components/desk/KpiGrid.tsx +80 -0
  231. package/src/components/desk/Sparkline.tsx +63 -0
  232. package/src/components/desk/StateBlock.tsx +132 -0
  233. package/src/components/desk/index.ts +18 -0
  234. package/src/components/diagrams/ArchitectureDiagram.tsx +350 -0
  235. package/src/components/diagrams/ArchitectureTour.tsx +912 -0
  236. package/src/components/diagrams/TerminalSchematic.tsx +192 -0
  237. package/src/components/diagrams/architecture-svg.tsx +384 -0
  238. package/src/components/diagrams/index.ts +30 -0
  239. package/src/components/diagrams/mermaid-theme.ts +115 -0
  240. package/src/components/diagrams/tour-camera.ts +171 -0
  241. package/src/components/docs/CodeTabs.tsx +90 -0
  242. package/src/components/docs/CopyCommand.tsx +151 -0
  243. package/src/components/docs/DocsLayout.tsx +197 -0
  244. package/src/components/docs/DocsSearch.tsx +97 -0
  245. package/src/components/docs/Endpoint.tsx +135 -0
  246. package/src/components/document/DocumentFrame.tsx +43 -0
  247. package/src/components/document/GlyphList.tsx +51 -0
  248. package/src/components/document/PageTitle.tsx +44 -0
  249. package/src/components/document/Prose.tsx +38 -0
  250. package/src/components/document/Section.tsx +55 -0
  251. package/src/components/document/index.ts +13 -0
  252. package/src/components/effects-chrome/AnnouncementBar.tsx +88 -0
  253. package/src/components/effects-chrome/HorizontalScrollTrack.tsx +461 -0
  254. package/src/components/effects-chrome/Pipeline.tsx +226 -0
  255. package/src/components/effects-chrome/RotatingPrompts.tsx +116 -0
  256. package/src/components/effects-chrome/StackingPanels.tsx +101 -0
  257. package/src/components/effects-chrome/TabStrip.tsx +232 -0
  258. package/src/components/effects-chrome/ToastStack.tsx +201 -0
  259. package/src/components/effects-chrome/horizontal-track-core.ts +70 -0
  260. package/src/components/effects-chrome/index.ts +35 -0
  261. package/src/components/emblems.tsx +188 -0
  262. package/src/components/faviconTheme.ts +23 -0
  263. package/src/components/figure/Figure.tsx +38 -0
  264. package/src/components/figure/index.ts +2 -0
  265. package/src/components/finance-charts/DrawdownPlot.tsx +76 -0
  266. package/src/components/finance-charts/EquityCurve.tsx +71 -0
  267. package/src/components/finance-charts/PriceChart.tsx +119 -0
  268. package/src/components/finance-charts/chart-host.tsx +236 -0
  269. package/src/components/finance-charts/chart-scale.ts +93 -0
  270. package/src/components/finance-charts/demo-data.ts +88 -0
  271. package/src/components/finance-charts/index.ts +46 -0
  272. package/src/components/finance-charts/lw-chart.tsx +292 -0
  273. package/src/components/finance-composites/MarketBar.tsx +243 -0
  274. package/src/components/finance-composites/OrderBook.tsx +133 -0
  275. package/src/components/finance-composites/PerformanceDashboard.tsx +185 -0
  276. package/src/components/finance-composites/SortableTable.tsx +144 -0
  277. package/src/components/finance-composites/StockTicker.tsx +65 -0
  278. package/src/components/finance-composites/SyncedTearsheet.tsx +226 -0
  279. package/src/components/finance-composites/index.ts +28 -0
  280. package/src/components/finance-tearsheet/KpiStrip.tsx +49 -0
  281. package/src/components/finance-tearsheet/LiveBadge.tsx +29 -0
  282. package/src/components/finance-tearsheet/ReturnsMatrix.tsx +321 -0
  283. package/src/components/finance-tearsheet/StrategyTearsheet.tsx +99 -0
  284. package/src/components/finance-tearsheet/TearsheetCard.tsx +54 -0
  285. package/src/components/finance-tearsheet/TradeLogTable.tsx +73 -0
  286. package/src/components/finance-tearsheet/charts.tsx +2624 -0
  287. package/src/components/finance-tearsheet/demo-data.ts +177 -0
  288. package/src/components/finance-tearsheet/format.ts +41 -0
  289. package/src/components/finance-tearsheet/index.ts +120 -0
  290. package/src/components/finance-tearsheet/print.ts +52 -0
  291. package/src/components/finance-tearsheet/risk-bands.ts +48 -0
  292. package/src/components/finance-tearsheet/stats.ts +35 -0
  293. package/src/components/finance-tearsheet/strategy-sheet-model.ts +238 -0
  294. package/src/components/finance-tearsheet/types.ts +58 -0
  295. package/src/components/footer/FooterCells.tsx +109 -0
  296. package/src/components/footer/index.ts +6 -0
  297. package/src/components/graph.tsx +158 -0
  298. package/src/components/icons.tsx +98 -0
  299. package/src/components/landing/PixelWordmark.tsx +36 -0
  300. package/src/components/logos.ts +39 -0
  301. package/src/components/marquee/Marquee.tsx +204 -0
  302. package/src/components/metrics/PerfMetrics.tsx +68 -0
  303. package/src/components/metrics/StatCounter.tsx +104 -0
  304. package/src/components/page-geometry.tsx +177 -0
  305. package/src/components/pricing/Pricing.tsx +174 -0
  306. package/src/components/pricing/PricingMatrix.tsx +219 -0
  307. package/src/components/prose/index.ts +2 -0
  308. package/src/components/prose/prose.tsx +86 -0
  309. package/src/components/repo-activity/RepoActivity.tsx +599 -0
  310. package/src/components/repo-activity/RepoHeatmap.tsx +225 -0
  311. package/src/components/repo-activity/demo.ts +201 -0
  312. package/src/components/repo-activity/fetch.ts +211 -0
  313. package/src/components/repo-activity/heatmap.ts +123 -0
  314. package/src/components/repo-activity/index.ts +28 -0
  315. package/src/components/repo-activity/types.ts +99 -0
  316. package/src/components/stages/NumberedStages.tsx +116 -0
  317. package/src/components/symbols/marks.tsx +116 -0
  318. package/src/components/symbols/terminal-lockup.tsx +136 -0
  319. package/src/components/symbols/terminal-marks.tsx +226 -0
  320. package/src/components/terminal/ChatResponseLoader.tsx +67 -0
  321. package/src/components/terminal/ContainerBootLoader.tsx +82 -0
  322. package/src/components/terminal/TerminalStepCaret.tsx +202 -0
  323. package/src/components/themeInit.ts +24 -0
  324. package/src/components/typography/MotionReveal.tsx +108 -0
  325. package/src/components/typography/TypewriterReveal.tsx +101 -0
  326. package/src/components/typography/WordReveal.tsx +143 -0
  327. package/src/data/modules.ts +378 -0
  328. package/src/data/subsystems.ts +101 -0
  329. package/src/index.ts +587 -0
  330. package/src/lib/base-path.ts +60 -0
  331. package/src/lib/cx.ts +25 -0
  332. package/src/lib/gloomberb.ts +69 -0
  333. package/src/lib/record.ts +19 -0
  334. package/src/lib/stub-payload.ts +183 -0
  335. package/src/lib/useBodyScrollLock.ts +44 -0
  336. package/src/lib/utils.ts +6 -0
  337. package/src/motion/primitives.tsx +105 -0
  338. package/src/motion/scroll-glide.ts +62 -0
  339. package/src/motion/scrolly-core.ts +38 -0
  340. package/src/motion/scrolly.tsx +95 -0
  341. package/src/navigation/HashScrollManager.tsx +167 -0
  342. package/src/navigation/hashScroll.ts +78 -0
  343. package/src/replay/browser.ts +280 -0
  344. package/src/replay/chat-export-run.ts +121 -0
  345. package/src/replay/export-format.ts +25 -0
  346. package/src/replay/export-mount.ts +32 -0
  347. package/src/replay/filename.ts +54 -0
  348. package/src/replay/index.ts +21 -0
  349. package/src/replay/redact-dom.ts +178 -0
  350. package/src/replay/redact.ts +113 -0
  351. package/src/replay/snapshot.ts +659 -0
  352. package/src/styles/account-auth.css +339 -0
  353. package/src/styles/brand-wordmark.css +11 -0
  354. package/src/styles/card-rail.css +120 -0
  355. package/src/styles/chat-aui.css +1143 -0
  356. package/src/styles/chat-core.css +469 -0
  357. package/src/styles/chat-digichat.css +2531 -0
  358. package/src/styles/chat-math.css +70 -0
  359. package/src/styles/chat-skin-palette.css +138 -0
  360. package/src/styles/chat-stock-menu.css +37 -0
  361. package/src/styles/chat-widgets.css +190 -0
  362. package/src/styles/command-palette.css +372 -0
  363. package/src/styles/controls-core.css +2261 -0
  364. package/src/styles/controls-overlay.css +603 -0
  365. package/src/styles/data-layout.css +288 -0
  366. package/src/styles/deck.css +141 -0
  367. package/src/styles/diagrams.css +1860 -0
  368. package/src/styles/digichat-app-theme.css +250 -0
  369. package/src/styles/digichat-boot-loader.css +487 -0
  370. package/src/styles/digichat-launcher.css +377 -0
  371. package/src/styles/docs.css +165 -0
  372. package/src/styles/effects-chrome.css +888 -0
  373. package/src/styles/finance-composites.css +214 -0
  374. package/src/styles/finance-tearsheet-overlay.css +90 -0
  375. package/src/styles/finance-tearsheet.css +945 -0
  376. package/src/styles/marquee.css +168 -0
  377. package/src/styles/metrics.css +82 -0
  378. package/src/styles/nav-shell.css +551 -0
  379. package/src/styles/pricing.css +79 -0
  380. package/src/styles/repo-activity.css +575 -0
  381. package/src/styles/stages.css +69 -0
  382. package/src/styles/terminal-loaders.css +122 -0
  383. package/src/styles/terminal-manifest.css +228 -0
  384. package/src/styles/web-theme.css +1007 -0
  385. package/src/styles/word-reveal.css +204 -0
  386. package/src/theme/apply-theme.ts +214 -0
  387. package/src/theme/index.ts +27 -0
  388. package/src/theme/palette-registry.ts +855 -0
  389. package/src/theme/tui-prefs.ts +64 -0
  390. package/src/ui/alert.tsx +78 -0
  391. package/src/ui/avatar.tsx +104 -0
  392. package/src/ui/badge.tsx +75 -0
  393. package/src/ui/breadcrumb.tsx +61 -0
  394. package/src/ui/button.tsx +67 -0
  395. package/src/ui/card.tsx +139 -0
  396. package/src/ui/checkbox.tsx +27 -0
  397. package/src/ui/collapsible.tsx +32 -0
  398. package/src/ui/date-pager.tsx +325 -0
  399. package/src/ui/dialog.tsx +157 -0
  400. package/src/ui/dropdown-menu.tsx +267 -0
  401. package/src/ui/empty-state.tsx +165 -0
  402. package/src/ui/field.tsx +104 -0
  403. package/src/ui/form.tsx +36 -0
  404. package/src/ui/icon-button.tsx +36 -0
  405. package/src/ui/index.ts +34 -0
  406. package/src/ui/input.tsx +33 -0
  407. package/src/ui/kbd.tsx +30 -0
  408. package/src/ui/label.tsx +32 -0
  409. package/src/ui/pager.tsx +119 -0
  410. package/src/ui/pagination.tsx +126 -0
  411. package/src/ui/radio-group.tsx +51 -0
  412. package/src/ui/search-bar.tsx +85 -0
  413. package/src/ui/segmented-control.tsx +76 -0
  414. package/src/ui/select.tsx +257 -0
  415. package/src/ui/separator.tsx +24 -0
  416. package/src/ui/sheet.tsx +141 -0
  417. package/src/ui/skeleton.tsx +76 -0
  418. package/src/ui/slider.tsx +76 -0
  419. package/src/ui/switch.tsx +31 -0
  420. package/src/ui/table.tsx +179 -0
  421. package/src/ui/tabs.tsx +89 -0
  422. package/src/ui/tags-input.tsx +168 -0
  423. package/src/ui/textarea.tsx +17 -0
  424. package/src/ui/tooltip.tsx +71 -0
@@ -0,0 +1,2261 @@
1
+ /* ==========================================================================
2
+ @digithings-ai/ui — chat-dress control CSS (styles/controls-core.css).
3
+
4
+ The legacy controls layer (`components/controls/*`, #1419) was retired in
5
+ batch K3 (#4306). What the kit still emits lives here: the chat dress
6
+ (`.ctl-btn-chat*`, `.ctl-badge-chat*`, `.ctl-input-chat`, `.ctl-label-chat`)
7
+ and the kit Card's `.ctl-card-*` part rules. The rest of the sheet is
8
+ historical. The #1548 families carry the reference
9
+ dress, plus dashboard's shipped dresses where the two differ (nb-pager
10
+ "capsule", nb-seg "accent" — see the NavButtons dresses section); they
11
+ follow the same layering split as the chat dress — single-class defaults
12
+ in @layer components, state/structural rules unlayered.
13
+
14
+ Every control carries TWO dresses (per the E3 ruling — API compatibility
15
+ beats aesthetic purity, digichat's rendered look must be reproducible
16
+ exactly):
17
+
18
+ - the REFERENCE dress (`*-ref` classes, the default): the design-reference
19
+ controls grammar, value-for-value copies of the specimen CSS —
20
+ .btn-primary / .btn-ghost / .btn-quiet / .btn-danger / .btn-icon
21
+ (reference/app/globals.css + styles/nav-shell.css), .ff-input
22
+ (reference/app/controls/controls.css), and the .dg-tier badge
23
+ (styles/web-theme.css). Mono type, radius 0, hairline borders,
24
+ srgb color-mix — kept verbatim so a specimen swap is pixel-faithful.
25
+
26
+ - the CHAT dress (`*-chat` classes): digichat's current shadcn-derived
27
+ control dress translated utility-for-utility from
28
+ digichat/src/components/ui/{button,badge,card,input,label,avatar}.tsx.
29
+ digichat's shadcn vars all resolve to canon tokens (#1403 reverse
30
+ bridge), so each rule reads `var(--shadcn-slot, var(--canon-token))`:
31
+ exact inside digichat (where the slot vars exist), canonical fallback
32
+ everywhere else. Opacity washes use `color-mix(in oklab, …)` — the
33
+ exact color space Tailwind v4 emits for `/NN` modifiers — so the
34
+ rendered pixels match digichat's current build. Dark rules key off
35
+ [data-theme="dark"], which digichat keeps in lockstep with its legacy
36
+ `.dark` class.
37
+
38
+ Wiring — import AFTER the token bridge in the app's globals.css:
39
+ @import "tailwindcss";
40
+ @import "@digithings-ai/design/tokens.css";
41
+ @import "@digithings-ai/ui/styles/web-theme.css";
42
+ @import "@digithings-ai/ui/styles/controls-core.css";
43
+ The controls components carry NO Tailwind utilities (all dress lives
44
+ here, keyed off data-slot / data-size / aria state), so no @source line
45
+ is needed for this family. Import PLAINLY (no layer(...)) — this file
46
+ manages its own layering:
47
+
48
+ Layering of the CHAT dress is deliberate and load-bearing (the same
49
+ doctrine as controls-overlay.css). digichat's old ui/* wrappers ran
50
+ every class through cn() = tailwind-merge, so a call-site utility
51
+ (p-8, text-[9px], h-6 …) deterministically replaced the base dress's
52
+ conflicting plain utility, while variant-ed base utilities (hover:,
53
+ focus-visible:, disabled:, aria-*, data-*, has-*) compiled to ≥(0,2,0)
54
+ specificity and kept beating plain call-site utilities. Reproducing
55
+ that split without tailwind-merge:
56
+ - single-class DEFAULT rules (idle dress, sizes, part defaults) sit in
57
+ `@layer components`, where the app's `utilities` layer wins — the
58
+ call-site-override semantic;
59
+ - STATE / STRUCTURAL rules (pseudo-classes, [aria-*], [data-size],
60
+ [data-theme=dark], :has(...), descendant sizing) stay UNLAYERED so
61
+ they keep beating plain call-site utilities, exactly like their
62
+ ≥(0,2,0) compiled ancestors did.
63
+ The REFERENCE dress rules stay unlayered verbatim — they are
64
+ value-for-value specimen copies and no consumer composes Tailwind
65
+ utility overrides onto them.
66
+ ========================================================================== */
67
+
68
+ /* ── Button — reference dress (reference/app/globals.css .btn-*) ────────── */
69
+
70
+ .ctl-btn-ref {
71
+ /* component tokens — the reference dress's local geometry */
72
+ --ctl-btn-ref-font: 0.78rem;
73
+ --ctl-btn-ref-pad-block: 0.62rem;
74
+ --ctl-btn-ref-pad-inline: 1.3rem;
75
+ --ctl-btn-ref-quiet-pad: 0.2rem;
76
+
77
+ font-family: var(--font-mono);
78
+ font-size: var(--ctl-btn-ref-font);
79
+ letter-spacing: 0.02em;
80
+ border-radius: 0;
81
+ padding: var(--ctl-btn-ref-pad-block) var(--ctl-btn-ref-pad-inline);
82
+ cursor: pointer;
83
+ border: 1px solid transparent;
84
+ }
85
+
86
+ .ctl-btn-ref--primary {
87
+ /* Utilitarian v0.1: loud = ink/paper fill; accent = focus/live only. */
88
+ background: var(--ink);
89
+ color: var(--bg);
90
+ border-color: var(--ink);
91
+ }
92
+
93
+ .ctl-btn-ref--primary:disabled {
94
+ opacity: 0.4;
95
+ cursor: not-allowed;
96
+ }
97
+
98
+ .ctl-btn-ref--ghost {
99
+ background: transparent;
100
+ color: var(--ink);
101
+ border-color: var(--hair);
102
+ }
103
+
104
+ .ctl-btn-ref--ghost:hover {
105
+ border-color: color-mix(in srgb, var(--accent) 50%, var(--hair));
106
+ }
107
+
108
+ .ctl-btn-ref--quiet {
109
+ background: transparent;
110
+ color: var(--ink-mute);
111
+ border-color: transparent;
112
+ padding-inline-start: var(--ctl-btn-ref-quiet-pad);
113
+ padding-inline-end: var(--ctl-btn-ref-quiet-pad);
114
+ }
115
+
116
+ .ctl-btn-ref--quiet:hover {
117
+ color: var(--ink);
118
+ }
119
+
120
+ .ctl-btn-ref--danger {
121
+ background: transparent;
122
+ color: var(--danger);
123
+ border-color: color-mix(in srgb, var(--danger) 40%, var(--hair));
124
+ }
125
+
126
+ .ctl-btn-ref--danger:hover {
127
+ background: color-mix(in srgb, var(--danger) 10%, transparent);
128
+ }
129
+
130
+ /* the borderless glyph button — the nav-shell tail-cluster grammar
131
+ (styles/nav-shell.css .btn-icon): no fill, no border, ink-mute → ink */
132
+ .ctl-btn-ref--icon {
133
+ display: inline-flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ padding: var(--space-2);
137
+ background: transparent;
138
+ border: none;
139
+ border-radius: 0;
140
+ color: var(--ink-mute);
141
+ transition: color 0.2s var(--ease);
142
+ }
143
+
144
+ .ctl-btn-ref--icon:hover {
145
+ color: var(--ink);
146
+ background: transparent;
147
+ }
148
+
149
+ .ctl-btn-ref--icon svg {
150
+ display: block;
151
+ }
152
+
153
+ /* loading state — .btn-loading / .btn-spinner grammar */
154
+ .ctl-btn-ref--loading {
155
+ display: inline-flex;
156
+ align-items: center;
157
+ gap: var(--space-2);
158
+ }
159
+
160
+ .ctl-btn-spinner {
161
+ --ctl-btn-spinner: 11px;
162
+
163
+ width: var(--ctl-btn-spinner);
164
+ height: var(--ctl-btn-spinner);
165
+ border-radius: 999px;
166
+ border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
167
+ border-top-color: currentColor;
168
+ animation: ctl-btn-spin 0.7s linear infinite;
169
+ }
170
+
171
+ @keyframes ctl-btn-spin {
172
+ to {
173
+ transform: rotate(360deg);
174
+ }
175
+ }
176
+
177
+ /* ── Button — chat dress (digichat ui/button.tsx, utility-for-utility) ────
178
+ defaults in @layer components (call-site utilities win, the cn()
179
+ semantic); states/structural rules unlayered (they beat plain
180
+ call-site utilities, as their ≥(0,2,0) compiled ancestors did) ─────── */
181
+
182
+ @layer components {
183
+ .ctl-btn-chat {
184
+ /* component tokens — chat-dress button geometry */
185
+ --ctl-btn-font: 0.875rem;
186
+ --ctl-btn-font-sm: 0.8rem;
187
+ --ctl-btn-gap: 0.375rem;
188
+ --ctl-btn-pad-inline: 0.625rem;
189
+ --ctl-btn-pad-tight: 0.5rem;
190
+ --ctl-btn-pad-lg: 0.75rem;
191
+ --ctl-btn-icon: 1rem;
192
+ --ctl-btn-icon-xs: 0.75rem;
193
+ --ctl-btn-icon-sm: 0.875rem;
194
+ --ctl-btn-h-sm: 1.75rem;
195
+ --ctl-btn-h-lg: 2.25rem;
196
+
197
+ display: inline-flex;
198
+ flex-shrink: 0;
199
+ align-items: center;
200
+ justify-content: center;
201
+ border-radius: 0;
202
+ border: 1px solid transparent;
203
+ background-clip: padding-box;
204
+ font-size: var(--ctl-btn-font);
205
+ line-height: calc(1.25 / 0.875);
206
+ font-weight: 500;
207
+ white-space: nowrap;
208
+ transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
209
+ outline: none;
210
+ user-select: none;
211
+ }
212
+
213
+ /* variants — hovers sit under (hover: hover) further down, exactly as
214
+ Tailwind v4 emits digichat's hover: utilities */
215
+ .ctl-btn-chat--default {
216
+ /* Utilitarian v0.1: loud fill is ink/paper; accent stays for focus/live. */
217
+ background: var(--ink);
218
+ color: var(--bg);
219
+ }
220
+
221
+ .ctl-btn-chat--outline {
222
+ border-color: var(--border, var(--hair));
223
+ background: var(--background, var(--bg));
224
+ }
225
+
226
+ .ctl-btn-chat--secondary {
227
+ background: var(--secondary, var(--surface-2));
228
+ color: var(--secondary-foreground, var(--ink));
229
+ }
230
+
231
+ .ctl-btn-chat--destructive {
232
+ background: color-mix(in oklab, var(--danger) 10%, transparent);
233
+ color: var(--danger);
234
+ }
235
+
236
+ .ctl-btn-chat--link {
237
+ color: var(--primary, var(--accent));
238
+ text-underline-offset: 4px;
239
+ }
240
+ }
241
+
242
+ .ctl-btn-chat:focus-visible {
243
+ outline: none;
244
+ border-color: var(--ring, var(--accent));
245
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring, var(--accent)) 50%, transparent);
246
+ }
247
+
248
+ .ctl-btn-chat:active {
249
+ transform: translateY(1px);
250
+ }
251
+
252
+ .ctl-btn-chat:disabled {
253
+ pointer-events: none;
254
+ opacity: 0.5;
255
+ }
256
+
257
+ .ctl-btn-chat[aria-invalid="true"] {
258
+ border-color: var(--danger);
259
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 20%, transparent);
260
+ }
261
+
262
+ [data-theme="dark"] .ctl-btn-chat[aria-invalid="true"] {
263
+ border-color: color-mix(in oklab, var(--danger) 50%, transparent);
264
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
265
+ }
266
+
267
+ .ctl-btn-chat svg {
268
+ pointer-events: none;
269
+ flex-shrink: 0;
270
+ }
271
+
272
+ .ctl-btn-chat svg:not([class*="size-"]) {
273
+ width: var(--ctl-btn-icon);
274
+ height: var(--ctl-btn-icon);
275
+ }
276
+
277
+ .ctl-btn-chat--outline[aria-expanded="true"] {
278
+ background: var(--muted, var(--surface-2));
279
+ color: var(--foreground, var(--ink));
280
+ }
281
+
282
+ [data-theme="dark"] .ctl-btn-chat--outline {
283
+ border-color: var(--input, var(--hair));
284
+ background: color-mix(in oklab, var(--input, var(--hair)) 30%, transparent);
285
+ }
286
+
287
+ .ctl-btn-chat--secondary[aria-expanded="true"] {
288
+ background: var(--secondary, var(--surface-2));
289
+ color: var(--secondary-foreground, var(--ink));
290
+ }
291
+
292
+ .ctl-btn-chat--ghost[aria-expanded="true"] {
293
+ background: var(--muted, var(--surface-2));
294
+ color: var(--foreground, var(--ink));
295
+ }
296
+
297
+ .ctl-btn-chat--destructive:focus-visible {
298
+ border-color: color-mix(in oklab, var(--danger) 40%, transparent);
299
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 20%, transparent);
300
+ }
301
+
302
+ [data-theme="dark"] .ctl-btn-chat--destructive {
303
+ background: color-mix(in oklab, var(--danger) 20%, transparent);
304
+ }
305
+
306
+ [data-theme="dark"] .ctl-btn-chat--destructive:focus-visible {
307
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
308
+ }
309
+
310
+ @media (hover: hover) {
311
+ a.ctl-btn-chat--default:hover {
312
+ background: color-mix(in oklab, var(--ink) 80%, transparent);
313
+ }
314
+
315
+ .ctl-btn-chat--outline:hover {
316
+ background: var(--muted, var(--surface-2));
317
+ color: var(--foreground, var(--ink));
318
+ }
319
+
320
+ [data-theme="dark"] .ctl-btn-chat--outline:hover {
321
+ background: color-mix(in oklab, var(--input, var(--hair)) 50%, transparent);
322
+ }
323
+
324
+ .ctl-btn-chat--secondary:hover {
325
+ background: color-mix(in oklab, var(--secondary, var(--surface-2)) 80%, transparent);
326
+ }
327
+
328
+ .ctl-btn-chat--ghost:hover {
329
+ background: var(--muted, var(--surface-2));
330
+ color: var(--foreground, var(--ink));
331
+ }
332
+
333
+ [data-theme="dark"] .ctl-btn-chat--ghost:hover {
334
+ background: color-mix(in oklab, var(--muted, var(--surface-2)) 50%, transparent);
335
+ }
336
+
337
+ .ctl-btn-chat--destructive:hover {
338
+ background: color-mix(in oklab, var(--danger) 20%, transparent);
339
+ }
340
+
341
+ [data-theme="dark"] .ctl-btn-chat--destructive:hover {
342
+ background: color-mix(in oklab, var(--danger) 30%, transparent);
343
+ }
344
+
345
+ .ctl-btn-chat--link:hover {
346
+ text-decoration-line: underline;
347
+ }
348
+ }
349
+
350
+ /* sizes — plain size dress in @layer components; icon-slot padding (:has)
351
+ and unsized-svg rules stay unlayered */
352
+ @layer components {
353
+ .ctl-btn-chat--size-default {
354
+ height: var(--space-5);
355
+ gap: var(--ctl-btn-gap);
356
+ padding-inline: var(--ctl-btn-pad-inline);
357
+ }
358
+
359
+ .ctl-btn-chat--size-xs {
360
+ height: var(--space-4);
361
+ gap: var(--space-1);
362
+ border-radius: 0;
363
+ padding-inline: var(--ctl-btn-pad-tight);
364
+ font-size: var(--type-meta);
365
+ line-height: calc(1 / 0.75);
366
+ }
367
+
368
+ .ctl-btn-chat--size-sm {
369
+ height: var(--ctl-btn-h-sm);
370
+ gap: var(--space-1);
371
+ border-radius: 0;
372
+ padding-inline: var(--ctl-btn-pad-inline);
373
+ font-size: var(--ctl-btn-font-sm);
374
+ }
375
+
376
+ .ctl-btn-chat--size-lg {
377
+ height: var(--ctl-btn-h-lg);
378
+ gap: var(--ctl-btn-gap);
379
+ padding-inline: var(--ctl-btn-pad-inline);
380
+ }
381
+
382
+ .ctl-btn-chat--size-icon {
383
+ width: var(--space-5);
384
+ height: var(--space-5);
385
+ }
386
+
387
+ .ctl-btn-chat--size-icon-xs {
388
+ width: var(--space-4);
389
+ height: var(--space-4);
390
+ border-radius: 0;
391
+ }
392
+
393
+ .ctl-btn-chat--size-icon-sm {
394
+ width: var(--ctl-btn-h-sm);
395
+ height: var(--ctl-btn-h-sm);
396
+ border-radius: 0;
397
+ }
398
+
399
+ .ctl-btn-chat--size-icon-lg {
400
+ width: var(--ctl-btn-h-lg);
401
+ height: var(--ctl-btn-h-lg);
402
+ }
403
+ }
404
+
405
+ .ctl-btn-chat--size-default:has([data-icon="inline-end"]) {
406
+ padding-inline-end: var(--ctl-btn-pad-tight);
407
+ }
408
+
409
+ .ctl-btn-chat--size-default:has([data-icon="inline-start"]) {
410
+ padding-inline-start: var(--ctl-btn-pad-tight);
411
+ }
412
+
413
+ .ctl-btn-chat--size-xs:has([data-icon="inline-end"]) {
414
+ padding-inline-end: var(--ctl-btn-gap);
415
+ }
416
+
417
+ .ctl-btn-chat--size-xs:has([data-icon="inline-start"]) {
418
+ padding-inline-start: var(--ctl-btn-gap);
419
+ }
420
+
421
+ .ctl-btn-chat--size-xs svg:not([class*="size-"]) {
422
+ width: var(--ctl-btn-icon-xs);
423
+ height: var(--ctl-btn-icon-xs);
424
+ }
425
+
426
+ .ctl-btn-chat--size-sm:has([data-icon="inline-end"]) {
427
+ padding-inline-end: var(--ctl-btn-gap);
428
+ }
429
+
430
+ .ctl-btn-chat--size-sm:has([data-icon="inline-start"]) {
431
+ padding-inline-start: var(--ctl-btn-gap);
432
+ }
433
+
434
+ .ctl-btn-chat--size-sm svg:not([class*="size-"]) {
435
+ width: var(--ctl-btn-icon-sm);
436
+ height: var(--ctl-btn-icon-sm);
437
+ }
438
+
439
+ .ctl-btn-chat--size-lg:has([data-icon="inline-end"]) {
440
+ padding-inline-end: var(--ctl-btn-pad-lg);
441
+ }
442
+
443
+ .ctl-btn-chat--size-lg:has([data-icon="inline-start"]) {
444
+ padding-inline-start: var(--ctl-btn-pad-lg);
445
+ }
446
+
447
+ .ctl-btn-chat--size-icon-xs svg:not([class*="size-"]) {
448
+ width: var(--ctl-btn-icon-xs);
449
+ height: var(--ctl-btn-icon-xs);
450
+ }
451
+
452
+ /* inside a button group the compact radii return to the group's lg radius */
453
+ [data-slot="button-group"] .ctl-btn-chat--size-xs,
454
+ [data-slot="button-group"] .ctl-btn-chat--size-sm,
455
+ [data-slot="button-group"] .ctl-btn-chat--size-icon-xs,
456
+ [data-slot="button-group"] .ctl-btn-chat--size-icon-sm {
457
+ border-radius: 0;
458
+ }
459
+
460
+ /* ── Badge — chat dress (digichat ui/badge.tsx) — defaults in
461
+ @layer components, states/structural rules unlayered (see header) ──── */
462
+
463
+ @layer components {
464
+ .ctl-badge-chat {
465
+ /* component tokens — chat-dress badge geometry */
466
+ --ctl-badge-h: 1.25rem;
467
+ --ctl-badge-pad-block: 0.125rem;
468
+ --ctl-badge-pad-inline: 0.5rem;
469
+ --ctl-badge-pad-tight: 0.375rem;
470
+ --ctl-badge-icon: 0.75rem;
471
+
472
+ display: inline-flex;
473
+ height: var(--ctl-badge-h);
474
+ width: fit-content;
475
+ flex-shrink: 0;
476
+ align-items: center;
477
+ justify-content: center;
478
+ gap: var(--space-1);
479
+ overflow: hidden;
480
+ border-radius: 0;
481
+ border: 1px solid transparent;
482
+ padding: var(--ctl-badge-pad-block) var(--ctl-badge-pad-inline);
483
+ font-size: var(--type-meta);
484
+ line-height: calc(1 / 0.75);
485
+ font-weight: 500;
486
+ white-space: nowrap;
487
+ transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
488
+ }
489
+
490
+ .ctl-badge-chat--default {
491
+ background: var(--primary, var(--accent));
492
+ color: var(--primary-foreground, var(--on-accent));
493
+ }
494
+
495
+ .ctl-badge-chat--secondary {
496
+ background: var(--secondary, var(--surface-2));
497
+ color: var(--secondary-foreground, var(--ink));
498
+ }
499
+
500
+ .ctl-badge-chat--destructive {
501
+ background: color-mix(in oklab, var(--danger) 10%, transparent);
502
+ color: var(--danger);
503
+ }
504
+
505
+ .ctl-badge-chat--outline {
506
+ border-color: var(--border, var(--hair));
507
+ color: var(--foreground, var(--ink));
508
+ }
509
+
510
+ .ctl-badge-chat--link {
511
+ color: var(--primary, var(--accent));
512
+ text-underline-offset: 4px;
513
+ }
514
+ }
515
+
516
+ .ctl-badge-chat:focus-visible {
517
+ border-color: var(--ring, var(--accent));
518
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring, var(--accent)) 50%, transparent);
519
+ }
520
+
521
+ .ctl-badge-chat:has([data-icon="inline-end"]) {
522
+ padding-inline-end: var(--ctl-badge-pad-tight);
523
+ }
524
+
525
+ .ctl-badge-chat:has([data-icon="inline-start"]) {
526
+ padding-inline-start: var(--ctl-badge-pad-tight);
527
+ }
528
+
529
+ .ctl-badge-chat[aria-invalid="true"] {
530
+ border-color: var(--danger);
531
+ }
532
+
533
+ .ctl-badge-chat[aria-invalid="true"]:focus-visible {
534
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 20%, transparent);
535
+ }
536
+
537
+ [data-theme="dark"] .ctl-badge-chat[aria-invalid="true"]:focus-visible {
538
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
539
+ }
540
+
541
+ .ctl-badge-chat > svg {
542
+ pointer-events: none;
543
+ width: var(--ctl-badge-icon) !important;
544
+ height: var(--ctl-badge-icon) !important;
545
+ }
546
+
547
+ .ctl-badge-chat--destructive:focus-visible {
548
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 20%, transparent);
549
+ }
550
+
551
+ [data-theme="dark"] .ctl-badge-chat--destructive {
552
+ background: color-mix(in oklab, var(--danger) 20%, transparent);
553
+ }
554
+
555
+ [data-theme="dark"] .ctl-badge-chat--destructive:focus-visible {
556
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
557
+ }
558
+
559
+ @media (hover: hover) {
560
+ a.ctl-badge-chat--default:hover {
561
+ background: color-mix(in oklab, var(--primary, var(--accent)) 80%, transparent);
562
+ }
563
+
564
+ a.ctl-badge-chat--secondary:hover {
565
+ background: color-mix(in oklab, var(--secondary, var(--surface-2)) 80%, transparent);
566
+ }
567
+
568
+ a.ctl-badge-chat--destructive:hover {
569
+ background: color-mix(in oklab, var(--danger) 20%, transparent);
570
+ }
571
+
572
+ a.ctl-badge-chat--outline:hover {
573
+ background: var(--muted, var(--surface-2));
574
+ color: var(--muted-foreground, var(--ink-soft));
575
+ }
576
+
577
+ .ctl-badge-chat--ghost:hover {
578
+ background: var(--muted, var(--surface-2));
579
+ color: var(--muted-foreground, var(--ink-soft));
580
+ }
581
+
582
+ [data-theme="dark"] .ctl-badge-chat--ghost:hover {
583
+ background: color-mix(in oklab, var(--muted, var(--surface-2)) 50%, transparent);
584
+ }
585
+
586
+ .ctl-badge-chat--link:hover {
587
+ text-decoration-line: underline;
588
+ }
589
+ }
590
+
591
+ /* ── Card — chat dress (digichat ui/card.tsx) — defaults (root + part
592
+ dress) in @layer components; :has()/data-size/structural rules
593
+ unlayered (see header) ───────────────────────────────────────────── */
594
+
595
+ @layer components {
596
+ .ctl-card-chat {
597
+ /* component tokens — chat-dress card geometry */
598
+ --ctl-card-gap: 1rem;
599
+ --ctl-card-pad: 1rem;
600
+ --ctl-card-pad-sm: 0.75rem;
601
+ --ctl-card-font: 0.875rem;
602
+ --ctl-card-title-font: 1rem;
603
+
604
+ display: flex;
605
+ flex-direction: column;
606
+ gap: var(--ctl-card-gap);
607
+ overflow: hidden;
608
+ border-radius: 0;
609
+ background: var(--card, var(--surface));
610
+ padding-block: var(--ctl-card-pad);
611
+ font-size: var(--ctl-card-font);
612
+ line-height: calc(1.25 / 0.875);
613
+ color: var(--card-foreground, var(--ink));
614
+ box-shadow: 0 0 0 1px color-mix(in oklab, var(--foreground, var(--ink)) 10%, transparent);
615
+ }
616
+
617
+ .ctl-card-chat .ctl-card-header {
618
+ container: card-header / inline-size;
619
+ display: grid;
620
+ grid-auto-rows: min-content;
621
+ align-items: start;
622
+ gap: var(--space-1);
623
+ border-start-start-radius: 0;
624
+ border-start-end-radius: 0;
625
+ padding-inline: var(--ctl-card-pad);
626
+ }
627
+
628
+ .ctl-card-chat .ctl-card-title {
629
+ font-family: var(--font-heading, var(--font-sans));
630
+ font-size: var(--ctl-card-title-font);
631
+ line-height: 1.375;
632
+ font-weight: 500;
633
+ }
634
+
635
+ .ctl-card-chat .ctl-card-description {
636
+ font-size: var(--ctl-card-font);
637
+ line-height: calc(1.25 / 0.875);
638
+ color: var(--muted-foreground, var(--ink-soft));
639
+ }
640
+
641
+ .ctl-card-chat .ctl-card-content {
642
+ padding-inline: var(--ctl-card-pad);
643
+ }
644
+
645
+ .ctl-card-chat .ctl-card-footer {
646
+ display: flex;
647
+ align-items: center;
648
+ border-top: 1px solid var(--border, var(--hair));
649
+ background: color-mix(in oklab, var(--muted, var(--surface-2)) 50%, transparent);
650
+ padding: var(--ctl-card-pad);
651
+ border-end-start-radius: 0;
652
+ border-end-end-radius: 0;
653
+ }
654
+
655
+ /* the action slot places itself the same way under both dresses */
656
+ .ctl-card-action {
657
+ grid-column-start: 2;
658
+ grid-row: 1 / span 2;
659
+ align-self: start;
660
+ justify-self: end;
661
+ }
662
+ }
663
+
664
+ .ctl-card-chat:has([data-slot="card-footer"]) {
665
+ padding-bottom: 0;
666
+ }
667
+
668
+ .ctl-card-chat:has(> img:first-child) {
669
+ padding-top: 0;
670
+ }
671
+
672
+ .ctl-card-chat[data-size="sm"] {
673
+ gap: var(--ctl-card-pad-sm);
674
+ padding-block: var(--ctl-card-pad-sm);
675
+ }
676
+
677
+ .ctl-card-chat[data-size="sm"]:has([data-slot="card-footer"]) {
678
+ padding-bottom: 0;
679
+ }
680
+
681
+ .ctl-card-chat > img:first-child {
682
+ border-start-start-radius: 0;
683
+ border-start-end-radius: 0;
684
+ }
685
+
686
+ .ctl-card-chat > img:last-child {
687
+ border-end-start-radius: 0;
688
+ border-end-end-radius: 0;
689
+ }
690
+
691
+ .ctl-card-chat .ctl-card-header:has([data-slot="card-action"]) {
692
+ grid-template-columns: 1fr auto;
693
+ }
694
+
695
+ .ctl-card-chat .ctl-card-header:has([data-slot="card-description"]) {
696
+ grid-template-rows: auto auto;
697
+ }
698
+
699
+ /* digichat's `[.border-b]:pb-4` — a header the consumer marked .border-b */
700
+ .ctl-card-chat .ctl-card-header.border-b {
701
+ padding-bottom: var(--ctl-card-pad);
702
+ }
703
+
704
+ .ctl-card-chat[data-size="sm"] .ctl-card-header {
705
+ padding-inline: var(--ctl-card-pad-sm);
706
+ }
707
+
708
+ .ctl-card-chat[data-size="sm"] .ctl-card-header.border-b {
709
+ padding-bottom: var(--ctl-card-pad-sm);
710
+ }
711
+
712
+ .ctl-card-chat[data-size="sm"] .ctl-card-title {
713
+ font-size: var(--ctl-card-font);
714
+ }
715
+
716
+ .ctl-card-chat[data-size="sm"] .ctl-card-content {
717
+ padding-inline: var(--ctl-card-pad-sm);
718
+ }
719
+
720
+ .ctl-card-chat[data-size="sm"] .ctl-card-footer {
721
+ padding: var(--ctl-card-pad-sm);
722
+ }
723
+
724
+ /* ── Input — chat dress (digichat ui/input.tsx) — defaults in
725
+ @layer components, states unlayered (see header) ────────────────────── */
726
+
727
+ @layer components {
728
+ .ctl-input-chat {
729
+ /* component tokens — chat-dress input geometry */
730
+ --ctl-input-h: 2rem;
731
+ --ctl-input-pad-block: 0.25rem;
732
+ --ctl-input-pad-inline: 0.625rem;
733
+ --ctl-input-font: 1rem;
734
+ --ctl-input-font-md: 0.875rem;
735
+ --ctl-input-file-h: 1.5rem;
736
+
737
+ height: var(--ctl-input-h);
738
+ width: 100%;
739
+ min-width: 0;
740
+ border-radius: 0;
741
+ border: 1px solid var(--input, var(--hair));
742
+ background: transparent;
743
+ padding: var(--ctl-input-pad-block) var(--ctl-input-pad-inline);
744
+ font-size: var(--ctl-input-font);
745
+ line-height: 1.5;
746
+ outline: none;
747
+ transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1),
748
+ background-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
749
+ border-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
750
+ text-decoration-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
751
+ fill 150ms cubic-bezier(0.4, 0, 0.2, 1), stroke 150ms cubic-bezier(0.4, 0, 0.2, 1);
752
+ }
753
+
754
+ @media (min-width: 48rem) {
755
+ .ctl-input-chat {
756
+ font-size: var(--ctl-input-font-md);
757
+ line-height: calc(1.25 / 0.875);
758
+ }
759
+ }
760
+
761
+ .ctl-input-chat::file-selector-button {
762
+ display: inline-flex;
763
+ height: var(--ctl-input-file-h);
764
+ border: 0;
765
+ background: transparent;
766
+ font-size: var(--ctl-input-font-md);
767
+ line-height: calc(1.25 / 0.875);
768
+ font-weight: 500;
769
+ color: var(--foreground, var(--ink));
770
+ }
771
+
772
+ .ctl-input-chat::placeholder {
773
+ color: var(--muted-foreground, var(--ink-soft));
774
+ }
775
+ }
776
+
777
+ .ctl-input-chat:focus-visible {
778
+ outline: none;
779
+ border-color: var(--ring, var(--accent));
780
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring, var(--accent)) 50%, transparent);
781
+ }
782
+
783
+ .ctl-input-chat:disabled {
784
+ pointer-events: none;
785
+ cursor: not-allowed;
786
+ background: color-mix(in oklab, var(--input, var(--hair)) 50%, transparent);
787
+ opacity: 0.5;
788
+ }
789
+
790
+ .ctl-input-chat[aria-invalid="true"] {
791
+ border-color: var(--danger);
792
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 20%, transparent);
793
+ }
794
+
795
+ [data-theme="dark"] .ctl-input-chat {
796
+ background: color-mix(in oklab, var(--input, var(--hair)) 30%, transparent);
797
+ }
798
+
799
+ [data-theme="dark"] .ctl-input-chat:disabled {
800
+ background: color-mix(in oklab, var(--input, var(--hair)) 80%, transparent);
801
+ }
802
+
803
+ [data-theme="dark"] .ctl-input-chat[aria-invalid="true"] {
804
+ border-color: color-mix(in oklab, var(--danger) 50%, transparent);
805
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
806
+ }
807
+
808
+ /* ── Label — reference dress (the form-fields mono micro-caps grammar) ──── */
809
+
810
+ .ctl-label-ref {
811
+ font-family: var(--font-mono);
812
+ font-size: 0.6rem;
813
+ text-transform: uppercase;
814
+ letter-spacing: 0.1em;
815
+ color: var(--ink-mute);
816
+ }
817
+
818
+ /* ── Label — chat dress (digichat ui/label.tsx) — default in
819
+ @layer components, group/peer dimming unlayered (see header) ─────────── */
820
+
821
+ @layer components {
822
+ .ctl-label-chat {
823
+ --ctl-label-font: 0.875rem;
824
+
825
+ display: flex;
826
+ align-items: center;
827
+ gap: var(--space-2);
828
+ font-size: var(--ctl-label-font);
829
+ line-height: 1;
830
+ font-weight: 500;
831
+ user-select: none;
832
+ }
833
+ }
834
+
835
+ .group[data-disabled="true"] .ctl-label-chat {
836
+ pointer-events: none;
837
+ opacity: 0.5;
838
+ }
839
+
840
+ .peer:disabled ~ .ctl-label-chat {
841
+ cursor: not-allowed;
842
+ opacity: 0.5;
843
+ }
844
+
845
+ /* ── Skeleton — the sk-* shimmer grammar (#1548, from
846
+ reference/app/controls/controls.css). Shape defaults in
847
+ @layer components so call-site width/height utilities can size them
848
+ (dashboard's snapshot skeleton uses h-20 blocks); the ::after shimmer,
849
+ keyframes, and reduced-motion guard stay unlayered ─────────────────── */
850
+
851
+ @layer components {
852
+ .sk {
853
+ position: relative;
854
+ overflow: hidden;
855
+ background: color-mix(in srgb, var(--ink) 9%, transparent);
856
+ border-radius: 0;
857
+ }
858
+
859
+ .sk-circle {
860
+ --sk-circle: 40px;
861
+
862
+ width: var(--sk-circle);
863
+ height: var(--sk-circle);
864
+ border-radius: 999px;
865
+ flex-shrink: 0;
866
+ }
867
+
868
+ .sk-line {
869
+ --sk-line-h: 0.72rem;
870
+
871
+ height: var(--sk-line-h);
872
+ }
873
+
874
+ .sk-line--sm {
875
+ height: var(--space-2);
876
+ }
877
+
878
+ .sk-btn {
879
+ --sk-btn-w: 6.6rem;
880
+ --sk-btn-h: 2rem;
881
+
882
+ width: var(--sk-btn-w);
883
+ height: var(--sk-btn-h);
884
+ border-radius: 0;
885
+ flex-shrink: 0;
886
+ }
887
+
888
+ .sk-block {
889
+ height: var(--space-4);
890
+ width: 60%;
891
+ border-radius: 0;
892
+ }
893
+ }
894
+
895
+ .sk::after {
896
+ content: "";
897
+ position: absolute;
898
+ inset: 0;
899
+ transform: translateX(-100%);
900
+ background: linear-gradient(
901
+ 90deg,
902
+ transparent,
903
+ color-mix(in srgb, var(--ink) 13%, transparent),
904
+ transparent
905
+ );
906
+ animation: sk-shimmer 1.5s var(--ease) infinite;
907
+ }
908
+
909
+ @keyframes sk-shimmer {
910
+ to {
911
+ transform: translateX(100%);
912
+ }
913
+ }
914
+
915
+ /* ── EmptyState — the no-results / first-run / error triple (#1548). The
916
+ specimen wore token utilities (empty-states-reference.tsx); translated
917
+ value-for-value here (the /NN washes as the color-mix(in oklab, …)
918
+ Tailwind v4 emits). Defaults in @layer components; the error-variant
919
+ glyph tint is a structural (descendant) rule and stays unlayered ───── */
920
+
921
+ @layer components {
922
+ .ctl-empty {
923
+ /* component tokens — empty-state geometry */
924
+ --ctl-empty-gap: 0.55rem;
925
+ --ctl-empty-pad-block: 1.8rem;
926
+ --ctl-empty-pad-inline: 1.3rem;
927
+ --ctl-empty-glyph: 2.75rem;
928
+ --ctl-empty-title-font: 0.86rem;
929
+ --ctl-empty-body-font: 0.8rem;
930
+ --ctl-empty-note-font: 0.75rem;
931
+ --ctl-empty-font-md: 0.875rem;
932
+
933
+ display: flex;
934
+ flex-direction: column;
935
+ align-items: center;
936
+ gap: var(--ctl-empty-gap);
937
+ border: 1px solid var(--hair);
938
+ border-radius: 0;
939
+ background: color-mix(in oklab, var(--surface) 55%, transparent);
940
+ padding: var(--ctl-empty-pad-block) var(--ctl-empty-pad-inline);
941
+ text-align: center;
942
+ }
943
+
944
+ .ctl-empty-glyph {
945
+ display: grid;
946
+ place-items: center;
947
+ width: var(--ctl-empty-glyph);
948
+ height: var(--ctl-empty-glyph);
949
+ margin-bottom: 0.15rem;
950
+ border-radius: 0;
951
+ background: color-mix(in oklab, var(--ink) 8%, transparent);
952
+ color: var(--ink-mute);
953
+ }
954
+
955
+ .ctl-empty-title {
956
+ margin: 0;
957
+ font-family: var(--font-mono);
958
+ font-size: var(--ctl-empty-title-font);
959
+ font-weight: 400;
960
+ color: var(--ink);
961
+ }
962
+
963
+ .ctl-empty-body {
964
+ margin: 0 0 0.4rem;
965
+ max-width: 24ch;
966
+ font-size: var(--ctl-empty-body-font);
967
+ line-height: 1.5;
968
+ color: var(--ink-mute);
969
+ }
970
+
971
+ /* note — the italic "why it's empty" secondary line (dashboard observability) */
972
+ .ctl-empty-note {
973
+ margin: 0;
974
+ max-width: 24ch;
975
+ font-size: var(--ctl-empty-note-font);
976
+ font-style: italic;
977
+ line-height: calc(1 / 0.75);
978
+ color: color-mix(in oklab, var(--ink-mute) 60%, transparent);
979
+ }
980
+ }
981
+
982
+ .ctl-empty--error .ctl-empty-glyph {
983
+ background: color-mix(in oklab, var(--danger) 12%, transparent);
984
+ color: var(--danger);
985
+ }
986
+
987
+ /* ── EmptyState glass dresses (#1548 dashboard adoption) — the app's shipped
988
+ looks, value-for-value from components/observability/shared.tsx and
989
+ components/db-unavailable.tsx. Type/spacing only: the surface stays the
990
+ call-site class (dashboard `.glass-card`, unlayered app CSS, so it wins
991
+ the card chrome AND keeps the app's motion-reveal hook). No state rules
992
+ here, so everything sits in @layer components — call-site utilities may
993
+ still adjust geometry (e.g. db-unavailable's `max-w-md mx-auto`). ────── */
994
+
995
+ @layer components {
996
+ /* dress="glass" — the observability quiet card (glass-card p-8 gap-2) */
997
+ .ctl-empty--glass {
998
+ justify-content: center;
999
+ gap: var(--space-2);
1000
+ padding: var(--space-5);
1001
+ }
1002
+
1003
+ .ctl-empty--glass .ctl-empty-title {
1004
+ font-family: var(--font-sans);
1005
+ font-size: var(--ctl-empty-font-md);
1006
+ line-height: calc(1.25 / 0.875);
1007
+ font-weight: 500;
1008
+ color: var(--ink-soft);
1009
+ }
1010
+
1011
+ .ctl-empty--glass .ctl-empty-body {
1012
+ margin: 0;
1013
+ max-width: 28rem; /* max-w-md */
1014
+ font-size: var(--type-meta);
1015
+ line-height: calc(1 / 0.75);
1016
+ color: var(--ink-mute);
1017
+ }
1018
+
1019
+ .ctl-empty--glass .ctl-empty-note {
1020
+ margin-top: var(--space-1);
1021
+ max-width: 28rem;
1022
+ }
1023
+
1024
+ /* dress="glass-display" — the full-page gate card (db-unavailable):
1025
+ font-display 2xl title, relaxed text-sm body, margin-driven rhythm */
1026
+ .ctl-empty--glass-display {
1027
+ --ctl-empty-display-title: 1.5rem;
1028
+
1029
+ justify-content: center;
1030
+ gap: 0;
1031
+ padding: var(--space-5) var(--space-4);
1032
+ }
1033
+
1034
+ .ctl-empty--glass-display .ctl-empty-title {
1035
+ font-family: var(--font-display);
1036
+ font-size: var(--ctl-empty-display-title);
1037
+ line-height: calc(2 / 1.5);
1038
+ letter-spacing: -0.025em; /* tracking-tight */
1039
+ font-weight: 400;
1040
+ color: var(--ink);
1041
+ }
1042
+
1043
+ .ctl-empty--glass-display .ctl-empty-body {
1044
+ margin: var(--space-2) 0 0;
1045
+ max-width: none;
1046
+ font-size: var(--ctl-empty-font-md);
1047
+ line-height: 1.625; /* leading-relaxed */
1048
+ color: var(--ink-mute);
1049
+ }
1050
+ }
1051
+
1052
+ /* ── NavButtons — segmented switch, pager, icon buttons (#1548, from
1053
+ reference/app/controls/controls.css). Defaults in @layer components;
1054
+ hover/disabled and the aria-pressed / aria-current selection states
1055
+ (replacing the specimen's .on classes) stay unlayered. Order matters:
1056
+ the aria selection rules sit after :hover at equal specificity, so a
1057
+ hovered selected cell keeps the selected dress, as in the specimen ── */
1058
+
1059
+ @layer components {
1060
+ .nb-seg-group {
1061
+ display: inline-flex;
1062
+ overflow: hidden;
1063
+ border: 1px solid var(--hair);
1064
+ border-radius: 0;
1065
+ }
1066
+
1067
+ .nb-seg {
1068
+ /* component tokens — segmented-switch cell geometry */
1069
+ --nb-seg-pad-block: 0.4rem;
1070
+ --nb-seg-pad-inline: 0.85rem;
1071
+ --nb-seg-font: 0.72rem;
1072
+
1073
+ padding: var(--nb-seg-pad-block) var(--nb-seg-pad-inline);
1074
+ border: none;
1075
+ border-inline-start: 1px solid var(--hair);
1076
+ background: var(--surface);
1077
+ color: var(--ink-mute);
1078
+ font-family: var(--font-mono);
1079
+ font-size: var(--nb-seg-font);
1080
+ cursor: pointer;
1081
+ transition: color 0.2s var(--ease), background-color 0.2s var(--ease);
1082
+ }
1083
+
1084
+ .nb-pager {
1085
+ --nb-pager-gap: 0.3rem;
1086
+
1087
+ display: inline-grid;
1088
+ grid-template-columns: auto minmax(0, max-content) auto;
1089
+ align-items: center;
1090
+ gap: var(--nb-pager-gap);
1091
+ }
1092
+
1093
+ .nb-pager-middle {
1094
+ display: flex;
1095
+ align-items: center;
1096
+ justify-content: center;
1097
+ min-width: 0;
1098
+ }
1099
+
1100
+ .nb-page,
1101
+ .nb-page-edge {
1102
+ --nb-page-pad-inline: 0.6rem;
1103
+ --nb-page-font: 0.72rem;
1104
+
1105
+ min-width: var(--space-5);
1106
+ height: var(--space-5);
1107
+ padding: 0 var(--nb-page-pad-inline);
1108
+ border: 1px solid var(--hair);
1109
+ border-radius: 0;
1110
+ background: var(--surface);
1111
+ color: var(--ink-soft);
1112
+ font-family: var(--font-mono);
1113
+ font-size: var(--nb-page-font);
1114
+ cursor: pointer;
1115
+ transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
1116
+ }
1117
+
1118
+ .nb-icon {
1119
+ --nb-icon-size: var(--space-5);
1120
+
1121
+ width: var(--nb-icon-size);
1122
+ height: var(--nb-icon-size);
1123
+ display: inline-flex;
1124
+ align-items: center;
1125
+ justify-content: center;
1126
+ border: none;
1127
+ border-radius: 0;
1128
+ background: transparent;
1129
+ color: var(--ink-mute);
1130
+ cursor: pointer;
1131
+ transition: color 0.2s var(--ease), background-color 0.2s var(--ease);
1132
+ }
1133
+
1134
+ /* Disabled is inert but still names the affordance: no click affordance,
1135
+ not-allowed cursor (mirrors the kit Button's disabled convention). */
1136
+ .nb-icon:disabled {
1137
+ cursor: not-allowed;
1138
+ }
1139
+
1140
+ .nb-seg:disabled {
1141
+ cursor: not-allowed;
1142
+ }
1143
+ }
1144
+
1145
+ .nb-seg:first-child {
1146
+ border-inline-start: none;
1147
+ }
1148
+
1149
+ .nb-seg:hover {
1150
+ color: var(--ink-soft);
1151
+ }
1152
+
1153
+ .nb-seg[aria-pressed="true"] {
1154
+ color: var(--ink);
1155
+ background: var(--accent-weak);
1156
+ }
1157
+
1158
+ .nb-page:hover:not([aria-current="page"]),
1159
+ .nb-page-edge:hover:not(:disabled) {
1160
+ color: var(--ink);
1161
+ border-color: color-mix(in srgb, var(--accent) 45%, var(--hair));
1162
+ }
1163
+
1164
+ .nb-page[aria-current="page"] {
1165
+ color: var(--on-accent);
1166
+ background: var(--accent);
1167
+ border-color: var(--accent);
1168
+ }
1169
+
1170
+ .nb-page-edge:disabled {
1171
+ opacity: 0.4;
1172
+ cursor: not-allowed;
1173
+ }
1174
+
1175
+ .nb-icon:hover {
1176
+ color: var(--ink);
1177
+ background: color-mix(in srgb, var(--ink) 6%, transparent);
1178
+ }
1179
+
1180
+ /* ── NavButtons dresses (#1548) — dashboard's shipped looks, translated
1181
+ exactly from its Tailwind utilities. Defaults in @layer components;
1182
+ state overrides unlayered AFTER the reference state rules so they win
1183
+ the tie at higher specificity. color-mix(in oklab, …) matches Tailwind
1184
+ v4's opacity-modifier compilation (bg-accent/20, hover:bg-ink/[0.04]).
1185
+
1186
+ - .nb-pager--capsule (Pager dress="capsule"): one bordered term-bg
1187
+ capsule (9px radius, mono 12.5px tabular) with borderless chevron
1188
+ edges pinned in auto columns — dashboard DatePager / pipeline + briefs.
1189
+ - .nb-pager--date .nb-pager-date: fixed-width centered label slot
1190
+ sized for the longest en-US short format ("Wed, Sep 30, 2026").
1191
+ - .nb-seg-group--accent (SegmentedControl dress="accent"): sans
1192
+ font-medium cells on a transparent track, accent/20 wash + accent
1193
+ text on the selection — dashboard PerformanceDateRange. ─────────── */
1194
+
1195
+ @layer components {
1196
+ .nb-pager--capsule {
1197
+ /* shipped capsule: gap-2 px-2.5 py-1.5 border-hair rounded-[9px]
1198
+ bg-term-bg font-mono text-[12.5px] tabular-nums; grid keeps edges
1199
+ fixed while the middle label width changes across dates */
1200
+ --nb-capsule-pad-block: 0.375rem;
1201
+ --nb-capsule-pad-inline: 0.625rem;
1202
+ --nb-capsule-font: 12.5px;
1203
+
1204
+ grid-template-columns: auto 1fr auto;
1205
+ gap: var(--space-2);
1206
+ padding: var(--nb-capsule-pad-block) var(--nb-capsule-pad-inline);
1207
+ border: 1px solid var(--hair);
1208
+ border-radius: 0;
1209
+ background: var(--term-bg);
1210
+ font-family: var(--font-mono);
1211
+ font-size: var(--nb-capsule-font);
1212
+ font-variant-numeric: tabular-nums;
1213
+ }
1214
+
1215
+ .nb-pager--capsule .nb-page-edge {
1216
+ /* shipped chevrons: borderless, transparent, content-sized */
1217
+ min-width: 0;
1218
+ height: auto;
1219
+ padding: 0;
1220
+ border: none;
1221
+ border-radius: 0;
1222
+ background: transparent;
1223
+ color: var(--ink-mute);
1224
+ transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);
1225
+ }
1226
+
1227
+ .nb-pager--capsule .nb-page-edge:hover:not(:disabled) {
1228
+ color: var(--ink);
1229
+ border-color: transparent;
1230
+ }
1231
+
1232
+ /* Date capsule: middle column sized for longest short weekday label so
1233
+ arrows never shift when the string grows/shrinks. */
1234
+ .nb-pager--date {
1235
+ --nb-date-ch: 17ch;
1236
+
1237
+ min-width: calc(var(--space-4) + var(--nb-date-ch) + var(--space-4));
1238
+ }
1239
+
1240
+ .nb-pager--date .nb-pager-middle {
1241
+ min-width: var(--nb-date-ch);
1242
+ }
1243
+
1244
+ .nb-pager-date {
1245
+ --nb-date-ch: 17ch;
1246
+
1247
+ display: inline-flex;
1248
+ align-items: center;
1249
+ justify-content: center;
1250
+ width: 100%;
1251
+ min-width: var(--nb-date-ch);
1252
+ margin: 0;
1253
+ padding: 0;
1254
+ border: none;
1255
+ border-radius: 0;
1256
+ background: transparent;
1257
+ color: var(--ink);
1258
+ font: inherit;
1259
+ font-variant-numeric: tabular-nums;
1260
+ cursor: pointer;
1261
+ transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1);
1262
+ }
1263
+
1264
+ .nb-pager-date:hover:not(:disabled),
1265
+ .nb-pager-date[data-popup-open] {
1266
+ color: var(--accent);
1267
+ background: color-mix(in oklab, var(--accent) 12%, transparent);
1268
+ }
1269
+
1270
+ .nb-pager-date:disabled {
1271
+ opacity: 0.4;
1272
+ cursor: not-allowed;
1273
+ }
1274
+
1275
+ .nb-pager-date-text {
1276
+ display: block;
1277
+ width: 100%;
1278
+ overflow: hidden;
1279
+ text-overflow: ellipsis;
1280
+ white-space: nowrap;
1281
+ text-align: center;
1282
+ }
1283
+
1284
+ .nb-seg-group--accent .nb-seg {
1285
+ /* shipped cells: px-3 py-1.5 sans text-xs font-medium, no fill */
1286
+ --nb-seg-accent-pad-block: 0.375rem;
1287
+ --nb-seg-accent-pad-inline: 0.75rem;
1288
+
1289
+ padding: var(--nb-seg-accent-pad-block) var(--nb-seg-accent-pad-inline);
1290
+ background: transparent;
1291
+ color: var(--ink-mute);
1292
+ font-family: var(--font-sans);
1293
+ font-size: var(--type-meta);
1294
+ font-weight: 500;
1295
+ transition:
1296
+ color 0.15s cubic-bezier(0.4, 0, 0.2, 1),
1297
+ background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1);
1298
+ }
1299
+ }
1300
+
1301
+ .nb-pager--capsule .nb-page-edge:disabled {
1302
+ /* shipped: disabled:opacity-30; cursor normalized to the kit's not-allowed
1303
+ convention (#4306, phase 0.3) */
1304
+ opacity: 0.3;
1305
+ cursor: not-allowed;
1306
+ }
1307
+
1308
+ .nb-seg-group--accent .nb-seg:hover:not([aria-pressed="true"]) {
1309
+ /* shipped: hover:bg-ink/[0.04] hover:text-ink (unselected only) */
1310
+ color: var(--ink);
1311
+ background: color-mix(in oklab, var(--ink) 4%, transparent);
1312
+ }
1313
+
1314
+ .nb-seg-group--accent .nb-seg[aria-pressed="true"] {
1315
+ /* shipped selection: bg-accent/20 text-accent (hover leaves it alone) */
1316
+ color: var(--accent);
1317
+ background: color-mix(in oklab, var(--accent) 20%, transparent);
1318
+ }
1319
+
1320
+ /* ── TagsInput — chips multi-select (#1548, from
1321
+ reference/app/controls/controls.css; the specimen's baked-in
1322
+ margin-top moved to the consumer). Defaults in @layer components;
1323
+ focus-within, hovers, and the chipless-input stretch (structural
1324
+ :first-child) stay unlayered ─────────────────────────────────────── */
1325
+
1326
+ @layer components {
1327
+ .tg-field {
1328
+ /* component tokens — tags-field geometry */
1329
+ --tg-gap: 0.4rem;
1330
+ --tg-pad-block: 0.5rem;
1331
+ --tg-pad-inline: 0.6rem;
1332
+
1333
+ width: min(100%, 30rem);
1334
+ display: flex;
1335
+ flex-wrap: wrap;
1336
+ align-items: center;
1337
+ gap: var(--tg-gap);
1338
+ padding: var(--tg-pad-block) var(--tg-pad-inline);
1339
+ border: 1px solid var(--hair);
1340
+ border-radius: 0;
1341
+ background: var(--surface);
1342
+ cursor: text;
1343
+ transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
1344
+ }
1345
+
1346
+ .tg-chip {
1347
+ /* chip tokens are declared on the chip itself: `.tg-chip` is rendered
1348
+ standalone (no `.tg-field` ancestor requirement) */
1349
+ --tg-chip-gap: 0.35rem;
1350
+ --tg-chip-pad-block: 0.22rem;
1351
+ --tg-chip-pad-inline: 0.35rem;
1352
+ --tg-chip-pad-lead: 0.6rem;
1353
+ --tg-chip-font: 0.74rem;
1354
+
1355
+ display: inline-flex;
1356
+ align-items: center;
1357
+ gap: var(--tg-chip-gap);
1358
+ padding: var(--tg-chip-pad-block) var(--tg-chip-pad-inline) var(--tg-chip-pad-block) var(--tg-chip-pad-lead);
1359
+ border: 1px solid color-mix(in srgb, var(--accent) 32%, var(--hair));
1360
+ border-radius: 0;
1361
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
1362
+ font-family: var(--font-mono);
1363
+ font-size: var(--tg-chip-font);
1364
+ color: var(--ink);
1365
+ white-space: nowrap;
1366
+ }
1367
+
1368
+ .tg-x {
1369
+ --tg-x-pad: 0.1rem;
1370
+
1371
+ display: inline-flex;
1372
+ align-items: center;
1373
+ justify-content: center;
1374
+ padding: var(--tg-x-pad);
1375
+ border: none;
1376
+ background: transparent;
1377
+ color: var(--ink-mute);
1378
+ cursor: pointer;
1379
+ border-radius: 0;
1380
+ transition: color 0.15s var(--ease);
1381
+ }
1382
+
1383
+ .tg-input {
1384
+ --tg-input-min-w: 7rem;
1385
+ --tg-input-pad: 0.2rem;
1386
+ --tg-input-font: 0.8rem;
1387
+
1388
+ min-width: var(--tg-input-min-w);
1389
+ border: none;
1390
+ background: transparent;
1391
+ padding: var(--tg-input-pad);
1392
+ font-family: var(--font-mono);
1393
+ font-size: var(--tg-input-font);
1394
+ color: var(--ink);
1395
+ outline: none;
1396
+ }
1397
+
1398
+ .tg-input::placeholder {
1399
+ color: var(--ink-mute);
1400
+ }
1401
+
1402
+ .tg-suggest-row {
1403
+ --tg-suggest-gap: 0.4rem;
1404
+
1405
+ margin-top: 0.7rem;
1406
+ display: flex;
1407
+ flex-wrap: wrap;
1408
+ align-items: center;
1409
+ gap: var(--tg-suggest-gap);
1410
+ }
1411
+
1412
+ .tg-suggest-label {
1413
+ margin-inline-end: 0.2rem;
1414
+ font-family: var(--font-mono);
1415
+ font-size: 0.56rem;
1416
+ text-transform: uppercase;
1417
+ letter-spacing: 0.1em;
1418
+ color: var(--ink-mute);
1419
+ }
1420
+
1421
+ .tg-suggest-chip {
1422
+ padding: var(--space-1) 0.6rem;
1423
+ border: 1px solid var(--hair);
1424
+ border-radius: 0;
1425
+ background: transparent;
1426
+ color: var(--ink-soft);
1427
+ font-family: var(--font-mono);
1428
+ font-size: 0.72rem;
1429
+ cursor: pointer;
1430
+ transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
1431
+ }
1432
+ }
1433
+
1434
+ .tg-field:focus-within {
1435
+ border-color: color-mix(in srgb, var(--accent) 55%, var(--hair));
1436
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
1437
+ }
1438
+
1439
+ /* the input stretches to the full row only while the field is chipless
1440
+ (the specimen's conditional flex-1: placeholder gets full width; once
1441
+ chips exist the input sits compact after the last one) */
1442
+ .tg-field .tg-input:first-child {
1443
+ flex: 1 1 0%;
1444
+ }
1445
+
1446
+ .tg-x:hover {
1447
+ color: var(--ink);
1448
+ }
1449
+
1450
+ .tg-suggest-chip:hover {
1451
+ color: var(--ink);
1452
+ border-color: color-mix(in srgb, var(--accent) 45%, var(--hair));
1453
+ }
1454
+
1455
+ /* quant dress (#1548 dashboard adoption — the NAV-comparables picker in
1456
+ components/portfolio/performance-chart-workspace.tsx): a rounded-md 11px
1457
+ accent chip, NOT the reference pill. Translated utility-for-utility from
1458
+ the shipped chip (gap-0.5 pl-2 pr-1 py-0.5 rounded-md text-[11px]
1459
+ font-medium border-accent/35 bg-accent/10 text-accent; × wears p-0.5
1460
+ rounded text-ink-soft hover:text-ink hover:bg-ink/10). Same layering
1461
+ split: single-class default in @layer components, descendant/state rules
1462
+ unlayered. */
1463
+ @layer components {
1464
+ .tg-chip.tg-chip-quant {
1465
+ --tg-chip-quant-gap: 0.125rem;
1466
+
1467
+ gap: var(--tg-chip-quant-gap);
1468
+ padding: var(--tg-chip-quant-gap) var(--space-1) var(--tg-chip-quant-gap) var(--space-2);
1469
+ border-color: color-mix(in srgb, var(--accent) 35%, transparent);
1470
+ border-radius: 0;
1471
+ background: color-mix(in srgb, var(--accent) 10%, transparent);
1472
+ font-size: var(--text-xs);
1473
+ font-weight: 500;
1474
+ color: var(--accent);
1475
+ }
1476
+ }
1477
+
1478
+ .tg-chip-quant .tg-x {
1479
+ padding: var(--tg-chip-quant-gap, 0.125rem);
1480
+ border-radius: 0;
1481
+ color: var(--ink-soft);
1482
+ }
1483
+
1484
+ .tg-chip-quant .tg-x:hover {
1485
+ color: var(--ink);
1486
+ background: color-mix(in srgb, var(--ink) 10%, transparent);
1487
+ }
1488
+
1489
+ /* ── SearchBar — glyph · input · clear/hint (#1548, from
1490
+ reference/app/controls/controls.css + the specimen's input/clear
1491
+ utilities; the baked-in margin-top moved to the consumer). Defaults
1492
+ in @layer components; focus-within, hover, and the UA search-cancel
1493
+ reset stay unlayered. The reference `/` keycap hint (.sb-hint) stays
1494
+ in the reference's controls.css — it is consumer markup, and its
1495
+ unlayered .kbd override depends on that sheet's import order ──────── */
1496
+
1497
+ @layer components {
1498
+ .ctl-search {
1499
+ /* component tokens — search-bar geometry */
1500
+ --ctl-search-gap: 0.55rem;
1501
+ --ctl-search-pad-block: 0.55rem;
1502
+ --ctl-search-pad-inline: 0.7rem;
1503
+ --ctl-search-font: 0.82rem;
1504
+
1505
+ width: min(100%, 26rem);
1506
+ display: flex;
1507
+ align-items: center;
1508
+ gap: var(--ctl-search-gap);
1509
+ padding: var(--ctl-search-pad-block) var(--ctl-search-pad-inline);
1510
+ border: 1px solid var(--hair);
1511
+ border-radius: 0;
1512
+ background: var(--surface);
1513
+ transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
1514
+ }
1515
+
1516
+ .sb-glyph {
1517
+ display: inline-flex;
1518
+ color: var(--ink-mute);
1519
+ }
1520
+
1521
+ .sb-input {
1522
+ flex: 1 1 0%;
1523
+ border: none;
1524
+ background: transparent;
1525
+ font-family: var(--font-mono);
1526
+ font-size: var(--ctl-search-font, 0.82rem);
1527
+ color: var(--ink);
1528
+ outline: none;
1529
+ }
1530
+
1531
+ .sb-input::placeholder {
1532
+ color: var(--ink-mute);
1533
+ }
1534
+
1535
+ .sb-clear {
1536
+ --sb-clear-pad-block: 0.1rem;
1537
+ --sb-clear-pad-inline: 0.2rem;
1538
+ --sb-clear-font: 0.7rem;
1539
+
1540
+ padding: var(--sb-clear-pad-block) var(--sb-clear-pad-inline);
1541
+ border: none;
1542
+ background: transparent;
1543
+ color: var(--ink-mute);
1544
+ font-size: var(--sb-clear-font);
1545
+ cursor: pointer;
1546
+ }
1547
+ }
1548
+
1549
+ .ctl-search:focus-within {
1550
+ border-color: color-mix(in srgb, var(--accent) 55%, var(--hair));
1551
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
1552
+ }
1553
+
1554
+ /* native search UA affordance has no Tailwind utility — kept as the
1555
+ .sb-input class's rule; the clear button replaces it */
1556
+ .sb-input::-webkit-search-cancel-button {
1557
+ display: none;
1558
+ }
1559
+
1560
+ .sb-clear:hover {
1561
+ color: var(--ink);
1562
+ }
1563
+
1564
+ /* ── Slider input — DigiWeb range mechanic (#3577)
1565
+ Native <input type="range"> restyled like the reference specimen: 4px
1566
+ track, accent fill supplied inline by the consumer, SQUARE thumb (sharp-
1567
+ corner grammar — not OS accent-color pills / round knobs). Wrapped by the
1568
+ shared <Slider/> composite (components/controls/Slider.tsx); apps may
1569
+ also use `.ctl-slider-input` alone. */
1570
+ @layer components {
1571
+ .ctl-slider-input {
1572
+ /* component tokens — native range mechanic */
1573
+ --ctl-slider-track-h: 4px;
1574
+ --ctl-slider-thumb: 14px;
1575
+ --ctl-slider-thumb-offset: -5px;
1576
+
1577
+ -webkit-appearance: none;
1578
+ appearance: none;
1579
+ width: 100%;
1580
+ height: var(--ctl-slider-track-h);
1581
+ border-radius: 0;
1582
+ cursor: pointer;
1583
+ background: color-mix(in srgb, var(--ink) 14%, transparent);
1584
+ }
1585
+
1586
+ .ctl-slider-input:focus-visible {
1587
+ outline: none;
1588
+ }
1589
+
1590
+ .ctl-slider-input::-webkit-slider-thumb {
1591
+ -webkit-appearance: none;
1592
+ appearance: none;
1593
+ width: var(--ctl-slider-thumb);
1594
+ height: var(--ctl-slider-thumb);
1595
+ margin-top: var(--ctl-slider-thumb-offset);
1596
+ border-radius: 0;
1597
+ background: var(--accent);
1598
+ border: 2px solid var(--surface);
1599
+ box-shadow: 0 0 0 1px var(--hair);
1600
+ cursor: pointer;
1601
+ }
1602
+
1603
+ .ctl-slider-input:focus-visible::-webkit-slider-thumb {
1604
+ box-shadow: 0 0 0 1px var(--hair), 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent);
1605
+ }
1606
+
1607
+ .ctl-slider-input::-moz-range-thumb {
1608
+ width: var(--ctl-slider-thumb);
1609
+ height: var(--ctl-slider-thumb);
1610
+ border-radius: 0;
1611
+ background: var(--accent);
1612
+ border: 2px solid var(--surface);
1613
+ box-shadow: 0 0 0 1px var(--hair);
1614
+ cursor: pointer;
1615
+ }
1616
+
1617
+ .ctl-slider-input:focus-visible::-moz-range-thumb {
1618
+ box-shadow: 0 0 0 1px var(--hair), 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent);
1619
+ }
1620
+
1621
+ .ctl-slider-input::-moz-range-track {
1622
+ height: var(--ctl-slider-track-h);
1623
+ border-radius: 0;
1624
+ background: transparent;
1625
+ }
1626
+ }
1627
+
1628
+
1629
+ /* ── Field — label + control + hint/error (components/controls/Field.tsx) ─
1630
+ The wrapper meshes the wiring (ids, aria-describedby, aria-invalid); the
1631
+ control's own dress (shared Input's [aria-invalid] ring, native fields)
1632
+ does the rest. Error is --danger, never --down (money colors are P&L). */
1633
+ @layer components {
1634
+ .ctl-field {
1635
+ --ctl-field-gap: 0.35rem;
1636
+ --ctl-field-hint-font: 0.62rem;
1637
+
1638
+ display: grid;
1639
+ gap: var(--ctl-field-gap);
1640
+ min-width: 0;
1641
+ }
1642
+
1643
+ /* Screen-reader-only (required) marker. Deliberately NOT Tailwind's
1644
+ `.sr-only`: that utility only exists in an app whose own sources use
1645
+ it (see .tm-sr) — shared components carry their own. */
1646
+ .ctl-sr {
1647
+ position: absolute;
1648
+ width: 1px;
1649
+ height: 1px;
1650
+ padding: 0;
1651
+ margin: -1px;
1652
+ overflow: hidden;
1653
+ clip: rect(0, 0, 0, 0);
1654
+ white-space: nowrap;
1655
+ border: 0;
1656
+ }
1657
+
1658
+ .ctl-field-required {
1659
+ color: var(--ink-soft);
1660
+ }
1661
+
1662
+ .ctl-field-hint {
1663
+ font-family: var(--font-mono);
1664
+ font-size: var(--ctl-field-hint-font, 0.62rem);
1665
+ line-height: 1.5;
1666
+ color: var(--ink-mute);
1667
+ margin: 0;
1668
+ }
1669
+
1670
+ .ctl-field-error {
1671
+ font-family: var(--font-mono);
1672
+ font-size: var(--ctl-field-hint-font, 0.62rem);
1673
+ line-height: 1.5;
1674
+ color: var(--danger);
1675
+ margin: 0;
1676
+ }
1677
+ }
1678
+
1679
+
1680
+ /* ── Checkbox / Radio / Switch (components/controls/Selection.tsx) ───────
1681
+ Values mirror the form-fields specimen (.ff-box / .ff-dot / .ff-toggle):
1682
+ 16px box/dot, hairline, accent check, accent focus ring. Base UI reports
1683
+ state on data-* (buttons have no :checked); focus travels natively. */
1684
+ @layer components {
1685
+ .ctl-check {
1686
+ /* component tokens — selection controls (16px box/dot, 34×20 switch) */
1687
+ --ctl-check-size: 16px;
1688
+ --ctl-check-glyph: 0.62rem;
1689
+
1690
+ appearance: none;
1691
+ display: inline-flex;
1692
+ align-items: center;
1693
+ justify-content: center;
1694
+ width: var(--ctl-check-size);
1695
+ height: var(--ctl-check-size);
1696
+ flex-shrink: 0;
1697
+ border: 1px solid var(--hair);
1698
+ border-radius: 0;
1699
+ background: var(--surface);
1700
+ cursor: pointer;
1701
+ padding: 0;
1702
+ margin: 0;
1703
+ transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease);
1704
+ }
1705
+
1706
+ /* the indicator mounts only when checked — the glyph lives in ::after */
1707
+ .ctl-check-indicator::after {
1708
+ content: "✓";
1709
+ display: flex;
1710
+ align-items: center;
1711
+ justify-content: center;
1712
+ font-size: var(--ctl-check-glyph, 0.62rem);
1713
+ line-height: 1;
1714
+ color: var(--on-accent);
1715
+ }
1716
+
1717
+ .ctl-radio-group {
1718
+ display: flex;
1719
+ flex-direction: column;
1720
+ gap: var(--space-2);
1721
+ }
1722
+
1723
+ .ctl-radio {
1724
+ --ctl-check-size: 16px;
1725
+
1726
+ appearance: none;
1727
+ position: relative;
1728
+ display: inline-flex;
1729
+ width: var(--ctl-check-size);
1730
+ height: var(--ctl-check-size);
1731
+ flex-shrink: 0;
1732
+ border: 1px solid var(--hair);
1733
+ border-radius: 999px;
1734
+ background: var(--surface);
1735
+ cursor: pointer;
1736
+ padding: 0;
1737
+ margin: 0;
1738
+ transition: border-color 0.15s var(--ease);
1739
+ }
1740
+
1741
+ /* the indicator mounts only when checked */
1742
+ .ctl-radio-dot {
1743
+ position: absolute;
1744
+ inset: 3px;
1745
+ border-radius: 999px;
1746
+ background: var(--accent);
1747
+ }
1748
+
1749
+ .ctl-switch {
1750
+ --ctl-switch-w: 34px;
1751
+ --ctl-switch-h: 20px;
1752
+ --ctl-switch-knob: 14px;
1753
+ --ctl-switch-knob-inset: 2px;
1754
+
1755
+ appearance: none;
1756
+ position: relative;
1757
+ width: var(--ctl-switch-w);
1758
+ height: var(--ctl-switch-h);
1759
+ flex-shrink: 0;
1760
+ border: 1px solid var(--hair);
1761
+ border-radius: 0;
1762
+ background: var(--surface);
1763
+ cursor: pointer;
1764
+ padding: 0;
1765
+ margin: 0;
1766
+ transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease);
1767
+ }
1768
+
1769
+ .ctl-switch-knob {
1770
+ position: absolute;
1771
+ top: var(--ctl-switch-knob-inset, 2px);
1772
+ inset-inline-start: var(--ctl-switch-knob-inset, 2px);
1773
+ display: block;
1774
+ width: var(--ctl-switch-knob, 14px);
1775
+ height: var(--ctl-switch-knob, 14px);
1776
+ border-radius: 0;
1777
+ background: var(--ink-mute);
1778
+ transition: transform 0.2s var(--ease), background-color 0.2s var(--ease);
1779
+ }
1780
+ }
1781
+
1782
+
1783
+ /* ── Select (components/controls/Select.tsx) ─────────────────────────────
1784
+ Trigger wears the ff-input grammar; the popup mirrors the menu-pane read
1785
+ (surface, hair ring, soft shadow) with the shared .ctl-pop travel. */
1786
+ @layer components {
1787
+ .ctl-select {
1788
+ /* component tokens — select trigger geometry */
1789
+ --ctl-select-gap: 0.6rem;
1790
+ --ctl-select-pad-block: 0.55rem;
1791
+ --ctl-select-pad-inline: 0.7rem;
1792
+ --ctl-select-font: 0.82rem;
1793
+ --ctl-select-icon: 0.9rem;
1794
+
1795
+ font: inherit;
1796
+ display: inline-flex;
1797
+ align-items: center;
1798
+ justify-content: space-between;
1799
+ gap: var(--ctl-select-gap);
1800
+ width: 100%;
1801
+ padding: var(--ctl-select-pad-block) var(--ctl-select-pad-inline);
1802
+ border: 1px solid var(--hair);
1803
+ border-radius: 0;
1804
+ background: var(--surface);
1805
+ color: var(--ink);
1806
+ font-family: var(--font-mono);
1807
+ font-size: var(--ctl-select-font);
1808
+ cursor: pointer;
1809
+ }
1810
+
1811
+ .ctl-select svg {
1812
+ width: var(--ctl-select-icon);
1813
+ height: var(--ctl-select-icon);
1814
+ flex-shrink: 0;
1815
+ color: var(--ink-mute);
1816
+ }
1817
+
1818
+ .ctl-select-value {
1819
+ flex: 1;
1820
+ min-width: 0;
1821
+ text-align: start;
1822
+ overflow: hidden;
1823
+ text-overflow: ellipsis;
1824
+ white-space: nowrap;
1825
+ }
1826
+
1827
+ .ctl-select-positioner {
1828
+ z-index: 50;
1829
+ }
1830
+
1831
+ .ctl-select-popup {
1832
+ --ctl-select-popup-min: 8rem;
1833
+ --ctl-select-popup-max: 16rem;
1834
+
1835
+ width: var(--anchor-width);
1836
+ min-width: var(--ctl-select-popup-min);
1837
+ max-height: var(--ctl-select-popup-max);
1838
+ overflow-y: auto;
1839
+ background: var(--surface);
1840
+ color: var(--ink);
1841
+ box-shadow:
1842
+ 0 0 0 1px color-mix(in oklab, var(--ink) 10%, transparent),
1843
+ 0 4px 6px -1px rgb(0 0 0 / 0.1),
1844
+ 0 2px 4px -2px rgb(0 0 0 / 0.1);
1845
+ padding: 0.25rem;
1846
+ }
1847
+
1848
+ .ctl-select-item {
1849
+ --ctl-select-item-gap: 0.6rem;
1850
+ --ctl-select-item-pad-block: 0.5rem;
1851
+ --ctl-select-item-pad-inline: 0.6rem;
1852
+ --ctl-select-item-font: 0.8rem;
1853
+
1854
+ display: flex;
1855
+ align-items: center;
1856
+ justify-content: space-between;
1857
+ gap: var(--ctl-select-item-gap);
1858
+ padding: var(--ctl-select-item-pad-block) var(--ctl-select-item-pad-inline);
1859
+ font-family: var(--font-mono);
1860
+ font-size: var(--ctl-select-item-font);
1861
+ color: var(--ink-soft);
1862
+ cursor: pointer;
1863
+ outline: none;
1864
+ }
1865
+
1866
+ .ctl-select-check {
1867
+ --ctl-select-check: 0.85rem;
1868
+
1869
+ display: inline-flex;
1870
+ width: var(--ctl-select-check);
1871
+ height: var(--ctl-select-check);
1872
+ flex-shrink: 0;
1873
+ color: var(--accent);
1874
+ }
1875
+
1876
+ .ctl-select-separator {
1877
+ --ctl-select-sep-margin: 0.6rem;
1878
+
1879
+ height: 1px;
1880
+ background: var(--hair);
1881
+ margin: var(--space-1) var(--ctl-select-sep-margin);
1882
+ }
1883
+ }
1884
+
1885
+
1886
+ /* ── Slider — the native parameter control (components/controls/Slider.tsx)
1887
+ Values mirror the reference slider mechanic: 4px track, computed accent
1888
+ fill from the tsx, 16px round thumb ringed on focus. */
1889
+ @layer components {
1890
+ .ctl-slider {
1891
+ /* component tokens — composite slider rhythm */
1892
+ --ctl-slider-head-gap: 0.8rem;
1893
+ --ctl-slider-head-mb: 0.6rem;
1894
+ --ctl-slider-label-font: 0.6rem;
1895
+ --ctl-slider-value-font: 0.86rem;
1896
+ --ctl-slider-ticks-top: 0.55rem;
1897
+ --ctl-slider-ticks-font: 0.58rem;
1898
+
1899
+ display: block;
1900
+ min-width: 0;
1901
+ }
1902
+
1903
+ .ctl-slider.is-disabled {
1904
+ opacity: 0.5;
1905
+ }
1906
+
1907
+ .ctl-slider-head {
1908
+ display: flex;
1909
+ align-items: baseline;
1910
+ justify-content: space-between;
1911
+ gap: var(--ctl-slider-head-gap, 0.8rem);
1912
+ margin-bottom: var(--ctl-slider-head-mb, 0.6rem);
1913
+ }
1914
+
1915
+ .ctl-slider-label {
1916
+ font-family: var(--font-mono);
1917
+ font-size: var(--ctl-slider-label-font, 0.6rem);
1918
+ text-transform: uppercase;
1919
+ letter-spacing: 0.1em;
1920
+ color: var(--ink-mute);
1921
+ }
1922
+
1923
+ .ctl-slider-value {
1924
+ font-family: var(--font-mono);
1925
+ font-size: var(--ctl-slider-value-font, 0.86rem);
1926
+ font-variant-numeric: tabular-nums;
1927
+ color: var(--ink);
1928
+ }
1929
+
1930
+
1931
+
1932
+
1933
+
1934
+
1935
+
1936
+
1937
+ .ctl-slider-ticks {
1938
+ display: flex;
1939
+ justify-content: space-between;
1940
+ margin-top: var(--ctl-slider-ticks-top, 0.55rem);
1941
+ font-family: var(--font-mono);
1942
+ font-size: var(--ctl-slider-ticks-font, 0.58rem);
1943
+ color: var(--ink-mute);
1944
+ }
1945
+ }
1946
+
1947
+
1948
+ /* ── Table — the plain hairline ledger (components/controls/Table.tsx) ────
1949
+ Mono numerals, one hairline per row, micro-cap header, caption below.
1950
+ Numeric columns take .ctl-table-num (right + tabular figures). Wrap wide
1951
+ tables in .ctl-table-scroll. density="compact" halves cell padding. */
1952
+ @layer components {
1953
+ .ctl-table-scroll {
1954
+ overflow-x: auto;
1955
+ }
1956
+
1957
+ .ctl-table {
1958
+ --ctl-table-font: 0.82rem;
1959
+ --ctl-table-th-font: 0.68rem;
1960
+ --ctl-table-th-pad-block: 0.55rem;
1961
+ --ctl-table-cell-pad-block: 0.6rem;
1962
+ --ctl-table-cell-pad-inline: 0.75rem;
1963
+ --ctl-table-caption-font: 0.72rem;
1964
+ --ctl-table-caption-top: 0.7rem;
1965
+ --ctl-table-compact-pad: 0.32rem;
1966
+
1967
+ width: 100%;
1968
+ border-collapse: collapse;
1969
+ font-family: var(--font-mono);
1970
+ font-size: var(--ctl-table-font);
1971
+ line-height: 1.5;
1972
+ color: var(--ink);
1973
+ }
1974
+
1975
+ .ctl-table-caption {
1976
+ caption-side: bottom;
1977
+ text-align: start;
1978
+ color: var(--ink-mute);
1979
+ font-size: var(--ctl-table-caption-font, 0.72rem);
1980
+ padding-top: var(--ctl-table-caption-top, 0.7rem);
1981
+ }
1982
+
1983
+ .ctl-table-th {
1984
+ text-align: start;
1985
+ font-weight: 500;
1986
+ font-size: var(--ctl-table-th-font, 0.68rem);
1987
+ text-transform: uppercase;
1988
+ letter-spacing: 0.08em;
1989
+ color: var(--ink-mute);
1990
+ padding: var(--ctl-table-th-pad-block, 0.55rem) var(--ctl-table-cell-pad-inline, 0.75rem);
1991
+ border-bottom: 1px solid var(--hair-strong);
1992
+ white-space: nowrap;
1993
+ }
1994
+
1995
+ .ctl-table-td {
1996
+ padding: var(--ctl-table-cell-pad-block, 0.6rem) var(--ctl-table-cell-pad-inline, 0.75rem);
1997
+ border-top: 1px solid var(--hair);
1998
+ color: var(--ink);
1999
+ vertical-align: top;
2000
+ }
2001
+
2002
+ /* the header owns the first divider — no doubled hairline */
2003
+ .ctl-table thead + tbody tr:first-child .ctl-table-td,
2004
+ .ctl-table tbody:first-child tr:first-child .ctl-table-td {
2005
+ border-top: 0;
2006
+ }
2007
+
2008
+ .ctl-table-foot .ctl-table-td {
2009
+ border-top: 1px solid var(--hair-strong);
2010
+ font-weight: 600;
2011
+ }
2012
+
2013
+ .ctl-table-num {
2014
+ text-align: end;
2015
+ font-variant-numeric: tabular-nums;
2016
+ white-space: nowrap;
2017
+ }
2018
+ }
2019
+
2020
+ /* row wash is state, not default — unlayered so it keeps beating utilities */
2021
+ .ctl-table tbody tr:hover .ctl-table-td {
2022
+ background: color-mix(in srgb, var(--ink) 3%, transparent);
2023
+ }
2024
+
2025
+ /* ── Breadcrumbs — the wayfinding trail (components/controls/Breadcrumbs.tsx) */
2026
+ @layer components {
2027
+ .ctl-crumbs ol {
2028
+ --ctl-crumbs-gap: 0.45rem;
2029
+ --ctl-crumbs-font: 0.78rem;
2030
+
2031
+ display: flex;
2032
+ flex-wrap: wrap;
2033
+ align-items: baseline;
2034
+ gap: var(--ctl-crumbs-gap);
2035
+ list-style: none;
2036
+ margin: 0;
2037
+ padding: 0;
2038
+ font-family: var(--font-mono);
2039
+ font-size: var(--ctl-crumbs-font);
2040
+ }
2041
+
2042
+ .ctl-crumbs li {
2043
+ display: inline-flex;
2044
+ align-items: baseline;
2045
+ gap: var(--ctl-crumbs-gap, 0.45rem);
2046
+ min-width: 0;
2047
+ }
2048
+
2049
+ .ctl-crumbs-sep {
2050
+ color: var(--ink-mute);
2051
+ }
2052
+
2053
+ .ctl-crumbs-link {
2054
+ color: var(--ink-soft);
2055
+ text-decoration: none;
2056
+ }
2057
+
2058
+ .ctl-crumbs-current {
2059
+ color: var(--ink);
2060
+ }
2061
+ }
2062
+
2063
+ /* ── Pagination — the numbered page trail (components/controls/Pagination.tsx)
2064
+ The current page is the one loud control (ink fill); siblings are ghosts. */
2065
+ @layer components {
2066
+ .ctl-pagination {
2067
+ --ctl-pagination-gap: 0.4rem;
2068
+ --ctl-pagination-font: 0.8rem;
2069
+ --ctl-pagination-cell: var(--space-5);
2070
+
2071
+ display: flex;
2072
+ align-items: center;
2073
+ gap: var(--ctl-pagination-gap);
2074
+ font-family: var(--font-mono);
2075
+ font-size: var(--ctl-pagination-font);
2076
+ }
2077
+
2078
+ .ctl-pagination-item,
2079
+ .ctl-pagination-step {
2080
+ display: inline-flex;
2081
+ align-items: center;
2082
+ justify-content: center;
2083
+ min-width: var(--ctl-pagination-cell, 2rem);
2084
+ height: var(--ctl-pagination-cell, 2rem);
2085
+ padding: 0 var(--space-2);
2086
+ border: 1px solid var(--hair);
2087
+ border-radius: 0;
2088
+ background: transparent;
2089
+ color: var(--ink-soft);
2090
+ font: inherit;
2091
+ text-decoration: none;
2092
+ cursor: pointer;
2093
+ }
2094
+
2095
+ .ctl-pagination-item.is-current {
2096
+ background: var(--ink);
2097
+ border-color: var(--ink);
2098
+ color: var(--bg);
2099
+ font-weight: 600;
2100
+ cursor: default;
2101
+ }
2102
+
2103
+ .ctl-pagination-gap {
2104
+ --ctl-pagination-gap-inset: 0.1rem;
2105
+
2106
+ color: var(--ink-mute);
2107
+ padding: 0 var(--ctl-pagination-gap-inset);
2108
+ }
2109
+ }
2110
+
2111
+
2112
+
2113
+ /* ── states (unlayered: must beat call-site utilities) ── */
2114
+
2115
+ .ctl-check[data-checked] {
2116
+ background: var(--accent);
2117
+ border-color: var(--accent);
2118
+ }
2119
+
2120
+ .ctl-check:focus-visible {
2121
+ outline: none;
2122
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
2123
+ }
2124
+
2125
+ .ctl-check[data-disabled] {
2126
+ opacity: 0.5;
2127
+ cursor: not-allowed;
2128
+ }
2129
+
2130
+ .ctl-radio[data-checked] {
2131
+ border-color: var(--accent);
2132
+ }
2133
+
2134
+ .ctl-radio:focus-visible {
2135
+ outline: none;
2136
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
2137
+ }
2138
+
2139
+ .ctl-radio[data-disabled] {
2140
+ opacity: 0.5;
2141
+ cursor: not-allowed;
2142
+ }
2143
+
2144
+ .ctl-switch[data-checked] {
2145
+ background: var(--accent-weak);
2146
+ border-color: color-mix(in srgb, var(--accent) 55%, var(--hair));
2147
+ }
2148
+
2149
+ .ctl-switch[data-checked] .ctl-switch-knob {
2150
+ transform: translateX(14px);
2151
+ background: var(--accent);
2152
+ }
2153
+
2154
+ /* the knob anchors at inline-start, so its checked travel is physical and
2155
+ must mirror under dir=rtl (translate has no logical form). */
2156
+ [dir="rtl"] .ctl-switch[data-checked] .ctl-switch-knob {
2157
+ transform: translateX(-14px);
2158
+ }
2159
+
2160
+ .ctl-switch:focus-visible {
2161
+ outline: none;
2162
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
2163
+ }
2164
+
2165
+ .ctl-switch[data-disabled] {
2166
+ opacity: 0.5;
2167
+ cursor: not-allowed;
2168
+ }
2169
+
2170
+ .ctl-select:focus-visible {
2171
+ outline: none;
2172
+ border-color: color-mix(in srgb, var(--accent) 55%, var(--hair));
2173
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
2174
+ }
2175
+
2176
+ .ctl-select[data-disabled] {
2177
+ opacity: 0.55;
2178
+ cursor: not-allowed;
2179
+ }
2180
+
2181
+ .ctl-select-value[data-placeholder] {
2182
+ color: var(--ink-mute);
2183
+ }
2184
+
2185
+ .ctl-select-item[data-highlighted] {
2186
+ background: color-mix(in srgb, var(--ink) 6%, transparent);
2187
+ color: var(--ink);
2188
+ }
2189
+
2190
+ .ctl-select-item[data-selected] {
2191
+ color: var(--accent);
2192
+ }
2193
+
2194
+ .ctl-select-item[data-disabled] {
2195
+ opacity: 0.5;
2196
+ cursor: not-allowed;
2197
+ }
2198
+
2199
+ .ctl-table[data-density="compact"] .ctl-table-th,
2200
+ .ctl-table[data-density="compact"] .ctl-table-td {
2201
+ padding-top: var(--ctl-table-compact-pad, 0.32rem);
2202
+ padding-bottom: var(--ctl-table-compact-pad, 0.32rem);
2203
+ }
2204
+
2205
+ .ctl-crumbs-link:hover {
2206
+ color: var(--ink);
2207
+ }
2208
+
2209
+ .ctl-pagination-item:hover,
2210
+ .ctl-pagination-step:hover:not(:disabled) {
2211
+ color: var(--ink);
2212
+ border-color: var(--hair-strong);
2213
+ }
2214
+
2215
+ .ctl-pagination-step:disabled {
2216
+ opacity: 0.4;
2217
+ cursor: not-allowed;
2218
+ }
2219
+
2220
+ /* F7: universal focus ring on wayfinding controls */
2221
+ .ctl-crumbs-link:focus-visible {
2222
+ outline: none;
2223
+ color: var(--ink);
2224
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
2225
+ }
2226
+ .ctl-pagination-item:focus-visible,
2227
+ .ctl-pagination-step:focus-visible {
2228
+ outline: none;
2229
+ border-color: var(--hair-strong);
2230
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
2231
+ }
2232
+
2233
+ /* ── direction-flipped glyph ──────────────────────────────────────────────
2234
+ For an arrow/chevron whose meaning is "toward the inline end" (pagination
2235
+ prev/next, DatePager month steps): the glyph is authored pointing right and
2236
+ mirrored wholesale under dir=rtl. display:inline-block makes a plain inline
2237
+ span transformable; SVGs are replaced elements and scale without it. */
2238
+ [dir="rtl"] .ctl-dir-flip {
2239
+ display: inline-block;
2240
+ transform: scaleX(-1);
2241
+ }
2242
+ /* ── reduced motion ─────────────────────────────────────────────────────── */
2243
+
2244
+ @media (prefers-reduced-motion: reduce) {
2245
+ .ctl-btn-spinner {
2246
+ animation: none !important;
2247
+ }
2248
+
2249
+ /* skeletons go static — the ink-tint placeholder stays, the sweep stops */
2250
+ .sk::after {
2251
+ animation: none;
2252
+ }
2253
+
2254
+ /* form-kit state swaps go instant */
2255
+ .ctl-check,
2256
+ .ctl-switch,
2257
+ .ctl-switch-knob {
2258
+ transition: none;
2259
+ }
2260
+ }
2261
+