@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,1860 @@
1
+ /*
2
+ * diagrams.css — the architecture-diagram family.
3
+ *
4
+ * The mermaid SVG brings its own inline stylesheet, so this file does two jobs
5
+ * only:
6
+ * 1. FRAME the figure (border, surface, caption, source fallback) in the same
7
+ * hairline grammar as every other figure on the site.
8
+ * 2. RE-ASSERT the tokens mermaid cannot know about. Its architecture
9
+ * stylesheet hardcodes `#fff` for service label text, which is correct on
10
+ * the dark canon palette by luck and wrong on the light one, and it draws
11
+ * group borders dashed by default — the boundary is a solid box in this
12
+ * system.
13
+ *
14
+ * Import once in an app's globals.css, after web-theme.css:
15
+ * @import "@digithings-ai/ui/styles/diagrams.css";
16
+ * and add the source path so Tailwind sees the family:
17
+ * @source "<path-to>/packages/ui/src/components/diagrams";
18
+ */
19
+
20
+ .arch-figure {
21
+ /* Component sizing tokens. The figure is retunable from here; hairlines and
22
+ the pending floor stay literal (a fractional hairline rounds away, and the
23
+ floor is measured against the tallest placeholder, not a fluid ramp). */
24
+ --figure-gap: 0.6rem;
25
+ --figure-pad: 1.1rem;
26
+ --figure-pending-min-h: 14rem;
27
+ --figure-source-pad-block: var(--space-3);
28
+ --figure-source-pad-inline: 1.1rem;
29
+ --figure-source-size: 0.72rem;
30
+ --figure-source-leading: 1.6;
31
+ --figure-caption-size: 0.68rem;
32
+ --figure-caption-leading: var(--leading, 1.55);
33
+ --figure-svg-text-size: var(--text-xs, 11px);
34
+ --figure-svg-tracking: 0.02em;
35
+ margin: 0;
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: var(--figure-gap);
39
+ min-width: 0;
40
+ }
41
+
42
+ .arch-figure__body {
43
+ min-width: 0;
44
+ overflow-x: auto;
45
+ border: 1px solid var(--hair);
46
+ background: var(--surface);
47
+ padding: var(--figure-pad);
48
+ }
49
+
50
+ .arch-figure[data-state="pending"] .arch-figure__body {
51
+ min-height: var(--figure-pending-min-h);
52
+ }
53
+
54
+ .arch-figure__body > svg {
55
+ display: block;
56
+ width: 100%;
57
+ height: auto;
58
+ max-width: 100%;
59
+ }
60
+
61
+ .arch-figure__source {
62
+ margin: 0;
63
+ padding: var(--figure-source-pad-block) var(--figure-source-pad-inline);
64
+ border: 1px solid var(--hair);
65
+ background: var(--surface);
66
+ font-family: var(--font-mono);
67
+ font-size: var(--figure-source-size);
68
+ line-height: var(--figure-source-leading);
69
+ color: var(--ink-soft);
70
+ overflow-x: auto;
71
+ }
72
+
73
+ .arch-figure__caption {
74
+ margin: 0;
75
+ font-family: var(--font-mono);
76
+ font-size: var(--figure-caption-size);
77
+ line-height: var(--figure-caption-leading);
78
+ color: var(--ink-mute);
79
+ }
80
+
81
+ /* mermaid's own chrome, brought back onto the palette. */
82
+ .arch-figure__body svg text,
83
+ .arch-figure__body svg .node-icon-text > div {
84
+ font-family: var(--font-mono);
85
+ color: var(--ink);
86
+ }
87
+
88
+ .arch-figure__body svg .node-icon-text > div {
89
+ fill: var(--ink);
90
+ }
91
+
92
+ .arch-figure__body svg .node-bkg {
93
+ stroke-dasharray: none;
94
+ }
95
+
96
+ /*
97
+ * mermaid's architecture chrome, forced back onto the palette.
98
+ *
99
+ * These need `!important` and that is not sloppiness: mermaid injects its own
100
+ * stylesheet from inside the rendered SVG, so it lands later in the document
101
+ * than this file and out-ranks it in source order. The icon plates are worse —
102
+ * they are drawn by mermaid's bundled icon set as an inline `style="fill:#087ebf"`
103
+ * on the plate rect, which nothing short of `!important` can reach.
104
+ *
105
+ * The icon plate is the only place a vendor-blue can appear, and it appears on
106
+ * every service, so leaving it is the difference between a canon diagram and a
107
+ * screenshot of one. If mermaid ever changes the built-in icon palette these
108
+ * overrides stop matching and the diagram reverts to mermaid's own colour —
109
+ * degraded, never broken.
110
+ */
111
+ .arch-figure__body svg svg rect[width="80"],
112
+ .arch-figure__body svg svg rect[width="60"] {
113
+ fill: color-mix(in srgb, var(--ink) 9%, var(--surface)) !important;
114
+ }
115
+
116
+ .arch-figure__body svg svg circle,
117
+ .arch-figure__body svg svg path,
118
+ .arch-figure__body svg svg line,
119
+ .arch-figure__body svg svg polyline,
120
+ .arch-figure__body svg svg polygon {
121
+ stroke: var(--ink) !important;
122
+ }
123
+
124
+ /* the boundary is a rule in this system, not a dashed sketch. */
125
+ .arch-figure__body svg .node-bkg {
126
+ stroke-dasharray: none !important;
127
+ }
128
+
129
+ /*
130
+ * Terminal polish. The owner: "make it more still terminal look simplicity but
131
+ * still modern polish". mermaid's default chrome is a soft floating glyph on an
132
+ * unfilled plate; the house grammar is a stamped chip on a hairline, square, in
133
+ * the mono ramp — so the plate gets a border, and the type goes on the ramp
134
+ * rather than mermaid's own family.
135
+ */
136
+ .arch-figure__body svg svg rect[width="80"],
137
+ .arch-figure__body svg svg rect[width="60"] {
138
+ stroke: var(--hair) !important;
139
+ stroke-width: 1 !important;
140
+ }
141
+
142
+ /*
143
+ * The icon is a fixed 80px plate in an 80px cell and mermaid draws the label on
144
+ * its bottom edge, so at full size each node reads as a wall of squares with the
145
+ * text sitting on top of the glyph. Scaling the plate back to half its cell
146
+ * gives the label clear air beneath it and turns the icon into what it should be
147
+ * here — a small monogram beside the words, not the whole node.
148
+ */
149
+ .arch-figure__body svg [viewBox="0 0 80 80"] {
150
+ transform: scale(0.5);
151
+ transform-origin: 50% 50%;
152
+ }
153
+
154
+ .arch-figure__body svg .architecture-service text {
155
+ font-size: var(--figure-svg-text-size);
156
+ letter-spacing: var(--figure-svg-tracking);
157
+ fill: var(--ink) !important;
158
+ }
159
+
160
+ /*
161
+ * mermaid leaves the edge labels at the browser default — nearly half again the
162
+ * size of a node label — which pulls the eye off the boxes and reads as clutter.
163
+ * Everything in the drawing sits on the same mono step.
164
+ */
165
+ .arch-figure__body svg text {
166
+ font-size: var(--figure-svg-text-size);
167
+ letter-spacing: var(--figure-svg-tracking);
168
+ }
169
+
170
+ /*
171
+ * mermaid sizes an edge label's box to the raw string and lets it wrap, which
172
+ * splits a word when the box is narrow ("heartbea / t"). The label is one line
173
+ * of mono here, so it is allowed to be wider than the box mermaid guessed.
174
+ */
175
+ .arch-figure__body svg foreignObject {
176
+ overflow: visible !important;
177
+ }
178
+
179
+ .arch-figure__body svg foreignObject > div {
180
+ white-space: nowrap !important;
181
+ overflow: visible !important;
182
+ }
183
+
184
+ /*
185
+ * ── the tour ────────────────────────────────────────────────────────────────
186
+ * Three treatments of one diagram. Static is the frame plus the steps; the two
187
+ * pinned treatments park the frame and let scroll drive it. The camera element
188
+ * gets its transform inline from JS (measured against the rendered SVG), so the
189
+ * only thing CSS owns here is the frame, the rail and the easing.
190
+ */
191
+
192
+ .arch-tour {
193
+ display: flex;
194
+ flex-direction: column;
195
+ gap: var(--tour-gap);
196
+ min-width: 0;
197
+
198
+ /* Component sizing tokens. Breakpoints and 1px hairlines stay literal:
199
+ var() cannot appear in a media condition, and a fractional hairline
200
+ rounds away at small sizes. Everything else is named once here and
201
+ consumed by the rules below, so a consumer can retune the tour without
202
+ editing its geometry. */
203
+ --tour-nav-h: var(--dq-nav-h, 4.5rem);
204
+ --tour-pin-gap: 1rem;
205
+ --tour-pin-gap-narrow: 0.5rem;
206
+ --tour-pin-max: 46rem;
207
+ --tour-head-gap: 0.5rem;
208
+ --tour-head-gap-narrow: 0.35rem;
209
+ --tour-grid-gap: 1.8rem;
210
+ --tour-frame-gap: 0.4rem;
211
+ --tour-frame-pad: 1.1rem;
212
+ --tour-frame-pad-top: 1.85rem;
213
+ --tour-frame-pad-inline: 0.85rem;
214
+ --tour-frame-pad-bottom: 0.85rem;
215
+ --tour-rail-gap: 0.9rem;
216
+ --tour-rail-min: 13.25rem;
217
+ --tour-rail-min-short: 4.5rem;
218
+ --tour-step-gap: 0.15rem;
219
+ --tour-caption-size: 0.82rem;
220
+ --tour-caption-leading: 1.6;
221
+ --tour-label-size: 0.9rem;
222
+ --tour-tag-inset-block: 0.42rem;
223
+ --tour-tag-inset-inline: 0.72rem;
224
+ /* Wave-1 additions: the tour rules that were still carrying raw values.
225
+ Nothing above is renamed or re-valued. */
226
+ --tour-gap: 1.6rem;
227
+ --tour-side-gap: 1.8rem;
228
+ --tour-side-row-gap: 0.6rem;
229
+ --tour-side-col-gap: 2.4rem;
230
+ --tour-static-gap: 1.2rem;
231
+ --tour-tag-size: 0.62rem;
232
+ --tour-tag-tracking: 0.1em;
233
+ --tour-tag-pad-block: 0.1rem;
234
+ --tour-tag-pad-inline: 0.45rem;
235
+ --tour-side-tag-gap: 0.15rem;
236
+ --tour-step-pad: 0.9rem;
237
+ }
238
+
239
+ /* Morph carries its own five values: they only apply when the variant class is
240
+ present, so they do not belong on every tour. */
241
+ .arch-tour.arch-tour-morph {
242
+ --tour-morph-pin-offset: 0.35rem;
243
+ --tour-morph-frame-pad-block: 0.55rem;
244
+ --tour-morph-frame-pad-inline: 0.7rem;
245
+ --tour-morph-svg-max: 26rem;
246
+ --tour-morph-svg-reserve: 19rem;
247
+ }
248
+
249
+ .arch-tour__frame {
250
+ border: 1px solid var(--hair);
251
+ background: var(--surface);
252
+ padding: var(--tour-frame-pad);
253
+ min-width: 0;
254
+ }
255
+
256
+ /*
257
+ * One diagram per side. The stage clips, which is what lets a leaving group
258
+ * travel past the frame edge and disappear instead of widening the page.
259
+ */
260
+ .arch-tour__stage {
261
+ position: relative;
262
+ overflow: hidden;
263
+ display: grid;
264
+ }
265
+
266
+ .arch-tour__pane {
267
+ grid-area: 1 / 1;
268
+ min-width: 0;
269
+ }
270
+
271
+ /*
272
+ * The swap. Each side is one group — its diagram and its own step list — so the
273
+ * rented group leaves to the left as a unit (list travelling with its diagram)
274
+ * and the owned group arrives from the right. No opacity anywhere: the two never
275
+ * share the frame, so nothing has to be faded in. `--arch-out` runs over the
276
+ * first half of the window and `--arch-in` over the second, so they pass each
277
+ * other off-screen rather than crossing in view.
278
+ */
279
+ .arch-tour__side {
280
+ display: grid;
281
+ /* Two tracks: the side tag on its own row at the top, then the diagram + its
282
+ * step list below. Without the explicit row the tag auto-placed into the
283
+ * diagram's cell and landed bottom-left. */
284
+ grid-template-rows: auto minmax(0, 1fr);
285
+ gap: var(--tour-side-gap);
286
+ min-width: 0;
287
+ transition: transform 1.7s cubic-bezier(0.22, 1, 0.36, 1);
288
+ }
289
+
290
+ /* Each rail's progress bar reads that side's own walk, not the whole walk. */
291
+ .arch-tour__side:nth-child(1) .arch-tour__rail {
292
+ --arch-rail-p: var(--arch-r0, 0);
293
+ }
294
+
295
+ .arch-tour__side:nth-child(2) .arch-tour__rail {
296
+ --arch-rail-p: var(--arch-r1, 0);
297
+ }
298
+
299
+ .arch-tour__side--leaving {
300
+ transform: translateX(calc(var(--arch-out, 0) * -112%));
301
+ }
302
+
303
+ .arch-tour__side--entering {
304
+ transform: translateX(calc((1 - var(--arch-in, 1)) * 112%));
305
+ }
306
+
307
+ .arch-tour__camera {
308
+ position: relative;
309
+ transform-origin: 0 0;
310
+ /* Short follow, not a 1.6s tween: the scroll pose writes the target every
311
+ frame, and a long transition would lag the narration. */
312
+ transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
313
+ will-change: transform;
314
+ }
315
+
316
+ /* Until the tour has painted its first pose (`data-settled`, set by the
317
+ scroll binding), sides and camera jump straight to it rather than tweening
318
+ in from the CSS defaults. */
319
+ .arch-tour:not([data-settled]) .arch-tour__side,
320
+ .arch-tour:not([data-settled]) .arch-tour__camera {
321
+ transition: none;
322
+ }
323
+
324
+ /* Skip-through: while the page is moving, or the camera is settling onto the
325
+ narrated boxes, the scroll pose is the motion. A time-based follow here
326
+ lags a flick and reads as the section fighting the page. */
327
+ .arch-tour[data-skipping] .arch-tour__camera,
328
+ .arch-tour[data-skipping] .arch-tour__side,
329
+ .arch-tour[data-skipping] .arch-tour__rail::before {
330
+ transition: none;
331
+ }
332
+
333
+ /* the frame already draws the box; the diagram inside must not draw a second. */
334
+ .arch-tour__camera .arch-figure__body {
335
+ border: 0;
336
+ background: transparent;
337
+ padding: 0;
338
+ }
339
+
340
+ .arch-tour__camera .arch-figure__caption {
341
+ display: none;
342
+ }
343
+
344
+ /* The selected box glows and everything outside it drops back a step. The huge
345
+ * flat shadow is the spotlight: it dims the whole pane except the rectangle it
346
+ * is cast from, and the stage's own overflow clips it, so there is no second
347
+ * layer to keep in step with the camera. There is exactly ONE such rectangle per
348
+ * step (the union of the lit boxes) — with one per box the dim stacked once per
349
+ * lit box and the last rented steps, which light most of the diagram, went to
350
+ * near-black. The dim stays light for the same reason: the architecture outside
351
+ * the glow has to remain readable. */
352
+ .arch-tour__spot {
353
+ position: absolute;
354
+ inset-block-start: 0;
355
+ left: 0; /* physical: the spot's translate is the camera frame's physical x */
356
+ pointer-events: none;
357
+ opacity: 0;
358
+ border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
359
+ background: color-mix(in srgb, var(--accent) 4%, transparent);
360
+ box-shadow: 0 0 0 9999px color-mix(in srgb, var(--bg) 22%, transparent);
361
+ /* Geometry is written from the scroll pose, placed by translate so a frame
362
+ of travel is not a layout shift. Opacity is the only fade, so a
363
+ whole-graph beat can drop the spotlight without sliding a box. */
364
+ transition: opacity 0.35s var(--ease);
365
+ }
366
+
367
+ /* The glow itself breathes on its own layer so only opacity is animated — an
368
+ * animated 9999px spread would repaint the whole frame every tick. */
369
+ .arch-tour__spot::after {
370
+ content: "";
371
+ position: absolute;
372
+ inset: -1px;
373
+ box-shadow:
374
+ 0 0 18px 2px color-mix(in srgb, var(--accent) 30%, transparent),
375
+ inset 0 0 10px color-mix(in srgb, var(--accent) 16%, transparent);
376
+ animation: arch-glow 2.6s ease-in-out infinite;
377
+ }
378
+
379
+ @keyframes arch-glow {
380
+ 0%,
381
+ 100% {
382
+ opacity: 0.45;
383
+ }
384
+ 50% {
385
+ opacity: 1;
386
+ }
387
+ }
388
+
389
+ /* The no-motion treatment renders both sides stacked, so it owns its own gap. */
390
+ .arch-tour__static {
391
+ display: flex;
392
+ flex-direction: column;
393
+ gap: var(--tour-static-gap);
394
+ min-width: 0;
395
+ }
396
+
397
+ .arch-tour__tag,
398
+ .arch-tour__side-tag {
399
+ font-family: var(--font-mono);
400
+ font-size: var(--tour-tag-size);
401
+ letter-spacing: var(--tour-tag-tracking);
402
+ text-transform: uppercase;
403
+ color: var(--ink-mute);
404
+ }
405
+
406
+ .arch-tour__side-tag {
407
+ display: block;
408
+ margin-bottom: var(--tour-side-tag-gap);
409
+ color: var(--accent);
410
+ }
411
+
412
+ /* Side emphasis is brightness, not hue (#4429 v16): the side being walked
413
+ reads bright white, the other subdued gray. The hooks are data attributes
414
+ the tour sets from its scroll state (`data-active` per side,
415
+ `data-active-side` on the root); the static fallback sets neither, so both
416
+ sides read full bright there. */
417
+ .arch-tour__tag[data-tag="their stack"],
418
+ .arch-tour__side-tag[data-tag="their stack"],
419
+ .arch-tour__tag[data-tag="digithings stack"],
420
+ .arch-tour__side-tag[data-tag="digithings stack"] {
421
+ color: var(--ink-mute);
422
+ border: 1px solid var(--hair);
423
+ padding: var(--tour-tag-pad-block) var(--tour-tag-pad-inline);
424
+ transition: color 0.35s var(--ease), border-color 0.35s var(--ease);
425
+ }
426
+ .arch-tour__side[data-active="true"] .arch-tour__tag,
427
+ .arch-tour__side[data-active="true"] > .arch-tour__side-tag,
428
+ .arch-tour[data-variant="static"] .arch-tour__tag {
429
+ color: var(--ink);
430
+ border-color: color-mix(in srgb, var(--ink) 45%, transparent);
431
+ }
432
+ /* The walked rail fills bright; the other rests on the hairline. The step
433
+ being walked keys off the same hook, both rail orientations. */
434
+ .arch-tour__side[data-active="true"] .arch-tour__rail::before {
435
+ background: var(--ink);
436
+ }
437
+ .arch-tour__side[data-active="true"] .arch-tour__step.on {
438
+ border-inline-start-color: var(--ink);
439
+ }
440
+ .arch-tour__side[data-active="true"][data-rail="end"] .arch-tour__step.on {
441
+ border-inline-end-color: var(--ink);
442
+ }
443
+ /* The why headline follows the walk: the side being walked reads bright
444
+ white, the other subdued gray, cross-fading on the swap. No attribute
445
+ (static fallback) leaves both halves bright. Kit-level hooks — the canon
446
+ guard refuses color literals in app files. */
447
+ .why-rent,
448
+ .why-own {
449
+ color: var(--ink);
450
+ transition: color 0.35s var(--ease);
451
+ }
452
+ .arch-tour[data-active-side="0"] .why-own,
453
+ .arch-tour[data-active-side="1"] .why-rent {
454
+ color: var(--ink-mute);
455
+ }
456
+
457
+ /* App-first dimming: configured boxes rest dimmed unless the walk lights
458
+ them. One rule per dimmable box — the walk's `on` always wins, so a step
459
+ about a dimmed box still spotlights it. */
460
+ .arch-tour-dim-vault #arch-service-vault:not(.is-on) {
461
+ opacity: 0.32;
462
+ }
463
+ .arch-tour-dim-memory #arch-service-memory:not(.is-on) {
464
+ opacity: 0.32;
465
+ }
466
+
467
+ /* The side tag prints ONCE per side, on its own row at the TOP, right-aligned
468
+ * above the step column — never on every step and never bottom-left. */
469
+ .arch-tour__side > .arch-tour__side-tag {
470
+ grid-row: 1;
471
+ grid-column: -2 / -1;
472
+ justify-self: end;
473
+ align-self: start;
474
+ margin-bottom: 0;
475
+ text-align: end;
476
+ }
477
+
478
+ .arch-tour__grid {
479
+ display: grid;
480
+ grid-template-columns: minmax(0, 1fr);
481
+ gap: var(--tour-grid-gap);
482
+ align-items: start;
483
+ }
484
+
485
+ .arch-tour__pin {
486
+ position: sticky;
487
+ top: calc(var(--tour-nav-h) + var(--space-3, 1rem));
488
+ /* Fill the space left under the nav, then centre the held block inside it.
489
+ * `justify-content: center` needs free space to distribute: without a
490
+ * min-height the pin is only as tall as its content and the block sits at
491
+ * the top of the viewport, which is what read as top-aligned. The min-height
492
+ * makes the pin symmetric about the viewport centre (it fills the box it is
493
+ * stuck into), and the cap keeps it shorter than the viewport so the sticky
494
+ * can still hold. */
495
+ min-height: min(
496
+ calc(100svh - var(--tour-nav-h) - var(--space-5, 2rem)),
497
+ var(--tour-pin-max)
498
+ );
499
+ display: flex;
500
+ flex-direction: column;
501
+ gap: var(--tour-pin-gap);
502
+ justify-content: center;
503
+ }
504
+
505
+ .arch-tour__head {
506
+ max-width: var(--frame-w, 1180px);
507
+ margin-inline: auto;
508
+ width: 100%;
509
+ display: flex;
510
+ flex-direction: column;
511
+ gap: var(--tour-head-gap);
512
+ }
513
+
514
+ .arch-tour__head p {
515
+ display: -webkit-box;
516
+ -webkit-box-orient: vertical;
517
+ -webkit-line-clamp: 3;
518
+ line-clamp: 3;
519
+ overflow: hidden;
520
+ }
521
+
522
+ .arch-tour__rail {
523
+ position: relative;
524
+ list-style: none;
525
+ margin: 0;
526
+ padding: 0;
527
+ display: grid;
528
+ align-content: start;
529
+ gap: var(--tour-rail-gap);
530
+ min-width: 0;
531
+ /* The rail owns the side it draws its rule on: the step column is text-start
532
+ * by default, and a right-side rail flips both the rule and the alignment
533
+ * (see [data-rail="end"]). */
534
+ --rail-rule: start;
535
+ }
536
+
537
+ .arch-tour__rail::before {
538
+ content: "";
539
+ position: absolute;
540
+ inset-block: 0;
541
+ inset-inline-start: 0;
542
+ width: 1px;
543
+ /* Neutral at rest — only the walked side's rail lights (ink), so no color
544
+ lingers on the subdued side after the swap. */
545
+ background: var(--hair-2);
546
+ transform-origin: 0 0;
547
+ /* The side-local walk progress, not the whole-walk value: on the rented rail
548
+ * the global value filled only its share and on the owned rail it was already
549
+ * full before that side began. Each side sets --arch-r<si> for its own rail. */
550
+ transform: scaleY(var(--arch-rail-p, 0));
551
+ transition: transform 0.35s linear;
552
+ }
553
+
554
+ .arch-tour__step {
555
+ display: grid;
556
+ gap: var(--tour-step-gap);
557
+ border-inline-start: 1px solid var(--hair);
558
+ padding-inline-start: var(--tour-step-pad);
559
+ opacity: 0.42;
560
+ transition:
561
+ opacity 0.6s var(--ease),
562
+ border-color 0.6s var(--ease);
563
+ }
564
+
565
+ .arch-tour__step.on {
566
+ opacity: 1;
567
+ border-inline-start-color: var(--accent);
568
+ }
569
+
570
+ /* Right-side rail: the vertical progress rule moves to the rail's right edge
571
+ * and the step text aligns to it. Set per side through `rail="end"`, so the
572
+ * rented column can sit right while the owned one keeps the left. */
573
+ .arch-tour__side[data-rail="end"] .arch-tour__rail {
574
+ --rail-rule: end;
575
+ }
576
+
577
+ .arch-tour__side[data-rail="end"] .arch-tour__rail::before {
578
+ inset-inline-start: auto;
579
+ inset-inline-end: 0;
580
+ transform-origin: 100% 0;
581
+ }
582
+
583
+ .arch-tour__side[data-rail="end"] .arch-tour__step {
584
+ border-inline-start: 0;
585
+ border-inline-end: 1px solid var(--hair);
586
+ padding-inline-start: 0;
587
+ padding-inline-end: var(--tour-step-pad);
588
+ text-align: end;
589
+ }
590
+
591
+ .arch-tour__side[data-rail="end"] .arch-tour__step.on {
592
+ border-inline-end-color: var(--accent);
593
+ }
594
+
595
+ .arch-tour__side[data-rail="end"] > .arch-tour__side-tag {
596
+ text-align: end;
597
+ }
598
+
599
+ .arch-tour__index {
600
+ font-family: var(--font-mono);
601
+ font-size: var(--tour-tag-size);
602
+ letter-spacing: var(--tour-tag-tracking);
603
+ color: var(--ink-mute);
604
+ }
605
+
606
+ .arch-tour__label {
607
+ font-family: var(--font-mono);
608
+ font-size: var(--tour-label-size);
609
+ color: var(--ink);
610
+ }
611
+
612
+ .arch-tour__line {
613
+ font-size: var(--tour-caption-size);
614
+ line-height: var(--tour-caption-leading);
615
+ color: var(--ink-soft);
616
+ }
617
+
618
+ /*
619
+ * The rail is an index, not a transcript: only the step being walked prints its
620
+ * line. Eleven full entries made the rail 1423px tall — taller than the viewport
621
+ * — and a sticky element taller than the viewport cannot hold, so the whole
622
+ * pinned treatment slid away instead of parking.
623
+ */
624
+ .arch-tour__line {
625
+ display: none;
626
+ }
627
+
628
+ .arch-tour__step.on .arch-tour__line {
629
+ display: block;
630
+ }
631
+
632
+ /*
633
+ * The two-column row, and with it the swap. Each side lays out as one row —
634
+ * diagram wide, its own step list narrow beside it — and the two sides are
635
+ * stacked in the same grid cell so each can slide as a unit. The owned side
636
+ * mirrors its tracks so its list lands in the bottom-left corner and its diagram
637
+ * to the right. The grid clips, which is what keeps a group parked off-screen
638
+ * from widening the page.
639
+ */
640
+ /* ── Narrow pinned tour: ONE pane ───────────────────────────────────────────
641
+ * Under the two-column breakpoint both stacks share a single graph box and the
642
+ * walk crossfades their-stack → digithings-stack inside it, with exactly one
643
+ * caption under the graph.
644
+ *
645
+ * READ THIS BEFORE CHANGING A ROW HERE. The pane's height must be owned by the
646
+ * pane, never by a drawing. The first cut of this layout left `align-items:
647
+ * start` on the grid and a content-sized row, so every side was exactly as tall
648
+ * as its own SVG — and because both sides sat in the same cell, the *hidden*
649
+ * one sized the row for both. The digithings drawing is taller (400px vs 315px
650
+ * at 390 wide), so the visible frame hung above a pane sized by a drawing nobody
651
+ * could see, the drawing spilled over the caption, and resizing re-measured into
652
+ * a different (equally wrong) answer. `align-items: stretch` + a definite
653
+ * `minmax(0, 1fr)` row + `height: 100%` down the frame → stage → pane chain is
654
+ * what makes the frame fill the space it is given, let the drawing shrink into
655
+ * it (`fitFrame` re-lays its gutters to the measured box), and keep the caption
656
+ * below the graph at every width and after any resize.
657
+ */
658
+ @media (max-width: 1023.98px) {
659
+ .arch-tour:not([data-variant="static"]) .arch-tour__pin {
660
+ /* The graph gets every pixel the header does not take: no 40rem cap (which
661
+ left a band under the pane) and no centring slack. `svh` keeps the pane
662
+ clear of the mobile URL bar; the app's max-height is the same sum. */
663
+ min-height: calc(100svh - var(--tour-nav-h) - var(--space-5, 2rem));
664
+ gap: var(--tour-pin-gap-narrow);
665
+ justify-content: flex-start;
666
+ }
667
+
668
+ .arch-tour:not([data-variant="static"]) .arch-tour__grid {
669
+ display: grid;
670
+ grid-template-columns: minmax(0, 1fr);
671
+ grid-template-rows: minmax(0, 1fr);
672
+ align-items: stretch;
673
+ gap: 0;
674
+ min-height: 0;
675
+ overflow: hidden;
676
+ flex: 1 1 0;
677
+ }
678
+
679
+ /* Both sides occupy the same cell as identical boxes, so the swap is a pure
680
+ crossfade: same frame, same caption baseline, no group sliding past a page
681
+ that is only one column wide. */
682
+ .arch-tour:not([data-variant="static"]) .arch-tour__side {
683
+ grid-area: 1 / 1;
684
+ position: relative;
685
+ display: grid;
686
+ grid-template-columns: minmax(0, 1fr);
687
+ grid-template-rows: minmax(0, 1fr) auto;
688
+ gap: var(--tour-frame-gap);
689
+ height: 100%;
690
+ min-height: 0;
691
+ min-width: 0;
692
+ }
693
+
694
+ /* The stack tag is a label on the pane's own top edge, not a row between the
695
+ tabs and the graph: it sits in the frame's reserved top band, so the graph
696
+ starts a half-rem under the header instead of 44.6px below it. */
697
+ .arch-tour:not([data-variant="static"]) .arch-tour__side > .arch-tour__side-tag {
698
+ position: absolute;
699
+ inset-block-start: var(--tour-tag-inset-block);
700
+ inset-inline-start: var(--tour-tag-inset-inline);
701
+ z-index: 2;
702
+ margin: 0;
703
+ /* A zoomed box can pass under the label; the chip stays legible on it. */
704
+ background: var(--surface);
705
+ text-align: start;
706
+ }
707
+
708
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__frame {
709
+ grid-row: 1;
710
+ grid-column: 1;
711
+ display: grid;
712
+ grid-template-rows: minmax(0, 1fr);
713
+ /* top band = the tag; the rest is the graph */
714
+ padding: var(--tour-frame-pad-top) var(--tour-frame-pad-inline)
715
+ var(--tour-frame-pad-bottom);
716
+ min-height: 0;
717
+ }
718
+
719
+ /* Definite heights all the way down, so the SVG is sized by the pane instead
720
+ of the pane being sized by the SVG. */
721
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__stage,
722
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__pane,
723
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__camera {
724
+ min-height: 0;
725
+ }
726
+
727
+ /* One caption at a time, under the graph, never on top of it. The floor is
728
+ the tallest full caption across all eleven steps at 390 (13.25rem = the
729
+ six-line mobile step plus its two-line label), measured rather than
730
+ guessed. Keeping the floor means the frame does not resize as the walk
731
+ moves between a short and a long caption; the caption itself is no longer
732
+ clamped, so a reader never loses the end of the explanation. */
733
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__rail {
734
+ grid-row: 2;
735
+ grid-column: 1;
736
+ display: block;
737
+ min-height: var(--tour-rail-min);
738
+ }
739
+
740
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__step {
741
+ display: none;
742
+ }
743
+
744
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__step.on {
745
+ display: grid;
746
+ border-inline-start: 0;
747
+ border-inline-end: 0;
748
+ padding-inline: 0;
749
+ text-align: start;
750
+ }
751
+
752
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__rail::before {
753
+ display: none;
754
+ }
755
+
756
+ /* The step line is deliberately not clamped: on a phone the caption is the
757
+ explanation, so it takes as many lines as it needs and the frame gives up
758
+ the height (the rail floor below keeps that height stable step to step).
759
+ Short viewports hide the line entirely and keep the label only. */
760
+
761
+ /* The price aside folds itself away whenever the rail sits under the frame
762
+ (there is no room to the right of it in one column); removing it here also
763
+ keeps it from adding an empty grid row under the caption. */
764
+ .arch-tour:not([data-variant="static"]) .arch-tour__side > .arch-tour__aside {
765
+ display: none;
766
+ }
767
+
768
+ /* On a phone the graph is the pane; the copy that would otherwise sit
769
+ between the tabs and the graph is the step caption below it. */
770
+ .arch-tour:not([data-variant="static"]) .arch-tour__head p {
771
+ display: none;
772
+ }
773
+
774
+ .arch-tour:not([data-variant="static"]) .arch-tour__head {
775
+ gap: var(--tour-head-gap-narrow);
776
+ }
777
+
778
+ /* One pane: swap by crossfade instead of two groups sliding past each other,
779
+ so the frame is never empty mid-scroll. The fade is scroll-driven (the
780
+ `--arch-out` / `--arch-in` custom properties move with every frame), so the
781
+ sides themselves carry no transition — a leftover 1.7s transform tween was
782
+ what made a 1440→390 resize slide the whole pane across the phone. */
783
+ .arch-tour:not([data-variant="static"]) .arch-tour__side--leaving {
784
+ transform: none;
785
+ transition: none;
786
+ opacity: calc(1 - var(--arch-out, 0));
787
+ }
788
+
789
+ .arch-tour:not([data-variant="static"]) .arch-tour__side--entering {
790
+ transform: none;
791
+ transition: none;
792
+ opacity: var(--arch-in, 1);
793
+ }
794
+ }
795
+
796
+ /* A short phone cannot hold the full explanation and a readable graph at the
797
+ same time. Show the step label only — never a mid-sentence ellipsis — and
798
+ give the frame the freed height. */
799
+ @media (max-width: 1023.98px) and (max-height: 700px) {
800
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__step.on .arch-tour__line {
801
+ display: none;
802
+ }
803
+
804
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__rail {
805
+ min-height: var(--tour-rail-min-short);
806
+ }
807
+ }
808
+
809
+ @media (min-width: 1024px) {
810
+ .arch-tour:not([data-variant="static"]) .arch-tour__grid {
811
+ grid-template-columns: minmax(0, 1fr);
812
+ gap: 0;
813
+ overflow: hidden;
814
+ }
815
+
816
+ .arch-tour:not([data-variant="static"]) .arch-tour__side {
817
+ grid-area: 1 / 1;
818
+ grid-template-columns: minmax(0, 1.35fr) minmax(0, 0.65fr);
819
+ grid-template-rows: auto minmax(0, 1fr);
820
+ gap: var(--tour-side-row-gap) var(--tour-side-col-gap);
821
+ align-items: start;
822
+ }
823
+
824
+ .arch-tour:not([data-variant="static"]) .arch-tour__side--entering {
825
+ grid-template-columns: minmax(0, 0.65fr) minmax(0, 1.35fr);
826
+ }
827
+
828
+ /* The tag sits above the RAIL column, so it must follow the rail as the
829
+ * tracks mirror: right column on the leaving side, left column on the
830
+ * entering side. */
831
+ .arch-tour:not([data-variant="static"]) .arch-tour__side > .arch-tour__side-tag {
832
+ grid-row: 1;
833
+ grid-column: 2;
834
+ }
835
+
836
+ .arch-tour:not([data-variant="static"]) .arch-tour__side--entering > .arch-tour__side-tag {
837
+ grid-column: 1;
838
+ /* The owned column sits left, so its tag sits top-LEFT with it. */
839
+ justify-self: start;
840
+ text-align: start;
841
+ }
842
+
843
+ /* Both runtime children are pinned to row 2: the tag owns row 1, and the
844
+ * owned side lists its columns in the opposite order, so grid auto-placement
845
+ * would otherwise drop the rail onto a third row below the diagram (which also
846
+ * made the pin taller than the viewport, so it could not hold). */
847
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__frame,
848
+ .arch-tour:not([data-variant="static"]) .arch-tour__side .arch-tour__rail {
849
+ grid-row: 2;
850
+ }
851
+
852
+ .arch-tour:not([data-variant="static"]) .arch-tour__side--entering .arch-tour__frame {
853
+ grid-column: 2;
854
+ }
855
+
856
+ .arch-tour:not([data-variant="static"]) .arch-tour__side--entering .arch-tour__rail {
857
+ grid-column: 1;
858
+ }
859
+
860
+ /* An aside takes a third row under the rail and sinks to the frame's
861
+ * bottom edge; the frame spans all three rows so the pin height holds. */
862
+ .arch-tour:not([data-variant="static"]) .arch-tour__side:has(> .arch-tour__aside) {
863
+ grid-template-rows: auto minmax(0, 1fr) auto;
864
+ }
865
+
866
+ .arch-tour:not([data-variant="static"]) .arch-tour__side:where(:has(> .arch-tour__aside)) .arch-tour__frame {
867
+ grid-row: 2 / 4;
868
+ }
869
+
870
+ .arch-tour:not([data-variant="static"]) .arch-tour__side > .arch-tour__aside {
871
+ grid-row: 3;
872
+ grid-column: 2;
873
+ align-self: end;
874
+ min-width: 0;
875
+ }
876
+
877
+ .arch-tour:not([data-variant="static"]) .arch-tour__side--entering > .arch-tour__aside {
878
+ grid-column: 1;
879
+ }
880
+ }
881
+
882
+ @media (prefers-reduced-motion: reduce) {
883
+ .arch-tour__camera,
884
+ .arch-tour__frame,
885
+ .arch-tour__rail,
886
+ .arch-tour__side,
887
+ .arch-tour__pane,
888
+ .arch-tour__spot,
889
+ .arch-tour__step,
890
+ .arch-tour__rail::before {
891
+ transition: none;
892
+ }
893
+
894
+ .arch-tour__spot::after {
895
+ animation: none;
896
+ opacity: 1;
897
+ }
898
+
899
+ /* The component renders the static treatment under reduced motion, so the
900
+ * animated markup is not on the page — this is belt-and-braces for the case
901
+ * where the media query flips after mount. */
902
+ .arch-tour__pane,
903
+ .arch-tour__frame,
904
+ .arch-tour__rail,
905
+ .arch-tour__side {
906
+ transform: none;
907
+ }
908
+
909
+ .arch-tour__rail::before {
910
+ transform: none;
911
+ display: none;
912
+ }
913
+ }
914
+
915
+ /*
916
+ * mermaid parks a boundary's title on the boundary's own top edge, so it lands
917
+ * on top of whatever the force layout put nearest. A surface-coloured halo
918
+ * behind the glyphs (paint-order: stroke) makes it legible without moving it.
919
+ */
920
+ .arch-figure__body svg .architecture-groups text {
921
+ paint-order: stroke;
922
+ stroke: var(--surface);
923
+ stroke-width: 4px;
924
+ stroke-linejoin: round;
925
+ }
926
+
927
+ /* ════════════════════════════════════════════════════════════════════════════
928
+ * The kit's own architecture SVG (architecture-svg.tsx)
929
+ *
930
+ * Drawn, not solved: a box with the label inside it, a 22px kind glyph on the
931
+ * left, orthogonal hairline connectors, and the boundary title ABOVE its rule
932
+ * instead of painted onto it. Mono, squared, one accent — the terminal look.
933
+ * The tour's glow rides the same .is-on hook the mermaid path used, so nothing
934
+ * in the tour changed when the renderer did.
935
+ * ════════════════════════════════════════════════════════════════════════════ */
936
+
937
+ .arch-svg {
938
+ /* Component sizing tokens for the drawn diagram. SVG user units, so px is
939
+ the honest unit for label steps; hairlines and the paint-order halos stay
940
+ literal. */
941
+ --svg-text-size: var(--text-xs, 11px);
942
+ --svg-tracking: 0.02em;
943
+ --svg-group-label-size: 10px;
944
+ --svg-group-label-tracking: 0.08em;
945
+ --svg-edge-label-size: 10px;
946
+ --svg-node-label-size: 11.5px;
947
+ display: block;
948
+ width: 100%;
949
+ height: auto;
950
+ max-width: 100%;
951
+ overflow: visible;
952
+ font-family: var(--font-mono);
953
+ }
954
+
955
+ .arch-svg text {
956
+ font-family: var(--font-mono);
957
+ font-size: var(--svg-text-size);
958
+ letter-spacing: var(--svg-tracking);
959
+ }
960
+
961
+ /* ── boundaries ─────────────────────────────────────────────────────────── */
962
+
963
+ .arch-svg .arch-group__box {
964
+ fill: color-mix(in srgb, var(--ink) 3%, transparent);
965
+ stroke: var(--hair-2);
966
+ stroke-width: 1;
967
+ stroke-dasharray: 4 3;
968
+ transition: stroke 0.25s var(--ease), fill 0.25s var(--ease);
969
+ }
970
+
971
+ .arch-svg .arch-group.is-on .arch-group__box {
972
+ stroke: color-mix(in srgb, var(--accent) 55%, var(--hair-2));
973
+ fill: color-mix(in srgb, var(--accent) 5%, transparent);
974
+ }
975
+
976
+ .arch-svg .arch-group__label {
977
+ fill: var(--ink-mute);
978
+ font-size: var(--svg-group-label-size);
979
+ letter-spacing: var(--svg-group-label-tracking);
980
+ text-transform: uppercase;
981
+ }
982
+
983
+ .arch-svg .arch-group.is-on .arch-group__label {
984
+ fill: var(--ink-soft);
985
+ }
986
+
987
+ /* ── connectors ─────────────────────────────────────────────────────────── */
988
+
989
+ .arch-svg .arch-edge__line {
990
+ fill: none;
991
+ stroke: var(--hair-2);
992
+ stroke-width: 1;
993
+ opacity: 0.5;
994
+ transition: stroke 0.25s var(--ease), opacity 0.25s var(--ease);
995
+ }
996
+
997
+ .arch-svg .arch-edge.is-on .arch-edge__line {
998
+ stroke: color-mix(in srgb, var(--accent) 60%, var(--hair-2));
999
+ opacity: 1;
1000
+ }
1001
+
1002
+ .arch-svg .arch-arrow {
1003
+ fill: var(--hair-2);
1004
+ stroke: none;
1005
+ }
1006
+
1007
+ .arch-svg .arch-edge.is-on .arch-arrow {
1008
+ fill: color-mix(in srgb, var(--accent) 60%, var(--hair-2));
1009
+ }
1010
+
1011
+ .arch-svg .arch-edge__label {
1012
+ fill: var(--ink-soft);
1013
+ font-size: var(--svg-edge-label-size);
1014
+ text-anchor: middle;
1015
+ paint-order: stroke;
1016
+ stroke: var(--surface);
1017
+ stroke-width: 3px;
1018
+ stroke-linejoin: round;
1019
+ }
1020
+
1021
+ /* ── services ───────────────────────────────────────────────────────────── */
1022
+
1023
+ .arch-svg .arch-node__box {
1024
+ fill: var(--surface);
1025
+ stroke: var(--hair);
1026
+ stroke-width: 1;
1027
+ transition: stroke 0.25s var(--ease), fill 0.25s var(--ease);
1028
+ }
1029
+
1030
+ .arch-svg .arch-node.is-on .arch-node__box {
1031
+ fill: color-mix(in srgb, var(--accent) 6%, var(--surface));
1032
+ stroke: color-mix(in srgb, var(--accent) 55%, var(--hair));
1033
+ }
1034
+
1035
+ .arch-svg .arch-node__glyph {
1036
+ color: var(--ink-mute);
1037
+ transition: color 0.25s var(--ease);
1038
+ }
1039
+
1040
+ .arch-svg .arch-node.is-on .arch-node__glyph {
1041
+ color: var(--accent);
1042
+ }
1043
+
1044
+ .arch-svg .arch-node__label {
1045
+ fill: var(--ink-soft);
1046
+ font-size: var(--svg-node-label-size);
1047
+ transition: fill 0.25s var(--ease);
1048
+ }
1049
+
1050
+ .arch-svg .arch-node.is-on .arch-node__label {
1051
+ fill: var(--ink);
1052
+ }
1053
+
1054
+ .arch-svg .arch-node__glyph svg {
1055
+ overflow: visible;
1056
+ }
1057
+
1058
+ /* ── morph variant (app-first why band) ────────────────────────────────────
1059
+ * Two diagrams share the frame: the provider stack rests full-lit on one
1060
+ * side while the other redraws box by box. No swipe — both transforms are
1061
+ * neutralised and the grid runs two columns. Boxes that have not swapped
1062
+ * yet rest dimmed (the walk's `on` always wins); boxes that never swap
1063
+ * (your product, sources, review) stay bright.
1064
+ */
1065
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__grid {
1066
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
1067
+ gap: 0 var(--tour-side-col-gap);
1068
+ overflow: visible;
1069
+ }
1070
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__side {
1071
+ grid-area: auto;
1072
+ grid-template-columns: minmax(0, 1fr);
1073
+ transform: none;
1074
+ }
1075
+ .arch-tour.arch-tour-morph .arch-tour__side--leaving,
1076
+ .arch-tour.arch-tour-morph .arch-tour__side--entering {
1077
+ transform: none;
1078
+ }
1079
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__side--entering {
1080
+ grid-template-columns: minmax(0, 1fr);
1081
+ }
1082
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__side > .arch-tour__side-tag {
1083
+ grid-column: 1;
1084
+ justify-self: start;
1085
+ text-align: start;
1086
+ }
1087
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__side .arch-tour__frame,
1088
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__side .arch-tour__rail {
1089
+ grid-row: auto;
1090
+ }
1091
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__side--entering .arch-tour__frame,
1092
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__side--entering .arch-tour__rail {
1093
+ grid-column: auto;
1094
+ }
1095
+ .arch-tour.arch-tour-morph .arch-tour__side--entering [id^="arch-service-"]:not(.is-on):not(#arch-service-app):not(#arch-service-review):not(#arch-service-feeds):not(#arch-service-filings):not(#arch-service-sources) {
1096
+ opacity: 0.32;
1097
+ }
1098
+ @media (max-width: 1023px) {
1099
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__grid {
1100
+ grid-template-columns: minmax(0, 1fr);
1101
+ }
1102
+ }
1103
+
1104
+ /* The why band narrates from its own column, so the tour's step lists would
1105
+ be a second copy and would push the pin taller than the viewport. The
1106
+ diagrams sit under the nav and above the sticky invoice. */
1107
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__pin {
1108
+ min-height: 0;
1109
+ justify-content: flex-start;
1110
+ top: calc(var(--dq-nav-h, 4.5rem) + var(--tour-morph-pin-offset));
1111
+ }
1112
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__rail {
1113
+ display: none;
1114
+ }
1115
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-tour__frame {
1116
+ padding: var(--tour-morph-frame-pad-block) var(--tour-morph-frame-pad-inline);
1117
+ }
1118
+ .arch-tour.arch-tour-morph:not([data-variant="static"]) .arch-svg {
1119
+ width: 100%;
1120
+ height: auto;
1121
+ max-height: min(
1122
+ var(--tour-morph-svg-max),
1123
+ calc(100svh - var(--dq-nav-h, 4.5rem) - var(--tour-morph-svg-reserve))
1124
+ );
1125
+ }
1126
+
1127
+ /* Walk-through text rail (AppFirstSection): remounts per step (key), so a
1128
+ * short rise-and-fade marks each beat of the scroll journey. */
1129
+ @keyframes why-rail-in {
1130
+ from {
1131
+ opacity: 0;
1132
+ transform: translateY(8px);
1133
+ }
1134
+ to {
1135
+ opacity: 1;
1136
+ transform: none;
1137
+ }
1138
+ }
1139
+ .why-rail-step {
1140
+ animation: why-rail-in 0.5s cubic-bezier(0.22, 1, 0.36, 1);
1141
+ }
1142
+
1143
+ /* ── build entrance ─────────────────────────────────────────────────────────
1144
+ * An ancestor carrying `.arch-build` makes every `.arch-svg` inside draw
1145
+ * itself: boundaries fade up, each box traces its outline in reading order and
1146
+ * then fills, and each connector draws once both of its ends exist
1147
+ * (`--arch-i`, set per element by the renderer). `.arch-build-pending` holds
1148
+ * the drawing blank until the caller starts the build. The end state is the
1149
+ * resting style, so the caller drops the class when the build is done and
1150
+ * nothing moves. */
1151
+ .arch-build-pending .arch-svg .arch-group,
1152
+ .arch-build-pending .arch-svg .arch-node,
1153
+ .arch-build-pending .arch-svg .arch-edge {
1154
+ opacity: 0;
1155
+ }
1156
+
1157
+ .arch-build .arch-svg .arch-group {
1158
+ animation: arch-build-fade 0.6s var(--ease) both;
1159
+ animation-delay: calc(var(--arch-i, 0) * 110ms);
1160
+ }
1161
+
1162
+ /* Opacity only on the box group: the tour measures box rects for its camera,
1163
+ and a scale here would read a few pixels short mid-build. */
1164
+ .arch-build .arch-svg .arch-node {
1165
+ animation: arch-build-fade 0.4s var(--ease) both;
1166
+ animation-delay: calc(180ms + var(--arch-i, 0) * 75ms);
1167
+ }
1168
+
1169
+ .arch-build .arch-svg .arch-node__box {
1170
+ stroke-dasharray: 1;
1171
+ animation:
1172
+ arch-build-draw 0.55s var(--ease) both,
1173
+ arch-build-fill 0.35s var(--ease) both;
1174
+ animation-delay:
1175
+ calc(180ms + var(--arch-i, 0) * 75ms),
1176
+ calc(480ms + var(--arch-i, 0) * 75ms);
1177
+ }
1178
+
1179
+ .arch-build .arch-svg .arch-node__glyph,
1180
+ .arch-build .arch-svg .arch-node__label {
1181
+ animation: arch-build-fade 0.35s var(--ease) both;
1182
+ animation-delay: calc(420ms + var(--arch-i, 0) * 75ms);
1183
+ }
1184
+
1185
+ .arch-build .arch-svg .arch-edge {
1186
+ animation: arch-build-fade 0.2s linear both;
1187
+ animation-delay: calc(560ms + var(--arch-i, 0) * 75ms);
1188
+ }
1189
+
1190
+ .arch-build .arch-svg .arch-edge__line {
1191
+ stroke-dasharray: 1;
1192
+ animation: arch-build-draw 0.5s var(--ease) both;
1193
+ animation-delay: calc(560ms + var(--arch-i, 0) * 75ms);
1194
+ }
1195
+
1196
+ .arch-build .arch-svg .arch-edge__label {
1197
+ animation: arch-build-fade 0.3s var(--ease) both;
1198
+ animation-delay: calc(900ms + var(--arch-i, 0) * 75ms);
1199
+ }
1200
+
1201
+ @keyframes arch-build-fade {
1202
+ from { opacity: 0; }
1203
+ }
1204
+
1205
+ @keyframes arch-build-draw {
1206
+ from { stroke-dashoffset: 1; }
1207
+ to { stroke-dashoffset: 0; }
1208
+ }
1209
+
1210
+ @keyframes arch-build-fill {
1211
+ from { fill-opacity: 0; }
1212
+ }
1213
+
1214
+ @media (prefers-reduced-motion: reduce) {
1215
+ .arch-svg .arch-group__box,
1216
+ .arch-svg .arch-edge__line,
1217
+ .arch-svg .arch-node__box,
1218
+ .arch-svg .arch-node__glyph,
1219
+ .arch-svg .arch-node__label {
1220
+ transition: none;
1221
+ }
1222
+ .why-rail-step {
1223
+ animation: none;
1224
+ }
1225
+ .arch-build-pending .arch-svg .arch-group,
1226
+ .arch-build-pending .arch-svg .arch-node,
1227
+ .arch-build-pending .arch-svg .arch-edge {
1228
+ opacity: 1;
1229
+ }
1230
+ .arch-build .arch-svg * {
1231
+ animation: none !important;
1232
+ }
1233
+ }
1234
+
1235
+ /* Terminal schematic — fully drawn flow. The window is a dark terminal even
1236
+ when the page is light: light theme flips --term-* and --surface to paper,
1237
+ so the schematic pins the dark ground and the dark semantic hues locally.
1238
+ Structure stays light gray; color is only for live data. */
1239
+ .tschem {
1240
+ --term-bg: #08090b;
1241
+ --term-ink: #e7eaec;
1242
+ --term-mute: #7e858b;
1243
+ --term-hair: rgba(255, 255, 255, 0.09);
1244
+ --warn: #e0b341;
1245
+ --up: #3dd6c4;
1246
+ --down: #e5533e;
1247
+ --accent-digisearch: #5aa3c4;
1248
+ --accent-digitrace: #6fa3a3;
1249
+ /* Component sizing tokens (wave 1). A terminal cell has its own type steps and
1250
+ gutters, so they are named here instead of borrowed from the page scale.
1251
+ Hairlines, the dotted rules and the keyframe end-points stay literal. */
1252
+ --tschem-size: var(--type-meta, 0.75rem);
1253
+ --tschem-leading: 1.45;
1254
+ --tschem-pad-block: 0.4rem;
1255
+ --tschem-pad-inline: 0.7rem;
1256
+ --tschem-title-tracking: 0.04em;
1257
+ --tschem-body-pad-top: 0.75rem;
1258
+ --tschem-body-pad-bottom: 0.55rem;
1259
+ --tschem-body-gap: 0.85rem;
1260
+ --tschem-legend-col-min: 7.5rem;
1261
+ --tschem-legend-col-max: 9.5rem;
1262
+ --tschem-notes-col-min: 10rem;
1263
+ --tschem-notes-col-max: 16rem;
1264
+ --tschem-legend-gap: 0.35rem;
1265
+ --tschem-legend-item-gap: 0.4rem;
1266
+ --tschem-notes-gap: 0.4rem;
1267
+ --tschem-note-item-gap: 0.45rem;
1268
+ --tschem-swatch: 0.55rem;
1269
+ --tschem-stack-pad: 0.95rem;
1270
+ --tschem-stack-rule-inset: 0.28rem;
1271
+ --tschem-stack-rule-top: 0.2rem;
1272
+ --tschem-stack-rule-bottom: 1.6rem;
1273
+ --tschem-small-size: 0.68rem;
1274
+ --tschem-drop-size: 0.7rem;
1275
+ --tschem-join-h: 0.85rem;
1276
+ --tschem-join-inset: 0.65rem;
1277
+ --tschem-join-arrow-x: -0.32rem;
1278
+ --tschem-join-arrow-y: -0.45rem;
1279
+ --tschem-bus-h: 1rem;
1280
+ --tschem-bus-gap: 0.3rem;
1281
+ --tschem-cluster-top: -0.2rem;
1282
+ --tschem-cluster-tracking: 0.14em;
1283
+ --tschem-node-min: 8.5rem;
1284
+ --tschem-node-gap: 0.4rem;
1285
+ --tschem-box-gap: 0.15rem;
1286
+ --tschem-box-pad-block: 0.35rem;
1287
+ --tschem-box-pad-inline: 0.45rem;
1288
+ --tschem-state-leading: 1.2;
1289
+ --tschem-loop-gap: 0.55rem;
1290
+ --tschem-loop-pad: 0.35rem;
1291
+ --tschem-receipt-h: 4.8rem;
1292
+ --tschem-receipt-pad-top: 0.45rem;
1293
+ --tschem-receipt-track-gap: 0.12rem;
1294
+ --tschem-foot-gap-block: 0.7rem;
1295
+ --tschem-foot-gap-inline: 0.9rem;
1296
+ --tschem-meter-w: 4.5rem;
1297
+ --tschem-meter-h: 0.35rem;
1298
+ display: flex;
1299
+ flex-direction: column;
1300
+ margin: 0;
1301
+ min-width: 0;
1302
+ background: var(--term-bg);
1303
+ color: var(--term-ink);
1304
+ border: 1px solid var(--term-hair);
1305
+ border-radius: 0;
1306
+ font-family: var(--font-mono);
1307
+ font-size: var(--tschem-size);
1308
+ line-height: var(--tschem-leading);
1309
+ font-variant-numeric: tabular-nums;
1310
+ }
1311
+
1312
+ .tschem__title {
1313
+ margin: 0;
1314
+ padding: var(--tschem-pad-block) var(--tschem-pad-inline);
1315
+ border-bottom: 1px solid var(--term-hair);
1316
+ color: var(--term-mute);
1317
+ letter-spacing: var(--tschem-title-tracking);
1318
+ }
1319
+
1320
+ .tschem__body {
1321
+ display: grid;
1322
+ grid-template-columns:
1323
+ minmax(var(--tschem-legend-col-min), var(--tschem-legend-col-max))
1324
+ minmax(0, 1fr)
1325
+ minmax(var(--tschem-notes-col-min), var(--tschem-notes-col-max));
1326
+ gap: var(--tschem-body-gap);
1327
+ padding: var(--tschem-body-pad-top) var(--tschem-pad-inline)
1328
+ var(--tschem-body-pad-bottom);
1329
+ }
1330
+
1331
+ .tschem__legend,
1332
+ .tschem__notes,
1333
+ .tschem__rows {
1334
+ list-style: none;
1335
+ margin: 0;
1336
+ padding: 0;
1337
+ }
1338
+
1339
+ .tschem__legend {
1340
+ display: flex;
1341
+ flex-direction: column;
1342
+ gap: var(--tschem-legend-gap);
1343
+ color: var(--term-mute);
1344
+ }
1345
+
1346
+ .tschem__legend li {
1347
+ display: flex;
1348
+ align-items: center;
1349
+ gap: var(--tschem-legend-item-gap);
1350
+ }
1351
+
1352
+ .tschem__swatch {
1353
+ width: var(--tschem-swatch);
1354
+ height: var(--tschem-swatch);
1355
+ border: 1px solid currentColor;
1356
+ flex: none;
1357
+ }
1358
+
1359
+ .tschem__swatch[data-tone="main"] { color: var(--warn); }
1360
+ .tschem__swatch[data-tone="model"] { color: var(--accent-digisearch); }
1361
+ .tschem__swatch[data-tone="side"] { color: var(--accent-digitrace); }
1362
+ .tschem__swatch[data-tone="ok"] { color: var(--up); }
1363
+ .tschem__swatch[data-tone="fallback"] { color: var(--down); }
1364
+ .tschem__swatch[data-tone="pending"] { color: var(--term-mute); }
1365
+
1366
+ .tschem__stack {
1367
+ position: relative;
1368
+ min-width: 0;
1369
+ padding-inline-start: var(--tschem-stack-pad);
1370
+ }
1371
+
1372
+ .tschem__stack::before {
1373
+ content: "";
1374
+ position: absolute;
1375
+ inset-inline-start: var(--tschem-stack-rule-inset);
1376
+ top: var(--tschem-stack-rule-top);
1377
+ bottom: var(--tschem-stack-rule-bottom);
1378
+ border-inline-start: 1px dotted var(--term-mute);
1379
+ }
1380
+
1381
+ .tschem__drop {
1382
+ position: absolute;
1383
+ inset-inline-start: 0;
1384
+ top: 0;
1385
+ color: var(--warn);
1386
+ font-size: var(--tschem-drop-size);
1387
+ line-height: 1;
1388
+ }
1389
+
1390
+ .tschem[data-motion="on"] .tschem__drop {
1391
+ animation: tschem-drop 2.6s linear infinite;
1392
+ }
1393
+
1394
+ .tschem__step {
1395
+ display: flex;
1396
+ flex-direction: column;
1397
+ }
1398
+
1399
+ .tschem__join {
1400
+ display: block;
1401
+ height: var(--tschem-join-h);
1402
+ margin-inline-start: var(--tschem-join-inset);
1403
+ border-inline-start: 1px dotted var(--term-mute);
1404
+ position: relative;
1405
+ }
1406
+
1407
+ .tschem__join::after {
1408
+ content: "↓";
1409
+ position: absolute;
1410
+ left: var(--tschem-join-arrow-x);
1411
+ bottom: var(--tschem-join-arrow-y);
1412
+ color: var(--term-mute);
1413
+ font-size: var(--tschem-small-size);
1414
+ line-height: 1;
1415
+ }
1416
+
1417
+ .tschem__bus {
1418
+ position: relative;
1419
+ height: var(--tschem-bus-h);
1420
+ margin: 0 0 var(--tschem-bus-gap);
1421
+ border-bottom: 1px dotted var(--term-mute);
1422
+ }
1423
+
1424
+ .tschem__par {
1425
+ color: var(--term-mute);
1426
+ font-size: var(--tschem-small-size);
1427
+ }
1428
+
1429
+ .tschem__cluster {
1430
+ position: absolute;
1431
+ left: 0;
1432
+ top: var(--tschem-cluster-top);
1433
+ color: var(--accent-digitrace);
1434
+ letter-spacing: var(--tschem-cluster-tracking);
1435
+ font-size: var(--tschem-small-size);
1436
+ visibility: hidden;
1437
+ }
1438
+
1439
+ .tschem__row[data-live="true"] .tschem__cluster {
1440
+ visibility: visible;
1441
+ }
1442
+
1443
+ .tschem[data-motion="on"] .tschem__row[data-live="true"] .tschem__cluster {
1444
+ animation: tschem-bus 1.8s linear infinite;
1445
+ }
1446
+
1447
+ .tschem__nodes {
1448
+ display: grid;
1449
+ grid-template-columns: repeat(auto-fit, minmax(var(--tschem-node-min), 1fr));
1450
+ gap: var(--tschem-node-gap);
1451
+ }
1452
+
1453
+ .tschem__box {
1454
+ min-width: 0;
1455
+ display: flex;
1456
+ flex-direction: column;
1457
+ align-items: stretch;
1458
+ gap: var(--tschem-box-gap);
1459
+ border: 1px solid var(--term-hair);
1460
+ background: transparent;
1461
+ color: var(--term-ink);
1462
+ padding: var(--tschem-box-pad-block) var(--tschem-box-pad-inline);
1463
+ }
1464
+
1465
+ .tschem__name {
1466
+ min-width: 0;
1467
+ color: var(--term-ink);
1468
+ overflow-wrap: break-word;
1469
+ }
1470
+
1471
+ .tschem__state {
1472
+ align-self: flex-end;
1473
+ white-space: nowrap;
1474
+ line-height: var(--tschem-state-leading);
1475
+ color: var(--term-mute);
1476
+ }
1477
+
1478
+ .tschem__box[data-status="running"][data-tone="main"] {
1479
+ border-color: var(--warn);
1480
+ }
1481
+ .tschem__box[data-status="running"][data-tone="main"] .tschem__state {
1482
+ color: var(--warn);
1483
+ }
1484
+ .tschem__box[data-status="running"][data-tone="model"] {
1485
+ border-color: var(--accent-digisearch);
1486
+ }
1487
+ .tschem__box[data-status="running"][data-tone="model"] .tschem__state {
1488
+ color: var(--accent-digisearch);
1489
+ }
1490
+ .tschem__box[data-status="running"][data-tone="side"] {
1491
+ border-color: var(--accent-digitrace);
1492
+ }
1493
+ .tschem__box[data-status="running"][data-tone="side"] .tschem__state {
1494
+ color: var(--accent-digitrace);
1495
+ }
1496
+ .tschem__box[data-status="running"][data-tone="fallback"] {
1497
+ border-color: var(--down);
1498
+ }
1499
+ .tschem__box[data-status="running"][data-tone="fallback"] .tschem__state {
1500
+ color: var(--down);
1501
+ }
1502
+ .tschem__box[data-status="ok"] {
1503
+ border-color: var(--up);
1504
+ }
1505
+ .tschem__box[data-status="ok"] .tschem__state {
1506
+ color: var(--up);
1507
+ }
1508
+ .tschem__box[data-status="pending"] {
1509
+ border-style: dashed;
1510
+ }
1511
+ .tschem__box[data-status="pending"] .tschem__name,
1512
+ .tschem__box[data-status="pending"] .tschem__state {
1513
+ color: var(--term-mute);
1514
+ }
1515
+ .tschem__box[data-status="hold"] {
1516
+ border-color: var(--down);
1517
+ }
1518
+ .tschem__box[data-status="hold"] .tschem__state {
1519
+ color: var(--down);
1520
+ }
1521
+
1522
+ .tschem__loop {
1523
+ margin: var(--tschem-loop-gap) 0 0;
1524
+ padding-top: var(--tschem-loop-pad);
1525
+ border-top: 1px dotted var(--term-mute);
1526
+ color: var(--term-mute);
1527
+ }
1528
+
1529
+ .tschem__notes {
1530
+ display: flex;
1531
+ flex-direction: column;
1532
+ gap: var(--tschem-notes-gap);
1533
+ color: var(--term-mute);
1534
+ }
1535
+
1536
+ .tschem__notes li {
1537
+ display: flex;
1538
+ gap: var(--tschem-note-item-gap);
1539
+ }
1540
+
1541
+ .tschem__num {
1542
+ flex: none;
1543
+ color: var(--term-ink);
1544
+ }
1545
+
1546
+ .tschem__receipt {
1547
+ box-sizing: border-box;
1548
+ flex: 0 0 auto;
1549
+ height: var(--tschem-receipt-h);
1550
+ overflow: hidden;
1551
+ border-top: 1px solid var(--term-hair);
1552
+ background: var(--term-bg);
1553
+ color: var(--term-mute);
1554
+ padding: var(--tschem-receipt-pad-top) var(--tschem-pad-inline)
1555
+ var(--tschem-pad-block);
1556
+ }
1557
+
1558
+ .tschem__receipt-track {
1559
+ display: flex;
1560
+ flex-direction: column;
1561
+ gap: var(--tschem-receipt-track-gap);
1562
+ }
1563
+
1564
+ .tschem[data-motion="on"] .tschem__receipt-track {
1565
+ animation: tschem-log 10s linear infinite;
1566
+ }
1567
+
1568
+ .tschem__foot {
1569
+ position: relative;
1570
+ z-index: 1;
1571
+ display: flex;
1572
+ flex: 0 0 auto;
1573
+ flex-wrap: wrap;
1574
+ align-items: center;
1575
+ gap: var(--tschem-foot-gap-block) var(--tschem-foot-gap-inline);
1576
+ margin: 0;
1577
+ border-top: 1px solid var(--term-hair);
1578
+ background: var(--term-bg);
1579
+ padding: var(--tschem-pad-block) var(--tschem-pad-inline);
1580
+ color: var(--term-mute);
1581
+ }
1582
+
1583
+ .tschem__foot b {
1584
+ font-weight: 500;
1585
+ color: var(--up);
1586
+ }
1587
+
1588
+ .tschem__phrase {
1589
+ color: var(--warn);
1590
+ }
1591
+
1592
+ .tschem__meter {
1593
+ width: var(--tschem-meter-w);
1594
+ height: var(--tschem-meter-h);
1595
+ border: 1px solid var(--term-hair);
1596
+ }
1597
+
1598
+ .tschem__meter > span {
1599
+ display: block;
1600
+ height: 100%;
1601
+ background: var(--up);
1602
+ transition: none;
1603
+ }
1604
+
1605
+ @keyframes tschem-drop {
1606
+ from { top: 0; }
1607
+ to { top: calc(100% - 1.7rem); }
1608
+ }
1609
+
1610
+ @keyframes tschem-bus {
1611
+ from { transform: translateX(0); }
1612
+ to { transform: translateX(65%); }
1613
+ }
1614
+
1615
+ @keyframes tschem-log {
1616
+ from { transform: translateY(0); }
1617
+ to { transform: translateY(-50%); }
1618
+ }
1619
+
1620
+ @media (max-width: 800px) {
1621
+ .tschem__body {
1622
+ grid-template-columns: 1fr;
1623
+ }
1624
+ }
1625
+
1626
+ @media (prefers-reduced-motion: reduce) {
1627
+ .tschem[data-motion="on"] .tschem__drop,
1628
+ .tschem[data-motion="on"] .tschem__row[data-live="true"] .tschem__cluster,
1629
+ .tschem[data-motion="on"] .tschem__receipt-track {
1630
+ animation: none;
1631
+ }
1632
+ .tschem__meter > span {
1633
+ transition: none;
1634
+ }
1635
+ }
1636
+
1637
+ /* ════════════════════════════════════════════════════════════════════════════
1638
+ * Pipeline flow — DIG-3312.
1639
+ *
1640
+ * Replaces the terminal schematic inside the agent-orchestration cards. No
1641
+ * legend, no notes column, no receipt: the shape of the chain (how many
1642
+ * stages, which ones fan out) already tells the story, and the only job left
1643
+ * for motion is to make it look alive. A pulse rides every conduit on an
1644
+ * infinite loop, staggered by `--pflow-i` (the stage's own depth), so a
1645
+ * four-stage chain and a six-stage chain read as two different rhythms
1646
+ * without any per-card wiring — the data shapes the motion, not the other
1647
+ * way round.
1648
+ * ════════════════════════════════════════════════════════════════════════════ */
1649
+
1650
+ .pflow {
1651
+ --flow-bg: #08090b;
1652
+ --flow-ink: #e7eaec;
1653
+ --flow-mute: #7e858b;
1654
+ --flow-hair: rgba(255, 255, 255, 0.1);
1655
+ --flow-main: #e0b341;
1656
+ --flow-model: #5aa3c4;
1657
+ --flow-side: #6fa3a3;
1658
+ --flow-fallback: #e5533e;
1659
+ /* Component sizing tokens (wave 1). `--flow-*` is the colour set above, so
1660
+ these take the `--pflow-` prefix to stay out of its way. */
1661
+ --pflow-gap: 0.6rem;
1662
+ --pflow-size: 0.78rem;
1663
+ --pflow-leading: 1.4;
1664
+ --pflow-pad-block-start: 1.1rem;
1665
+ --pflow-pad-inline: 1rem;
1666
+ --pflow-pad-block-end: 0.9rem;
1667
+ --pflow-conduit-h: 1.5rem;
1668
+ --pflow-pulse-size: 5px;
1669
+ --pflow-pulse-inset-x: -2px;
1670
+ --pflow-pulse-inset-y: -3px;
1671
+ --pflow-nodes-gap: var(--space-2);
1672
+ --pflow-parallel-pad-top: 1.3rem;
1673
+ --pflow-bus-size: 0.62rem;
1674
+ --pflow-bus-tracking: 0.08em;
1675
+ --pflow-node-pad-block: 0.4rem;
1676
+ --pflow-node-pad-inline: 0.75rem;
1677
+ --pflow-loop-margin: 0.35rem;
1678
+ --pflow-loop-pad: 0.6rem;
1679
+ --pflow-loop-size: 0.7rem;
1680
+ --pflow-loop-gap: var(--space-2);
1681
+ --pflow-arc-size: 0.7rem;
1682
+ margin: 0;
1683
+ display: flex;
1684
+ flex-direction: column;
1685
+ gap: var(--pflow-gap);
1686
+ min-width: 0;
1687
+ background: var(--flow-bg);
1688
+ color: var(--flow-ink);
1689
+ border: 1px solid var(--flow-hair);
1690
+ padding: var(--pflow-pad-block-start) var(--pflow-pad-inline)
1691
+ var(--pflow-pad-block-end);
1692
+ font-family: var(--font-mono);
1693
+ font-size: var(--pflow-size);
1694
+ line-height: var(--pflow-leading);
1695
+ }
1696
+
1697
+ .pflow__chain {
1698
+ list-style: none;
1699
+ margin: 0;
1700
+ padding: 0;
1701
+ display: flex;
1702
+ flex-direction: column;
1703
+ min-width: 0;
1704
+ }
1705
+
1706
+ .pflow__stage {
1707
+ display: flex;
1708
+ flex-direction: column;
1709
+ min-width: 0;
1710
+ }
1711
+
1712
+ /* The conduit is the whole story's motion: a hairline trunk with a bright
1713
+ dot travelling its length forever, delayed per stage so the eye reads a
1714
+ single signal falling through the chain rather than four things blinking
1715
+ independently. */
1716
+ .pflow__conduit {
1717
+ position: relative;
1718
+ align-self: center;
1719
+ width: 1px;
1720
+ height: var(--pflow-conduit-h);
1721
+ background: var(--flow-hair);
1722
+ }
1723
+
1724
+ .pflow__conduit[data-pending="true"] {
1725
+ background-image: linear-gradient(var(--flow-mute) 0 2px, transparent 2px 5px);
1726
+ background-repeat: repeat-y;
1727
+ background-size: 1px 5px;
1728
+ background-color: transparent;
1729
+ }
1730
+
1731
+ .pflow__pulse {
1732
+ position: absolute;
1733
+ inset-inline-start: var(--pflow-pulse-inset-x);
1734
+ top: var(--pflow-pulse-inset-y);
1735
+ width: var(--pflow-pulse-size);
1736
+ height: var(--pflow-pulse-size);
1737
+ border-radius: 50%;
1738
+ background: var(--flow-side);
1739
+ box-shadow: 0 0 6px 1px var(--flow-side);
1740
+ opacity: 0;
1741
+ }
1742
+
1743
+ .pflow[data-motion="on"] .pflow__conduit:not([data-pending="true"]) .pflow__pulse {
1744
+ animation: pflow-fall 2.6s linear infinite;
1745
+ animation-delay: calc(var(--pflow-i, 1) * -0.45s);
1746
+ }
1747
+
1748
+ .pflow__nodes {
1749
+ display: flex;
1750
+ flex-wrap: wrap;
1751
+ justify-content: center;
1752
+ align-items: center;
1753
+ gap: var(--pflow-nodes-gap);
1754
+ position: relative;
1755
+ min-width: 0;
1756
+ }
1757
+
1758
+ .pflow__nodes[data-parallel="true"] {
1759
+ padding-top: var(--pflow-parallel-pad-top);
1760
+ }
1761
+
1762
+ .pflow__bus {
1763
+ position: absolute;
1764
+ top: 0;
1765
+ left: 50%;
1766
+ transform: translateX(-50%);
1767
+ font-size: var(--pflow-bus-size);
1768
+ letter-spacing: var(--pflow-bus-tracking);
1769
+ text-transform: uppercase;
1770
+ color: var(--flow-mute);
1771
+ white-space: nowrap;
1772
+ }
1773
+
1774
+ .pflow__node {
1775
+ min-width: 0;
1776
+ border: 1px solid var(--flow-hair);
1777
+ padding: var(--pflow-node-pad-block) var(--pflow-node-pad-inline);
1778
+ color: var(--flow-ink);
1779
+ text-align: center;
1780
+ transition: border-color 0.3s ease, box-shadow 0.3s ease;
1781
+ }
1782
+
1783
+ .pflow__node[data-tone="main"] { border-color: color-mix(in srgb, var(--flow-main) 55%, var(--flow-hair)); }
1784
+ .pflow__node[data-tone="model"] { border-color: color-mix(in srgb, var(--flow-model) 55%, var(--flow-hair)); }
1785
+ .pflow__node[data-tone="side"] { border-color: color-mix(in srgb, var(--flow-side) 45%, var(--flow-hair)); }
1786
+ .pflow__node[data-tone="fallback"] { border-color: color-mix(in srgb, var(--flow-fallback) 55%, var(--flow-hair)); }
1787
+ .pflow__node[data-tone="pending"] {
1788
+ border-style: dashed;
1789
+ color: var(--flow-mute);
1790
+ }
1791
+
1792
+ .pflow[data-motion="on"] .pflow__node[data-tone="main"] { animation: pflow-glow 3.2s ease-in-out infinite; }
1793
+ .pflow[data-motion="on"] .pflow__node[data-tone="model"] { animation: pflow-glow 3.2s ease-in-out infinite; animation-delay: 0.6s; }
1794
+ .pflow[data-motion="on"] .pflow__node[data-tone="side"] { animation: pflow-glow 3.2s ease-in-out infinite; animation-delay: 1.2s; }
1795
+
1796
+ .pflow__node[data-tone="main"] { --flow-glow: var(--flow-main); }
1797
+ .pflow__node[data-tone="model"] { --flow-glow: var(--flow-model); }
1798
+ .pflow__node[data-tone="side"] { --flow-glow: var(--flow-side); }
1799
+
1800
+ .pflow__loop {
1801
+ margin: var(--pflow-loop-margin) 0 0;
1802
+ padding-top: var(--pflow-loop-pad);
1803
+ border-top: 1px dotted var(--flow-hair);
1804
+ color: var(--flow-mute);
1805
+ font-size: var(--pflow-loop-size);
1806
+ display: flex;
1807
+ align-items: center;
1808
+ gap: var(--pflow-loop-gap);
1809
+ }
1810
+
1811
+ .pflow__loop-arc {
1812
+ display: inline-block;
1813
+ width: var(--pflow-arc-size);
1814
+ height: var(--pflow-arc-size);
1815
+ flex: none;
1816
+ border: 1px dotted var(--flow-mute);
1817
+ border-radius: 50%;
1818
+ border-right-color: transparent;
1819
+ border-bottom-color: transparent;
1820
+ }
1821
+
1822
+ .pflow[data-motion="on"] .pflow__loop-arc {
1823
+ animation: pflow-spin 3s linear infinite;
1824
+ }
1825
+
1826
+ @keyframes pflow-fall {
1827
+ 0% { top: -3px; opacity: 0; }
1828
+ 8% { opacity: 1; }
1829
+ 92% { opacity: 1; }
1830
+ 100% { top: calc(100% - 2px); opacity: 0; }
1831
+ }
1832
+
1833
+ @keyframes pflow-glow {
1834
+ 0%, 100% { box-shadow: none; }
1835
+ 50% { box-shadow: 0 0 10px 1px color-mix(in srgb, var(--flow-glow) 55%, transparent); }
1836
+ }
1837
+
1838
+ @keyframes pflow-spin {
1839
+ to { transform: rotate(360deg); }
1840
+ }
1841
+
1842
+ @media (max-width: 480px) {
1843
+ .pflow__nodes {
1844
+ flex-direction: column;
1845
+ align-items: stretch;
1846
+ }
1847
+ .pflow__node {
1848
+ text-align: start;
1849
+ }
1850
+ }
1851
+
1852
+ @media (prefers-reduced-motion: reduce) {
1853
+ .pflow[data-motion="on"] .pflow__conduit:not([data-pending="true"]) .pflow__pulse,
1854
+ .pflow[data-motion="on"] .pflow__node[data-tone="main"],
1855
+ .pflow[data-motion="on"] .pflow__node[data-tone="model"],
1856
+ .pflow[data-motion="on"] .pflow__node[data-tone="side"],
1857
+ .pflow[data-motion="on"] .pflow__loop-arc {
1858
+ animation: none;
1859
+ }
1860
+ }