altitude-design 0.2.1 → 0.3.1

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 (295) hide show
  1. package/README.md +42 -7
  2. package/dist/adapters/codemirror.cjs +88 -0
  3. package/dist/adapters/codemirror.cjs.map +7 -0
  4. package/dist/adapters/codemirror.js +67 -0
  5. package/dist/adapters/codemirror.js.map +7 -0
  6. package/dist/adapters/react-flow.css +102 -0
  7. package/dist/adapters/tiptap.css +71 -0
  8. package/dist/altitude.css +3121 -244
  9. package/dist/altitude.iife.js +33 -5
  10. package/dist/altitude.no-fonts.css +3121 -244
  11. package/dist/chart-themes.cjs +195 -0
  12. package/dist/chart-themes.cjs.map +7 -0
  13. package/dist/chart-themes.js +165 -0
  14. package/dist/chart-themes.js.map +7 -0
  15. package/dist/cli/altitude.mjs +134 -0
  16. package/dist/guidance/README.md +32 -0
  17. package/dist/guidance/agent-setup.md +22 -0
  18. package/dist/guidance/app-skeleton.md +17 -0
  19. package/dist/guidance/evidence.json +95 -0
  20. package/dist/guidance/index.json +493 -0
  21. package/dist/guidance/llms.txt +20 -0
  22. package/dist/guidance/manifest.json +92 -0
  23. package/dist/guidance/pattern.schema.json +181 -0
  24. package/dist/guidance/patterns/agent-action.json +77 -0
  25. package/dist/guidance/patterns/agent-action.md +39 -0
  26. package/dist/guidance/patterns/assistant-conversation.json +77 -0
  27. package/dist/guidance/patterns/assistant-conversation.md +50 -0
  28. package/dist/guidance/patterns/async-collection.json +71 -0
  29. package/dist/guidance/patterns/async-collection.md +29 -0
  30. package/dist/guidance/patterns/audit-log.json +75 -0
  31. package/dist/guidance/patterns/audit-log.md +27 -0
  32. package/dist/guidance/patterns/auth.json +86 -0
  33. package/dist/guidance/patterns/auth.md +29 -0
  34. package/dist/guidance/patterns/billing.json +80 -0
  35. package/dist/guidance/patterns/billing.md +29 -0
  36. package/dist/guidance/patterns/consequential-action.json +76 -0
  37. package/dist/guidance/patterns/consequential-action.md +27 -0
  38. package/dist/guidance/patterns/developer-settings.json +79 -0
  39. package/dist/guidance/patterns/developer-settings.md +29 -0
  40. package/dist/guidance/patterns/edit-record.json +74 -0
  41. package/dist/guidance/patterns/edit-record.md +51 -0
  42. package/dist/guidance/patterns/import.json +77 -0
  43. package/dist/guidance/patterns/import.md +31 -0
  44. package/dist/guidance/patterns/members.json +77 -0
  45. package/dist/guidance/patterns/members.md +27 -0
  46. package/dist/guidance/patterns/onboarding.json +78 -0
  47. package/dist/guidance/patterns/onboarding.md +31 -0
  48. package/dist/guidance/patterns/review-submit.json +72 -0
  49. package/dist/guidance/patterns/review-submit.md +27 -0
  50. package/dist/guidance/patterns/screen-layout.json +79 -0
  51. package/dist/guidance/patterns/screen-layout.md +40 -0
  52. package/dist/guidance/patterns/settings.json +78 -0
  53. package/dist/guidance/patterns/settings.md +32 -0
  54. package/dist/guidance/patterns/sync.json +77 -0
  55. package/dist/guidance/patterns/sync.md +31 -0
  56. package/dist/guidance/principles.md +23 -0
  57. package/dist/guidance/references/build/checks/agent-action-pattern.mjs +57 -0
  58. package/dist/guidance/references/build/checks/agent-setup.mjs +78 -0
  59. package/dist/guidance/references/build/checks/assistant-conversation-pattern.mjs +49 -0
  60. package/dist/guidance/references/build/checks/audit-log-pattern.mjs +63 -0
  61. package/dist/guidance/references/build/checks/auth-pattern.mjs +67 -0
  62. package/dist/guidance/references/build/checks/billing-pattern.mjs +63 -0
  63. package/dist/guidance/references/build/checks/developer-settings-pattern.mjs +76 -0
  64. package/dist/guidance/references/build/checks/guidance.mjs +71 -0
  65. package/dist/guidance/references/build/checks/import-pattern.mjs +69 -0
  66. package/dist/guidance/references/build/checks/members-pattern.mjs +71 -0
  67. package/dist/guidance/references/build/checks/onboarding-pattern.mjs +54 -0
  68. package/dist/guidance/references/build/checks/pattern-types.mjs +13 -0
  69. package/dist/guidance/references/build/checks/pattern-workflows.mjs +226 -0
  70. package/dist/guidance/references/build/checks/primary-scope.mjs +49 -0
  71. package/dist/guidance/references/build/checks/render-tells.mjs +72 -0
  72. package/dist/guidance/references/build/checks/screen-layout-pattern.mjs +111 -0
  73. package/dist/guidance/references/build/checks/semantics.mjs +72 -0
  74. package/dist/guidance/references/build/checks/settings-pattern.mjs +73 -0
  75. package/dist/guidance/references/build/checks/sync-pattern.mjs +62 -0
  76. package/dist/guidance/references/build/checks/tells-lint.mjs +164 -0
  77. package/dist/guidance/references/build/fixtures/pattern-workflows.tsx +83 -0
  78. package/dist/guidance/references/build/fixtures/patterns/agent-action.jsx +60 -0
  79. package/dist/guidance/references/build/fixtures/patterns/app-skeleton.jsx +94 -0
  80. package/dist/guidance/references/build/fixtures/patterns/assistant-conversation.jsx +79 -0
  81. package/dist/guidance/references/build/fixtures/patterns/audit-log.jsx +81 -0
  82. package/dist/guidance/references/build/fixtures/patterns/auth.jsx +150 -0
  83. package/dist/guidance/references/build/fixtures/patterns/billing.jsx +96 -0
  84. package/dist/guidance/references/build/fixtures/patterns/developer-settings.jsx +95 -0
  85. package/dist/guidance/references/build/fixtures/patterns/import.jsx +102 -0
  86. package/dist/guidance/references/build/fixtures/patterns/members.jsx +106 -0
  87. package/dist/guidance/references/build/fixtures/patterns/onboarding.jsx +70 -0
  88. package/dist/guidance/references/build/fixtures/patterns/screen-layout-decision.jsx +74 -0
  89. package/dist/guidance/references/build/fixtures/patterns/screen-layout-editor.jsx +56 -0
  90. package/dist/guidance/references/build/fixtures/patterns/screen-layout-report.jsx +42 -0
  91. package/dist/guidance/references/build/fixtures/patterns/settings.jsx +73 -0
  92. package/dist/guidance/references/build/fixtures/patterns/sync.jsx +78 -0
  93. package/dist/guidance/references/project/components/src/pattern-async-collection.tsx +57 -0
  94. package/dist/guidance/references/project/components/src/pattern-consequential-action.tsx +77 -0
  95. package/dist/guidance/references/project/components/src/pattern-edit-record.tsx +127 -0
  96. package/dist/guidance/references/project/components/src/pattern-review-submit.tsx +47 -0
  97. package/dist/guidance/references/project/components/src/settings.tsx +79 -0
  98. package/dist/guidance/references/scripts/build-framework-fixture.mjs +20 -0
  99. package/dist/guidance/rules.json +653 -0
  100. package/dist/guidance/visual/Elevation.md +194 -0
  101. package/dist/guidance/visual/Fundamentals.md +83 -0
  102. package/dist/guidance/visual/Research.md +1620 -0
  103. package/dist/index.cjs +18795 -1925
  104. package/dist/index.cjs.map +4 -4
  105. package/dist/index.js +18795 -1925
  106. package/dist/index.js.map +4 -4
  107. package/dist/tokens.css +84 -84
  108. package/dist/tokens.json +301 -128
  109. package/dist/types/adapter-codemirror.d.ts +13 -0
  110. package/dist/types/agent-fleet.d.ts +166 -0
  111. package/dist/types/ai-label.d.ts +30 -0
  112. package/dist/types/app-shell.d.ts +83 -0
  113. package/dist/types/area-chart.d.ts +80 -0
  114. package/dist/types/artifact.d.ts +65 -0
  115. package/dist/types/bar-chart.d.ts +83 -0
  116. package/dist/types/bar-list.d.ts +35 -0
  117. package/dist/types/breadcrumb.d.ts +62 -0
  118. package/dist/types/chart-brush.d.ts +35 -0
  119. package/dist/types/chart-themes.d.ts +229 -0
  120. package/dist/types/chart.d.ts +570 -0
  121. package/dist/types/checklist.d.ts +71 -0
  122. package/dist/types/code-block.d.ts +32 -0
  123. package/dist/types/collections.d.ts +111 -4
  124. package/dist/types/color-picker.d.ts +38 -0
  125. package/dist/types/command.d.ts +6 -3
  126. package/dist/types/comment-thread.d.ts +48 -0
  127. package/dist/types/confirmation.d.ts +60 -0
  128. package/dist/types/context-menu.d.ts +17 -0
  129. package/dist/types/controls.d.ts +100 -17
  130. package/dist/types/conversation.d.ts +130 -0
  131. package/dist/types/copy-button.d.ts +19 -0
  132. package/dist/types/core.d.ts +139 -10
  133. package/dist/types/data-grid.d.ts +76 -0
  134. package/dist/types/date-picker.d.ts +96 -0
  135. package/dist/types/diff-view.d.ts +54 -0
  136. package/dist/types/dot-plot.d.ts +54 -0
  137. package/dist/types/edit-region.d.ts +121 -0
  138. package/dist/types/feature-gate.d.ts +22 -0
  139. package/dist/types/filter-bar.d.ts +65 -0
  140. package/dist/types/formatted-number.d.ts +38 -0
  141. package/dist/types/heatmap.d.ts +64 -0
  142. package/dist/types/in-place.d.ts +24 -0
  143. package/dist/types/index.d.ts +158 -15
  144. package/dist/types/inline-citation.d.ts +35 -0
  145. package/dist/types/inline-edit.d.ts +71 -0
  146. package/dist/types/input-otp.d.ts +37 -0
  147. package/dist/types/item-group.d.ts +104 -0
  148. package/dist/types/line-chart.d.ts +116 -0
  149. package/dist/types/link.d.ts +11 -0
  150. package/dist/types/markdown.d.ts +88 -0
  151. package/dist/types/meter.d.ts +43 -0
  152. package/dist/types/modal-state.d.ts +62 -0
  153. package/dist/types/nav.d.ts +8 -1
  154. package/dist/types/notifications.d.ts +42 -0
  155. package/dist/types/number-input.d.ts +30 -0
  156. package/dist/types/overlays.d.ts +51 -17
  157. package/dist/types/page-header.d.ts +27 -0
  158. package/dist/types/pagination.d.ts +29 -0
  159. package/dist/types/palette.d.ts +64 -0
  160. package/dist/types/password-input.d.ts +22 -0
  161. package/dist/types/pattern-assistant-conversation.d.ts +55 -0
  162. package/dist/types/pattern-async-collection.d.ts +34 -0
  163. package/dist/types/pattern-consequential-action.d.ts +53 -0
  164. package/dist/types/pattern-edit-record.d.ts +72 -0
  165. package/dist/types/pattern-review-submit.d.ts +43 -0
  166. package/dist/types/permission-matrix.d.ts +34 -0
  167. package/dist/types/plan-picker.d.ts +40 -0
  168. package/dist/types/prompt-input.d.ts +106 -0
  169. package/dist/types/property-list.d.ts +67 -0
  170. package/dist/types/question.d.ts +64 -0
  171. package/dist/types/relative-time.d.ts +21 -0
  172. package/dist/types/removal-receipt.d.ts +1180 -0
  173. package/dist/types/resizable.d.ts +48 -0
  174. package/dist/types/roving.d.ts +44 -0
  175. package/dist/types/save-bar.d.ts +38 -0
  176. package/dist/types/saved-views.d.ts +72 -0
  177. package/dist/types/scatter-chart.d.ts +60 -0
  178. package/dist/types/schedule.d.ts +80 -0
  179. package/dist/types/secret-field.d.ts +33 -0
  180. package/dist/types/select.d.ts +44 -2
  181. package/dist/types/separator.d.ts +11 -0
  182. package/dist/types/settings.d.ts +39 -0
  183. package/dist/types/sheet.d.ts +1 -1
  184. package/dist/types/sparkline.d.ts +46 -0
  185. package/dist/types/split-button.d.ts +21 -0
  186. package/dist/types/stat.d.ts +54 -0
  187. package/dist/types/stepper.d.ts +6 -3
  188. package/dist/types/suggestions.d.ts +29 -0
  189. package/dist/types/suspense.d.ts +12 -1
  190. package/dist/types/sync-indicator.d.ts +24 -0
  191. package/dist/types/tag-input.d.ts +65 -0
  192. package/dist/types/terminal.d.ts +33 -0
  193. package/dist/types/thread-list.d.ts +58 -0
  194. package/dist/types/time-input.d.ts +33 -0
  195. package/dist/types/timeline.d.ts +38 -0
  196. package/dist/types/toggle.d.ts +46 -0
  197. package/dist/types/toolbar.d.ts +34 -0
  198. package/dist/types/tour.d.ts +23 -0
  199. package/dist/types/trace.d.ts +65 -0
  200. package/dist/types/tree.d.ts +71 -0
  201. package/dist/types-cjs/adapter-codemirror.d.cts +13 -0
  202. package/dist/types-cjs/agent-fleet.d.cts +166 -0
  203. package/dist/types-cjs/ai-label.d.cts +30 -0
  204. package/dist/types-cjs/app-shell.d.cts +83 -0
  205. package/dist/types-cjs/area-chart.d.cts +80 -0
  206. package/dist/types-cjs/artifact.d.cts +65 -0
  207. package/dist/types-cjs/bar-chart.d.cts +83 -0
  208. package/dist/types-cjs/bar-list.d.cts +35 -0
  209. package/dist/types-cjs/breadcrumb.d.cts +62 -0
  210. package/dist/types-cjs/chart-brush.d.cts +35 -0
  211. package/dist/types-cjs/chart-themes.d.cts +229 -0
  212. package/dist/types-cjs/chart.d.cts +570 -0
  213. package/dist/types-cjs/checklist.d.cts +71 -0
  214. package/dist/types-cjs/code-block.d.cts +32 -0
  215. package/dist/types-cjs/collections.d.cts +246 -0
  216. package/dist/types-cjs/color-picker.d.cts +38 -0
  217. package/dist/types-cjs/command.d.cts +66 -0
  218. package/dist/types-cjs/comment-thread.d.cts +48 -0
  219. package/dist/types-cjs/confirmation.d.cts +60 -0
  220. package/dist/types-cjs/context-menu.d.cts +17 -0
  221. package/dist/types-cjs/controls.d.cts +360 -0
  222. package/dist/types-cjs/conversation.d.cts +130 -0
  223. package/dist/types-cjs/copy-button.d.cts +19 -0
  224. package/dist/types-cjs/core.d.cts +256 -0
  225. package/dist/types-cjs/data-grid.d.cts +76 -0
  226. package/dist/types-cjs/date-picker.d.cts +96 -0
  227. package/dist/types-cjs/diff-view.d.cts +54 -0
  228. package/dist/types-cjs/dot-plot.d.cts +54 -0
  229. package/dist/types-cjs/edit-region.d.cts +121 -0
  230. package/dist/types-cjs/empty.d.cts +24 -0
  231. package/dist/types-cjs/feature-gate.d.cts +22 -0
  232. package/dist/types-cjs/filter-bar.d.cts +65 -0
  233. package/dist/types-cjs/formatted-number.d.cts +38 -0
  234. package/dist/types-cjs/heatmap.d.cts +64 -0
  235. package/dist/types-cjs/in-place.d.cts +24 -0
  236. package/dist/types-cjs/index.d.cts +154 -0
  237. package/dist/types-cjs/inline-citation.d.cts +35 -0
  238. package/dist/types-cjs/inline-edit.d.cts +71 -0
  239. package/dist/types-cjs/input-otp.d.cts +37 -0
  240. package/dist/types-cjs/item-group.d.cts +104 -0
  241. package/dist/types-cjs/line-chart.d.cts +116 -0
  242. package/dist/types-cjs/link.d.cts +11 -0
  243. package/dist/types-cjs/markdown.d.cts +88 -0
  244. package/dist/types-cjs/meter.d.cts +43 -0
  245. package/dist/types-cjs/modal-state.d.cts +62 -0
  246. package/dist/types-cjs/nav.d.cts +25 -0
  247. package/dist/types-cjs/notifications.d.cts +42 -0
  248. package/dist/types-cjs/number-input.d.cts +30 -0
  249. package/dist/types-cjs/overlays.d.cts +170 -0
  250. package/dist/types-cjs/page-header.d.cts +27 -0
  251. package/dist/types-cjs/pagination.d.cts +29 -0
  252. package/dist/types-cjs/palette.d.cts +64 -0
  253. package/dist/types-cjs/password-input.d.cts +22 -0
  254. package/dist/types-cjs/pattern-assistant-conversation.d.cts +55 -0
  255. package/dist/types-cjs/pattern-async-collection.d.cts +34 -0
  256. package/dist/types-cjs/pattern-consequential-action.d.cts +53 -0
  257. package/dist/types-cjs/pattern-edit-record.d.cts +72 -0
  258. package/dist/types-cjs/pattern-review-submit.d.cts +43 -0
  259. package/dist/types-cjs/permission-matrix.d.cts +34 -0
  260. package/dist/types-cjs/pile.d.cts +27 -0
  261. package/dist/types-cjs/plan-picker.d.cts +40 -0
  262. package/dist/types-cjs/prompt-input.d.cts +106 -0
  263. package/dist/types-cjs/property-list.d.cts +67 -0
  264. package/dist/types-cjs/question.d.cts +64 -0
  265. package/dist/types-cjs/relative-time.d.cts +21 -0
  266. package/dist/types-cjs/removal-receipt.d.cts +1180 -0
  267. package/dist/types-cjs/resizable.d.cts +48 -0
  268. package/dist/types-cjs/roving.d.cts +44 -0
  269. package/dist/types-cjs/save-bar.d.cts +38 -0
  270. package/dist/types-cjs/saved-views.d.cts +72 -0
  271. package/dist/types-cjs/scatter-chart.d.cts +60 -0
  272. package/dist/types-cjs/schedule.d.cts +80 -0
  273. package/dist/types-cjs/secret-field.d.cts +33 -0
  274. package/dist/types-cjs/select.d.cts +109 -0
  275. package/dist/types-cjs/separator.d.cts +11 -0
  276. package/dist/types-cjs/settings.d.cts +39 -0
  277. package/dist/types-cjs/sheet.d.cts +27 -0
  278. package/dist/types-cjs/sparkline.d.cts +46 -0
  279. package/dist/types-cjs/split-button.d.cts +21 -0
  280. package/dist/types-cjs/stat.d.cts +54 -0
  281. package/dist/types-cjs/stepper.d.cts +26 -0
  282. package/dist/types-cjs/suggestions.d.cts +29 -0
  283. package/dist/types-cjs/suspense.d.cts +85 -0
  284. package/dist/types-cjs/sync-indicator.d.cts +24 -0
  285. package/dist/types-cjs/tag-input.d.cts +65 -0
  286. package/dist/types-cjs/terminal.d.cts +33 -0
  287. package/dist/types-cjs/thread-list.d.cts +58 -0
  288. package/dist/types-cjs/time-input.d.cts +33 -0
  289. package/dist/types-cjs/timeline.d.cts +38 -0
  290. package/dist/types-cjs/toggle.d.cts +46 -0
  291. package/dist/types-cjs/toolbar.d.cts +34 -0
  292. package/dist/types-cjs/tour.d.cts +23 -0
  293. package/dist/types-cjs/trace.d.cts +65 -0
  294. package/dist/types-cjs/tree.d.cts +71 -0
  295. package/package.json +89 -9
@@ -1,4 +1,4 @@
1
- :root, [data-theme="daylight"] {
1
+ :root, [data-theme="daylight"], [data-theme="mesa"] {
2
2
  --surface-trench: #e1e4ea;
3
3
  --surface-well: #eaedf1;
4
4
  --surface-ground: #f3f4f7;
@@ -27,6 +27,28 @@
27
27
  --presence-2: #0b7285;
28
28
  --presence-3: #7046c9;
29
29
  --on-presence: #ffffff;
30
+ --data-1: #5d7bff;
31
+ --data-2: #e65000;
32
+ --data-3: #0498b9;
33
+ --data-4: #2a8f00;
34
+ --data-5: #643980;
35
+ --data-6: #c56f99;
36
+ --data-other: #31343c;
37
+ --data-context: #898a8e;
38
+ --data-seq-1: #e4ebff;
39
+ --data-seq-2: #c1cdef;
40
+ --data-seq-3: #a0b0de;
41
+ --data-seq-4: #8093cd;
42
+ --data-seq-5: #6176bb;
43
+ --data-seq-6: #4559a9;
44
+ --data-seq-7: #2b3c97;
45
+ --data-div-neg-3: #503000;
46
+ --data-div-neg-2: #966112;
47
+ --data-div-neg-1: #c6a178;
48
+ --data-div-mid: #e6e8ed;
49
+ --data-div-pos-1: #97a7d6;
50
+ --data-div-pos-2: #5569ad;
51
+ --data-div-pos-3: #1d2a84;
30
52
  --scrim: #12161f52;
31
53
  --trench: inset 0 1px 3px #0f17292e, 0 0 0 0 #0f172900, 0 0 0 0 #0f172900, 0 0 0 0 #0f172900;
32
54
  --well: inset 0 1px 2px #0f17291a, 0 0 0 0 #0f172900, 0 0 0 0 #0f172900, 0 0 0 0 #0f172900;
@@ -39,7 +61,7 @@
39
61
  --lip-top: inset 0 18px 18px -18px #0f17293d;
40
62
  --lip-bottom: inset 0 -18px 18px -18px #0f17293d;
41
63
  }
42
- [data-theme="nightfall"] {
64
+ [data-theme="nightfall"], [data-theme="alpine"] {
43
65
  --surface-trench: #08090c;
44
66
  --surface-well: #0c0e12;
45
67
  --surface-ground: #121419;
@@ -68,88 +90,28 @@
68
90
  --presence-2: #3fc1d0;
69
91
  --presence-3: #b39dff;
70
92
  --on-presence: #0b0d12;
71
- --scrim: #00000080;
72
- --trench: inset 0 2px 4px #000000b3, 0 0 0 0 #00000000, 0 0 0 0 #00000000, 0 0 0 0 #00000000;
73
- --well: inset 0 1px 2px #00000099, 0 0 0 0 #00000000, 0 0 0 0 #00000000, 0 0 0 0 #00000000;
74
- --elev-0: inset 0 1px 0 #ffffff00, 0 0 0 0 #00000000, 0 0 0 0 #00000000, 0 0 0 0 #00000000;
75
- --elev-1: inset 0 1px 0 #ffffff12, 0 0 0 1px #ffffff0a, 0 1px 2px #00000080, 0 2px 6px -1px #00000052;
76
- --elev-2: inset 0 1px 0 #ffffff17, 0 0 0 1px #ffffff0d, 0 2px 4px -1px #00000073, 0 10px 20px -6px #00000066;
77
- --elev-3: inset 0 1px 0 #ffffff1c, 0 0 0 1px #ffffff0f, 0 4px 8px -2px #00000073, 0 18px 36px -10px #00000080;
78
- --elev-4: inset 0 1px 0 #ffffff21, 0 0 0 1px #ffffff12, 0 8px 16px -4px #00000080, 0 30px 60px -16px #0000008c;
79
- --elev-5: inset 0 1px 0 #ffffff26, 0 0 0 1px #ffffff14, 0 12px 24px -6px #0000008c, 0 48px 96px -24px #00000099;
80
- --lip-top: inset 0 18px 18px -18px #000000cc;
81
- --lip-bottom: inset 0 -18px 18px -18px #000000cc;
82
- }
83
- [data-theme="mesa"] {
84
- --surface-trench: #e5ddd2;
85
- --surface-well: #eee7dd;
86
- --surface-ground: #f6f1ea;
87
- --surface-raised: #fffcf8;
88
- --surface-lifted: #fffdfa;
89
- --surface-floating: #ffffff;
90
- --surface-overlay: #ffffff;
91
- --surface-modal: #ffffff;
92
- --ink: #221b14;
93
- --ink-muted: #62564a;
94
- --ink-faint: #a09484;
95
- --line: #e6ded3;
96
- --line-control: #807467;
97
- --accent: #b4441b;
98
- --on-accent: #ffffff;
99
- --accent-ink: #a63e17;
100
- --accent-soft: #f8e4d9;
101
- --positive: #2c6e3f;
102
- --positive-soft: #e3efdc;
103
- --caution: #8a5000;
104
- --caution-soft: #fbeccf;
105
- --critical: #b3261e;
106
- --critical-soft: #f9e1dc;
107
- --on-critical: #ffffff;
108
- --presence-1: #a8256a;
109
- --presence-2: #0b7285;
110
- --presence-3: #6e44c2;
111
- --on-presence: #ffffff;
112
- --scrim: #221b144d;
113
- --trench: inset 0 1px 3px #2a1a0c2e, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00;
114
- --well: inset 0 1px 2px #2a1a0c1a, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00;
115
- --elev-0: inset 0 1px 0 #ffffff00, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00;
116
- --elev-1: inset 0 1px 0 #ffffffb3, 0 0 0 1px #2a1a0c17, 0 1px 2px #2a1a0c12, 0 2px 4px -1px #2a1a0c0f;
117
- --elev-2: inset 0 1px 0 #ffffffcc, 0 0 0 1px #2a1a0c12, 0 2px 4px -1px #2a1a0c14, 0 8px 16px -6px #2a1a0c24;
118
- --elev-3: inset 0 1px 0 #ffffffe6, 0 0 0 1px #2a1a0c0f, 0 4px 8px -2px #2a1a0c14, 0 16px 32px -10px #2a1a0c33;
119
- --elev-4: inset 0 1px 0 #ffffff, 0 0 0 1px #2a1a0c0d, 0 8px 16px -4px #2a1a0c17, 0 28px 56px -16px #2a1a0c3d;
120
- --elev-5: inset 0 1px 0 #ffffff, 0 0 0 1px #2a1a0c0a, 0 12px 24px -6px #2a1a0c1a, 0 48px 96px -24px #2a1a0c4d;
121
- --lip-top: inset 0 18px 18px -18px #2a1a0c3d;
122
- --lip-bottom: inset 0 -18px 18px -18px #2a1a0c3d;
123
- }
124
- [data-theme="alpine"] {
125
- --surface-trench: #060a09;
126
- --surface-well: #0a0f0e;
127
- --surface-ground: #0f1513;
128
- --surface-raised: #151e1b;
129
- --surface-lifted: #1a2522;
130
- --surface-floating: #202c28;
131
- --surface-overlay: #25332f;
132
- --surface-modal: #2a3935;
133
- --ink: #eaf3ef;
134
- --ink-muted: #9db0a9;
135
- --ink-faint: #5d6c67;
136
- --line: #222c29;
137
- --line-control: #62736d;
138
- --accent: #5fd6b2;
139
- --on-accent: #04140f;
140
- --accent-ink: #6fdcbc;
141
- --accent-soft: #13302a;
142
- --positive: #8ed67a;
143
- --positive-soft: #1a2e17;
144
- --caution: #efbd5b;
145
- --caution-soft: #2f2712;
146
- --critical: #ff8a70;
147
- --critical-soft: #3a1a14;
148
- --on-critical: #1a0905;
149
- --presence-1: #f07ab8;
150
- --presence-2: #5cc8e0;
151
- --presence-3: #b9a3ff;
152
- --on-presence: #06100d;
93
+ --data-1: #455ee1;
94
+ --data-2: #989902;
95
+ --data-3: #983dbc;
96
+ --data-4: #ce3401;
97
+ --data-5: #0397d2;
98
+ --data-6: #227702;
99
+ --data-other: #676a73;
100
+ --data-context: #86888b;
101
+ --data-seq-1: #2d3242;
102
+ --data-seq-2: #404966;
103
+ --data-seq-3: #54618c;
104
+ --data-seq-4: #687ab3;
105
+ --data-seq-5: #7e94dc;
106
+ --data-seq-6: #97b0ff;
107
+ --data-seq-7: #bfd0ff;
108
+ --data-div-neg-3: #ffc580;
109
+ --data-div-neg-2: #c68e48;
110
+ --data-div-neg-1: #816038;
111
+ --data-div-mid: #36383c;
112
+ --data-div-pos-1: #576590;
113
+ --data-div-pos-2: #8097df;
114
+ --data-div-pos-3: #bfd0ff;
153
115
  --scrim: #00000080;
154
116
  --trench: inset 0 2px 4px #000000b3, 0 0 0 0 #00000000, 0 0 0 0 #00000000, 0 0 0 0 #00000000;
155
117
  --well: inset 0 1px 2px #00000099, 0 0 0 0 #00000000, 0 0 0 0 #00000000, 0 0 0 0 #00000000;
@@ -177,6 +139,14 @@
177
139
  --radius-lg: 14px;
178
140
  --radius-xl: 20px;
179
141
  --radius-full: 9999px;
142
+ --size-target-min: 24px;
143
+ --size-control-sm: 30px;
144
+ --size-control-md: 36px;
145
+ --size-field-sm: 34px;
146
+ --size-field-md: 40px;
147
+ --size-row-md: 32px;
148
+ --breakpoint-narrow: 480px;
149
+ --breakpoint-compact: 640px;
180
150
  --dur-press: 90ms;
181
151
  --dur-travel-1: 140ms;
182
152
  --dur-travel-2: 180ms;
@@ -184,11 +154,16 @@
184
154
  --dur-travel-4: 260ms;
185
155
  --dur-travel-5: 300ms;
186
156
  --dur-sweep: 1600ms;
157
+ --dur-breath: 4800ms;
158
+ --dur-wave: 1800ms;
187
159
  --dur-grace: 300ms;
188
160
  --dur-onset: 500ms;
189
161
  --dur-hold: 1000ms;
190
162
  --dur-hover: 1800ms;
163
+ --dur-linger: 2000ms;
191
164
  --dur-settle: 8000ms;
165
+ --dur-arrive: 1100ms;
166
+ --dur-plate: 600ms;
192
167
  --ease-rise: cubic-bezier(0.2, 0.9, 0.25, 1);
193
168
  --ease-sink: cubic-bezier(0.45, 0, 0.8, 0.35);
194
169
  --ease-settle: cubic-bezier(0.3, 1.3, 0.45, 1);
@@ -203,6 +178,31 @@
203
178
  --font-sans: "Instrument Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
204
179
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
205
180
  }
181
+ @media (pointer: coarse) {
182
+ :root {
183
+ --size-target-min: 44px;
184
+ --size-control-sm: 40px;
185
+ --size-control-md: 44px;
186
+ --size-field-sm: 44px;
187
+ --size-field-md: 48px;
188
+ --size-row-md: 44px;
189
+ }
190
+ }
191
+ [data-density="comfortable"] {
192
+ --size-target-min: 44px;
193
+ --size-control-sm: 40px;
194
+ --size-control-md: 44px;
195
+ --size-field-sm: 44px;
196
+ --size-field-md: 48px;
197
+ --size-row-md: 44px;
198
+ }
199
+ [data-density="compact"] {
200
+ --size-control-sm: 30px;
201
+ --size-control-md: 36px;
202
+ --size-field-sm: 34px;
203
+ --size-field-md: 40px;
204
+ --size-row-md: 32px;
205
+ }
206
206
 
207
207
 
208
208
  /* Altitude — component styles. Every value comes from tokens.css; the only
@@ -213,6 +213,13 @@
213
213
  --alt-travel: 1;
214
214
  /* Convexity cue: a 2–5 L* light-to-dark face gradient on things you press (measured; see Research). Pressing flattens it. */
215
215
  --alt-sheen: linear-gradient(to bottom, rgb(255 255 255 / 0.06), rgb(0 0 0 / 0.03));
216
+ /* Insets that step down on a phone (Mobile, M11), so a small screen doesn't stack a desktop's padding at every level
217
+ of nesting. Each is full from the compact breakpoint (640px) up and eases to space-4 below it: the page's gutter and
218
+ an overlay's inset from space-6 (16px by 427px), a card's from space-5 (16px by 512px). They follow the viewport,
219
+ like a density, so a narrow card in a desktop grid keeps its padding; what changes form in a narrow slot still
220
+ follows its container. */
221
+ --alt-inset-page: clamp(var(--space-4), 3.75vw, var(--space-6));
222
+ --alt-inset-box: clamp(var(--space-4), 3.125vw, var(--space-5));
216
223
  }
217
224
  :root, [data-theme] {
218
225
  /* The hairline that outlines a slab, face and wall together, so its edges read on light grounds. */
@@ -229,7 +236,7 @@
229
236
  in use stays calm. 80% ink keeps a 3:1 change from line-control in every scheme. */
230
237
  --alt-rim-held: color-mix(in oklab, var(--ink) 80%, var(--line-control));
231
238
  }
232
- [data-theme="mesa"] { --alt-cast: rgb(42 26 12 / 0.22); --alt-cast-deep: rgb(42 26 12 / 0.36); }
239
+ /* alpine is a retired name for nightfall (#324). */
233
240
  [data-theme="nightfall"], [data-theme="alpine"] {
234
241
  --alt-ring: color-mix(in oklab, var(--ink) 9%, transparent); --alt-cast: rgb(0 0 0 / 0.8); --alt-cast-deep: rgb(0 0 0 / 1);
235
242
  --alt-wall-in: color-mix(in oklab, var(--surface-trench), #000 55%); --alt-lip: rgb(255 255 255 / 0.035);
@@ -272,10 +279,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
272
279
  cursor: pointer;
273
280
  transition: box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-2) var(--ease-settle);
274
281
  }
275
- .alt-lift:hover, .alt-lift:focus-visible { background: var(--alt-bg-up); box-shadow: var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel))); }
282
+ .alt-lift:focus-visible { background: var(--alt-bg-up); box-shadow: var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel))); }
283
+ @media (hover: hover) { .alt-lift:hover { background: var(--alt-bg-up); box-shadow: var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel))); } }
276
284
  .alt-lift:active { box-shadow: var(--elev-0); transform: translateY(calc(1px * var(--alt-travel))); transition-duration: var(--dur-press); transition-timing-function: ease-out; }
277
285
  .alt-lift:focus-visible, .alt-tabs-trigger:focus-visible, .alt-acc-trigger:focus-visible,
278
- .alt-slider-thumb:focus-visible, .alt-switch:focus-visible, .alt-tabs-content:focus-visible, .alt-toast-close:focus-visible {
286
+ .alt-slider-thumb:focus-visible, .alt-switch:focus-visible, .alt-tabs-content:focus-visible, .alt-toast-close:focus-visible, .alt-chart-pin-remove:focus-visible {
279
287
  outline: 2px solid var(--accent-ink); outline-offset: 2px;
280
288
  }
281
289
 
@@ -289,7 +297,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
289
297
  --wall: var(--wall-1); --wall-c: var(--surface-trench);
290
298
  --alt-bg: var(--surface-raised); --alt-bg-up: var(--surface-lifted); --alt-fg: var(--ink); --alt-sh: var(--elev-1); --alt-sh-up: var(--elev-2);
291
299
  position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none;
292
- height: 36px; padding: 0 var(--space-4); border: 0; border-radius: var(--radius-md);
300
+ height: var(--size-control-md); padding: 0 var(--space-4); border: 0; border-radius: var(--radius-md);
293
301
  font: 500 14px/20px var(--font-sans); letter-spacing: -0.003em; white-space: nowrap; user-select: none; cursor: pointer;
294
302
  background: var(--alt-bg); background-image: var(--alt-sheen); color: var(--alt-fg);
295
303
  /* A button stays exactly where it is until the pointer is on it: no approach, no lean. */
@@ -302,14 +310,14 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
302
310
  outline 2px off a box that covers the face and the wall, because an outline never adds to the page's scrollable
303
311
  area: a button at the edge of a narrow screen must not give the page a sideways scroll. At rest the box is the face
304
312
  alone, so only a keyboard-focused button's wall (the box's bottom edge, at most 5px) reaches past its own box. */
305
- .alt-btn:focus-visible, .alt-crumbs button:focus-visible { outline: none; }
306
- .alt-btn::after, .alt-crumbs button::after {
313
+ .alt-btn:focus-visible, .alt-crumbs .alt-crumb-link:focus-visible { outline: none; }
314
+ .alt-btn::after, .alt-crumbs .alt-crumb-link::after {
307
315
  content: ""; position: absolute; pointer-events: none; opacity: 0;
308
316
  inset: 0; outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--radius-md);
309
317
  transition: opacity var(--dur-travel-1) var(--ease-rise), bottom var(--dur-press) ease-out;
310
318
  }
311
- .alt-btn[data-size="sm"]::after, .alt-crumbs button::after { border-radius: var(--radius-sm); }
312
- .alt-btn:focus-visible::after, .alt-crumbs button:focus-visible::after { opacity: 1; bottom: calc(-1 * var(--wall, 0px) - 1px); }
319
+ .alt-btn[data-size="sm"]::after, .alt-crumbs .alt-crumb-link::after { border-radius: var(--radius-sm); }
320
+ .alt-btn:focus-visible::after, .alt-crumbs .alt-crumb-link:focus-visible::after { opacity: 1; bottom: calc(-1 * var(--wall, 0px) - 1px); }
313
321
  .alt-btn:active::after, .alt-btn[data-loading]::after { bottom: 0; }
314
322
  .alt-btn-label { display: inline-flex; align-items: center; gap: var(--space-2); transition: opacity var(--dur-travel-1) var(--ease-rise); }
315
323
  .alt-btn-label > svg { flex: none; margin-inline: -2px; }
@@ -322,16 +330,21 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
322
330
  --alt-bg: var(--critical); --alt-bg-up: var(--critical); --alt-fg: var(--on-critical); --alt-sh: var(--elev-2); --alt-sh-up: var(--elev-3);
323
331
  }
324
332
  .alt-btn[data-variant="ghost"] { --wall: 0px; --alt-bg: transparent; --alt-bg-up: var(--surface-raised); --alt-sh: var(--elev-0); --alt-sh-up: var(--elev-1); background-image: none; }
325
- .alt-btn[data-variant="ghost"]:hover:not(:disabled), .alt-btn[data-variant="ghost"]:focus-visible { --wall: var(--wall-1); background-image: var(--alt-sheen); }
326
- .alt-btn[data-size="sm"] { height: 30px; padding: 0 var(--space-3); font-size: 13px; border-radius: var(--radius-sm); }
333
+ .alt-btn[data-variant="ghost"]:focus-visible { --wall: var(--wall-1); background-image: var(--alt-sheen); }
334
+ @media (hover: hover) { .alt-btn[data-variant="ghost"]:hover:not(:disabled) { --wall: var(--wall-1); background-image: var(--alt-sheen); } }
335
+ .alt-btn[data-size="sm"] { height: var(--size-control-sm); padding: 0 var(--space-3); font-size: 13px; border-radius: var(--radius-sm); }
327
336
  .alt-btn[data-size="lg"] { height: 44px; padding: 0 var(--space-5); font-size: 15px; }
328
- .alt-btn[data-square] { width: 36px; padding: 0; }
329
- .alt-btn[data-square][data-size="sm"] { width: 30px; }
337
+ .alt-btn[data-square] { width: var(--size-control-md); padding: 0; }
338
+ .alt-btn[data-square][data-size="sm"] { width: var(--size-control-sm); }
330
339
  .alt-btn[data-square][data-size="lg"] { width: 44px; }
331
- .alt-btn:hover:not(:disabled):not([data-loading]), .alt-btn:focus-visible {
340
+ .alt-btn:focus-visible {
332
341
  background: var(--alt-bg-up); background-image: var(--alt-sheen);
333
342
  box-shadow: 0 calc(var(--wall) + 1px) 0 0 var(--wall-c), var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel)));
334
343
  }
344
+ @media (hover: hover) { .alt-btn:hover:not(:disabled):not([data-loading]) {
345
+ background: var(--alt-bg-up); background-image: var(--alt-sheen);
346
+ box-shadow: 0 calc(var(--wall) + 1px) 0 0 var(--wall-c), var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel)));
347
+ } }
335
348
  .alt-btn:active:not(:disabled):not([data-loading]) {
336
349
  box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); background-image: none; transform: translateY(var(--wall));
337
350
  transition-duration: var(--dur-press); transition-timing-function: ease-out;
@@ -348,13 +361,14 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
348
361
  background: var(--alt-bg); box-shadow: var(--alt-sh); border-radius: var(--radius-lg); color: var(--ink);
349
362
  display: flex; flex-direction: column;
350
363
  }
351
- .alt-card.alt-lift:hover, .alt-card.alt-lift:focus-visible { transform: translateY(calc(-2px * var(--alt-travel))); }
352
- .alt-card-header { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-5) var(--space-5) 0; }
364
+ .alt-card.alt-lift:focus-visible { transform: translateY(calc(-2px * var(--alt-travel))); }
365
+ @media (hover: hover) { .alt-card.alt-lift:hover { transform: translateY(calc(-2px * var(--alt-travel))); } }
366
+ .alt-card-header { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--alt-inset-box) var(--alt-inset-box) 0; }
353
367
  .alt-card-title { margin: 0; font: 600 17px/24px var(--font-sans); letter-spacing: -0.01em; }
354
368
  .alt-card-desc { margin: 0; font: 400 14px/20px var(--font-sans); color: var(--ink-muted); }
355
- .alt-card-content { padding: var(--space-4) var(--space-5) var(--space-5); font: 400 15px/23px var(--font-sans); }
356
- .alt-card-header + .alt-card-footer { padding-top: var(--space-5); }
357
- .alt-card-footer { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2); padding: 0 var(--space-5) var(--space-5); }
369
+ .alt-card-content { padding: var(--space-4) var(--alt-inset-box) var(--alt-inset-box); font: 400 15px/23px var(--font-sans); }
370
+ .alt-card-header + .alt-card-footer { padding-top: var(--alt-inset-box); }
371
+ .alt-card-footer { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2); padding: 0 var(--alt-inset-box) var(--alt-inset-box); }
358
372
 
359
373
  /* ── Input / Textarea: a field is an opening cut into the page, and its depth says whether it still needs filling.
360
374
  One camera for everything: under a raised face you see its front wall; inside an opening you see its back wall,
@@ -369,15 +383,15 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
369
383
  .alt-input {
370
384
  --w: 4px; --wc: var(--alt-wall-in); --halo: 0px; --halo-c: transparent;
371
385
  --wd: calc(var(--w) + var(--n) * 2px);
372
- position: relative; isolation: isolate; display: flex; align-items: center; gap: var(--space-2); height: 40px; padding: 0 var(--space-3);
386
+ position: relative; isolation: isolate; display: flex; align-items: center; gap: var(--space-2); height: var(--size-field-md); padding: 0 var(--space-3);
373
387
  border: 1px solid var(--line-control); border-radius: var(--radius-md);
374
388
  background: var(--surface-well); color: var(--ink); cursor: text;
375
389
  box-shadow: inset 0 var(--wd) 0 0 var(--wc), inset 0 calc(var(--wd) + 3px) 3px -2px var(--alt-cast), 0 1px 0 0 var(--alt-lip), 0 0 0 var(--halo) var(--halo-c);
376
390
  transition: background-color var(--dur-travel-2) var(--ease-rise), box-shadow var(--dur-travel-2) var(--ease-settle), border-color var(--dur-travel-1) var(--ease-rise), outline-color var(--dur-travel-1) var(--ease-rise);
377
391
  outline: 1px solid transparent; outline-offset: 0;
378
392
  }
379
- .alt-input[data-size="sm"] { --w: 3px; height: 34px; border-radius: var(--radius-sm); }
380
- .alt-input:hover { border-color: var(--ink-muted); }
393
+ .alt-input[data-size="sm"] { --w: 3px; height: var(--size-field-sm); border-radius: var(--radius-sm); }
394
+ @media (hover: hover) { .alt-input:hover { border-color: var(--ink-muted); } }
381
395
  /* Filled: the floor rises flush with the page. Nothing left to receive, so approach no longer deepens it. */
382
396
  .alt-input:has(input:not(:placeholder-shown)), .alt-input:has(textarea:not(:placeholder-shown)) { --w: 0px; --n: 0; background: var(--surface-raised); }
383
397
  /* Focused: the floor lights up, and an empty field stays open (at the depth approach previewed) until something is put
@@ -395,6 +409,19 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
395
409
  .alt-input[data-multiline] { height: auto; padding: var(--space-2) var(--space-3); align-items: stretch; flex-direction: column; overflow: hidden; }
396
410
  .alt-input textarea { min-height: 84px; resize: vertical; line-height: 21px; }
397
411
  .alt-input-adorn { display: inline-flex; color: var(--ink-muted); flex: none; transform: translateY(calc(var(--wd) * 0.5)); transition: transform var(--dur-travel-2) var(--ease-settle); }
412
+ /* Suggested: a value proposed for the field is a ghost of the value in ink-faint, the feedforward vocabulary (the ghost
413
+ of the check a box would get). It isn't the value, so an empty field with a ghost stays an opening; Tab accepts,
414
+ raising the floor flush with one afterglow. The ghost appears without motion: nothing has arrived in the record. */
415
+ .alt-input-field { position: relative; flex: 1; min-width: 0; display: flex; align-self: stretch; }
416
+ .alt-input-field[data-multiline] { flex-direction: column; }
417
+ .alt-input-ghost {
418
+ position: absolute; inset: 0; display: flex; align-items: center; overflow: hidden; white-space: pre; pointer-events: none;
419
+ font: 400 14px/20px var(--font-sans); color: var(--ink-faint);
420
+ transform: translateY(calc(var(--wd) * 0.5)); transition: transform var(--dur-travel-2) var(--ease-settle);
421
+ }
422
+ .alt-input-ghost[data-multiline] { display: block; white-space: pre-wrap; overflow-wrap: break-word; line-height: 21px; }
423
+ .alt-input-ghost-typed { color: transparent; }
424
+ @media (forced-colors: active) { .alt-input-ghost { color: GrayText; } }
398
425
  /* Fill line: with a maxLength, the well fills from the floor as the text approaches the limit. */
399
426
  .alt-input-gauge {
400
427
  --g: var(--accent-ink);
@@ -447,18 +474,18 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
447
474
  transition: transform var(--dur-travel-2) var(--ease-settle), filter var(--dur-travel-1) var(--ease-rise), color var(--dur-travel-1);
448
475
  }
449
476
  /* Hover is feedforward: an unchecked box shows a ghost of the check it would get; a checked one lifts its mark. */
450
- .alt-check:not(.alt-radio):hover:not([data-disabled]) input:not(:checked):not(:indeterminate) + .alt-check-track .alt-check-mark { color: var(--ink-faint); filter: none; }
451
- .alt-check:not(.alt-radio):hover:not([data-disabled]) input:not(:checked):not(:indeterminate) + .alt-check-track .alt-check-tick { stroke-dashoffset: 0; opacity: 0.55; transition: stroke-dashoffset var(--dur-travel-2) var(--ease-rise); }
452
- .alt-check:not(.alt-radio):hover:not([data-disabled]) input:checked + .alt-check-track .alt-check-mark { transform: translateY(calc(-0.5px * var(--alt-travel))); filter: drop-shadow(0 1.5px 1px rgb(0 0 0 / 0.32)); }
453
- .alt-check input:checked + .alt-check-track, .alt-check input:indeterminate + .alt-check-track { border-color: var(--accent); }
454
- .alt-check input:checked + .alt-check-track .alt-check-fill, .alt-check input:indeterminate + .alt-check-track .alt-check-fill { opacity: 1; clip-path: circle(75% at 50% 50%); }
455
- .alt-check:hover:not([data-disabled]) input:not(:checked):not(:indeterminate) + .alt-check-track { border-color: var(--ink-muted); }
477
+ @media (hover: hover) { .alt-check:not(.alt-radio):hover:not([data-disabled]) input:not(:checked):not(:indeterminate) + .alt-check-track .alt-check-mark { color: var(--ink-faint); filter: none; } }
478
+ @media (hover: hover) { .alt-check:not(.alt-radio):hover:not([data-disabled]) input:not(:checked):not(:indeterminate) + .alt-check-track .alt-check-tick { stroke-dashoffset: 0; opacity: 0.55; transition: stroke-dashoffset var(--dur-travel-2) var(--ease-rise); } }
479
+ @media (hover: hover) { .alt-check:not(.alt-radio):hover:not([data-disabled]) input:checked + .alt-check-track .alt-check-mark { transform: translateY(calc(-0.5px * var(--alt-travel))); filter: drop-shadow(0 1.5px 1px rgb(0 0 0 / 0.32)); } }
480
+ .alt-check input:checked + .alt-check-track, .alt-check input[type="checkbox"]:indeterminate + .alt-check-track { border-color: var(--accent); }
481
+ .alt-check input:checked + .alt-check-track .alt-check-fill, .alt-check input[type="checkbox"]:indeterminate + .alt-check-track .alt-check-fill { opacity: 1; clip-path: circle(75% at 50% 50%); }
482
+ @media (hover: hover) { .alt-check:hover:not([data-disabled]) input:not(:checked):not(:indeterminate) + .alt-check-track { border-color: var(--ink-muted); } }
456
483
  .alt-check:not(.alt-radio):active:not([data-disabled]) .alt-check-track { box-shadow: var(--trench); }
457
484
  .alt-check:not(.alt-radio):active:not([data-disabled]) .alt-check-mark { transform: scale(calc(1 - 0.12 * var(--alt-travel))); transition-duration: var(--dur-press); }
458
485
  .alt-check input:focus-visible + .alt-check-track { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
459
486
  .alt-check-mark path { stroke-dasharray: 16; stroke-dashoffset: 16; transition: stroke-dashoffset var(--dur-travel-1) var(--ease-sink); }
460
487
  .alt-check input:checked:not(:indeterminate) + .alt-check-track .alt-check-tick,
461
- .alt-check input:indeterminate + .alt-check-track .alt-check-dash { stroke-dashoffset: 0; transition: stroke-dashoffset var(--dur-travel-2) var(--ease-rise) 60ms; }
488
+ .alt-check input[type="checkbox"]:indeterminate + .alt-check-track .alt-check-dash { stroke-dashoffset: 0; transition: stroke-dashoffset var(--dur-travel-2) var(--ease-rise) 60ms; }
462
489
  .alt-check-text { display: flex; flex-direction: column; gap: 2px; padding-top: 1px; }
463
490
  .alt-check-label { font: 500 14px/20px var(--font-sans); }
464
491
  .alt-check-desc { font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
@@ -479,7 +506,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
479
506
  transition: box-shadow var(--dur-travel-1) var(--ease-rise), scale var(--dur-travel-2) var(--ease-settle);
480
507
  }
481
508
  .alt-radio-thumb[data-ready] { transition: transform var(--dur-travel-3) var(--ease-settle), box-shadow var(--dur-travel-1) var(--ease-rise), scale var(--dur-travel-2) var(--ease-settle); }
482
- .alt-radio-group:has(.alt-radio:hover input:checked) .alt-radio-thumb { box-shadow: var(--elev-2); }
509
+ @media (hover: hover) { .alt-radio-group:has(.alt-radio:hover input:checked) .alt-radio-thumb { box-shadow: var(--elev-2); } }
483
510
  .alt-radio-group:has(.alt-radio:active input:checked) .alt-radio-thumb { scale: calc(1 + 0.25 * var(--alt-travel)) calc(1 - 0.08 * var(--alt-travel)); }
484
511
  /* Feedforward: hovering an unchosen option shows a ghost of the thumb where it would land. */
485
512
  .alt-radio .alt-check-track::after {
@@ -487,7 +514,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
487
514
  background: var(--surface-raised); box-shadow: var(--elev-1); opacity: 0; scale: 0.5; pointer-events: none;
488
515
  transition: opacity var(--dur-travel-1) var(--ease-sink), scale var(--dur-travel-2) var(--ease-settle);
489
516
  }
490
- .alt-radio:hover:not([data-disabled]) input:not(:checked) + .alt-check-track::after { opacity: 0.5; scale: 0.85; transition-timing-function: var(--ease-rise), var(--ease-settle); }
517
+ @media (hover: hover) { .alt-radio:hover:not([data-disabled]) input:not(:checked) + .alt-check-track::after { opacity: 0.5; scale: 0.85; transition-timing-function: var(--ease-rise), var(--ease-settle); } }
491
518
  .alt-radio-group[data-disabled] .alt-radio-thumb { background: var(--surface-well); background-image: none; box-shadow: var(--elev-0); }
492
519
 
493
520
  /* ── Switch: a trench with a raised thumb; the accent pours in behind the thumb. ── */
@@ -510,7 +537,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
510
537
  .alt-switch[aria-checked="true"] { border-color: var(--accent); }
511
538
  .alt-switch[aria-checked="true"] .alt-switch-fill { opacity: 1; clip-path: inset(0 0 0 0 round 999px); }
512
539
  .alt-switch[aria-checked="true"] .alt-switch-thumb { transform: translateX(calc(var(--track) - var(--tw))); }
513
- .alt-switch:hover:not(:disabled) .alt-switch-thumb { box-shadow: var(--elev-2); }
540
+ @media (hover: hover) { .alt-switch:hover:not(:disabled) .alt-switch-thumb { box-shadow: var(--elev-2); } }
514
541
  .alt-switch:active:not(:disabled) { --tw: calc(18px + 5px * var(--alt-travel)); }
515
542
  .alt-switch:disabled { cursor: not-allowed; border-color: var(--line); background: var(--surface-ground); box-shadow: var(--elev-0); }
516
543
  .alt-switch:disabled .alt-switch-thumb { box-shadow: var(--elev-0); background: var(--surface-well); }
@@ -525,7 +552,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
525
552
  variant="segmented": the options sit in a well and one raised pill glides between them. ── */
526
553
  .alt-tabs { display: flex; flex-direction: column; gap: var(--space-4); }
527
554
  .alt-tabs-list {
528
- position: relative; display: inline-flex; align-self: flex-start; gap: 2px; padding: var(--space-1);
555
+ position: relative; display: inline-flex; align-self: flex-start; max-width: 100%; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain; gap: 2px; padding: var(--space-1);
529
556
  border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-well); box-shadow: var(--well);
530
557
  }
531
558
  .alt-tabs-indicator {
@@ -534,21 +561,30 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
534
561
  }
535
562
  .alt-tabs-indicator[data-ready] { transition: transform var(--dur-travel-3) var(--ease-settle), width var(--dur-travel-3) var(--ease-settle), opacity var(--dur-travel-1); }
536
563
  .alt-tabs-trigger {
537
- position: relative; z-index: 1; height: 30px; padding: 0 var(--space-3); border: 0; border-radius: calc(var(--radius-md) - var(--space-1));
564
+ position: relative; z-index: 1; height: var(--size-control-sm); padding: 0 var(--space-3); border: 0; border-radius: calc(var(--radius-md) - var(--space-1));
538
565
  background: none; color: var(--ink-muted); font: 500 14px/20px var(--font-sans); cursor: pointer; white-space: nowrap;
539
566
  transition: color var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1) var(--ease-rise);
540
567
  }
541
- .alt-tabs-trigger:hover, .alt-tabs-trigger[aria-selected="true"] { color: var(--ink); }
542
- .alt-tabs[data-variant="segmented"] .alt-tabs-trigger:hover:not([aria-selected="true"]) { background: color-mix(in oklab, var(--surface-raised) 40%, transparent); }
568
+ .alt-tabs-trigger[aria-selected="true"], .alt-tabs-trigger[aria-checked="true"] { color: var(--ink); }
569
+ @media (hover: hover) { .alt-tabs-trigger:hover { color: var(--ink); } }
570
+ @media (hover: hover) { .alt-tabs[data-variant="segmented"] .alt-tabs-trigger:hover:not([aria-selected="true"]), .alt-tgroup .alt-tabs-trigger:hover:not([aria-checked="true"]):not(:disabled) { background: color-mix(in oklab, var(--surface-raised) 40%, transparent); } }
571
+ /* Narrow (#259): the list scrolls within itself; an edge with more tabs past it fades out, so you can see there's more. */
572
+ .alt-tabs-list::-webkit-scrollbar { display: none; }
573
+ .alt-tabs-list[data-more-end] { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); }
574
+ .alt-tabs-list[data-more-start] { -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent); }
575
+ .alt-tabs-list[data-more-start][data-more-end] { -webkit-mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent); }
543
576
  .alt-tabs-content { animation: alt-emerge var(--dur-travel-2) var(--ease-rise); border-radius: var(--radius-sm); }
544
577
 
545
578
  .alt-tabs[data-variant="folder"] { gap: 0; }
579
+ /* The list makes no layer of its own, so each tab stacks against the panel: only the chosen tab lies over it (covering its
580
+ hairline); the others, and a tab dropping back, sit behind the sheet. */
546
581
  .alt-tabs[data-variant="folder"] > .alt-tabs-list {
547
- --r: 10px; align-self: stretch; z-index: 2; gap: var(--space-1); margin-bottom: -1px; padding: 0 var(--space-5);
582
+ /* It scrolls when narrow (#259), so the tab's shadow above and its 1px reach into the panel sit in its padding. */
583
+ --r: 10px; align-self: stretch; z-index: auto; gap: var(--space-1); margin: -6px 0 -2px; padding: 6px var(--alt-inset-box) 1px;
548
584
  border: 0; border-radius: 0; background: none; box-shadow: none;
549
585
  }
550
586
  .alt-tabs[data-variant="folder"] .alt-tabs-indicator { display: none; }
551
- .alt-tabs[data-variant="folder"] .alt-tabs-trigger { isolation: isolate; height: 38px; padding: 0 var(--space-4); border-radius: var(--r) var(--r) 0 0; }
587
+ .alt-tabs[data-variant="folder"] .alt-tabs-trigger { z-index: 0; isolation: isolate; height: calc(var(--size-control-md) + 2px); padding: 0 var(--space-4); border-radius: var(--r) var(--r) 0 0; }
552
588
  .alt-tabs[data-variant="folder"] .alt-tabs-trigger:focus-visible { outline-offset: -5px; }
553
589
  /* The tab: the top edge of its sheet. It reaches 1px into the panel to cover the panel's top hairline. */
554
590
  .alt-tabs[data-variant="folder"] .alt-tabs-trigger::before {
@@ -565,17 +601,18 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
565
601
  radial-gradient(circle at 100% 0, transparent calc(var(--r) - 0.75px), var(--alt-ring) calc(var(--r) - 0.5px), var(--alt-ring) calc(var(--r) + 0.25px), var(--surface-raised) calc(var(--r) + 0.75px)) right / var(--r) var(--r) no-repeat;
566
602
  opacity: 0; transition: opacity var(--dur-travel-1) var(--ease-sink);
567
603
  }
604
+ .alt-tabs[data-variant="folder"] .alt-tabs-trigger[aria-selected="true"] { z-index: 2; }
568
605
  .alt-tabs[data-variant="folder"] .alt-tabs-trigger[aria-selected="true"]::before {
569
606
  opacity: 1; transform: none; transition: opacity var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-3) var(--ease-settle);
570
607
  }
571
608
  .alt-tabs[data-variant="folder"] .alt-tabs-trigger[aria-selected="true"]::after { opacity: 1; transition: opacity var(--dur-travel-2) var(--ease-rise) 60ms; }
572
- .alt-tabs[data-variant="folder"] .alt-tabs-trigger:hover:not([aria-selected="true"])::before {
609
+ @media (hover: hover) { .alt-tabs[data-variant="folder"] .alt-tabs-trigger:hover:not([aria-selected="true"])::before {
573
610
  opacity: 1; transform: translateY(calc(5px * var(--alt-travel))); clip-path: inset(-8px -8px calc(1px + 5px * var(--alt-travel)) -8px);
574
611
  background: color-mix(in oklab, var(--surface-raised) 50%, var(--surface-ground)); box-shadow: 0 0 0 1px color-mix(in oklab, var(--ink) 6%, transparent);
575
612
  transition: opacity var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-2) var(--ease-settle);
576
- }
613
+ } }
577
614
  .alt-tabs[data-variant="folder"] > .alt-tabs-content {
578
- position: relative; z-index: 1; padding: var(--space-5); border-radius: var(--radius-lg);
615
+ position: relative; z-index: 1; padding: var(--alt-inset-box); border-radius: var(--radius-lg);
579
616
  background: var(--surface-raised); box-shadow: var(--elev-2); color: var(--ink);
580
617
  animation: alt-sheet-surface var(--dur-travel-3) var(--ease-rise);
581
618
  }
@@ -591,9 +628,9 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
591
628
  border: 2px solid var(--accent); border-radius: var(--radius-full); background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1);
592
629
  transition: left var(--dur-travel-1) var(--ease-glide), box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-2) var(--ease-settle);
593
630
  }
594
- .alt-slider:hover .alt-slider-thumb { box-shadow: var(--elev-2); }
631
+ @media (hover: hover) { .alt-slider:hover .alt-slider-thumb { box-shadow: var(--elev-2); } }
595
632
  .alt-slider[data-dragging] .alt-slider-fill, .alt-slider[data-dragging] .alt-slider-thumb { transition-property: box-shadow, transform; }
596
- .alt-slider[data-dragging] .alt-slider-thumb { box-shadow: var(--elev-3); transform: scale(calc(1 + 0.12 * var(--alt-travel))); cursor: grabbing; }
633
+ .alt-slider[data-dragging] .alt-slider-thumb:not([data-rest]) { box-shadow: var(--elev-3); transform: scale(calc(1 + 0.12 * var(--alt-travel))); cursor: grabbing; }
597
634
  .alt-slider-bubble {
598
635
  position: absolute; left: 50%; bottom: calc(100% + 10px); translate: -50% 0; min-width: 32px; padding: 3px 8px; text-align: center;
599
636
  border-radius: var(--radius-sm); background: var(--surface-floating); box-shadow: var(--elev-1); color: var(--ink);
@@ -601,13 +638,23 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
601
638
  opacity: 0; transform: translateY(calc(4px * var(--alt-travel))) scale(calc(1 - 0.1 * var(--alt-travel))); transform-origin: 50% 100%;
602
639
  transition: opacity var(--dur-travel-1) var(--ease-sink), transform var(--dur-travel-1) var(--ease-sink), box-shadow var(--dur-travel-1) var(--ease-sink);
603
640
  }
604
- .alt-slider[data-dragging] .alt-slider-bubble, .alt-slider-thumb:focus-visible .alt-slider-bubble {
641
+ .alt-slider[data-dragging] .alt-slider-thumb:not([data-rest]) .alt-slider-bubble, .alt-slider-thumb:focus-visible .alt-slider-bubble {
605
642
  opacity: 1; transform: none; box-shadow: var(--elev-3);
606
643
  transition: opacity var(--dur-travel-3) var(--ease-rise), transform var(--dur-travel-3) var(--ease-rise), box-shadow var(--dur-travel-3) var(--ease-rise);
607
644
  }
608
645
  .alt-slider[data-disabled] { cursor: not-allowed; }
609
646
  .alt-slider[data-disabled] .alt-slider-fill { background: var(--line-control); box-shadow: none; }
610
647
  .alt-slider[data-disabled] .alt-slider-thumb { border-color: var(--line-control); box-shadow: var(--elev-0); }
648
+ /* A range: two thumbs with the raised fill between them. Only the thumb under the pointer lifts, and the one held
649
+ floats; the other stays at rest. The fill lifts on hover too, because dragging it moves both thumbs. */
650
+ .alt-slider[data-range] .alt-slider-fill { position: absolute; top: 0; cursor: grab; transition: left var(--dur-travel-1) var(--ease-glide), width var(--dur-travel-1) var(--ease-glide), box-shadow var(--dur-travel-1) var(--ease-rise); }
651
+ @media (hover: hover) { .alt-slider[data-range]:not([data-disabled]):hover .alt-slider-thumb { box-shadow: var(--elev-1); } }
652
+ .alt-slider-fill[data-grabbed] { box-shadow: var(--elev-2); }
653
+ @media (hover: hover) { .alt-slider[data-range]:not([data-disabled]) :is(.alt-slider-thumb:hover, .alt-slider-fill:hover) { box-shadow: var(--elev-2); } }
654
+ .alt-slider[data-range][data-dragging] .alt-slider-thumb[data-grabbed] { box-shadow: var(--elev-3); }
655
+ .alt-slider[data-range][data-dragging] .alt-slider-fill { transition-property: box-shadow; }
656
+ .alt-slider-fill[data-grabbed] { cursor: grabbing; }
657
+ .alt-slider[data-range][data-disabled] .alt-slider-fill { cursor: not-allowed; box-shadow: none; }
611
658
 
612
659
  /* ── Progress ── */
613
660
  .alt-progress { position: relative; height: 8px; border-radius: var(--radius-full); background: var(--surface-trench); box-shadow: var(--trench); overflow: hidden; }
@@ -661,71 +708,76 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
661
708
  @media (prefers-reduced-motion: reduce) { .alt-badge-lamp[data-pulse] { animation: none; } }
662
709
  [data-motion="reduced"] .alt-badge-lamp[data-pulse] { animation: none; }
663
710
 
664
- /* ── Avatar: presence as height, drawn the way the eye judges height above a floor: by the gap between a thing and
665
- its shadow (Kersten, Mamassian & Knill 1997). Every face is a disc with a cast shadow on the ground beneath it.
666
- Active people hover: the face lifts off and its shadow shrinks and fades. Idle people rest: a tight contact
667
- shadow right under them. Offline people lie flat in the page, grey, with no shadow at all. Someone live
668
- (speaking, typing, presenting) drifts gently up and down. --hr is the height as a fraction of the face. ── */
711
+ /* ── Avatar: a person is a disc. While they're here (active or idle) it rests on the page like a card, at level 1 with
712
+ its contact shadow; when they're not (offline) it's part of the record, a plate set flush into the page, grey. Faces
713
+ are neutral: status hues never stand for a person, and colour stays in the lamp, as on a Badge (filled when active,
714
+ an empty ring when idle). Only someone live (speaking, typing, presenting), or an avatar you can press under the
715
+ pointer, leaves the ground: the face lifts and a shadow appears on the page beneath it, smaller and fainter the
716
+ higher it goes. A detached shadow is what makes an object read as floating rather than resting (Kersten, Mamassian
717
+ & Knill 1997). --hr is the height as a fraction of the face. ── */
669
718
  @property --hr { syntax: "<number>"; inherits: true; initial-value: 0; }
670
719
  .alt-avatar {
671
720
  --size: 36px; --hr: 0; --lift: calc(var(--size) * var(--hr) * var(--alt-travel));
721
+ --av-sh: var(--elev-1); --av-plate: inset 0 1px 1.5px color-mix(in oklab, var(--ink) 16%, transparent), inset 0 0 0 1px color-mix(in oklab, var(--ink) 9%, transparent);
672
722
  position: relative; isolation: isolate; display: inline-grid; flex: none; width: var(--size); height: var(--size); vertical-align: middle;
673
723
  transition: --hr var(--dur-travel-3) var(--ease-settle);
674
724
  }
675
725
  .alt-avatar-face {
676
- position: relative; z-index: 1; display: grid; place-items: center; width: 100%; height: 100%; overflow: hidden; border-radius: var(--radius-full);
677
- background-color: var(--surface-raised); background-image: radial-gradient(120% 90% at 50% 0%, rgb(255 255 255 / 0.2), transparent 60%);
678
- box-shadow: inset 0 0 0 1px var(--alt-ring); color: var(--ink);
679
- font: 600 calc(var(--size) * 0.36) / 1 var(--font-sans); letter-spacing: 0.01em; user-select: none;
726
+ position: relative; z-index: 1; display: grid; place-items: center; width: 100%; height: 100%; overflow: hidden; border-radius: var(--av-r, var(--radius-full));
727
+ background: var(--surface-raised); box-shadow: var(--av-sh); color: var(--ink);
728
+ font: 600 calc(var(--size) * 0.36) / 1 var(--font-sans); user-select: none;
680
729
  transform: translateY(calc(-1 * var(--lift)));
681
- transition: background-color var(--dur-travel-2), color var(--dur-travel-2), filter var(--dur-travel-2), box-shadow var(--dur-travel-2);
730
+ transition: background-color var(--dur-travel-2), color var(--dur-travel-2), box-shadow var(--dur-travel-2) var(--ease-rise);
682
731
  }
683
732
  .alt-avatar-face img { width: 100%; height: 100%; object-fit: cover; }
684
- /* The cast shadow. The higher the face, the smaller and fainter it gets — that gap is what reads as height. */
733
+ /* One letter has room to be bigger: small faces (under 28px) show only the first. */
734
+ .alt-avatar-face[data-one] { font-size: calc(var(--size) * 0.46); }
735
+ /* A team, an organisation, a bot or an agent is a square tile; its corners (--av-r) grow with its size, from
736
+ radius-xs to radius-lg. Its shadow, lamp and states are a person's. */
737
+ .alt-avatar[data-shape="square"] .alt-avatar-lamp { inset-inline-end: calc(var(--lamp) * -0.3); bottom: calc(var(--lamp) * -0.3); }
738
+ .alt-avatar[data-shape="square"] .alt-avatar-shadow { left: 8%; right: 8%; border-radius: var(--radius-sm); }
739
+ /* The shadow on the page. At rest it isn't there (the face's own level-1 shadow is its contact shadow); it fades in
740
+ as the face leaves the ground, then shrinks and fades the higher the face goes: that gap is what reads as height. */
685
741
  .alt-avatar-shadow {
686
- position: absolute; left: 7%; right: 7%; top: 100%; height: calc(var(--size) * 0.2); margin-top: calc(var(--size) * -0.1);
687
- border-radius: 50%; background: radial-gradient(closest-side, var(--alt-cast-deep), var(--alt-cast-deep) 25%, transparent); pointer-events: none;
688
- scale: calc(1 - var(--hr) * 1.15); opacity: calc(1 - var(--hr) * 1.7);
689
- }
690
- .alt-avatar[data-tint="accent"] .alt-avatar-face { background-color: var(--accent-soft); }
691
- .alt-avatar[data-tint="positive"] .alt-avatar-face { background-color: var(--positive-soft); }
692
- .alt-avatar[data-tint="caution"] .alt-avatar-face { background-color: var(--caution-soft); }
693
- .alt-avatar[data-tint="critical"] .alt-avatar-face { background-color: var(--critical-soft); }
694
- .alt-avatar[data-status="active"] { --hr: 0.15; }
695
- .alt-avatar[data-status="offline"] .alt-avatar-face, .alt-avatar[data-tint="more"] .alt-avatar-face {
696
- background-color: var(--surface-well); background-image: none; color: var(--ink-muted); box-shadow: inset 0 0 0 1px var(--alt-ring), inset 0 1px 2px var(--alt-cast);
697
- }
698
- .alt-avatar[data-status="offline"] .alt-avatar-face { filter: grayscale(1); }
699
- .alt-avatar[data-status="offline"] .alt-avatar-face img { opacity: 0.6; }
700
- .alt-avatar[data-status="offline"] .alt-avatar-shadow, .alt-avatar[data-tint="more"] .alt-avatar-shadow { opacity: 0; }
701
- .alt-avatar[data-tint="more"] .alt-avatar-face { font-size: calc(var(--size) * 0.32); }
702
- .alt-avatar[data-live] { animation: alt-drift var(--dur-hover) ease-in-out infinite alternate; }
703
- @keyframes alt-drift { from { --hr: 0.15; } to { --hr: 0.24; } }
704
- @media (prefers-reduced-motion: reduce) { .alt-avatar[data-live] { animation: none; --hr: 0.2; } }
705
- [data-motion="reduced"] .alt-avatar[data-live] { animation: none; --hr: 0.2; }
706
- .alt-avatar[data-presence] .alt-avatar-face { outline: 2px solid var(--alt-presence); outline-offset: 1px; }
742
+ position: absolute; left: 14%; right: 14%; top: 100%; height: calc(var(--size) * 0.14); margin-top: calc(var(--size) * -0.07);
743
+ border-radius: 50%; pointer-events: none;
744
+ background: radial-gradient(closest-side, var(--alt-cast-deep), color-mix(in oklab, var(--alt-cast-deep) 55%, transparent) 50%, transparent);
745
+ opacity: calc(clamp(0, var(--hr) * 12, 1) * (1 - var(--hr) * 1.4)); scale: calc(1 - var(--hr) * 1.1);
746
+ }
747
+ .alt-avatar:is([data-status="offline"], [data-more]) .alt-avatar-face {
748
+ background: color-mix(in oklab, var(--surface-ground), var(--surface-well) 70%); color: var(--ink-muted); box-shadow: var(--av-plate), 0 1px 0 0 var(--alt-lip);
749
+ }
750
+ .alt-avatar[data-status="offline"] .alt-avatar-face img { filter: grayscale(1); opacity: 0.5; }
751
+ .alt-avatar[data-more] .alt-avatar-face { font-size: calc(var(--size) * 0.32); font-weight: 550; font-variant-numeric: tabular-nums; }
752
+ .alt-avatar[data-live] { --av-sh: var(--elev-2); animation: alt-drift var(--dur-hover) ease-in-out infinite alternate; }
753
+ @keyframes alt-drift { from { --hr: 0.14; } to { --hr: 0.22; } }
754
+ @media (prefers-reduced-motion: reduce) { .alt-avatar[data-live] { animation: none; --hr: 0.18; } }
755
+ [data-motion="reduced"] .alt-avatar[data-live] { animation: none; --hr: 0.18; }
756
+ /* Presence colour: a 2px ring drawn tight around the face. */
757
+ .alt-avatar[data-presence] .alt-avatar-face { outline: 2px solid var(--alt-presence); outline-offset: 0; }
758
+ /* The lamp is a Badge lamp, cut out of the face by a ring of ground: filled when active, an empty ring when idle. */
707
759
  .alt-avatar-lamp {
708
- --b-lamp: var(--positive);
709
- position: absolute; z-index: 2; right: 0; bottom: 0; width: max(8px, calc(var(--size) * 0.24)); height: max(8px, calc(var(--size) * 0.24)); border-radius: var(--radius-full);
760
+ --b-lamp: var(--positive); --lamp: clamp(8px, calc(var(--size) * 0.2), 12px);
761
+ position: absolute; z-index: 2; inset-inline-end: 0; bottom: 0; width: var(--lamp); height: var(--lamp); border-radius: var(--radius-full);
710
762
  background: var(--b-lamp); box-shadow: 0 0 0 2px var(--surface-ground); transform: translateY(calc(-1 * var(--lift)));
711
763
  }
712
- .alt-avatar[data-status="idle"] .alt-avatar-lamp { --b-lamp: var(--caution); }
713
- .alt-avatar[data-interactive]:hover, .alt-avatar[data-interactive]:focus-visible { --hr: 0.3; }
764
+ .alt-avatar[data-status="idle"] .alt-avatar-lamp { background: var(--surface-raised); box-shadow: 0 0 0 2px var(--surface-ground), inset 0 0 0 clamp(2px, calc(var(--size) * 0.05), 3px) var(--caution); }
714
765
  .alt-avatar[data-interactive] { cursor: pointer; outline: none; }
766
+ .alt-avatar[data-interactive]:focus-visible { --hr: 0.26; --av-sh: var(--elev-2); }
767
+ @media (hover: hover) { .alt-avatar[data-interactive]:hover { --hr: 0.26; --av-sh: var(--elev-2); } }
768
+ .alt-avatar[data-interactive]:active { --hr: 0; --av-sh: var(--elev-0); transition-duration: var(--dur-press); }
715
769
  .alt-avatar[data-interactive]:focus-visible .alt-avatar-face { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
716
- .alt-avatar-group { isolation: isolate; display: inline-flex; align-items: flex-end; padding: calc(var(--size, 36px) * 0.32) 0 calc(var(--size, 36px) * 0.1); }
770
+ /* Group: every face stands on one ground line, cut out of the one before it by a ring of ground. Later faces are
771
+ laid on top, overlapping by 15% of a face, which keeps the initials beneath whole. */
772
+ .alt-avatar-group { isolation: isolate; display: inline-flex; align-items: flex-end; padding: calc(var(--size, 36px) * 0.26) 0 calc(var(--size, 36px) * 0.08); }
717
773
  .alt-avatar-group .alt-avatar { isolation: auto; }
718
- /* Faces overlap by a fifth. An avatar that waited for its data sits inside its loading wrapper (display: contents at
719
- rest), so the overlap is counted from the group's own children either way, imprints included. */
774
+ /* An avatar that waited for its data sits inside its loading wrapper (display: contents at rest), so the overlap is
775
+ counted from the group's own children either way, imprints included. */
720
776
  .alt-avatar-group > .alt-avatar:not(:first-child),
721
- .alt-avatar-group > :is(.alt-await, .alt-await-wait):not(:first-child) :is(.alt-avatar, .alt-avatar-imprint) { margin-left: calc(var(--size, 36px) * -0.22); }
722
- /* Overlapping faces are cut out of each other with a ring of ground; higher faces sit in front. */
723
- .alt-avatar-group .alt-avatar .alt-avatar-face { box-shadow: 0 0 0 2px var(--surface-ground), inset 0 0 0 1px var(--alt-ring); }
724
- .alt-avatar-group .alt-avatar[data-status="offline"] .alt-avatar-face { box-shadow: 0 0 0 2px var(--surface-ground), inset 0 0 0 1px var(--alt-ring), inset 0 1px 2px var(--alt-cast); }
725
- .alt-avatar-group .alt-avatar[data-status="active"] .alt-avatar-face { z-index: 3; }
726
- .alt-avatar-group .alt-avatar[data-status="idle"] .alt-avatar-face { z-index: 2; }
727
- .alt-avatar-group .alt-avatar:not([data-tint="more"]):hover { --hr: 0.32; }
728
- .alt-avatar-group .alt-avatar:not([data-tint="more"]):hover .alt-avatar-face { z-index: 5; }
777
+ .alt-avatar-group > :is(.alt-await, .alt-await-wait):not(:first-child) :is(.alt-avatar, .alt-avatar-imprint) { margin-inline-start: calc(var(--size, 36px) * -0.12); }
778
+ .alt-avatar-group .alt-avatar .alt-avatar-face { box-shadow: 0 0 0 2px var(--surface-ground), var(--av-sh); }
779
+ .alt-avatar-group .alt-avatar:is([data-status="offline"], [data-more]) .alt-avatar-face { box-shadow: 0 0 0 2px var(--surface-ground), var(--av-plate); }
780
+ @media (hover: hover) { .alt-avatar-group .alt-avatar:is([data-live], [data-interactive]:hover, [data-interactive]:focus-visible) .alt-avatar-face { z-index: 5; } }
729
781
  .alt-avatar-group .alt-avatar-lamp { display: none; }
730
782
 
731
783
  /* ── Skeleton: an imprint of the content to come, pressed into the page. Light passes over it while it waits; when the
@@ -773,8 +825,77 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
773
825
  /* Text imprints are as tall as the text they stand for: one line-height of the surrounding type per line. */
774
826
  .alt-await-lines { display: flex; flex-direction: column; justify-content: space-around; height: calc(var(--lines, 1) * 1lh); max-width: 100%; }
775
827
  .alt-await-lines > .alt-skeleton { flex: none; height: 0.6em; }
776
- .alt-await-card { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-5); }
828
+ .alt-await-card { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--alt-inset-box); }
777
829
  .alt-await-card-body { font: 400 15px/23px var(--font-sans); }
830
+
831
+ /* ── Breath (AltitudeProvider loading="breath", #343). Instead of light sweeping over it, the imprint breathes: its floor
832
+ deepens from well to trench over 40% of dur-breath and releases over the rest, slower and less even than a pending
833
+ write's breath. In Nightfall, where well and trench are nearly one tone, the back wall (lip-top) breathes instead.
834
+ Every imprint takes its phase from one page clock, lagged by its height in the view (--alt-breath-delay, set by
835
+ useBreath), so they change in step and a swell crosses the view in dur-wave. Ten seconds in, it calms to a shallow,
836
+ slower breath. Depth and tone only: nothing travels. Charts, stats and meters hold still (their imprints stand for
837
+ amounts). When content lands: raised content (settle="raised") rises 8px to level 2 and settles to its rest over
838
+ dur-arrive; flush content fills past the ground (its floor passes just above it, then fills in flush); content that
839
+ arrives into a view already showing gets a plate that dissolves over dur-plate, no tint, so it isn't an afterglow. ── */
840
+ [data-loading="breath"] .alt-skeleton::after { content: none; }
841
+ [data-loading="breath"] .alt-skeleton { animation: alt-breath var(--dur-breath) linear var(--alt-breath-delay, 0ms) infinite; }
842
+ [data-loading="breath"][data-theme="nightfall"] .alt-skeleton, [data-theme="nightfall"] [data-loading="breath"] .alt-skeleton { animation-name: alt-breath-wall; }
843
+ [data-loading="breath"] :is([data-calm] .alt-skeleton, .alt-skeleton[data-calm]) { animation: alt-breath-calm calc(var(--dur-breath) * 0.75) ease-in-out infinite alternate; }
844
+ [data-loading="breath"] :is(.alt-chart-imprint .alt-skeleton, .alt-stat-imprint, .alt-sparkline-imprint, .alt-meter-imprint) { animation: none; }
845
+ @keyframes alt-breath {
846
+ 0% { background-color: var(--surface-well); box-shadow: var(--well); animation-timing-function: ease-in-out; }
847
+ 40% { background-color: var(--surface-trench); box-shadow: var(--trench); animation-timing-function: ease-in-out; }
848
+ 100% { background-color: var(--surface-well); box-shadow: var(--well); }
849
+ }
850
+ @keyframes alt-breath-wall {
851
+ 0% { box-shadow: var(--well), inset 0 0 0 0 transparent; animation-timing-function: ease-in-out; }
852
+ 40% { box-shadow: var(--well), var(--lip-top); animation-timing-function: ease-in-out; }
853
+ 100% { box-shadow: var(--well), inset 0 0 0 0 transparent; }
854
+ }
855
+ @keyframes alt-breath-calm { to { background-color: color-mix(in oklab, var(--surface-well), var(--surface-trench) 45%); } }
856
+ [data-loading="breath"] .alt-await[data-phase="reveal"] > .alt-await-face { position: relative; animation: alt-fill-ride calc(var(--dur-travel-3) * 2) linear var(--alt-rise-delay, 0ms) backwards; }
857
+ [data-loading="breath"] .alt-await[data-phase="reveal"] > .alt-await-imprint { animation: alt-fill-past calc(var(--dur-travel-3) * 2) linear var(--alt-rise-delay, 0ms) forwards; }
858
+ [data-loading="breath"] .alt-await[data-phase="reveal"][data-arrival="late"] > .alt-await-imprint { animation: alt-await-fill var(--dur-travel-1) linear var(--alt-rise-delay, 0ms) forwards; }
859
+ [data-loading="breath"] .alt-await[data-phase="reveal"][data-arrival="late"]:not([data-rest="raised"]) > .alt-await-face::before {
860
+ content: ""; position: absolute; inset: -3px -6px; z-index: -1; border-radius: var(--radius-sm); pointer-events: none;
861
+ background: var(--surface-raised); box-shadow: var(--elev-1); animation: alt-plate var(--dur-plate) linear var(--alt-rise-delay, 0ms) both;
862
+ }
863
+ [data-loading="breath"] .alt-await[data-phase="reveal"][data-rest="raised"] > .alt-await-face { animation: alt-arrive var(--dur-arrive) linear var(--alt-rise-delay, 0ms) backwards; }
864
+ [data-loading="breath"] .alt-await[data-phase="reveal"][data-rest="raised"] > .alt-await-face::before {
865
+ content: ""; position: absolute; inset: 0; z-index: -1; border-radius: var(--alt-await-radius, var(--radius-lg)); pointer-events: none;
866
+ box-shadow: var(--elev-2); animation: alt-plate var(--dur-arrive) linear var(--alt-rise-delay, 0ms) both;
867
+ }
868
+ [data-loading="breath"] .alt-await[data-phase="reveal"][data-rest="raised"] > .alt-await-imprint { animation: alt-await-fill var(--dur-travel-3) linear var(--alt-rise-delay, 0ms) forwards; }
869
+ [data-loading="breath"] .alt-reveal[data-reveal] { animation: alt-reveal-past calc(var(--dur-travel-3) * 2) linear var(--alt-rise-delay, 0ms) backwards; }
870
+ /* A chart's marks still fade in place (Charts: "fade, don't rise"), under breath too. */
871
+ [data-loading="breath"] .alt-await.alt-chart-await[data-phase="reveal"] > .alt-await-face { animation: alt-emerge var(--dur-travel-3) var(--ease-rise) var(--alt-rise-delay, 0ms) backwards; }
872
+ [data-loading="breath"] .alt-await.alt-chart-await[data-phase="reveal"] > .alt-await-imprint { animation: alt-await-fill var(--dur-travel-3) linear var(--alt-rise-delay, 0ms) forwards; }
873
+ [data-loading="breath"] .alt-reveal[data-reveal] > .alt-reveal-inner { animation: alt-fill-ride calc(var(--dur-travel-3) * 2) linear var(--alt-rise-delay, 0ms) backwards; }
874
+ @keyframes alt-fill-ride {
875
+ 0% { opacity: 0; transform: translateY(calc(3px * var(--alt-travel))); animation-timing-function: var(--ease-rise); }
876
+ 50% { opacity: 1; transform: translateY(calc(-1.5px * var(--alt-travel))); animation-timing-function: var(--ease-decay); }
877
+ 100% { transform: none; }
878
+ }
879
+ @keyframes alt-fill-past {
880
+ 0% { background-color: var(--surface-well); box-shadow: var(--well); animation-timing-function: var(--ease-rise); }
881
+ 50% { opacity: 1; background-color: var(--surface-raised); box-shadow: var(--elev-1); animation-timing-function: var(--ease-decay); }
882
+ 100% { opacity: 0; background-color: var(--surface-ground); box-shadow: var(--elev-0); }
883
+ }
884
+ @keyframes alt-reveal-past {
885
+ 0% { background-color: var(--surface-well); box-shadow: var(--well); animation-timing-function: var(--ease-rise); }
886
+ 50% { background-color: var(--surface-raised); box-shadow: var(--elev-1); animation-timing-function: var(--ease-decay); }
887
+ 100% { background-color: transparent; box-shadow: var(--elev-0); }
888
+ }
889
+ @keyframes alt-arrive {
890
+ 0% { opacity: 0; transform: translateY(calc(8px * var(--alt-travel))); animation-timing-function: var(--ease-rise); }
891
+ 20% { opacity: 1; transform: translateY(calc(-3px * var(--alt-travel))); animation-timing-function: var(--ease-decay); }
892
+ 100% { transform: none; }
893
+ }
894
+ @keyframes alt-plate { 0% { opacity: 0; } 20% { opacity: 1; animation-timing-function: var(--ease-decay); } 100% { opacity: 0; } }
895
+ /* Reduced motion and forced colours: the breath stops, as a pending write's does; the imprint holds still at well depth. */
896
+ @media (prefers-reduced-motion: reduce) { [data-loading="breath"] :is(.alt-skeleton, [data-calm] .alt-skeleton, .alt-skeleton[data-calm]) { animation: none; } }
897
+ [data-motion="reduced"] :is(.alt-skeleton, [data-calm] .alt-skeleton, .alt-skeleton[data-calm]) { animation: none; }
898
+ @media (forced-colors: active) { [data-loading="breath"] :is(.alt-skeleton, [data-calm] .alt-skeleton, .alt-skeleton[data-calm]) { animation: none; } }
778
899
  .alt-avatar-imprint { display: inline-block; vertical-align: middle; }
779
900
  .alt-badge-imprint { display: inline-block; vertical-align: middle; width: 64px; height: 22px; }
780
901
  .alt-badge-imprint[data-variant="count"] { width: 22px; border-radius: var(--radius-full); }
@@ -820,8 +941,38 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
820
941
  flex: none; width: 24px; height: 24px; margin: -2px -6px 0 0; display: grid; place-items: center; padding: 0; border: 0; border-radius: var(--radius-xs);
821
942
  background: none; color: var(--ink-muted); cursor: pointer; transition: background-color var(--dur-travel-1), color var(--dur-travel-1);
822
943
  }
823
- .alt-alert-close:hover { background: color-mix(in oklab, var(--ink) 8%, transparent); color: var(--ink); }
944
+ @media (hover: hover) { .alt-alert-close:hover { background: color-mix(in oklab, var(--ink) 8%, transparent); color: var(--ink); } }
824
945
  .alt-alert-close:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
946
+ .alt-alert-close[data-labelled] { width: auto; height: auto; padding: 4px 8px; font: 500 13px/18px var(--font-sans); white-space: nowrap; align-self: center; }
947
+ /* A page banner: the same sheet, laid edge to edge across the top of the app or page, so it has no side rounding and
948
+ no side edges, only its bottom edge and contact shadow. It informs (flat, ground) or asks for action (raised), as
949
+ any Alert does. Sticky, it stays at the top while the page scrolls. Its text wraps; its padding follows the page's. */
950
+ .alt-banner[data-sticky] { position: sticky; top: 0; z-index: 30; }
951
+ .alt-alert[data-placement="page"] { border-radius: 0; box-shadow: 0 1px 0 0 color-mix(in oklab, var(--a-fg) 22%, transparent), 0 1.5px 3px -0.5px var(--alt-cast); }
952
+ .alt-alert[data-placement="page"][data-variant="raised"] { box-shadow: var(--elev-1); }
953
+ .alt-alert[data-placement="page"] .alt-alert-inner { flex-wrap: wrap; padding: var(--space-3) var(--alt-page-pad, var(--space-4)); }
954
+ .alt-alert[data-placement="page"] .alt-alert-body { min-width: min(100%, 240px); }
955
+ .alt-alert-stack[data-placement="page"] .alt-alert-sheet { border-radius: 0; left: 0; right: 0; }
956
+
957
+ /* ── RemovalReceipt. Left in place, then closed: the result shows in the item's slot, at its height, on the ground
958
+ (a trace of what happened, not a live surface). It lifts off as an Alert does, and only then does the gap glide shut. ── */
959
+ .alt-receipt-item { display: contents; }
960
+ /* The receipt lays itself out, whatever the item's own layout was (a row stacked on a phone included); at the item's
961
+ height, its line sits in the middle of the slot. */
962
+ .alt-receipt[data-removed] { box-sizing: border-box; display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; align-content: center; justify-content: flex-start; gap: var(--space-2) var(--space-3); }
963
+ .alt-receipt-note:empty { display: none; }
964
+ .alt-receipt-note { flex: 1 1 12rem; min-width: 0; display: flex; align-items: center; gap: var(--space-2); color: var(--ink-muted); font: 400 14px/20px var(--font-sans);
965
+ animation: alt-receipt-in var(--dur-travel-2) ease-out both; }
966
+ .alt-receipt-icon { display: inline-flex; flex: none; }
967
+ .alt-receipt[data-tone="critical"] .alt-receipt-note { color: var(--ink); }
968
+ .alt-receipt[data-tone="critical"] .alt-receipt-icon { color: var(--critical); }
969
+ .alt-receipt-action { display: flex; flex-wrap: wrap; gap: var(--space-2); animation: alt-receipt-in var(--dur-travel-2) ease-out both; }
970
+ @keyframes alt-receipt-in { from { opacity: 0; } }
971
+ /* Like every pending surface, it only shows as pending after dur-grace, so a fast write never flickers. */
972
+ .alt-receipt[data-status="pending"] { background: var(--surface-lifted); border-radius: var(--radius-md); transition: background-color var(--dur-travel-1) var(--ease-rise) var(--dur-grace); animation-fill-mode: forwards; }
973
+ .alt-receipt[data-leaving] { pointer-events: none; }
974
+ .alt-receipt[data-leaving] > .alt-receipt-note, .alt-receipt[data-leaving] > .alt-receipt-action { animation: alt-lifted-off calc(var(--dur-travel-3) * 0.7) var(--ease-sink) both; }
975
+ @media (forced-colors: active) { .alt-receipt[data-removed] { outline: 1px solid CanvasText; outline-offset: -1px; } }
825
976
 
826
977
  .alt-alert-stack { position: relative; display: grid; gap: var(--space-2); }
827
978
  .alt-alert-stack[data-sheets="1"] { --sheets: 1; } .alt-alert-stack[data-sheets="2"] { --sheets: 2; }
@@ -843,11 +994,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
843
994
  background: var(--surface-floating); color: var(--ink); box-shadow: var(--elev-2); font: 600 12px/1 var(--font-sans); cursor: pointer;
844
995
  transition: box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-2) var(--ease-settle);
845
996
  }
846
- .alt-alert-more:hover { box-shadow: var(--elev-3); transform: translateY(calc(-1px * var(--alt-travel))); }
997
+ @media (hover: hover) { .alt-alert-more:hover { box-shadow: var(--elev-3); transform: translateY(calc(-1px * var(--alt-travel))); } }
847
998
  .alt-alert-more:active { box-shadow: var(--elev-1); transform: none; transition-duration: var(--dur-press); }
848
999
  .alt-alert-more:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
849
1000
  .alt-alert-stack[data-expanded] .alt-alert-more { position: static; justify-self: end; height: 26px; background: none; color: var(--ink-muted); box-shadow: none; transform: none; }
850
- .alt-alert-stack[data-expanded] .alt-alert-more:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 6%, transparent); }
1001
+ @media (hover: hover) { .alt-alert-stack[data-expanded] .alt-alert-more:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 6%, transparent); } }
851
1002
  .alt-alert-stack-item { animation: alt-fan var(--dur-travel-3) var(--ease-settle) calc(var(--i) * 30ms) backwards; }
852
1003
  .alt-alert-stack-item:first-child { animation: none; }
853
1004
  @keyframes alt-fan { from { opacity: 0; transform: translateY(calc(var(--i) * -12px * var(--alt-travel))) scale(calc(1 - 0.02 * var(--alt-travel))); } }
@@ -867,8 +1018,8 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
867
1018
  .alt-acc-item[data-state="open"] { background: var(--surface-raised); box-shadow: var(--elev-2); margin: var(--space-2) 0; }
868
1019
  .alt-acc-item[data-state="open"]:first-child { margin-top: 0; }
869
1020
  .alt-acc-item[data-state="open"]:last-child { margin-bottom: 0; }
870
- .alt-acc-item[data-state="open"]::before, .alt-acc-item[data-state="open"] + .alt-acc-item::before,
871
- .alt-acc-item:has(> .alt-acc-heading .alt-acc-trigger:hover)::before, .alt-acc-item:has(> .alt-acc-heading .alt-acc-trigger:hover) + .alt-acc-item::before { opacity: 0; }
1021
+ .alt-acc-item[data-state="open"]::before, .alt-acc-item[data-state="open"] + .alt-acc-item::before { opacity: 0; }
1022
+ @media (hover: hover) { .alt-acc-item:has(> .alt-acc-heading .alt-acc-trigger:hover)::before, .alt-acc-item:has(> .alt-acc-heading .alt-acc-trigger:hover) + .alt-acc-item::before { opacity: 0; } }
872
1023
  .alt-acc-heading { margin: 0; }
873
1024
  .alt-acc-trigger {
874
1025
  display: flex; width: 100%; align-items: center; justify-content: space-between; gap: var(--space-3);
@@ -876,11 +1027,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
876
1027
  font: 500 15px/22px var(--font-sans); text-align: left; cursor: pointer;
877
1028
  transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise);
878
1029
  }
879
- .alt-acc-item[data-state="closed"] .alt-acc-trigger:hover {
1030
+ @media (hover: hover) { .alt-acc-item[data-state="closed"] .alt-acc-trigger:hover {
880
1031
  background: color-mix(in oklab, var(--surface-raised) 55%, transparent); box-shadow: 0 0 0 1px color-mix(in oklab, var(--ink) 5%, transparent), 0 1px 2px var(--alt-cast);
881
- }
1032
+ } }
882
1033
  .alt-acc-chevron { flex: none; color: var(--ink-muted); transition: transform var(--dur-travel-3) var(--ease-settle); }
883
- .alt-acc-trigger:hover .alt-acc-chevron { transform: translateY(calc(2px * var(--alt-travel))); }
1034
+ @media (hover: hover) { .alt-acc-trigger:hover .alt-acc-chevron { transform: translateY(calc(2px * var(--alt-travel))); } }
884
1035
  .alt-acc-item[data-state="open"] .alt-acc-chevron { transform: rotate(180deg); }
885
1036
  .alt-acc-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--dur-travel-3) var(--ease-glide); }
886
1037
  .alt-acc-item[data-state="open"] .alt-acc-panel { grid-template-rows: 1fr; }
@@ -918,15 +1069,19 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
918
1069
  .alt-tip {
919
1070
  left: 50%; bottom: calc(100% + 8px); translate: -50% 0; transform-origin: 50% 100%;
920
1071
  padding: 5px 10px; border-radius: var(--radius-sm); background: var(--surface-floating); box-shadow: var(--elev-3);
921
- font: 500 12.5px/17px var(--font-sans); white-space: nowrap; pointer-events: none;
1072
+ font: 500 12.5px/17px var(--font-sans); width: max-content; max-width: min(320px, calc(100vw - 16px)); pointer-events: none;
922
1073
  }
923
1074
  .alt-tip[data-side="bottom"] { bottom: auto; top: calc(100% + 8px); transform-origin: 50% 0; }
924
1075
  .alt-tip[data-state="closed"] { transform: translateY(calc(4px * var(--alt-travel))) scale(calc(1 - 0.04 * var(--alt-travel))); }
925
1076
  .alt-tip[data-side="bottom"][data-state="closed"] { transform: translateY(calc(-4px * var(--alt-travel))) scale(calc(1 - 0.04 * var(--alt-travel))); }
1077
+ .alt-tip[data-side="right"] { left: calc(100% + 8px); bottom: auto; top: 50%; translate: 0 -50%; transform-origin: 0 50%; }
1078
+ .alt-tip[data-side="right"][data-state="closed"] { transform: translateX(calc(-4px * var(--alt-travel))) scale(calc(1 - 0.04 * var(--alt-travel))); }
926
1079
  .alt-tip[data-warm] { transition-duration: var(--dur-travel-1); }
927
1080
 
1081
+ /* On screen at any width (#248): a panel is capped at the viewport's width, and useFitFloating shifts it sideways to
1082
+ stay 8px clear of the edges when it opens. */
928
1083
  .alt-pop {
929
- top: calc(100% + 8px); left: 0; transform-origin: 0 0; min-width: 220px; padding: var(--space-4);
1084
+ top: calc(100% + 8px); left: 0; transform-origin: 0 0; min-width: min(220px, calc(100vw - 16px)); max-width: calc(100vw - 16px); box-sizing: border-box; padding: var(--space-4);
930
1085
  border-radius: var(--radius-lg); background: var(--surface-floating); box-shadow: var(--elev-3); outline: none;
931
1086
  }
932
1087
  .alt-pop[data-align="end"] { left: auto; right: 0; transform-origin: 100% 0; }
@@ -936,14 +1091,14 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
936
1091
  .alt-pop[data-state="closed"] { transform: translateY(calc(-6px * var(--alt-travel))) scale(calc(1 - 0.04 * var(--alt-travel))); }
937
1092
  .alt-pop[data-side="top"][data-state="closed"] { transform: translateY(calc(6px * var(--alt-travel))) scale(calc(1 - 0.04 * var(--alt-travel))); }
938
1093
 
939
- .alt-menu { padding: var(--space-1); min-width: 228px; }
1094
+ .alt-menu { padding: var(--space-1); min-width: min(228px, calc(100vw - 16px)); }
940
1095
  .alt-menu-hl {
941
1096
  position: absolute; top: 0; left: var(--space-1); right: var(--space-1); border-radius: var(--radius-sm); background: var(--accent-soft);
942
1097
  pointer-events: none; transition: transform var(--dur-travel-2) var(--ease-settle), height var(--dur-travel-2) var(--ease-settle), opacity var(--dur-travel-1), background-color var(--dur-travel-1);
943
1098
  }
944
1099
  .alt-menu-hl[data-tone="critical"] { background: var(--critical-soft); }
945
1100
  .alt-menu-item, .alt-menu-label {
946
- position: relative; display: flex; align-items: center; gap: 10px; min-height: 34px; padding: 0 10px; border-radius: var(--radius-sm);
1101
+ position: relative; display: flex; align-items: center; gap: 10px; min-height: calc(var(--size-control-md) - 2px); padding: 0 10px; border-radius: var(--radius-sm);
947
1102
  font: 500 14px/20px var(--font-sans); color: var(--ink); cursor: pointer; user-select: none;
948
1103
  opacity: 0; transform: translateY(calc(-3px * var(--alt-travel)));
949
1104
  transition: opacity var(--dur-travel-2) var(--ease-rise), transform var(--dur-travel-2) var(--ease-rise);
@@ -959,14 +1114,15 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
959
1114
  .alt-menu-sep { height: 1px; margin: var(--space-1) 6px; background: var(--line); }
960
1115
 
961
1116
  /* ── Dialog: level 5. The page recedes, the scrim veils it, the dialog rises with its shadow growing. ── */
962
- .alt-dialog-root { position: fixed; inset: 0; z-index: var(--z-modal); display: grid; place-items: center; padding: var(--space-6); }
1117
+ /* The modal roots cover the visible viewport (#251), not the largest one, so a footer is never under the keyboard. */
1118
+ .alt-dialog-root { position: fixed; inset: 0; top: var(--alt-vv-top, 0px); bottom: auto; height: var(--alt-vvh, 100dvh); z-index: var(--z-modal); display: grid; place-items: center; padding: var(--alt-inset-page); padding: calc(var(--alt-inset-page) + env(safe-area-inset-top, 0px)) calc(var(--alt-inset-page) + env(safe-area-inset-right, 0px)) calc(var(--alt-inset-page) + env(safe-area-inset-bottom, 0px)) calc(var(--alt-inset-page) + env(safe-area-inset-left, 0px)); }
963
1119
  .alt-scrim {
964
- position: absolute; inset: 0; background: var(--scrim); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
1120
+ position: absolute; inset: 0; background: var(--scrim); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); /* tells-ignore: CM2 the scrim recedes the page behind a dialog (Elevation), not decoration */
965
1121
  transition: opacity var(--dur-travel-5) var(--ease-rise);
966
1122
  }
967
1123
  .alt-dialog {
968
- position: relative; width: min(100%, 460px); max-height: calc(100vh - 48px); overflow: auto; outline: none;
969
- padding: var(--space-6); border-radius: var(--radius-xl); background: var(--surface-modal); box-shadow: var(--elev-5); color: var(--ink);
1124
+ position: relative; width: min(100%, 460px); max-height: calc(100vh - 48px); max-height: calc(var(--alt-vvh, 100dvh) - 2 * var(--alt-inset-page)); overflow: auto; outline: none;
1125
+ padding: var(--alt-inset-page); border-radius: var(--radius-xl); background: var(--surface-modal); box-shadow: var(--elev-5); color: var(--ink);
970
1126
  transition: opacity var(--dur-travel-5) var(--ease-rise), transform var(--dur-travel-5) var(--ease-rise), box-shadow var(--dur-travel-5) var(--ease-rise);
971
1127
  }
972
1128
  .alt-dialog[data-size="sm"] { width: min(100%, 380px); }
@@ -987,10 +1143,16 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
987
1143
  .alt-dialog-close { position: absolute; top: var(--space-4); right: var(--space-4); color: var(--ink-muted); }
988
1144
 
989
1145
  /* ── Toast: the stack is a depth stack. Newest in front at level 4; older ones sink behind it. ── */
1146
+ /* Safe areas (#252): every layer fixed to the screen's edge adds env(safe-area-inset-*) to its offset, so in an
1147
+ edge-to-edge page (viewport-fit=cover) it clears the notch, the rounded corners and the home indicator. Without
1148
+ viewport-fit=cover env() is 0 and nothing moves. The mode frame stays at the physical edge: it frames the screen. */
990
1149
  .alt-toaster {
991
- position: fixed; z-index: var(--z-toast); right: var(--space-6); bottom: var(--space-6); width: min(360px, calc(100vw - 32px));
1150
+ position: fixed; z-index: var(--z-toast); right: calc(var(--space-6) + env(safe-area-inset-right, 0px)); bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px)); width: min(360px, calc(100vw - 32px - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px)));
992
1151
  transition: height var(--dur-travel-4) var(--ease-rise);
993
1152
  }
1153
+ /* On touch (#253): a tap opens the stack and holds it (timers paused) until a tap outside; a horizontal swipe dismisses
1154
+ a toast, so the toast lets vertical scrolling through. */
1155
+ .alt-toast { touch-action: pan-y; }
994
1156
  .alt-toast {
995
1157
  position: absolute; right: 0; bottom: 0; width: 100%; display: flex; align-items: flex-start; gap: var(--space-3);
996
1158
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4); border-radius: var(--radius-lg);
@@ -1011,7 +1173,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1011
1173
  display: grid; place-items: center; width: 24px; height: 24px; margin: -2px -2px 0 0; padding: 0; flex: none; border: 0; border-radius: var(--radius-xs);
1012
1174
  background: none; color: var(--ink-muted); cursor: pointer; transition: background-color var(--dur-travel-1), color var(--dur-travel-1);
1013
1175
  }
1014
- .alt-toast-close:hover { background: var(--surface-well); color: var(--ink); }
1176
+ @media (hover: hover) { .alt-toast-close:hover { background: var(--surface-well); color: var(--ink); } }
1015
1177
 
1016
1178
 
1017
1179
  /* ════════════════════ Depth signals: what height can tell you ════════════════════ */
@@ -1075,7 +1237,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1075
1237
  /* ── HoldButton: holding pushes it through the ground; the fuse along the bottom shows how far. ── */
1076
1238
  .alt-hold { overflow: visible; }
1077
1239
  .alt-hold .alt-btn-label { position: static; }
1078
- .alt-hold-text { position: relative; }
1240
+ /* All of its labels share one grid cell and only the current one is visible, so the button keeps the width of its
1241
+ longest label: holding and completing never change its size or move what's beside it. */
1242
+ .alt-hold-text { position: relative; display: inline-grid; justify-items: center; }
1243
+ .alt-hold-label { grid-area: 1 / 1; display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
1244
+ .alt-hold-label:not([data-shown]) { visibility: hidden; }
1079
1245
  .alt-hold-fill {
1080
1246
  position: absolute; left: 8px; right: 8px; bottom: 3px; height: 2px; border-radius: var(--radius-full); background: currentColor; opacity: 0.9;
1081
1247
  transform: scaleX(0); transform-origin: left; transition: transform var(--dur-travel-2) var(--ease-sink);
@@ -1087,13 +1253,1366 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1087
1253
  .alt-hold[data-hold="holding"] .alt-hold-fill { transform: scaleX(1); transition: transform var(--alt-hold-dur) linear; }
1088
1254
  .alt-hold[data-hold="done"]:not([data-final]) { animation: alt-land-a calc(var(--dur-travel-3) * 1.6) var(--ease-settle); }
1089
1255
  /* A completed hold is final by default: the action is now part of the record, so the button lies flat and can't be pressed again. */
1090
- .alt-btn.alt-hold[data-final], .alt-btn.alt-hold[data-final]:hover, .alt-btn.alt-hold[data-final]:active, .alt-btn.alt-hold[data-final]:focus-visible {
1256
+ .alt-btn.alt-hold[data-final], .alt-btn.alt-hold[data-final]:active, .alt-btn.alt-hold[data-final]:focus-visible {
1091
1257
  background: var(--surface-well); background-image: none; color: var(--ink-muted); box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); transform: none; cursor: default;
1092
1258
  transition: box-shadow var(--dur-travel-2) var(--ease-settle), transform var(--dur-travel-2) var(--ease-settle), background-color var(--dur-travel-2), color var(--dur-travel-2);
1093
1259
  }
1260
+ @media (hover: hover) { .alt-btn.alt-hold[data-final]:hover {
1261
+ background: var(--surface-well); background-image: none; color: var(--ink-muted); box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); transform: none; cursor: default;
1262
+ transition: box-shadow var(--dur-travel-2) var(--ease-settle), transform var(--dur-travel-2) var(--ease-settle), background-color var(--dur-travel-2), color var(--dur-travel-2);
1263
+ } }
1094
1264
 
1095
1265
  .alt-hold[data-final] .alt-hold-fill { opacity: 0; }
1096
1266
 
1267
+ /* ── Separator: a divider is part of the page, so it's 1px of line on the ground: no shadow, no wall, never
1268
+ line-control (that's for the edges of controls). A vertical one stretches to its row. A labelled one ("or") is the
1269
+ word in ink-muted between two lines; its lines are drawn, so only the word is read. ── */
1270
+ .alt-sep { flex: none; margin: 0; border: 0; background: var(--line); }
1271
+ .alt-sep[data-orientation="horizontal"] { width: 100%; height: 1px; }
1272
+ .alt-sep[data-orientation="vertical"] { display: inline-block; width: 1px; min-height: 1em; align-self: stretch; vertical-align: middle; }
1273
+ /* A card's header has no bottom padding (its content brings the gap), so a separator under it brings the same gap. */
1274
+ .alt-card-header + .alt-sep { margin-top: var(--space-4); }
1275
+ .alt-sep-labelled, .alt-sep-labelled[data-orientation] { display: flex; align-items: center; gap: var(--space-3); width: auto; height: auto; background: none; font: 500 12.5px/17px var(--font-sans); letter-spacing: 0.005em; color: var(--ink-muted); }
1276
+ .alt-sep-labelled[data-orientation="vertical"] { flex-direction: column; min-height: 4em; }
1277
+ .alt-sep-labelled::before, .alt-sep-labelled::after { content: ""; flex: 1; min-width: 1px; min-height: 1px; background: var(--line); }
1278
+ @media (prefers-contrast: more) { .alt-sep:not(.alt-sep-labelled), .alt-sep-labelled::before, .alt-sep-labelled::after { background: var(--line-control); } }
1279
+ [data-contrast="more"] .alt-sep:not(.alt-sep-labelled), [data-contrast="more"] .alt-sep-labelled::before, [data-contrast="more"] .alt-sep-labelled::after { background: var(--line-control); }
1280
+ @media (forced-colors: active) { .alt-sep:not(.alt-sep-labelled), .alt-sep-labelled::before, .alt-sep-labelled::after { background: CanvasText; forced-color-adjust: none; } }
1281
+
1282
+ /* ── Link: navigation is text in the record, so it never lifts: lifting a word would break its line. It's accent-ink
1283
+ with an underline (never colour alone); hover thickens the underline, focus is the 2px accent-ink ring. An external
1284
+ link ends with an icon and says it opens a new tab. ── */
1285
+ .alt-link {
1286
+ color: var(--accent-ink); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.2em;
1287
+ border-radius: var(--radius-xs); cursor: pointer; transition: text-decoration-thickness var(--dur-travel-1) var(--ease-rise);
1288
+ }
1289
+ @media (hover: hover) { .alt-link:hover { text-decoration-thickness: 2px; } }
1290
+ .alt-link:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1291
+ .alt-link[data-visited]:visited { text-decoration-color: var(--ink-muted); }
1292
+ .alt-link-tail { white-space: nowrap; }
1293
+ .alt-link-ext { display: inline-block; margin-left: 0.2em; vertical-align: -0.1em; }
1294
+ @media (prefers-contrast: more) { .alt-link { text-decoration-thickness: 2px; } }
1295
+ [data-contrast="more"] .alt-link { text-decoration-thickness: 2px; }
1296
+ @media (forced-colors: active) { .alt-link { color: LinkText; } .alt-link:focus-visible { outline-color: Highlight; } }
1297
+
1298
+ /* ── FormattedNumber: a number is the record, so it lies flush in its sentence or cell, in tabular figures. A changed
1299
+ value cross-fades (the old figure fading out over the new) and takes a ground-level tint that decays over dur-settle:
1300
+ the afterglow without the lift, because a word in a line can't rise. It never counts. ── */
1301
+ .alt-num { display: inline-grid; padding: 0 3px; margin: 0 -3px; border-radius: var(--radius-xs); font-variant-numeric: tabular-nums; white-space: nowrap; }
1302
+ .alt-num-text { grid-area: 1 / 1; }
1303
+ .alt-num-text[data-in] { animation: alt-stat-in var(--dur-travel-2) var(--ease-glide) both; }
1304
+ .alt-num-text[data-out] { animation: alt-stat-out var(--dur-travel-2) var(--ease-glide) both; }
1305
+ .alt-num[data-settle]::before { animation-name: alt-chart-tint-a; }
1306
+ .alt-num[data-settle="b"]::before { animation-name: alt-chart-tint-b; }
1307
+ @media (prefers-reduced-motion: reduce) { .alt-num-text[data-in] { animation: none; } .alt-num-text[data-out] { display: none; } }
1308
+ [data-motion="reduced"] .alt-num-text[data-in] { animation: none; }
1309
+ [data-motion="reduced"] .alt-num-text[data-out] { display: none; }
1310
+ @media (forced-colors: active) { .alt-num[data-settle]::before { border: 1px solid Highlight; } }
1311
+
1312
+ /* ── RelativeTime: a timestamp is part of the record, so it's plain text on the ground. Ticking forward ("3 min" to
1313
+ "4 min") isn't a change to the record and gets nothing; a new moment (the record edited again) takes the same
1314
+ ground-level tint as a changed number. It's focusable for its Tooltip, so it carries the focus ring. ── */
1315
+ .alt-reltime { padding: 0 2px; border-radius: var(--radius-xs); font-variant-numeric: tabular-nums; }
1316
+ .alt-reltime:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 0; }
1317
+ .alt-reltime[data-settle]::before { animation-name: alt-chart-tint-a; }
1318
+ .alt-reltime[data-settle="b"]::before { animation-name: alt-chart-tint-b; }
1319
+ @media (forced-colors: active) { .alt-reltime[data-settle]::before { border: 1px solid Highlight; } .alt-reltime:focus-visible { outline-color: Highlight; } }
1320
+
1321
+ /* ── CopyButton: copying is a press that confirms in words. It's a Button (ghost by default); once copied it stays
1322
+ pressed to the ground for dur-linger, its icon cross-fades from copy to a check and its words to "Copied" (all its
1323
+ words share one cell, so its width never changes). No rise and no afterglow: nothing new arrived on the page. A
1324
+ failure says so in words, with the way to copy by hand announced. ── */
1325
+ .alt-copy-icons, .alt-copy-words { display: inline-grid; place-items: center; }
1326
+ .alt-copy-icons > svg, .alt-copy-words > span { grid-area: 1 / 1; transition: opacity var(--dur-travel-1) var(--ease-rise); }
1327
+ .alt-copy-icons > svg:not([data-shown]), .alt-copy-words > span:not([data-shown]) { opacity: 0; visibility: hidden; }
1328
+ .alt-copy-words > span { white-space: nowrap; }
1329
+ .alt-btn.alt-copy[data-copy="copied"], .alt-btn.alt-copy[data-copy="copied"]:focus-visible {
1330
+ box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); background-image: none; transform: translateY(var(--wall));
1331
+ }
1332
+ @media (hover: hover) { .alt-btn.alt-copy[data-copy="copied"]:hover {
1333
+ box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); background-image: none; transform: translateY(var(--wall));
1334
+ } }
1335
+ .alt-btn.alt-copy[data-variant="ghost"][data-copy="copied"] { background: transparent; }
1336
+ .alt-btn.alt-copy[data-copy="failed"] .alt-copy-icons { color: var(--critical); }
1337
+ @media (forced-colors: active) { .alt-btn.alt-copy:hover:not([data-copy="copied"]) { border: 1px solid ButtonText; } }
1338
+
1339
+ /* ── Toggle and ToggleGroup: a button that stays on. On is a choice, so it's built like the Switch, not like a held
1340
+ press (which would read as loading): the face is cut into a shallow well with accent-soft poured in and the label in
1341
+ accent-ink. Off is the ghost (ground) or secondary (level 1) slab it started as. Pointing at an on toggle previews
1342
+ off: the well lightens toward the raised face it will become. Sinking uses ease-sink, rising ease-rise.
1343
+ type="single" is segmented Tabs without panels: options in a well, one raised pill that glides (the same classes).
1344
+ type="multiple" is a row of Toggles. ── */
1345
+ .alt-btn.alt-toggle[aria-pressed="true"], .alt-btn.alt-toggle[aria-pressed="true"]:focus-visible, .alt-btn.alt-toggle[aria-pressed="true"]:active:not(:disabled) {
1346
+ --wall: 0px; background: var(--accent-soft); background-image: none; color: var(--accent-ink); box-shadow: var(--well); transform: none;
1347
+ transition: background-color var(--dur-travel-2) var(--ease-sink), box-shadow var(--dur-travel-2) var(--ease-sink), color var(--dur-travel-2) var(--ease-sink);
1348
+ }
1349
+ @media (hover: hover) { .alt-btn.alt-toggle[aria-pressed="true"]:hover:not(:disabled):not([data-loading]) {
1350
+ --wall: 0px; background: var(--accent-soft); background-image: none; color: var(--accent-ink); box-shadow: var(--well); transform: none;
1351
+ transition: background-color var(--dur-travel-2) var(--ease-sink), box-shadow var(--dur-travel-2) var(--ease-sink), color var(--dur-travel-2) var(--ease-sink);
1352
+ } }
1353
+ @media (hover: hover) { .alt-btn.alt-toggle[aria-pressed="true"]:hover:not(:disabled):not([data-loading]) { background: color-mix(in oklab, var(--accent-soft) 55%, var(--surface-raised)); } }
1354
+ .alt-btn.alt-toggle[aria-pressed="true"]:disabled { background: var(--surface-well); color: var(--ink-faint); }
1355
+ .alt-tgroup[data-type="multiple"] { display: inline-flex; align-items: center; gap: 2px; }
1356
+ .alt-tgroup[data-orientation="vertical"] { flex-direction: column; align-items: stretch; }
1357
+ .alt-tgroup[data-type="single"] .alt-tabs-indicator { transition: none; }
1358
+ .alt-tgroup[data-type="single"] .alt-tabs-indicator[data-ready] { transition: transform var(--dur-travel-3) var(--ease-glide), top var(--dur-travel-3) var(--ease-glide), width var(--dur-travel-3) var(--ease-glide), height var(--dur-travel-3) var(--ease-glide), opacity var(--dur-travel-1); }
1359
+ @media (prefers-reduced-motion: reduce) { .alt-tgroup[data-type="single"] .alt-tabs-indicator[data-ready] { transition: opacity var(--dur-travel-1); } }
1360
+ [data-motion="reduced"] .alt-tgroup[data-type="single"] .alt-tabs-indicator[data-ready] { transition: opacity var(--dur-travel-1); }
1361
+ .alt-tgroup .alt-tabs-trigger:disabled { color: var(--ink-faint); cursor: not-allowed; }
1362
+ @media (forced-colors: active) {
1363
+ .alt-btn.alt-toggle[aria-pressed="true"] { border: 1px dashed CanvasText; background: Highlight; color: HighlightText; forced-color-adjust: none; }
1364
+ .alt-btn.alt-toggle[data-variant="secondary"][aria-pressed="false"] { border: 1px solid CanvasText; }
1365
+ }
1366
+
1367
+ /* ── Toolbar: the bar lies on the ground (a line rim when bordered); its tools are ghost buttons that lift one level
1368
+ when pointed at, and latched tools are Toggles. Groups are parted by decorative separators. With overflow="menu" the
1369
+ tools that don't fit are hidden from the end and offered in a "More tools" menu; they don't animate as they go. ── */
1370
+ .alt-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); min-width: 0; }
1371
+ .alt-toolbar[data-overflow="menu"] { flex-wrap: nowrap; }
1372
+ .alt-toolbar[data-orientation="vertical"] { flex-direction: column; align-items: stretch; }
1373
+ .alt-toolbar[data-bordered] { padding: var(--space-1); border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-ground); }
1374
+ .alt-toolbar > [data-overflowed] { display: none; }
1375
+ .alt-toolbar-group { display: inline-flex; align-items: center; gap: 2px; }
1376
+ .alt-toolbar[data-orientation="vertical"] .alt-toolbar-group { flex-direction: column; align-items: stretch; }
1377
+ .alt-toolbar .alt-sep[data-orientation="vertical"] { align-self: center; height: 20px; min-height: 0; margin-inline: var(--space-1); }
1378
+ .alt-toolbar[data-orientation="vertical"] .alt-sep[data-orientation="vertical"] { width: auto; height: 1px; margin: var(--space-1) 0; }
1379
+ .alt-toolbar-more { flex: none; margin-left: auto; }
1380
+ @media (forced-colors: active) { .alt-toolbar-btn:hover:not([aria-pressed="true"]), .alt-toolbar-btn:focus-visible:not([aria-pressed="true"]) { border: 1px solid CanvasText; } }
1381
+
1382
+ /* ── SplitButton: one action with its alternatives. Two faces on one slab, sharing one wall: the action and a chevron,
1383
+ parted by a 1px notch (line-control). Each face lifts on its own when pointed at, because each does something
1384
+ different, and each has its own focus ring around its face and wall. A loading action is held pressed while the
1385
+ chevron stays usable. ── */
1386
+ .alt-split { display: inline-flex; align-items: stretch; isolation: isolate; }
1387
+ .alt-split .alt-pop-anchor { display: inline-flex; }
1388
+ .alt-split .alt-split-action { border-top-right-radius: 0; border-bottom-right-radius: 0; }
1389
+ .alt-split .alt-split-menu { border-top-left-radius: 0; border-bottom-left-radius: 0; width: 32px; }
1390
+ .alt-split .alt-split-menu[data-size="sm"] { width: 26px; }
1391
+ .alt-split .alt-split-action::after { border-top-right-radius: 0; border-bottom-right-radius: 0; }
1392
+ .alt-split .alt-split-menu::after { border-top-left-radius: 0; border-bottom-left-radius: 0; }
1393
+ .alt-split .alt-split-menu::before { content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 1px; background: var(--line-control); pointer-events: none; }
1394
+ .alt-split[data-variant="primary"] .alt-split-menu::before { background: color-mix(in oklab, var(--on-accent) 45%, var(--accent)); }
1395
+ .alt-split .alt-split-action:focus-visible, .alt-split .alt-split-menu:focus-visible { z-index: 1; }
1396
+ /* One slab, so it moves as one: pointing at either face lifts both, and pressing either presses both, so the faces
1397
+ never stand at two heights with a step between them. Which face you're on (and so what a click does) is said by a
1398
+ tint on that face alone, not by height. */
1399
+ @media (hover: hover) {
1400
+ .alt-split:has(:is(.alt-split-action, .alt-split-menu):hover:not(:disabled)) :is(.alt-split-action, .alt-split-menu):not(:disabled) {
1401
+ background: var(--alt-bg-up); background-image: var(--alt-sheen);
1402
+ box-shadow: 0 calc(var(--wall) + 1px) 0 0 var(--wall-c), var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel)));
1403
+ }
1404
+ .alt-split:has(:is(.alt-split-action, .alt-split-menu):hover:not(:disabled)) :is(.alt-split-action, .alt-split-menu):hover:not(:disabled) { background-color: color-mix(in oklab, var(--alt-bg-up), var(--alt-fg) 12%); }
1405
+ }
1406
+ .alt-split:has(:is(.alt-split-action, .alt-split-menu):focus-visible) :is(.alt-split-action, .alt-split-menu):not(:disabled) {
1407
+ background-image: var(--alt-sheen); box-shadow: 0 calc(var(--wall) + 1px) 0 0 var(--wall-c), var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel)));
1408
+ }
1409
+ .alt-split:has(:is(.alt-split-action, .alt-split-menu):active:not(:disabled)) :is(.alt-split-action, .alt-split-menu):not(:disabled) {
1410
+ box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); background-image: none; transform: translateY(var(--wall));
1411
+ transition-duration: var(--dur-press); transition-timing-function: ease-out;
1412
+ }
1413
+ @media (forced-colors: active) { .alt-split .alt-split-menu::before { background: CanvasText; } }
1414
+
1415
+ /* ── NumberInput: an opening that fills, like Input, with two small raised steppers in its end cap: you press them,
1416
+ so they're slabs (push up, fill down), level 1 with a 1px wall, lifting to 2 when pointed at, flush and faint at a
1417
+ limit. The unit sits beside the number in ink-muted. Editing isn't a change to the record, so nothing glows. ── */
1418
+ .alt-numfield { padding-right: 3px; }
1419
+ .alt-numfield input { font-variant-numeric: tabular-nums; }
1420
+ .alt-numfield-unit { font: 400 14px/20px var(--font-sans); }
1421
+ /* Side by side, 24×24 each (#261): stacked, two 15px steppers could never each reach the 24px minimum. Down first. */
1422
+ .alt-numfield-steps { display: flex; flex-direction: row-reverse; gap: 2px; flex: none; align-self: center; }
1423
+ .alt-numfield-step {
1424
+ display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: var(--radius-xs); cursor: pointer;
1425
+ background: var(--surface-raised); background-image: var(--alt-sheen); color: var(--ink-muted);
1426
+ box-shadow: 0 1px 0 0 var(--surface-trench), var(--elev-1);
1427
+ transition: box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-2) var(--ease-settle), color var(--dur-travel-1);
1428
+ }
1429
+ .alt-numfield[data-size="sm"] .alt-numfield-step { height: 24px; }
1430
+ @media (hover: hover) { .alt-numfield-step:hover:not(:disabled) { background-color: var(--surface-lifted); color: var(--ink); box-shadow: 0 2px 0 0 var(--surface-trench), var(--elev-2); transform: translateY(calc(-1px * var(--alt-travel))); } }
1431
+ .alt-numfield-step:active:not(:disabled) { box-shadow: 0 0 0 0 var(--surface-trench), var(--elev-0); background-image: none; transform: translateY(1px); transition-duration: var(--dur-press); }
1432
+ .alt-numfield-step:disabled { background: transparent; background-image: none; box-shadow: none; color: var(--ink-faint); cursor: default; }
1433
+ [data-contrast="more"] .alt-numfield-step:not(:disabled) { box-shadow: 0 1px 0 0 var(--ink), var(--elev-1); }
1434
+ @media (forced-colors: active) { .alt-numfield-step:not(:disabled) { border: 1px solid ButtonText; } }
1435
+
1436
+ /* ── InputOTP: a one-time code as a row of openings. One real input lies invisible over the row and drives it; each
1437
+ slot is built like an Input (the same well, wall and floor): empty, its back wall shows; the slot the next character
1438
+ goes into is held (floor lit, the depth approach would preview, the neutral rim when reached by keyboard) with a
1439
+ caret; once its character lands its floor rises flush. A pasted or autofilled code fills left to right, staggered
1440
+ 35ms a slot. A rejected code drops the whole row to a trench on critical-soft. ── */
1441
+ .alt-otp { position: relative; display: inline-flex; align-items: center; gap: clamp(4px, 2vw, var(--space-2)); max-width: 100%; min-width: 0; }
1442
+ .alt-otp-input {
1443
+ position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; outline: 0; opacity: 0;
1444
+ background: transparent; color: transparent; caret-color: transparent; font-size: 16px; cursor: text;
1445
+ }
1446
+ .alt-otp-input:disabled { cursor: not-allowed; }
1447
+ /* Narrow (#266): slots give way from 40px to 28px, and gaps tighten, so an eight-character code fits a 320px screen. */
1448
+ .alt-otp-group { display: inline-flex; gap: clamp(3px, 1.6vw, 6px); min-width: 0; }
1449
+ .alt-otp-slot { justify-content: center; flex: 0 1 40px; width: 40px; min-width: 28px; height: 48px; padding: 0; font: 600 20px/1 var(--font-mono); cursor: text; }
1450
+ .alt-otp-slot[data-filled]:not([data-invalid]):not([data-disabled]) { --w: 0px; --n: 0; background: var(--surface-raised); }
1451
+ .alt-otp-slot[data-active]:not([data-invalid]) { background: var(--surface-raised); --n: var(--alt-travel); }
1452
+ .alt-otp[data-key-focus] .alt-otp-slot[data-active] { border-color: var(--alt-rim-held); outline-color: var(--alt-rim-held); }
1453
+ .alt-otp[data-key-focus] .alt-otp-slot[data-active][data-invalid] { border-color: var(--critical); outline-color: var(--critical); }
1454
+ .alt-otp-char { transform: translateY(calc(var(--wd) * 0.5)); transition: transform var(--dur-travel-2) var(--ease-settle); }
1455
+ .alt-otp-caret { position: absolute; left: 50%; top: 50%; width: 1.5px; height: 22px; margin: -11px 0 0 -0.75px; background: var(--ink); animation: alt-otp-blink 1.1s steps(1) infinite; }
1456
+ @keyframes alt-otp-blink { 50% { opacity: 0; } }
1457
+ @media (prefers-reduced-motion: reduce) { .alt-otp-caret { animation: none; } }
1458
+ [data-motion="reduced"] .alt-otp-caret { animation: none; }
1459
+ .alt-otp[data-burst] .alt-otp-slot { transition-delay: calc(var(--i) * 35ms); }
1460
+ .alt-otp-sep { flex: none; width: 10px; height: 2px; border-radius: var(--radius-full); background: var(--line-control); }
1461
+ @media (forced-colors: active) { .alt-otp-slot[data-filled]:not([data-invalid]) { border-style: solid !important; } .alt-otp-caret { background: CanvasText; } }
1462
+
1463
+ /* ── TimeInput: a time of day typed by segment, built like DatePicker's field (the same segments and well). It's an
1464
+ opening that fills only when every segment holds a value: a partial time ("09:––") is still a hole. A time zone's
1465
+ short name sits after it in ink-muted, and is spoken with it. ── */
1466
+ .alt-time { width: fit-content; min-width: 132px; max-width: 100%; }
1467
+ .alt-time-icon { margin-left: -2px; }
1468
+ .alt-time-gap { display: inline-block; width: 0.35em; }
1469
+ .alt-time-zone { font: 400 13px/20px var(--font-sans); }
1470
+
1471
+ /* ── PasswordInput: an Input with a reveal Toggle in its end cap (sunk and filled while the password shows). Creating
1472
+ a password, its strength is a reading, so it's a Meter (a flush track with flat ink), never Progress's raised fill,
1473
+ with the word beside its label; each requirement is stamped as met, like a done Checklist item, muted with a check.
1474
+ Caps Lock is a caption with an icon. ── */
1475
+ .alt-pw { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
1476
+ .alt-pw .alt-input { padding-right: 4px; }
1477
+ .alt-pw-reveal.alt-btn[data-square][data-size="sm"] { width: var(--size-control-sm); height: var(--size-control-sm); color: var(--ink-muted); }
1478
+ .alt-pw-caps { display: flex; align-items: center; gap: 6px; margin: 0; font: 500 12.5px/17px var(--font-sans); color: var(--ink); }
1479
+ .alt-pw-caps svg { flex: none; color: var(--caution); }
1480
+ .alt-pw-strength .alt-meter-label { display: inline-flex; gap: 8px; }
1481
+ .alt-pw-word { font-weight: 600; color: var(--ink); }
1482
+ .alt-pw-reqs { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; font: 400 13px/18px var(--font-sans); color: var(--ink); }
1483
+ .alt-pw-reqs li { display: flex; align-items: center; gap: 8px; transition: color var(--dur-travel-1) var(--ease-rise); }
1484
+ .alt-pw-reqs li[data-met] { color: var(--ink-muted); }
1485
+ .alt-pw-stamp { display: grid; place-items: center; flex: none; width: 16px; height: 16px; border-radius: var(--radius-full); border: 1px solid var(--line-control); color: var(--ink-muted); }
1486
+ .alt-pw-reqs li[data-met] .alt-pw-stamp { border-color: transparent; background: var(--surface-well); box-shadow: var(--well); }
1487
+ @media (forced-colors: active) { .alt-pw-stamp { border-color: CanvasText; } .alt-pw-caps svg { color: CanvasText; } }
1488
+
1489
+ /* ── SecretField: two readings. A new secret is an opening, something to take away: a sunk well (its back wall
1490
+ showing, though it holds text) in mono, with a raised Copy beside it and a caution warning, until you confirm you've
1491
+ saved it; then its floor rises flush and it masks to its tail. A stored secret is settled: flush, masked, with a
1492
+ reveal Toggle (if allowed), Rotate and a held revoke. ── */
1493
+ .alt-secret { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
1494
+ .alt-secret-row { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
1495
+ .alt-secret-field { flex: 1; min-width: 0; cursor: default; }
1496
+ .alt-input.alt-secret-field[data-open] { --w: 4px; background: var(--surface-well); }
1497
+ .alt-input.alt-secret-field[data-filled] { border-color: var(--line); }
1498
+ .alt-secret-value { flex: 1; min-width: 0; font: 400 13.5px/20px var(--font-mono); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; animation: alt-emerge var(--dur-travel-2) var(--ease-rise); --alt-from-y: 0px; }
1499
+ .alt-input input.alt-secret-value { font: 400 13.5px/20px var(--font-mono); cursor: text; }
1500
+ .alt-secret-dots { letter-spacing: 0.1em; color: var(--ink-muted); }
1501
+ .alt-secret-warning { display: flex; align-items: flex-start; gap: 6px; margin: 0; font: 500 12.5px/17px var(--font-sans); color: var(--ink); }
1502
+ .alt-secret-warning svg { flex: none; margin-top: 1.5px; color: var(--caution); }
1503
+ .alt-secret-saved:focus { outline: none; }
1504
+ .alt-secret-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
1505
+ @media (forced-colors: active) { .alt-input.alt-secret-field[data-filled] { border-style: solid !important; } .alt-secret-warning svg { color: CanvasText; } }
1506
+
1507
+ /* ── CodeBlock: code shown to read and copy is the record, so it lies flush on the ground: a 1px line rim, mono type,
1508
+ no shadow. The header carries its name, a segmented switch between forms (npm, pnpm) and a ghost CopyButton.
1509
+ Highlighted lines are accent-soft bands behind the text; line numbers sit in an ink-faint gutter. Code still
1510
+ arriving has a dashed line-control rule down its left and says "Writing…". Syntax colours are yours (ink, ink-muted
1511
+ and accent-ink by default, never the data palette) and carry nothing on their own. ── */
1512
+ .alt-code { --alt-code-lh: 20px; margin: 0; min-width: 0; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface-ground); color: var(--ink); overflow: hidden; }
1513
+ .alt-code[data-streaming] { border-left: 2px dashed var(--line-control); }
1514
+ .alt-code-tabs { gap: 0; }
1515
+ .alt-code-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); min-height: 44px; padding: 6px 6px 6px var(--space-4); border-bottom: 1px solid var(--line); }
1516
+ .alt-code-head .alt-tabs-list { padding: 2px; }
1517
+ .alt-code-head .alt-tabs-trigger { height: 26px; padding: 0 var(--space-2); font-size: 13px; }
1518
+ .alt-code-title { font: 500 13px/18px var(--font-sans); color: var(--ink); }
1519
+ .alt-code-title[data-language] { font-family: var(--font-mono); color: var(--ink-muted); }
1520
+ .alt-code-tools { display: inline-flex; align-items: center; gap: var(--space-2); margin-left: auto; }
1521
+ .alt-code-writing { font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); }
1522
+ .alt-code-panel { border-radius: 0; animation-duration: var(--dur-travel-1); --alt-from-y: 0px; }
1523
+ /* Scrolling in the page chains: a region on the ground (CodeBlock, ScrollArea, Table, Conversation, Terminal,
1524
+ streaming reasoning) hands the wheel or swipe to the page once it reaches its edge, so it never traps you. Only layers
1525
+ above the page (the CommandPalette list, Sheet, a Select's list, a menu as a sheet) contain their overscroll, since
1526
+ the page under them shouldn't move. */
1527
+ .alt-code-scroll { overflow: auto; }
1528
+ .alt-code-scroll:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1529
+ .alt-code-grid { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); min-width: max-content; padding: var(--space-3) 0; }
1530
+ .alt-code-grid[data-numbers] { grid-template-columns: auto minmax(0, 1fr); }
1531
+ .alt-code-band { position: absolute; left: 0; right: 0; top: calc(var(--space-3) + var(--l) * var(--alt-code-lh)); height: var(--alt-code-lh); background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent-ink); }
1532
+ .alt-code-gutter { position: relative; display: flex; flex-direction: column; padding: 0 var(--space-3) 0 var(--space-4); text-align: right; font: 400 12.5px/var(--alt-code-lh) var(--font-mono); color: var(--ink-faint); user-select: none; }
1533
+ .alt-code-pre { position: relative; margin: 0; padding: 0 var(--space-4); font: 400 13px/var(--alt-code-lh) var(--font-mono); tab-size: 2; white-space: pre; }
1534
+ .alt-code-grid[data-numbers] .alt-code-pre { padding-left: 0; }
1535
+ .alt-code-pre code { font: inherit; }
1536
+ .alt-code .tok-comment, .alt-code .token.comment, .alt-code .hljs-comment { color: var(--ink-muted); font-style: italic; }
1537
+ .alt-code .tok-keyword, .alt-code .token.keyword, .alt-code .hljs-keyword { color: var(--accent-ink); }
1538
+ .alt-code .tok-string, .alt-code .token.string, .alt-code .hljs-string { color: var(--ink); }
1539
+ @media (prefers-contrast: more) { .alt-code { border-color: var(--line-control); } }
1540
+ [data-contrast="more"] .alt-code { border-color: var(--line-control); }
1541
+ @media (forced-colors: active) { .alt-code { border: 1px solid CanvasText; } .alt-code-band { background: Mark; forced-color-adjust: none; box-shadow: none; } }
1542
+
1543
+ /* ── ColorPicker: colours as choices, built like RadioGroup. Each swatch is a trench (line-control rim) the colour
1544
+ pours into; the chosen one carries the raised white mark, one mark that travels between swatches on ease-glide.
1545
+ Pointing at another swatch shows a ghost of the mark (feedforward). The hex field is an Input that fills only when
1546
+ it holds a whole colour. The full picker floats in a Popover: the area and the hue strip are wells, their handles
1547
+ raised. A contrast check is a Badge plate that says in words whether text on the colour passes. ── */
1548
+ .alt-color { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
1549
+ .alt-swatches { position: relative; display: flex; flex-wrap: wrap; gap: 10px; }
1550
+ .alt-swatch {
1551
+ position: relative; flex: none; width: 28px; height: 28px; padding: 0; border: 1px solid var(--line-control); border-radius: var(--radius-full);
1552
+ background: var(--surface-trench); box-shadow: var(--trench); cursor: pointer; -webkit-tap-highlight-color: transparent;
1553
+ }
1554
+ .alt-swatch-fill { position: absolute; inset: -1px; border-radius: inherit; background: var(--swatch); box-shadow: inset 0 3px 3px -2px var(--alt-cast-deep, rgb(0 0 0 / 0.35)), inset 0 0 0 1px rgb(0 0 0 / 0.12); }
1555
+ .alt-swatch:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1556
+ .alt-swatch::after {
1557
+ content: ""; position: absolute; left: 50%; top: 50%; width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: var(--radius-full);
1558
+ background: var(--surface-raised); box-shadow: var(--elev-1); opacity: 0; scale: 0.5; pointer-events: none;
1559
+ transition: opacity var(--dur-travel-1) var(--ease-sink), scale var(--dur-travel-2) var(--ease-settle);
1560
+ }
1561
+ @media (hover: hover) { .alt-swatch:hover:not([aria-checked="true"])::after { opacity: 0.5; scale: 0.85; } }
1562
+ .alt-swatch-mark {
1563
+ position: absolute; left: 0; top: 0; z-index: 1; width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: var(--radius-full); pointer-events: none;
1564
+ background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1);
1565
+ }
1566
+ .alt-swatch-mark[data-ready] { transition: transform var(--dur-travel-3) var(--ease-glide), opacity var(--dur-travel-1); }
1567
+ @media (prefers-reduced-motion: reduce) { .alt-swatch-mark[data-ready] { transition: opacity var(--dur-travel-1); } }
1568
+ [data-motion="reduced"] .alt-swatch-mark[data-ready] { transition: opacity var(--dur-travel-1); }
1569
+ .alt-color-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
1570
+ .alt-color-hex { width: 150px; }
1571
+ .alt-color-hex input { font-family: var(--font-mono); text-transform: lowercase; }
1572
+ .alt-input.alt-color-partial:has(input:not(:placeholder-shown)):not(:focus-within) { --w: 4px; background: var(--surface-well); }
1573
+ .alt-color-chip { display: block; width: 16px; height: 16px; border-radius: var(--radius-xs); box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.15); }
1574
+ .alt-color-checks { display: flex; flex-wrap: wrap; gap: var(--space-2); }
1575
+ .alt-color-custom { display: flex; flex-direction: column; gap: 12px; width: 240px; max-width: 100%; padding: var(--space-3); }
1576
+ .alt-color-area, .alt-color-hue { position: relative; touch-action: none; cursor: crosshair; box-shadow: var(--well); }
1577
+ .alt-color-area { height: 150px; border-radius: var(--radius-md); background: linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(var(--hue) 100% 50%)); }
1578
+ /* tells-ignore: CO1 the hue track is the whole spectrum, purple included */
1579
+ .alt-color-hue { height: 14px; border-radius: var(--radius-full); background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); }
1580
+ .alt-color-area:focus-visible, .alt-color-hue:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1581
+ .alt-color-handle { position: absolute; width: 16px; height: 16px; margin: -8px 0 0 -8px; border: 2px solid var(--surface-raised); border-radius: var(--radius-full); box-shadow: var(--elev-2); pointer-events: none; }
1582
+ [data-contrast="more"] .alt-swatch[aria-checked="true"] { outline: 2px solid var(--ink); outline-offset: 2px; }
1583
+ @media (forced-colors: active) {
1584
+ .alt-swatch-fill, .alt-color-area, .alt-color-hue, .alt-color-chip { forced-color-adjust: none; }
1585
+ .alt-swatch { border: 1px solid CanvasText; } .alt-swatch[aria-checked="true"] { border-width: 2px; } .alt-swatch-mark { border: 1px solid CanvasText; }
1586
+ }
1587
+
1588
+ /* ── Pagination: the page numbers sit in a trench (surface-well) and the current page is one raised thumb that
1589
+ travels to it on ease-glide, as RadioGroup's does. Pointing at another page shows a ghost of the thumb there. While
1590
+ the next page loads the thumb is held pressed. Previous and Next are ghost buttons, flush and faint at the ends. ── */
1591
+ .alt-pager { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); }
1592
+ .alt-pager-pages { display: inline-flex; align-items: center; gap: 2px; margin: 0; padding: 0; list-style: none; }
1593
+ .alt-pager-track { position: relative; display: inline-flex; align-items: center; gap: 2px; padding: var(--space-1); border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-well); box-shadow: var(--well); }
1594
+ .alt-pager-track li { display: flex; }
1595
+ .alt-pager-page {
1596
+ position: relative; z-index: 1; display: inline-grid; place-items: center; min-width: 30px; height: 28px; padding: 0 var(--space-2); border: 0; border-radius: calc(var(--radius-md) - var(--space-1));
1597
+ background: none; color: var(--ink-muted); font: 500 13px/18px var(--font-sans); font-variant-numeric: tabular-nums; text-decoration: none; cursor: pointer;
1598
+ transition: color var(--dur-travel-1) var(--ease-rise);
1599
+ }
1600
+ .alt-pager-page::after { content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: var(--surface-raised); box-shadow: var(--elev-1); opacity: 0; transition: opacity var(--dur-travel-1) var(--ease-sink); }
1601
+ @media (hover: hover) { .alt-pager-page:hover:not([aria-current])::after { opacity: 0.45; transition-timing-function: var(--ease-rise); } }
1602
+ .alt-pager-page[aria-current] { color: var(--ink); }
1603
+ @media (hover: hover) { .alt-pager-page:hover { color: var(--ink); } }
1604
+ .alt-pager-page[aria-current] { font-weight: 600; cursor: default; }
1605
+ .alt-pager-page:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1606
+ .alt-pager-thumb { position: absolute; top: var(--space-1); bottom: var(--space-1); left: 0; border-radius: calc(var(--radius-md) - var(--space-1)); background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1); pointer-events: none; }
1607
+ .alt-pager-thumb[data-ready] { transition: transform var(--dur-travel-1) var(--ease-glide), width var(--dur-travel-1) var(--ease-glide), box-shadow var(--dur-travel-1), opacity var(--dur-travel-1); }
1608
+ .alt-pager-thumb[data-loading] { box-shadow: var(--elev-0); background-image: none; }
1609
+ .alt-pager-gap { display: inline-grid; place-items: center; min-width: 24px; color: var(--ink-faint); font: 500 13px/18px var(--font-sans); }
1610
+ .alt-pager-step { gap: 4px; text-decoration: none; }
1611
+ .alt-pager-step[aria-disabled] { color: var(--ink-faint); cursor: default; pointer-events: none; }
1612
+ .alt-pager-summary { font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
1613
+ @media (max-width: 480px) { .alt-pager-step-text { display: none; } }
1614
+ @media (prefers-reduced-motion: reduce) { .alt-pager-thumb[data-ready] { transition: opacity var(--dur-travel-1); } }
1615
+ [data-motion="reduced"] .alt-pager-thumb[data-ready] { transition: opacity var(--dur-travel-1); }
1616
+ [data-contrast="more"] .alt-pager-thumb { box-shadow: 0 2px 0 0 var(--ink), var(--elev-1); }
1617
+ @media (forced-colors: active) { .alt-pager-track { border: 1px dashed CanvasText; } .alt-pager-thumb { border: 1px solid CanvasText; } }
1618
+
1619
+ /* ── PageHeader: the page's name is the record, so the block lies on the ground; only its actions are live, with one
1620
+ slot for the primary (level 2) and the rest secondary or ghost. Sticky, it rises one level (surface-raised, elev-1)
1621
+ when content passes under it, as ScrollArea's header does. ── */
1622
+ /* --alt-page-pad is the page's side padding (AppShell sets it): the header reaches through it, so a sticky header's
1623
+ rise spans the page edge to edge. */
1624
+ .alt-page-head { display: flex; flex-direction: column; gap: var(--space-3); margin-inline: calc(-1 * var(--alt-page-pad, 0px)); padding: var(--space-4) var(--alt-page-pad, 0px); background: var(--surface-ground); }
1625
+ .alt-page-head[data-sticky] { position: sticky; top: 0; z-index: 10; transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise); }
1626
+ .alt-page-head[data-under] { background: var(--surface-raised); box-shadow: var(--elev-1); }
1627
+ .alt-page-head-sentinel { display: block; height: 1px; margin-bottom: -1px; }
1628
+ .alt-page-head-crumbs .alt-crumbs ol { padding-top: 6px; padding-bottom: 0; }
1629
+ .alt-page-head-main { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--space-3) var(--space-5); }
1630
+ .alt-page-head-text { display: flex; flex-direction: column; gap: var(--space-1); min-width: min(100%, 240px); flex: 1; }
1631
+ .alt-page-head-title { margin: 0; font: 600 24px/30px var(--font-sans); letter-spacing: -0.015em; color: var(--ink); }
1632
+ .alt-page-head-desc { margin: 0; font: 400 14px/21px var(--font-sans); color: var(--ink-muted); max-width: 70ch; }
1633
+ .alt-page-head-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
1634
+ .alt-page-head-tabs .alt-tabs { gap: 0; }
1635
+ @media (forced-colors: active) { .alt-page-head[data-under] { border-bottom: 1px solid CanvasText; } }
1636
+
1637
+ /* ── AppShell and Sidebar: where you are is part of the record, so the frame lies on the ground: sidebar, top bar
1638
+ and page on surface-ground, divided by line. Only what acts is live: a destination lifts one level when pointed at,
1639
+ and the current one sits on a raised pill (segmented Tabs' build) that glides between destinations. Only a count
1640
+ floats. Collapsing to a rail changes width on ease-glide, not level. The top bar rises one level while content
1641
+ passes under it. On narrow screens the sidebar is a Sheet drawer. ── */
1642
+ .alt-shell { --alt-sidebar-w: 248px; --alt-rail-w: 56px; position: relative; display: flex; height: 100%; min-height: 0; background: var(--surface-ground); color: var(--ink); }
1643
+ .alt-skip { position: absolute; left: var(--space-2); top: var(--space-2); z-index: 50; padding: 6px 12px; border-radius: var(--radius-sm); background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--ink); font: 500 13px/18px var(--font-sans); transform: translateY(-200%); }
1644
+ .alt-skip:focus { transform: none; outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1645
+ .alt-sidebar { display: flex; flex-direction: column; flex: none; width: var(--alt-sidebar-w); min-height: 0; border-right: 1px solid var(--line); background: var(--surface-ground); overflow: hidden; transition: width var(--dur-travel-3) var(--ease-glide); }
1646
+ .alt-sidebar[data-rail] { width: var(--alt-rail-w); }
1647
+ .alt-sidebar-drawer .alt-sidebar { width: auto; border: 0; background: none; margin-inline: calc(-1 * var(--space-2)); }
1648
+ .alt-sidebar-header, .alt-sidebar-footer { flex: none; padding: var(--space-2); }
1649
+ .alt-sidebar-header { border-bottom: 1px solid var(--line); }
1650
+ .alt-sidebar-footer { border-top: 1px solid var(--line); }
1651
+ .alt-sidebar-content { position: relative; flex: 1; min-height: 0; overflow: auto; padding: var(--space-2); }
1652
+ .alt-sidebar-pill { position: absolute; left: 0; top: 0; border-radius: var(--radius-sm); background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1); pointer-events: none; }
1653
+ .alt-sidebar-pill[data-ready] { transition: transform var(--dur-travel-3) var(--ease-glide), width var(--dur-travel-3) var(--ease-glide), height var(--dur-travel-3) var(--ease-glide), opacity var(--dur-travel-1); }
1654
+ .alt-sidebar-group + .alt-sidebar-group { margin-top: var(--space-3); }
1655
+ .alt-sidebar-label { display: flex; align-items: center; gap: 4px; width: 100%; height: 28px; padding: 0 var(--space-2); border: 0; background: none; font: 500 12px/16px var(--font-sans); letter-spacing: 0.01em; color: var(--ink-muted); text-align: left; white-space: nowrap; }
1656
+ button.alt-sidebar-label { cursor: pointer; border-radius: var(--radius-sm); }
1657
+ button.alt-sidebar-label:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1658
+ .alt-sidebar-chev { margin-left: auto; transition: transform var(--dur-travel-2) var(--ease-settle); }
1659
+ .alt-sidebar-label[aria-expanded="false"] .alt-sidebar-chev { transform: rotate(-90deg); }
1660
+ .alt-sidebar[data-rail] .alt-sidebar-label { visibility: hidden; height: 12px; }
1661
+ .alt-sidebar-menu { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
1662
+ .alt-sidebar-item { position: relative; display: flex; align-items: center; }
1663
+ .alt-sidebar-button {
1664
+ position: relative; z-index: 1; display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; height: calc(var(--size-control-md) - 2px); padding: 0 var(--space-2); border-radius: var(--radius-sm);
1665
+ font: 500 14px/20px var(--font-sans); color: var(--ink-muted); text-decoration: none; white-space: nowrap; cursor: pointer;
1666
+ transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), color var(--dur-travel-1);
1667
+ }
1668
+ @media (hover: hover) { .alt-sidebar-button:hover:not([aria-current]) { background: var(--surface-raised); box-shadow: var(--elev-1); color: var(--ink); } }
1669
+ .alt-sidebar-button[aria-current] { color: var(--ink); font-weight: 600; }
1670
+ .alt-sidebar-button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1671
+ .alt-sidebar-icon { display: inline-flex; flex: none; width: 20px; justify-content: center; }
1672
+ .alt-sidebar-text { overflow: hidden; text-overflow: ellipsis; transition: opacity var(--dur-travel-2) var(--ease-rise); }
1673
+ /* Artifact (#271): what an agent is making, in a pane on the ground beside the conversation. Being written isn't the
1674
+ record yet: a dashed line-control rule at the pane's edge; complete goes solid with one afterglow; failed, critical.
1675
+ Viewing an earlier version is a mode, so it's a flush strip with a line-control border, not a lift. The reference in
1676
+ a message is a plate that acts. */
1677
+ .alt-artifact { display: flex; flex-direction: column; min-width: 0; min-height: 0; height: 100%; background: var(--surface-ground); border-left: 2px solid var(--line); outline: none; }
1678
+ .alt-artifact[data-status="writing"] { border-left-style: dashed; border-left-color: var(--line-control); }
1679
+ .alt-artifact-head { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-3) var(--space-3) var(--space-2) var(--space-4); border-bottom: 1px solid var(--line); }
1680
+ .alt-artifact-titles { flex: 1; min-width: 0; }
1681
+ .alt-artifact-title { margin: 0; font: 600 15px/21px var(--font-sans); color: var(--ink); overflow-wrap: anywhere; outline: none; }
1682
+ .alt-artifact-title:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--radius-sm); }
1683
+ .alt-artifact-state { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
1684
+ .alt-artifact-state[data-tone="critical"] { color: var(--critical); }
1685
+ .alt-artifact-tools { display: inline-flex; gap: 2px; }
1686
+ .alt-artifact-versions { display: flex; align-items: center; gap: 2px; padding: 4px var(--space-3); }
1687
+ .alt-artifact-version { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
1688
+ .alt-artifact-earlier { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding: 6px var(--space-3) 6px var(--space-4); border-block: 1px solid var(--line-control); font: 500 13px/18px var(--font-sans); color: var(--ink); }
1689
+ .alt-artifact-earlier > span { margin-right: auto; }
1690
+ .alt-artifact-views { display: flex; flex-direction: column; flex: 1; min-height: 0; }
1691
+ .alt-artifact-views > .alt-tabs-list { align-self: flex-start; margin: var(--space-2) var(--space-3); }
1692
+ .alt-artifact-body { flex: 1; min-height: 0; overflow: auto; padding: var(--space-3); animation: alt-fade-in var(--dur-travel-1) var(--ease-rise) both; }
1693
+ .alt-artifact-body iframe { display: block; width: 100%; height: 100%; min-height: 280px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-ground); }
1694
+ .alt-artifact-ref { display: inline-flex; align-items: center; gap: 10px; max-width: 100%; padding: 8px 12px 8px 10px; border: 0; border-radius: var(--radius-md); text-align: left; cursor: pointer;
1695
+ background: color-mix(in oklab, var(--surface-ground), var(--surface-well) 70%); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--ink) 9%, transparent); color: var(--ink);
1696
+ transition: transform var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1) var(--ease-rise); }
1697
+ @media (hover: hover) { .alt-artifact-ref:hover { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
1698
+ .alt-artifact-ref:active { transform: none; box-shadow: none; transition-duration: var(--dur-press); }
1699
+ .alt-artifact-ref:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1700
+ .alt-artifact-ref-icon { display: grid; place-items: center; width: 28px; height: 28px; border-radius: var(--radius-sm); background: var(--surface-raised); box-shadow: var(--elev-1); color: var(--ink-muted); }
1701
+ .alt-artifact-ref-text { display: flex; flex-direction: column; min-width: 0; }
1702
+ .alt-artifact-ref-title { font: 600 13.5px/19px var(--font-sans); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1703
+ .alt-artifact-ref-meta { font: 400 12px/16px var(--font-sans); color: var(--ink-muted); }
1704
+ @media (prefers-reduced-motion: reduce) { .alt-artifact-body { animation: none; } }
1705
+ [data-contrast="more"] .alt-artifact { border-left-color: var(--line-control); }
1706
+ @media (forced-colors: active) {
1707
+ .alt-artifact { border-left-color: CanvasText; }
1708
+ .alt-artifact[data-status="writing"] { border-left-style: dotted; }
1709
+ .alt-artifact-ref { border: 1px solid CanvasText; }
1710
+ }
1711
+ /* ThreadList (#269): conversations as Sidebar destinations, each with its state in words. Working isn't pending, so it
1712
+ never hovers: a plate with a steady lamp. Needs-you is a caution plate (a row can't lift into a card inside a nav).
1713
+ Unread is a dot and weight 600. Renaming opens a well in the row; deleting leaves a receipt in its slot. */
1714
+ .alt-threads { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
1715
+ .alt-threads-search { margin: 0 var(--space-2); }
1716
+ .alt-thread .alt-thread-link { height: auto; min-height: 44px; padding-block: 5px; align-items: flex-start; }
1717
+ .alt-thread .alt-sidebar-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; width: 100%; }
1718
+ .alt-thread-main { display: flex; align-items: center; gap: 6px; min-width: 0; }
1719
+ .alt-thread-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1720
+ .alt-thread[data-unread] .alt-thread-title { font-weight: 600; color: var(--ink); }
1721
+ .alt-thread-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
1722
+ .alt-thread-meta { display: flex; align-items: center; gap: 6px; min-width: 0; font: 400 12px/16px var(--font-sans); color: var(--ink-muted); }
1723
+ .alt-thread-plate { flex: none; }
1724
+ .alt-thread-time { white-space: nowrap; }
1725
+ .alt-thread-more { position: absolute; right: 4px; top: 6px; opacity: 0; transition: opacity var(--dur-travel-1); }
1726
+ .alt-thread:focus-within .alt-thread-more, .alt-thread-more[aria-expanded="true"] { opacity: 1; }
1727
+ @media (hover: hover) { .alt-thread:hover .alt-thread-more { opacity: 1; } }
1728
+ @media (hover: none) { .alt-thread-more { opacity: 1; } }
1729
+ .alt-thread:has(.alt-thread-more) .alt-thread-main { padding-right: 28px; }
1730
+ .alt-thread-rename { display: block; width: 100%; padding: 4px; }
1731
+ .alt-threads-new { width: 100%; justify-content: center; }
1732
+ /* AgentFleet: every agent in a run of work, grouped by what they need from you. Rows are the record, flush, with their
1733
+ state in words on a plate (the lamp only repeats it); working never hovers. The open agent is selected: tinted and
1734
+ raised one level, as a table's selected row. Finished agents are stamped: a check and muted ink. Agents an agent
1735
+ started hang under it in a 1px line-control bracket, as TraceParallel's subagents do. */
1736
+ .alt-fleet { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
1737
+ .alt-fleet-summary { margin: 0; font: 500 13px/18px var(--font-sans); color: var(--ink-muted); }
1738
+ .alt-fleet-group { display: flex; flex-direction: column; gap: 2px; }
1739
+ .alt-fleet-group-head { display: flex; align-items: center; gap: 6px; min-height: 28px; margin: 0; padding: 0 var(--space-2); border: 0; background: none; color: var(--ink-muted); font: 600 13px/18px var(--font-sans); text-align: start; }
1740
+ button.alt-fleet-group-head { cursor: pointer; border-radius: var(--radius-sm); }
1741
+ button.alt-fleet-group-head:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1742
+ .alt-fleet-chevron { transform: rotate(-90deg); transition: transform var(--dur-travel-1) var(--ease-glide); }
1743
+ .alt-fleet-group[data-open] .alt-fleet-chevron { transform: none; }
1744
+ .alt-fleet-group-count { font-variant-numeric: tabular-nums; font-weight: 500; }
1745
+ .alt-fleet-empty { margin: 0; padding: var(--space-1) var(--space-2); font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
1746
+ .alt-fleet-list, .alt-agent-children { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
1747
+ .alt-fleet-list[hidden] { display: none; }
1748
+ .alt-agent-children { margin: 2px 0 2px 18px; padding-left: 8px; border-left: 1px solid var(--line-control); }
1749
+ .alt-agent-line { position: relative; }
1750
+ .alt-agent-row { --row-bg: transparent; --row-sh: var(--elev-0);
1751
+ display: flex; flex-direction: column; gap: 3px; width: 100%; min-width: 0; padding: 8px var(--space-2); border: 0; border-radius: var(--radius-md);
1752
+ background: var(--row-bg); box-shadow: var(--row-sh); color: var(--ink); font: 400 13px/18px var(--font-sans); text-align: start;
1753
+ transition: transform var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1) var(--ease-rise); }
1754
+ button.alt-agent-row { cursor: pointer; }
1755
+ @media (hover: hover) { button.alt-agent-row:hover { --row-bg: var(--surface-raised); --row-sh: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
1756
+ button.alt-agent-row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1757
+ .alt-agent[data-selected] > .alt-agent-line > .alt-agent-row { --row-bg: var(--accent-soft); --row-sh: var(--elev-1); }
1758
+ .alt-agent-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
1759
+ .alt-agent-line:has(.alt-agent-output) .alt-agent-head { padding-right: 64px; }
1760
+ .alt-agent-mark { display: inline-flex; flex: none; width: 14px; justify-content: center; color: var(--ink-muted); }
1761
+ .alt-agent-mark i { width: 7px; height: 7px; border-radius: 50%; border: 1.5px solid currentColor; }
1762
+ .alt-agent[data-agent-state="working"] > .alt-agent-line .alt-agent-mark { color: var(--accent); }
1763
+ .alt-agent[data-agent-state="working"] > .alt-agent-line .alt-agent-mark i { background: currentColor; }
1764
+ .alt-agent[data-agent-state="needs-input"] > .alt-agent-line .alt-agent-mark, .alt-agent[data-agent-state="stalled"] > .alt-agent-line .alt-agent-mark { color: var(--caution); }
1765
+ .alt-agent[data-agent-state="needs-input"] > .alt-agent-line .alt-agent-mark i { background: currentColor; }
1766
+ .alt-agent[data-agent-state="failed"] > .alt-agent-line .alt-agent-mark { color: var(--critical); }
1767
+ .alt-agent[data-agent-state="done"] > .alt-agent-line .alt-agent-mark { color: var(--positive); }
1768
+ .alt-agent-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
1769
+ .alt-agent-time { flex: none; font: 400 12px/16px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
1770
+ .alt-agent-activity { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; padding-left: 22px; color: var(--ink-muted); }
1771
+ .alt-agent-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; padding-left: 22px; font: 400 12px/16px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
1772
+ .alt-agent-conflict { display: flex; align-items: flex-start; gap: 6px; margin: 3px 0 0 22px; padding: 4px 8px; border-radius: var(--radius-sm); background: var(--caution-soft); color: var(--ink); font: 400 12px/16px var(--font-sans); }
1773
+ .alt-agent-conflict svg { flex: none; margin-top: 1px; color: var(--caution); }
1774
+ .alt-agent-conflict code { font: 400 11.5px/16px var(--font-code); }
1775
+ .alt-agent-output { position: absolute; top: 7px; right: var(--space-2); }
1776
+ .alt-agent:is([data-agent-state="done"], [data-agent-state="stopped"]) > .alt-agent-line .alt-agent-name { font-weight: 500; color: var(--ink-muted); }
1777
+ @media (forced-colors: active) {
1778
+ .alt-agent[data-selected] > .alt-agent-line > .alt-agent-row { outline: 1px solid Highlight; }
1779
+ .alt-agent-children { border-left-color: CanvasText; }
1780
+ .alt-agent-conflict { border: 1px dashed CanvasText; }
1781
+ }
1782
+ /* AgentRequests: the decisions agents are waiting on, oldest first. Each asks for action, so it's a raised card (level
1783
+ 1) that arrives lifted and settles; nothing in it has been dispatched, so nothing hovers. Its answers are secondary
1784
+ and ghost: the view's one primary stays where it is. With nothing waiting it says so, flush. */
1785
+ .alt-requests { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
1786
+ .alt-requests-head { display: flex; align-items: center; gap: 8px; min-height: 28px; }
1787
+ .alt-requests-title { margin: 0; font: 600 14px/20px var(--font-sans); color: var(--ink); }
1788
+ .alt-requests-oldest { margin-left: auto; font: 400 12px/16px var(--font-sans); color: var(--ink-muted); }
1789
+ .alt-requests-empty { display: flex; align-items: center; gap: 8px; margin: 0; padding: var(--space-2) 0; font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
1790
+ .alt-requests-empty svg { flex: none; color: var(--positive); }
1791
+ .alt-requests-list { display: flex; flex-direction: column; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
1792
+ .alt-request { display: flex; flex-direction: column; gap: 6px; padding: var(--space-3); border-radius: var(--radius-md); }
1793
+ .alt-request-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; font: 400 12px/16px var(--font-sans); color: var(--ink-muted); }
1794
+ .alt-request-agent { font-weight: 600; color: var(--ink); }
1795
+ .alt-request-time { margin-left: auto; font-variant-numeric: tabular-nums; }
1796
+ .alt-request-title { margin: 0; font: 600 14px/20px var(--font-sans); color: var(--ink); }
1797
+ .alt-request-detail { margin: 0; font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
1798
+ .alt-request-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-top: 2px; }
1799
+ .alt-request-actions > .alt-btn[data-variant="ghost"]:last-child { margin-left: auto; }
1800
+ /* AgentLanes: where each agent's time went, on one clock. A chart, so nothing here has height (Charts): stretches are
1801
+ equal bars lying on each lane, told apart by fill and pattern and named in the key. Working is data-1; waiting on you
1802
+ is caution hatched at 45°; waiting on another agent is a 1px dashed outline; failed is critical. "Now" is a 1px
1803
+ line-control rule. The selected lane's name is accent-ink with aria-pressed; lanes never lift. */
1804
+ .alt-lanes { --lane-name: 132px; display: flex; flex-direction: column; gap: var(--space-2); margin: 0; min-width: 0; }
1805
+ .alt-lanes-takeaway { font: 500 14px/20px var(--font-sans); color: var(--ink); }
1806
+ .alt-lanes-legend { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 0; padding: 0; list-style: none; font: 400 12px/16px var(--font-sans); color: var(--ink-muted); }
1807
+ .alt-lanes-legend li { display: inline-flex; align-items: center; gap: 6px; }
1808
+ .alt-lane-swatch { display: inline-block; width: 14px; height: 10px; border-radius: 2px; }
1809
+ .alt-lanes-plot { position: relative; display: flex; flex-direction: column; gap: 6px; }
1810
+ .alt-lane { display: grid; grid-template-columns: var(--lane-name) 1fr; align-items: center; gap: var(--space-2); }
1811
+ .alt-lane-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0; border: 0; background: none; color: var(--ink); font: 500 12.5px/24px var(--font-sans); text-align: start; }
1812
+ button.alt-lane-name { cursor: pointer; text-decoration: underline transparent; text-underline-offset: 3px; border-radius: var(--radius-xs); }
1813
+ @media (hover: hover) { button.alt-lane-name:hover { text-decoration-color: currentColor; } }
1814
+ button.alt-lane-name:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
1815
+ .alt-lane[data-selected] .alt-lane-name { color: var(--accent-ink); font-weight: 650; }
1816
+ .alt-lane-track { position: relative; height: 14px; border-bottom: 1px solid var(--line); }
1817
+ .alt-lane-bar { position: absolute; top: 1px; bottom: 1px; min-width: 2px; border-radius: 2px; }
1818
+ .alt-lane-swatch[data-lane="working"], .alt-lane-bar[data-lane="working"] { background: var(--data-1); }
1819
+ .alt-lane-swatch[data-lane="waiting"], .alt-lane-bar[data-lane="waiting"] { background: repeating-linear-gradient(45deg, var(--caution) 0 2px, var(--caution-soft) 2px 5px); box-shadow: inset 0 0 0 1px var(--caution); }
1820
+ .alt-lane-swatch[data-lane="blocked"], .alt-lane-bar[data-lane="blocked"] { background: transparent; box-shadow: none; border: 1px dashed var(--ink-muted); }
1821
+ .alt-lane-swatch[data-lane="failed"], .alt-lane-bar[data-lane="failed"] { background: var(--critical); }
1822
+ .alt-lanes-axis { display: grid; grid-template-columns: var(--lane-name) 1fr; gap: var(--space-2); }
1823
+ .alt-lanes-ticks { position: relative; height: 16px; font: 400 11.5px/16px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
1824
+ .alt-lanes-ticks > span { position: absolute; transform: translateX(-50%); white-space: nowrap; }
1825
+ .alt-lanes-ticks > span[data-edge="start"] { transform: none; }
1826
+ .alt-lanes-ticks > span[data-edge="end"] { transform: translateX(-100%); color: var(--ink); }
1827
+ .alt-lanes-now { position: absolute; top: 0; bottom: 18px; right: 0; width: 1px; background: var(--line-control); }
1828
+ @media (forced-colors: active) {
1829
+ .alt-lane-bar, .alt-lane-swatch { forced-color-adjust: none; }
1830
+ .alt-lane-bar[data-lane="working"], .alt-lane-swatch[data-lane="working"] { background: CanvasText; }
1831
+ .alt-lane-bar[data-lane="waiting"], .alt-lane-swatch[data-lane="waiting"] { background: repeating-linear-gradient(45deg, CanvasText 0 2px, Canvas 2px 5px); box-shadow: inset 0 0 0 1px CanvasText; }
1832
+ .alt-lane-bar[data-lane="blocked"], .alt-lane-swatch[data-lane="blocked"] { border-color: CanvasText; }
1833
+ .alt-lane-bar[data-lane="failed"], .alt-lane-swatch[data-lane="failed"] { background: repeating-linear-gradient(-45deg, CanvasText 0 1px, Canvas 1px 3px); border: 2px solid CanvasText; }
1834
+ .alt-lanes-now { background: CanvasText; }
1835
+ }
1836
+ @media (max-width: 480px) { .alt-lanes { --lane-name: 88px; } }
1837
+ @media (pointer: coarse) { button.alt-fleet-group-head, button.alt-lane-name { min-height: 44px; } }
1838
+ /* In the rail only icons show: each destination is a square, and the rail lets its tooltips out to the right. */
1839
+ .alt-sidebar[data-rail], .alt-sidebar[data-rail] .alt-sidebar-content { overflow: visible; }
1840
+ .alt-sidebar[data-rail] .alt-sidebar-item .alt-tip-anchor { display: flex; flex: 1; }
1841
+ .alt-sidebar[data-rail] .alt-sidebar-button { justify-content: center; padding: 0; }
1842
+ .alt-sidebar[data-rail] .alt-sidebar-text { display: none; }
1843
+ .alt-sidebar[data-rail] .alt-sidebar-header .alt-sidebar-text { display: none; }
1844
+ .alt-sidebar-badge { position: absolute; right: var(--space-2); z-index: 2; pointer-events: none; }
1845
+ .alt-sidebar[data-rail] .alt-sidebar-badge { right: 2px; top: 0; transform: scale(0.8); }
1846
+ .alt-shell-page { position: relative; flex: 1; min-width: 0; min-height: 0; overflow: auto; }
1847
+ .alt-topbar { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: var(--space-2); height: 52px; padding: 0 var(--space-4); border-bottom: 1px solid var(--line); background: var(--surface-ground); transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise); }
1848
+ .alt-topbar[data-under] { background: var(--surface-raised); box-shadow: var(--elev-1); }
1849
+ .alt-shell-sentinel { display: block; height: 1px; margin-bottom: -1px; }
1850
+ .alt-inset { --alt-page-pad: var(--alt-inset-page); display: block; padding: 0 var(--alt-page-pad) var(--alt-page-pad); outline: none; }
1851
+ @media (prefers-reduced-motion: reduce) { .alt-sidebar { transition: none; } .alt-sidebar-pill[data-ready] { transition: opacity var(--dur-travel-1); } }
1852
+ [data-motion="reduced"] .alt-sidebar { transition: none; }
1853
+ [data-motion="reduced"] .alt-sidebar-pill[data-ready] { transition: opacity var(--dur-travel-1); }
1854
+ @media (prefers-contrast: more) { .alt-sidebar, .alt-sidebar-header, .alt-sidebar-footer, .alt-topbar { border-color: var(--line-control); } }
1855
+ [data-contrast="more"] .alt-sidebar, [data-contrast="more"] .alt-sidebar-header, [data-contrast="more"] .alt-sidebar-footer, [data-contrast="more"] .alt-topbar { border-color: var(--line-control); }
1856
+ [data-contrast="more"] .alt-sidebar-pill { box-shadow: 0 2px 0 0 var(--ink), var(--elev-1); }
1857
+ @media (forced-colors: active) { .alt-sidebar-pill { border: 1px solid CanvasText; } .alt-sidebar-button:hover:not([aria-current]) { border: 1px solid CanvasText; } }
1858
+
1859
+ /* ── Resizable: panes beside each other on the ground, parted by 1px of line. The panes never lift; only the grip is
1860
+ live: a small line-control plate, flush at rest, lifting one level when pointed at or focused, floating at level 3
1861
+ while held (whatever is in a hand floats). Collapsing a pane changes its size on ease-glide, not its level. ── */
1862
+ .alt-resizable { display: flex; width: 100%; height: 100%; min-width: 0; min-height: 0; }
1863
+ .alt-resizable[data-direction="vertical"] { flex-direction: column; }
1864
+ .alt-resizable-panel { flex: 0 0 auto; min-width: 0; min-height: 0; overflow: auto; background: var(--surface-ground); }
1865
+ .alt-resizable-panel[data-collapsed] { overflow: hidden; }
1866
+ .alt-resizable[data-animate] .alt-resizable-panel { transition: flex-basis var(--dur-travel-3) var(--ease-glide); }
1867
+ .alt-resizable-handle { position: relative; z-index: 1; flex: none; display: grid; place-items: center; width: 1px; background: var(--line); cursor: col-resize; touch-action: none; outline: none; }
1868
+ .alt-resizable[data-direction="vertical"] > .alt-resizable-handle { width: auto; height: 1px; cursor: row-resize; }
1869
+ .alt-resizable-handle::before { content: ""; position: absolute; inset: 0 calc((var(--size-target-min) - 1px) / -2); }
1870
+ .alt-resizable[data-direction="vertical"] > .alt-resizable-handle::before { inset: calc((var(--size-target-min) - 1px) / -2) 0; }
1871
+ .alt-resizable-grip {
1872
+ position: relative; width: 8px; height: 28px; border-radius: var(--radius-full); border: 1px solid var(--line-control); background: var(--surface-ground);
1873
+ transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-2) var(--ease-settle), border-color var(--dur-travel-1);
1874
+ }
1875
+ .alt-resizable[data-direction="vertical"] .alt-resizable-grip { width: 28px; height: 8px; }
1876
+ .alt-resizable-handle:focus-visible .alt-resizable-grip { background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1); border-color: transparent; }
1877
+ @media (hover: hover) { .alt-resizable-handle:hover .alt-resizable-grip { background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1); border-color: transparent; } }
1878
+ .alt-resizable-handle:focus-visible .alt-resizable-grip { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1879
+ .alt-resizable-handle[data-held] .alt-resizable-grip { background: var(--surface-floating); box-shadow: var(--elev-3); border-color: transparent; transition-timing-function: var(--ease-rise); }
1880
+ @media (prefers-reduced-motion: reduce) { .alt-resizable[data-animate] .alt-resizable-panel { transition: none; } }
1881
+ [data-motion="reduced"] .alt-resizable[data-animate] .alt-resizable-panel { transition: none; }
1882
+ @media (prefers-contrast: more) { .alt-resizable-handle { background: var(--line-control); } }
1883
+ [data-contrast="more"] .alt-resizable-handle { background: var(--line-control); }
1884
+ [data-contrast="more"] .alt-resizable-handle[data-held] .alt-resizable-grip { box-shadow: 0 2px 0 0 var(--ink), var(--elev-3); }
1885
+ @media (forced-colors: active) { .alt-resizable-handle { background: CanvasText; } .alt-resizable-grip { border: 1px solid CanvasText; } .alt-resizable-handle[data-held] .alt-resizable-grip { border-width: 2px; } }
1886
+
1887
+ /* ── ContextMenu: DropdownMenu's menu (level 3, the gliding highlight), opened where you right-click or long-press, or
1888
+ at the thing with Shift+F10. The thing shows it's the subject with a 2px accent-ink outline: a ring, never a height,
1889
+ so a flush row doesn't lift out of its table. ── */
1890
+ .alt-context { position: fixed; z-index: var(--z-floating); }
1891
+ .alt-context .alt-context-menu { position: relative; top: auto; left: auto; right: auto; bottom: auto; margin: 0; }
1892
+ [data-context-open] { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1893
+ @media (forced-colors: active) { [data-context-open] { outline-color: Highlight; } }
1894
+
1895
+ /* ── App mode (AltitudeProvider mode): test mode, viewing as a customer, read-only, a preview. The page is the ground,
1896
+ so height can't say "this whole ground is different"; the mode is said in words and a border (law 7 as the primary
1897
+ channel): a flush strip on the tone's soft colour naming it at the top, a 2px inset frame around the viewport in its
1898
+ tone that stays while scrolling and above dialogs and sheets, and a plate in their headers. Nothing moves. ── */
1899
+ .alt-mode-strip { --m: var(--caution); --m-soft: var(--caution-soft); display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--space-3); padding: 6px var(--space-4); background: var(--m-soft); border-bottom: 1px solid color-mix(in oklab, var(--m) 35%, transparent); font: 400 13px/18px var(--font-sans); color: var(--ink); }
1900
+ .alt-mode-strip[data-tone="accent"], .alt-mode-frame[data-tone="accent"] { --m: var(--accent); --m-soft: var(--accent-soft); }
1901
+ .alt-mode-strip[data-tone="critical"], .alt-mode-frame[data-tone="critical"] { --m: var(--critical); --m-soft: var(--critical-soft); }
1902
+ .alt-mode-strip[data-tone="neutral"], .alt-mode-frame[data-tone="neutral"] { --m: var(--ink-muted); --m-soft: var(--surface-well); }
1903
+ .alt-mode-label { font-weight: 600; }
1904
+ .alt-mode-desc { color: var(--ink); }
1905
+ .alt-mode-action { margin-left: auto; }
1906
+ .alt-mode-frame { --m: var(--caution); position: fixed; inset: 0; z-index: 2147483000; border: 2px solid var(--m); pointer-events: none; }
1907
+ .alt-mode-plate { margin-bottom: var(--space-1); align-self: flex-start; }
1908
+ @media (prefers-contrast: more) { .alt-mode-frame { border-width: 3px; } }
1909
+ [data-contrast="more"] .alt-mode-frame { border-width: 3px; }
1910
+ @media (forced-colors: active) { .alt-mode-frame { border: 4px double CanvasText; } .alt-mode-strip { border-bottom: 1px solid CanvasText; } }
1911
+
1912
+ /* ── DropZone's queue: each file is a slat on the ground under the zone. Uploading is a task, so a Progress fill rises
1913
+ out of its trench; processing is a write the server hasn't confirmed, so the slat hovers as pending; done lands and
1914
+ settles with the afterglow; failed drops to a trench with its reason and Retry. ── */
1915
+ .alt-drop-wrap { display: flex; flex-direction: column; gap: var(--space-3); }
1916
+ .alt-upload-list { display: flex; flex-direction: column; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
1917
+ .alt-upload { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); border-radius: var(--radius-md); background: var(--surface-ground); box-shadow: 0 0 0 1px var(--line); }
1918
+ .alt-upload-text { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; }
1919
+ .alt-upload-name { font: 500 14px/20px var(--font-sans); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1920
+ .alt-upload-state { display: inline-flex; align-items: center; gap: 4px; font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
1921
+ .alt-upload[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench); }
1922
+ .alt-upload[data-state="failed"] .alt-upload-state { color: var(--critical); }
1923
+ .alt-upload .alt-progress { margin-top: 2px; }
1924
+ .alt-upload-actions { display: flex; flex: none; align-items: center; gap: var(--space-1); }
1925
+
1926
+ /* ── FilterBar groups: nested logic drawn as brackets on the ground. A group is a flush 1px line-control rule around its
1927
+ own conditions and its own all/any switch: part of the record of the query, not a live surface. ── */
1928
+ .alt-filter-tree { display: flex; flex-direction: column; gap: var(--space-2); }
1929
+ .alt-filter-group { position: relative; display: flex; align-items: flex-start; gap: var(--space-2); margin-left: var(--space-4); padding: var(--space-2); padding-right: 36px; border: 1px solid var(--line-control); border-radius: var(--radius-md); }
1930
+ .alt-filter-group-remove { position: absolute; top: 6px; right: 6px; }
1931
+ .alt-filter-add-group { align-self: flex-start; margin-left: var(--space-4); }
1932
+ @media (forced-colors: active) { .alt-filter-group { border-color: CanvasText; } }
1933
+
1934
+ /* ── Timeline: the ground is the record, literally. Events stand on a ground line turned upright (1px ink-muted, the
1935
+ rule Stepper and the chart baseline stand on) as flush entries stamped onto it, each with a flush plate on the line.
1936
+ A new event arrives lifted and settles (the afterglow, staggered); one you caused hovers until it lands, or drops to a
1937
+ trench with Retry; an event that links lifts when pointed at; its detail opens as a raised card, as an Accordion lid
1938
+ does. ── */
1939
+ .alt-timeline-list { position: relative; display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0 0 0 36px; list-style: none; }
1940
+ .alt-timeline-list::before { content: ""; position: absolute; left: 13px; top: 6px; bottom: 6px; width: 1px; background: var(--ink-muted); }
1941
+ .alt-timeline-day { padding: var(--space-3) 0 var(--space-1); }
1942
+ .alt-timeline-day:first-child { padding-top: 0; }
1943
+ .alt-timeline-day-label { margin: 0 0 0 -36px; padding-left: 36px; font: 600 12.5px/17px var(--font-sans); color: var(--ink-muted); background: var(--surface-ground); position: relative; }
1944
+ .alt-timeline-item { position: relative; border-radius: var(--radius-md); }
1945
+ .alt-timeline-marker { position: absolute; left: -36px; top: 7px; display: grid; place-items: center; width: 27px; height: 27px; border-radius: var(--radius-full); background: var(--surface-ground); box-shadow: inset 0 0 0 1px var(--line); color: var(--ink-muted); }
1946
+ .alt-timeline-marker svg { width: 14px; height: 14px; }
1947
+ .alt-timeline-dot { width: 7px; height: 7px; border-radius: var(--radius-full); background: var(--ink-muted); }
1948
+ .alt-timeline-body { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; }
1949
+ .alt-timeline-entry, .alt-timeline-link { display: flex; flex-direction: column; gap: 2px; font: 400 14px/20px var(--font-sans); color: var(--ink); }
1950
+ .alt-timeline-link { margin: -6px -8px; padding: 6px 8px; border-radius: var(--radius-sm); text-decoration: none; transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise); }
1951
+ @media (hover: hover) { .alt-timeline-link:hover { background: var(--surface-raised); box-shadow: var(--elev-1); } }
1952
+ .alt-timeline-link:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1953
+ .alt-timeline-meta { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
1954
+ .alt-timeline-state { display: inline-flex; align-items: center; gap: 4px; }
1955
+ .alt-timeline-state[data-failed] { color: var(--critical); }
1956
+ /* A failed event isn't new to the record: it drops to its trench without the afterglow's tint. */
1957
+ .alt-timeline-item[data-status="failed"]::before { animation: none; }
1958
+ .alt-timeline-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
1959
+ .alt-timeline-detail { padding: var(--space-3); border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--elev-1); animation: alt-emerge var(--dur-travel-2) var(--ease-rise); --alt-from-y: -4px; }
1960
+ @media (prefers-contrast: more) { .alt-timeline-list::before { width: 2px; } }
1961
+ [data-contrast="more"] .alt-timeline-list::before { width: 2px; }
1962
+ @media (forced-colors: active) { .alt-timeline-list::before { background: CanvasText; } .alt-timeline-marker { box-shadow: none; border: 1px solid CanvasText; } }
1963
+
1964
+ /* ── NotificationCenter: the bell is live (a ghost button), only its count floats, the panel floats at level 3 (a
1965
+ Sheet at 4 on narrow screens), and the list in it lies on the ground: a record of what happened. Unread is a mark,
1966
+ not a height: an accent dot and weight 600, with "Unread" in the name. A new notification arrives lifted and
1967
+ settles; an item that links lifts when pointed at. Mark all as read removes the dots with a cross-fade. ── */
1968
+ .alt-notes-bell { position: relative; }
1969
+ .alt-notes-badge { position: absolute; top: -4px; right: -6px; pointer-events: none; }
1970
+ .alt-notes-pop { width: min(380px, calc(100vw - 32px)); padding: var(--space-3); }
1971
+ .alt-notes-title-bar { margin: 0 0 var(--space-1); font: 600 15px/20px var(--font-sans); color: var(--ink); }
1972
+ .alt-notes { display: flex; flex-direction: column; gap: var(--space-2); }
1973
+ .alt-notes-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-height: 30px; }
1974
+ .alt-notes-count { font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); }
1975
+ .alt-notes-list { display: flex; flex-direction: column; gap: 2px; max-height: 420px; overflow: auto; margin: 0 -4px; padding: 0 4px; list-style: none; }
1976
+ .alt-notes-day { padding: var(--space-2) var(--space-2) 2px; font: 600 12px/16px var(--font-sans); color: var(--ink-muted); }
1977
+ .alt-notes-item { border-radius: var(--radius-md); }
1978
+ .alt-notes-link { display: flex; gap: 10px; padding: 8px; border-radius: var(--radius-md); color: var(--ink); text-decoration: none; transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise); }
1979
+ @media (hover: hover) { a.alt-notes-link:hover { background: var(--surface-raised); box-shadow: var(--elev-1); } }
1980
+ a.alt-notes-link:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1981
+ .alt-notes-dot { flex: none; width: 8px; height: 8px; margin-top: 6px; border-radius: var(--radius-full); background: var(--accent); opacity: 0; transition: opacity var(--dur-travel-2) var(--ease-rise); }
1982
+ .alt-notes-item[data-unread] .alt-notes-dot { opacity: 1; }
1983
+ .alt-notes-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1984
+ .alt-notes-title { font: 400 14px/20px var(--font-sans); }
1985
+ .alt-notes-item[data-unread] .alt-notes-title { font-weight: 600; }
1986
+ .alt-notes-body { font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
1987
+ .alt-notes-time { font: 400 12px/16px var(--font-sans); color: var(--ink-muted); }
1988
+ .alt-notes-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0 8px 8px 26px; }
1989
+ .alt-notes-empty { margin: 0; padding: var(--space-4) var(--space-2); font: 400 14px/20px var(--font-sans); color: var(--ink-muted); text-align: center; }
1990
+ .alt-notes-foot { padding-top: var(--space-2); border-top: 1px solid var(--line); font: 400 13px/18px var(--font-sans); }
1991
+ @media (forced-colors: active) { .alt-notes-dot { forced-color-adjust: none; background: CanvasText; } }
1992
+
1993
+ /* ── SyncIndicator: whether work has reached the server, said on a flush Badge plate whose lamp relights when the
1994
+ state changes (saving pulses). A label never lifts, so a change of state cross-fades; only when the plate opens the
1995
+ queue of waiting changes does it lift when pointed at, because then it acts. ── */
1996
+ .alt-sync { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
1997
+ .alt-sync-button { padding: 0; border: 0; border-radius: var(--radius-sm); background: none; cursor: pointer; transition: box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-1) var(--ease-rise); }
1998
+ @media (hover: hover) { .alt-sync-button:hover { box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
1999
+ .alt-sync-button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
2000
+ .alt-sync-time { font-weight: 400; color: var(--ink-muted); }
2001
+ .alt-sync-queue { display: flex; flex-direction: column; gap: var(--space-2); width: 280px; max-width: 100%; }
2002
+ .alt-sync-queue-title { margin: 0; font: 600 13px/18px var(--font-sans); color: var(--ink); }
2003
+ [data-contrast="more"] .alt-sync-plate { box-shadow: inset 0 0 0 1px var(--line-control); }
2004
+
2005
+ /* ── DiffView: both versions are the record, so it lies flush with a line rim (as CodeBlock). Additions are positive-
2006
+ soft with a + sign, removals critical-soft and struck through with a − sign, and words that changed inside a line take
2007
+ a stronger tint; a changed property shows the old value struck through beside the new. Signs and words carry it, never
2008
+ colour alone. Long unchanged runs fold behind lids. ── */
2009
+ .alt-diff { margin: 0; min-width: 0; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface-ground); overflow: hidden; }
2010
+ .alt-diff-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); padding: 6px 6px 6px var(--space-4); border-bottom: 1px solid var(--line); }
2011
+ .alt-diff-caption { font: 500 13px/18px var(--font-sans); color: var(--ink); }
2012
+ .alt-diff-summary { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2013
+ /* The buttons wrap rather than run past the frame, which clips them (a review's four don't fit a phone on one line). */
2014
+ .alt-diff-nav { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; gap: 2px; margin-left: auto; min-width: 0; }
2015
+ .alt-diff-body { overflow: auto; max-height: 420px; padding: var(--space-2) 0; }
2016
+ .alt-diff-lines { min-width: max-content; font: 400 13px/20px var(--font-mono); }
2017
+ .alt-diff-line { display: flex; padding-right: var(--space-4); outline: none; }
2018
+ .alt-diff-line:focus-visible { box-shadow: inset 2px 0 0 var(--accent-ink); }
2019
+ .alt-diff-sign { flex: none; width: 28px; text-align: center; color: var(--ink-muted); user-select: none; }
2020
+ .alt-diff-text { white-space: pre; text-decoration: none; color: var(--ink); }
2021
+ .alt-diff-line[data-kind="add"] { background: var(--positive-soft); }
2022
+ .alt-diff-line[data-kind="del"] { background: var(--critical-soft); }
2023
+ .alt-diff-line[data-kind="del"] .alt-diff-text { text-decoration: line-through; text-decoration-color: color-mix(in oklab, var(--critical) 60%, transparent); }
2024
+ .alt-diff-line[data-kind="add"] .alt-diff-sign { color: var(--positive); }
2025
+ .alt-diff-line[data-kind="del"] .alt-diff-sign { color: var(--critical); }
2026
+ .alt-diff-word { color: inherit; border-radius: 2px; }
2027
+ .alt-diff-line[data-kind="add"] .alt-diff-word { background: color-mix(in oklab, var(--positive) 22%, transparent); }
2028
+ .alt-diff-line[data-kind="del"] .alt-diff-word { background: color-mix(in oklab, var(--critical) 18%, transparent); }
2029
+ .alt-diff-fold button { display: flex; align-items: center; gap: 6px; width: 100%; padding: 4px var(--space-4) 4px 28px; border: 0; border-block: 1px solid var(--line); background: var(--surface-ground); font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); cursor: pointer; text-align: left; }
2030
+ /* Reviewing (#282): a change awaiting a decision is a proposal, raised at level 1 around its lines (as PropertyList's
2031
+ proposed value); deciding lowers it to the record: accepted flush with one afterglow, rejected muted and collapsed. */
2032
+ .alt-diff-hunk { position: relative; margin: 4px 6px; border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--elev-1); overflow: hidden;
2033
+ transition: box-shadow var(--dur-travel-1) var(--ease-sink), background-color var(--dur-travel-1) var(--ease-sink), margin var(--dur-travel-1) var(--ease-sink); }
2034
+ .alt-diff-hunk[data-decision] { margin-inline: 0; border-radius: 0; background: none; box-shadow: none; }
2035
+ .alt-diff-hunk[data-decision="reject"] { color: var(--ink-muted); }
2036
+ .alt-diff-hunk[data-decision="reject"] .alt-diff-line { opacity: 0.6; }
2037
+ .alt-diff-hunk-bar { position: sticky; left: 0; display: flex; align-items: center; justify-content: flex-end; gap: 2px; width: min(100%, 100cqw); padding: 2px 6px 2px 28px; font: 500 12.5px/17px var(--font-sans); }
2038
+ .alt-diff-hunk-state { display: inline-flex; align-items: center; gap: 5px; margin-right: auto; color: var(--ink-muted); }
2039
+ .alt-diff-hunk-folded { display: block; width: 100%; padding: 4px var(--space-4) 4px 28px; border: 0; background: none; color: var(--ink-muted); font: 500 12.5px/17px var(--font-sans); text-align: left; cursor: pointer; }
2040
+ .alt-diff-hunk-folded:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
2041
+ .alt-diff-body { container-type: inline-size; }
2042
+ @media (prefers-reduced-motion: reduce) { .alt-diff-hunk { transition: none; } }
2043
+ @media (forced-colors: active) { .alt-diff-hunk:not([data-decision]) { border: 1px solid CanvasText; } }
2044
+ @media (hover: hover) { .alt-diff-fold button:hover { background: var(--surface-raised); color: var(--ink); } }
2045
+ .alt-diff-fold button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
2046
+ .alt-diff-props { display: flex; flex-direction: column; margin: 0; padding: 0 var(--space-4); }
2047
+ .alt-diff-prop { display: grid; grid-template-columns: minmax(96px, 30%) 1fr; gap: var(--space-3); padding: 8px 0; border-bottom: 1px solid var(--line); font: 400 14px/20px var(--font-sans); outline: none; }
2048
+ .alt-diff-prop:last-child { border-bottom: 0; }
2049
+ .alt-diff-prop:focus-visible { box-shadow: inset 2px 0 0 var(--accent-ink); }
2050
+ .alt-diff-prop dt { color: var(--ink-muted); }
2051
+ .alt-diff-prop dd { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; }
2052
+ .alt-diff-old { padding: 0 4px; border-radius: var(--radius-xs); background: var(--critical-soft); text-decoration: line-through; color: var(--ink); }
2053
+ .alt-diff-new { padding: 0 4px; border-radius: var(--radius-xs); background: var(--positive-soft); text-decoration: none; color: var(--ink); font-weight: 500; }
2054
+ .alt-diff-arrow { transform: rotate(-90deg); color: var(--ink-muted); }
2055
+ [data-contrast="more"] .alt-diff-line[data-kind="add"] { box-shadow: inset 2px 0 0 var(--positive); }
2056
+ [data-contrast="more"] .alt-diff-line[data-kind="del"] { box-shadow: inset 2px 0 0 var(--critical); }
2057
+ @media (forced-colors: active) { .alt-diff-line[data-kind], .alt-diff-old, .alt-diff-new, .alt-diff-word { background: none; } .alt-diff { border: 1px solid CanvasText; } }
2058
+
2059
+ /* ── SaveBar: an unsaved draft at page scope is something in your hand, so the bar floats at level 3, docked to the
2060
+ bottom edge (below a Sheet or a Dialog, never competing with them). It rises from the edge once a change has settled
2061
+ into the draft; saving hovers as a pending write; saved, it lowers away while the fields settle; a failed save drops
2062
+ it to a trench, keeping the draft. ── */
2063
+ .alt-savebar {
2064
+ position: fixed; left: 50%; bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px)); z-index: var(--z-floating); translate: -50% 0;
2065
+ display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); width: max-content; max-width: calc(100vw - 32px);
2066
+ padding: 8px 8px 8px var(--space-4); border-radius: var(--radius-lg); background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--ink);
2067
+ transition: opacity var(--dur-travel-3) var(--ease-rise), transform var(--dur-travel-3) var(--ease-rise), background-color var(--dur-travel-2), box-shadow var(--dur-travel-2);
2068
+ }
2069
+ .alt-savebar[data-state="closed"] { opacity: 0; transform: translateY(calc(16px * var(--alt-travel))); transition-timing-function: var(--ease-settle); }
2070
+ .alt-savebar[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench); outline: 1px solid var(--critical); animation-duration: var(--dur-travel-5); }
2071
+ .alt-savebar-text { display: inline-flex; align-items: center; gap: 6px; font: 500 14px/20px var(--font-sans); }
2072
+ .alt-savebar[data-status="failed"] .alt-savebar-text svg { color: var(--critical); }
2073
+ .alt-savebar-actions { display: inline-flex; gap: var(--space-2); margin-left: auto; }
2074
+ [data-contrast="more"] .alt-savebar { box-shadow: 0 3px 0 0 var(--ink), var(--elev-3); }
2075
+ @media (forced-colors: active) { .alt-savebar { border: 2px solid CanvasText; } .alt-savebar[data-status="pending"] { border-style: dotted; } .alt-savebar[data-status="failed"] { border: 3px dashed CanvasText; } }
2076
+
2077
+ /* ── Settings: a section is a raised Card (level 1) of rows; the danger zone is the same card with a critical rim,
2078
+ never higher. A row is flush, label and description on the start side, the control on the end side, stacked when
2079
+ the section is narrow. An apply-now control's write is the row's: pending hovers it, committed lands, failed drops
2080
+ it into a trench, with the state in words beside the control. ── */
2081
+ .alt-settings-section { container-type: inline-size; padding: var(--alt-inset-box); gap: var(--space-3); }
2082
+ .alt-settings-section[data-tone="critical"] { box-shadow: var(--elev-1), inset 0 0 0 1px var(--critical); }
2083
+ .alt-settings-head { display: flex; flex-direction: column; gap: var(--space-1); }
2084
+ .alt-settings-title { margin: 0; font: 600 17px/24px var(--font-sans); letter-spacing: -0.01em; }
2085
+ .alt-settings-desc, .alt-settings-readonly { margin: 0; font: 400 14px/20px var(--font-sans); color: var(--ink-muted); }
2086
+ .alt-settings-readonly { display: flex; gap: 6px; align-items: flex-start; }
2087
+ .alt-settings-readonly svg { flex: none; margin-top: 3px; }
2088
+ .alt-settings-rows { margin: 0; padding: 0; border: 0; min-width: 0; display: flex; flex-direction: column; }
2089
+ .alt-settings-row {
2090
+ display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-2) var(--space-5);
2091
+ margin-inline: calc(-1 * var(--space-3)); padding: var(--space-3); border-radius: var(--radius-md);
2092
+ }
2093
+ .alt-settings-row + .alt-settings-row { box-shadow: inset 0 1px 0 var(--line); }
2094
+ .alt-settings-row[data-status="pending"] { background: var(--surface-raised); }
2095
+ .alt-settings-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
2096
+ .alt-settings-label { font: 500 14px/20px var(--font-sans); color: var(--ink); }
2097
+ .alt-settings-rowdesc, .alt-settings-error { margin: 0; font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
2098
+ .alt-settings-error { display: flex; gap: 6px; align-items: flex-start; color: var(--critical); }
2099
+ .alt-settings-error svg { flex: none; margin-top: 2px; }
2100
+ .alt-settings-control { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; min-width: 0; }
2101
+ .alt-settings-control > .alt-input, .alt-settings-control > .alt-select { width: min(280px, 100%); }
2102
+ .alt-settings-status { font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2103
+ .alt-settings-status:empty { display: none; }
2104
+ .alt-settings-row[data-status="failed"] .alt-settings-status { color: var(--critical); }
2105
+ @container (max-width: 480px) {
2106
+ .alt-settings-row { grid-template-columns: minmax(0, 1fr); }
2107
+ .alt-settings-control { align-items: flex-start; }
2108
+ }
2109
+ @media (forced-colors: active) { .alt-settings-section[data-tone="critical"] { border: 2px solid CanvasText; } }
2110
+
2111
+ /* ── PermissionMatrix: roles against permissions. The grid is settled, on the ground with line dividers. An editable
2112
+ grant is a choice, a Checkbox's trench that the accent pours into; an inherited or fixed grant is already decided, so
2113
+ it's a flush stamped check (or a dash), muted, like a done Checklist item: pressed into the record, not disabled. The
2114
+ active cell is a 2px accent-ink ring, never a height. A saving cell hovers; a saved one settles. ── */
2115
+ .alt-matrix { overflow-x: auto; }
2116
+ .alt-matrix-grid { border-collapse: separate; border-spacing: 0; min-width: 100%; font: 400 14px/20px var(--font-sans); color: var(--ink); }
2117
+ .alt-matrix-grid th, .alt-matrix-grid td { padding: 8px 12px; border-bottom: 1px solid var(--line); text-align: center; }
2118
+ .alt-matrix-grid thead th { font: 600 13px/18px var(--font-sans); color: var(--ink); white-space: nowrap; }
2119
+ .alt-matrix-grid .alt-matrix-perm { text-align: left; font-weight: 400; white-space: nowrap; }
2120
+ .alt-matrix-group th { padding-top: var(--space-4); text-align: left; font: 600 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2121
+ .alt-matrix-cell { position: relative; }
2122
+ .alt-matrix-box { display: inline-grid; place-items: center; width: 30px; height: 30px; border-radius: var(--radius-sm); cursor: pointer; outline: none; }
2123
+ .alt-matrix-box[data-fixed] { cursor: help; }
2124
+ .alt-matrix-box:focus-visible { box-shadow: 0 0 0 2px var(--accent-ink); }
2125
+ .alt-matrix-box[data-on] .alt-check-fill { opacity: 1; clip-path: circle(75% at 50% 50%); }
2126
+ .alt-matrix-box[data-on] .alt-check-tick { stroke-dashoffset: 0; transition: stroke-dashoffset var(--dur-travel-2) var(--ease-rise) 60ms; }
2127
+ .alt-matrix-box[data-on] .alt-check-track { border-color: var(--accent); }
2128
+ .alt-matrix-stamp { display: grid; place-items: center; width: 22px; height: 22px; color: var(--ink-muted); }
2129
+ .alt-matrix-dash { display: block; width: 10px; height: 2px; border-radius: 1px; background: var(--ink-faint); }
2130
+ [data-contrast="more"] .alt-matrix-box:focus-visible { box-shadow: 0 0 0 3px var(--accent-ink); }
2131
+ @media (forced-colors: active) { .alt-matrix-box:focus-visible { box-shadow: none; outline: 2px solid Highlight; } .alt-matrix-dash { background: CanvasText; } }
2132
+
2133
+ /* ── PlanPicker: plans are positions in one choice, so the cards lie flush on the ground (a line rim) and one raised
2134
+ mark, the radio thumb's build, travels to the chosen card's corner on ease-glide; the chosen card also takes an
2135
+ accent rim. Pointing at another card shows a ghost of the mark there. "Current plan" and "Recommended" are flush plates,
2136
+ never extra height: the only level-2 slab is the Continue button beside it. A period change cross-fades the prices. ── */
2137
+ .alt-plans { display: flex; flex-direction: column; gap: var(--space-4); }
2138
+ .alt-plans-period { align-self: flex-start; }
2139
+ .alt-plans-grid { position: relative; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-3); }
2140
+ .alt-plan {
2141
+ position: relative; display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4); border-radius: var(--radius-lg);
2142
+ background: var(--surface-ground); box-shadow: inset 0 0 0 1px var(--line); cursor: pointer; outline: none;
2143
+ transition: box-shadow var(--dur-travel-2) var(--ease-rise);
2144
+ }
2145
+ .alt-plan[data-chosen] { box-shadow: inset 0 0 0 2px var(--accent); }
2146
+ .alt-plan[aria-disabled] { cursor: not-allowed; color: var(--ink-faint); }
2147
+ .alt-plan:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
2148
+ .alt-plan::after {
2149
+ content: ""; position: absolute; top: 15px; right: 15px; width: 14px; height: 14px; border-radius: var(--radius-full);
2150
+ background: var(--surface-raised); box-shadow: var(--elev-1); opacity: 0; scale: 0.5; pointer-events: none; transition: opacity var(--dur-travel-1) var(--ease-sink), scale var(--dur-travel-2) var(--ease-settle);
2151
+ }
2152
+ @media (hover: hover) { .alt-plan:hover:not([data-chosen]):not([aria-disabled])::after { opacity: 0.5; scale: 0.85; } }
2153
+ .alt-plans-mark { position: absolute; left: 0; top: 0; width: 14px; height: 14px; margin: -7px 0 0 -7px; border-radius: var(--radius-full); background: var(--accent); box-shadow: var(--elev-1), inset 0 0 0 3px var(--surface-raised); pointer-events: none; }
2154
+ .alt-plans-mark[data-ready] { transition: transform var(--dur-travel-3) var(--ease-glide), opacity var(--dur-travel-1); }
2155
+ .alt-plan-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding-right: 24px; }
2156
+ .alt-plan-name { font: 600 16px/22px var(--font-sans); }
2157
+ .alt-plan-plates { display: inline-flex; flex-wrap: wrap; gap: 4px; }
2158
+ .alt-plan-price { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px; }
2159
+ .alt-plan-amount { font: 600 26px/32px var(--font-sans); letter-spacing: -0.015em; }
2160
+ .alt-plan-per { font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
2161
+ .alt-plan-desc { font: 400 13.5px/19px var(--font-sans); color: var(--ink-muted); }
2162
+ .alt-plan-desc p { margin: 0 0 var(--space-2); }
2163
+ .alt-plan-desc ul { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; color: var(--ink); }
2164
+ .alt-plan-desc li { display: flex; gap: 6px; align-items: flex-start; }
2165
+ .alt-plan-desc li svg { flex: none; margin-top: 2px; color: var(--positive); }
2166
+ .alt-plan-why { margin: 0; color: var(--ink-muted); }
2167
+ @media (prefers-reduced-motion: reduce) { .alt-plans-mark[data-ready] { transition: opacity var(--dur-travel-1); } }
2168
+ [data-motion="reduced"] .alt-plans-mark[data-ready] { transition: opacity var(--dur-travel-1); }
2169
+ [data-contrast="more"] .alt-plans-mark { box-shadow: 0 2px 0 0 var(--ink), inset 0 0 0 3px var(--surface-raised); }
2170
+ @media (forced-colors: active) { .alt-plan { border: 1px solid CanvasText; } .alt-plan[data-chosen] { border-width: 2px; } .alt-plans-mark { forced-color-adjust: none; outline: 1px solid CanvasText; } }
2171
+
2172
+ /* ── FeatureGate: locked, not disabled. Disabled means nothing happens; a locked control explains the plan and offers
2173
+ the upgrade, so it keeps its own build and normal ink and lifts when pointed at (hover is a promise), with a lock and
2174
+ a flush plan plate. Pressing it opens a Popover at level 3 with the upgrade as its one primary. ── */
2175
+ .alt-gate { display: inline-flex; }
2176
+ .alt-gate-trigger .alt-btn-label, .alt-gate-trigger { gap: 6px; }
2177
+ .alt-gate-lock { flex: none; color: var(--ink-muted); }
2178
+ .alt-gate-plan { margin-left: 2px; }
2179
+ .alt-gate-pop { width: min(300px, calc(100vw - 32px)); }
2180
+ .alt-gate-body { display: flex; flex-direction: column; gap: var(--space-2); }
2181
+ .alt-gate-title { display: flex; align-items: center; gap: 6px; margin: 0; font: 600 14px/20px var(--font-sans); color: var(--ink); }
2182
+ .alt-gate-desc, .alt-gate-admin { margin: 0; font: 400 13.5px/19px var(--font-sans); color: var(--ink-muted); }
2183
+ .alt-gate-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin-top: var(--space-1); font: 400 13.5px/19px var(--font-sans); }
2184
+
2185
+ /* ── CommentThread: posted comments are the record, flush on the ground; the composer is an Input well that fills; a
2186
+ comment being posted hovers as pending, lands and settles, or drops to a trench keeping its text. Mentions are flush
2187
+ accent-soft plates in the text and a listbox at level 3 while writing. A resolved thread is stamped like a done
2188
+ Checklist item: muted, collapsed to one line, kept in place. Someone typing shows their presence colour, quietly. ── */
2189
+ .alt-comments { display: flex; flex-direction: column; gap: var(--space-3); }
2190
+ .alt-comment-list { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
2191
+ .alt-comment { border-radius: var(--radius-md); }
2192
+ .alt-comment-article { display: flex; gap: 10px; padding: 8px; }
2193
+ .alt-comment-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
2194
+ .alt-comment-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
2195
+ .alt-comment-author { font: 600 14px/20px var(--font-sans); color: var(--ink); }
2196
+ .alt-comment-time { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2197
+ .alt-comment-failed { display: inline-flex; align-items: center; gap: 4px; color: var(--critical); }
2198
+ .alt-comment-body { margin: 0; font: 400 14px/21px var(--font-sans); color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
2199
+ .alt-comment-mention { padding: 0 3px; border-radius: var(--radius-xs); background: var(--accent-soft); color: var(--accent-ink); font-weight: 500; }
2200
+ .alt-comment-actions { display: flex; gap: var(--space-2); margin-top: 4px; }
2201
+ .alt-comment[data-deleted] { display: flex; align-items: center; gap: var(--space-2); padding: 8px 8px 8px 46px; font: 400 13px/18px var(--font-sans); color: var(--ink-muted); font-style: italic; }
2202
+ .alt-comments-resolved { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding: 6px 8px; border-radius: var(--radius-md); background: var(--surface-well); box-shadow: var(--well); }
2203
+ .alt-comments-stamp { display: inline-flex; align-items: center; gap: 6px; margin-right: auto; font: 500 13px/18px var(--font-sans); color: var(--ink-muted); }
2204
+ .alt-comments[data-resolved] .alt-comment-list { opacity: 0.7; }
2205
+ .alt-comments-compose { display: flex; flex-direction: column; gap: var(--space-2); }
2206
+ .alt-comments-field { position: relative; }
2207
+ .alt-comments-field textarea { min-height: 56px; }
2208
+ .alt-comments-mentions { top: calc(100% + 4px); left: 0; min-width: 220px; padding: var(--space-1); }
2209
+ .alt-comments-option { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: var(--radius-sm); font: 400 14px/20px var(--font-sans); cursor: pointer; }
2210
+ .alt-comments-option[data-active] { background: var(--accent-soft); }
2211
+ .alt-comments-actions { display: flex; justify-content: flex-end; gap: var(--space-2); }
2212
+ .alt-comments-typing { display: flex; align-items: center; gap: 6px; margin: 0; font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2213
+ .alt-comments-dots { display: inline-flex; gap: 3px; }
2214
+ .alt-comments-dots i { width: 5px; height: 5px; border-radius: var(--radius-full); background: var(--presence-1); animation: alt-typing 1.2s ease-in-out infinite; }
2215
+ .alt-comments-dots i:nth-child(2) { animation-delay: 0.15s; } .alt-comments-dots i:nth-child(3) { animation-delay: 0.3s; }
2216
+ @keyframes alt-typing { 50% { opacity: 0.35; } }
2217
+ @media (prefers-reduced-motion: reduce) { .alt-comments-dots i { animation: none; } }
2218
+ [data-motion="reduced"] .alt-comments-dots i { animation: none; }
2219
+ @media (forced-colors: active) { .alt-comment-mention { border: 1px solid CanvasText; } }
2220
+
2221
+ /* ── Tour: a note in the air above the page. One step at a time floats at level 3 beside its target, which takes a
2222
+ 2px accent-ink ring (a ring, not a lift: the target may not be something you press). It never dims the page,
2223
+ because receding is the summit's privilege, and it isn't modal. Moving on lifts the step off; the next rises at its
2224
+ own target. ── */
2225
+ .alt-tour {
2226
+ position: fixed; z-index: var(--z-floating); width: min(320px, calc(100vw - 16px)); padding: var(--space-4); border-radius: var(--radius-lg);
2227
+ background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--ink); outline: none;
2228
+ animation: alt-emerge var(--dur-travel-3) var(--ease-rise); --alt-from-y: 6px;
2229
+ }
2230
+ .alt-tour[data-side="above"] { --alt-from-y: -6px; }
2231
+ .alt-tour:focus-visible { box-shadow: var(--elev-3), 0 0 0 2px var(--accent-ink); }
2232
+ .alt-tour-count { margin: 0 0 2px; font: 500 12px/16px var(--font-sans); color: var(--ink-muted); }
2233
+ .alt-tour-title { margin: 0 24px 6px 0; font: 600 15px/21px var(--font-sans); }
2234
+ .alt-tour-body { font: 400 14px/20px var(--font-sans); color: var(--ink-muted); }
2235
+ .alt-tour-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-3); }
2236
+ .alt-tour-dots { display: inline-flex; gap: 4px; margin-right: auto; }
2237
+ .alt-tour-dots i { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--line-control); }
2238
+ .alt-tour-dots i[data-on] { background: var(--accent); }
2239
+ .alt-tour-close { position: absolute; top: 8px; right: 8px; }
2240
+ [data-tour-target] { outline: 2px solid var(--accent-ink) !important; outline-offset: 3px !important; border-radius: var(--radius-sm); }
2241
+ [data-contrast="more"] .alt-tour { box-shadow: 0 4px 0 0 var(--ink), var(--elev-3); }
2242
+ @media (forced-colors: active) { .alt-tour { border: 2px solid CanvasText; } [data-tour-target] { outline-color: Highlight !important; } }
2243
+
2244
+ /* ── PromptInput: where a person writes to an agent. The whole composer is the Input well (empty: its back wall shows and
2245
+ it deepens as the pointer nears; holding text: the floor rises flush), with attachments as Tag plates above the text
2246
+ and a toolbar below. Send is the level-2 primary; while generating it becomes Stop in the same slot and size, its icon
2247
+ cross-fading without travel. An empty composer's Send is flush and faint but stays focusable (aria-disabled).
2248
+ Dragging a file over drops the well to a trench, as DropZone. ── */
2249
+ .alt-prompt { position: relative; }
2250
+ .alt-prompt-well { position: relative; gap: var(--space-2); padding: var(--space-3); border-radius: var(--radius-lg); }
2251
+ .alt-prompt-well .alt-prompt-text { width: 100%; min-height: 24px; resize: none; transition: height var(--dur-travel-2) var(--ease-glide); }
2252
+ .alt-prompt-files { display: flex; flex-wrap: wrap; gap: var(--space-2); }
2253
+ /* @ mentions and / commands (#283): the list is a floating Combobox list (level 3) rising above the composer, grouped
2254
+ with headings; what's chosen is a flush plate in the composer's context row ("@label" stays in the text too). */
2255
+ .alt-prompt-suggest { position: absolute; left: 0; bottom: calc(100% + 6px); z-index: 20; width: min(420px, 100%); max-height: 280px; overflow: auto; padding: 4px; }
2256
+ .alt-prompt-suggest-head { margin: 4px 8px 2px; font: 500 12px/16px var(--font-sans); color: var(--ink-muted); }
2257
+ .alt-prompt-option { display: flex; flex-direction: column; gap: 1px; padding: 6px 8px; border-radius: var(--radius-sm); cursor: pointer; }
2258
+ .alt-prompt-option[data-active] { background: var(--surface-raised); box-shadow: var(--elev-1); }
2259
+ .alt-prompt-option-label { font: 500 14px/20px var(--font-sans); color: var(--ink); }
2260
+ .alt-prompt-option-label[data-kind="command"], .alt-prompt-command .alt-tag-face { font-family: var(--font-mono); }
2261
+ .alt-prompt-option-desc { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2262
+ .alt-prompt-searching { display: flex; align-items: center; gap: 8px; margin: 0; padding: 8px; font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
2263
+ .alt-prompt-searching .alt-skeleton { width: 80px; height: 10px; }
2264
+ /* Dictation (#284): listening is a latched toggle (a shallow well with accent-soft poured in, a steady lamp and the
2265
+ word), never a pulse or a waveform; interim words are a ghost in ink-faint, like a suggestion, never in the value. */
2266
+ .alt-btn.alt-prompt-dictate[data-listening], .alt-btn.alt-prompt-dictate[data-listening]:focus-visible, .alt-btn.alt-prompt-dictate[data-listening]:active:not(:disabled) {
2267
+ --wall: 0px; background: var(--accent-soft); background-image: none; color: var(--accent-ink); box-shadow: var(--well); transform: none; }
2268
+ .alt-prompt-listening { display: inline-flex; align-items: center; gap: 5px; margin-left: 4px; font: 500 12.5px/17px var(--font-sans); }
2269
+ .alt-prompt-lamp { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 22%, transparent); }
2270
+ .alt-prompt-dictate[data-blocked] { color: var(--ink-faint); }
2271
+ .alt-prompt-interim { margin: 2px 0 0; font: 400 15px/23px var(--font-sans); color: var(--ink-faint); white-space: pre-wrap; }
2272
+ @media (forced-colors: active) { .alt-btn.alt-prompt-dictate[data-listening] { border: 2px solid Highlight; } }
2273
+ .alt-prompt-context { display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 8px 0; }
2274
+ .alt-prompt-kind { width: 6px; height: 6px; border-radius: 50%; background: var(--ink-muted); }
2275
+ .alt-prompt-kind[data-kind="file"] { background: var(--accent-ink); }
2276
+ .alt-prompt-kind[data-kind="person"] { background: var(--positive); }
2277
+ .alt-prompt-chip { display: inline-flex; border-radius: var(--radius-sm); }
2278
+ .alt-prompt-uploading { color: var(--ink-muted); }
2279
+ .alt-prompt-bar { display: flex; align-items: center; gap: var(--space-2); }
2280
+ .alt-prompt-send { margin-left: auto; }
2281
+ .alt-prompt-send svg { animation: alt-fade-in var(--dur-travel-1) var(--ease-rise); }
2282
+ @keyframes alt-fade-in { from { opacity: 0; } }
2283
+ .alt-prompt-send[aria-disabled] {
2284
+ background: var(--surface-well); background-image: none; color: var(--ink-faint); box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); transform: none; cursor: not-allowed;
2285
+ }
2286
+ @media (hover: hover) { .alt-prompt-send[aria-disabled]:is(:hover, :active, :focus-visible) {
2287
+ background: var(--surface-well); background-image: none; color: var(--ink-faint); box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); transform: none; cursor: not-allowed;
2288
+ } }
2289
+ .alt-prompt-well[data-drop="over"] { background: var(--accent-soft); box-shadow: var(--trench); border-color: var(--accent-ink); }
2290
+ .alt-prompt-drop { position: absolute; inset: 0; display: grid; place-items: center; font: 500 14px/20px var(--font-sans); color: var(--accent-ink); background: color-mix(in oklab, var(--accent-soft), transparent 15%); pointer-events: none; }
2291
+ @media (prefers-reduced-motion: reduce) { .alt-prompt-text { transition: none; } }
2292
+ [data-motion="reduced"] .alt-prompt-text { transition: none; }
2293
+ @media (forced-colors: active) { .alt-prompt-send[aria-disabled] { border: 1px solid GrayText; color: GrayText; } .alt-prompt-well[data-drop="over"] { border: 2px dashed CanvasText; } }
2294
+
2295
+ /* ── Conversation: settled messages are the record. A person's message is a flush accent-soft sheet; an assistant's is
2296
+ flush with no bubble. A streaming answer isn't the record yet and isn't a pending write either (nothing you sent is in
2297
+ flight), so it rests on the ground with charts' "not yet the record" treatment: a dashed line-control rule down its
2298
+ left edge, "Generating…" and a caret. Completing makes the rule solid and settles it once. "Jump to latest" floats at
2299
+ level 3 once you've scrolled up. ── */
2300
+ .alt-convo { position: relative; display: flex; flex-direction: column; min-height: 0; }
2301
+ .alt-convo-scroll { flex: 1; min-height: 0; overflow-y: auto; outline: none; border-radius: var(--radius-md); }
2302
+ .alt-convo-scroll:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
2303
+ /* The scrollbar sits beside the messages, never over them: a classic bar gets a gutter of its own, and the content keeps
2304
+ 12px clear of the right edge, where an overlay bar (macOS, phones) is drawn. */
2305
+ .alt-convo-scroll { scrollbar-gutter: stable; scrollbar-width: thin; }
2306
+ .alt-convo-content { display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-3) var(--space-3) var(--space-3) var(--space-1); }
2307
+ .alt-msg { display: flex; flex-direction: column; gap: 4px; max-width: 72ch; border-radius: var(--radius-md); }
2308
+ .alt-msg[data-from="user"] { align-self: flex-end; max-width: min(72ch, 85%); padding: 10px 14px; background: var(--accent-soft); color: var(--ink); }
2309
+ /* Attachments (#274): part of the record, on the ground. Images are 64px thumbnails in a 1px line rim; files are the
2310
+ composer's Tag plates, settled. One that opens lifts a level when pointed at, as any plate that acts. */
2311
+ .alt-msg-attachments { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; padding: 0; list-style: none; }
2312
+ .alt-msg[data-from="user"] .alt-msg-attachments { justify-content: flex-end; }
2313
+ .alt-msg-attachment { display: flex; min-width: 0; }
2314
+ .alt-msg-thumb { position: relative; display: block; height: 64px; padding: 0; border: 0; border-radius: var(--radius-md); background: none; overflow: hidden; cursor: default; transition: transform var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise); }
2315
+ .alt-msg-thumb img { display: block; height: 64px; width: auto; max-width: 160px; min-width: 48px; object-fit: cover; }
2316
+ .alt-msg-thumb::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px var(--line); pointer-events: none; }
2317
+ :is(a, button).alt-msg-thumb { cursor: pointer; }
2318
+ .alt-msg-file { height: 28px; }
2319
+ .alt-msg-file .alt-tag-face { max-width: 36ch; text-decoration: none; }
2320
+ :is(a, button).alt-tag-face { cursor: pointer; }
2321
+ .alt-msg-file-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2322
+ .alt-msg-file-meta { flex: none; color: var(--ink-muted); font-weight: 450; }
2323
+ .alt-msg-file[data-unavailable] { color: var(--ink-muted); }
2324
+ @media (hover: hover) {
2325
+ .alt-msg-file[data-acts]:hover { --t-bg: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
2326
+ :is(a, button).alt-msg-thumb:hover { box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
2327
+ }
2328
+ .alt-msg-thumb:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
2329
+ .alt-msg-file[data-acts]:active, :is(a, button).alt-msg-thumb:active { transform: none; box-shadow: none; transition-duration: var(--dur-press); }
2330
+ @media (forced-colors: active) { .alt-msg-thumb { border: 1px solid CanvasText; } }
2331
+ .alt-msg[data-from="assistant"] { border-radius: 0; padding: 2px 0 8px 14px; box-shadow: inset 2px 0 0 0 transparent; transition: box-shadow var(--dur-travel-2) var(--ease-rise); }
2332
+ .alt-msg[data-stream="streaming"] { box-shadow: none; background-image: linear-gradient(var(--line-control) 50%, transparent 50%); background-size: 2px 8px; background-repeat: repeat-y; background-position: left top; }
2333
+ .alt-msg[data-stream="complete"], .alt-msg[data-stream="stopped"], .alt-msg[data-stream="error"] { box-shadow: inset 2px 0 0 0 var(--line); }
2334
+ .alt-msg[data-stream="error"] { box-shadow: inset 2px 0 0 0 var(--critical); }
2335
+ .alt-msg-name { margin: 0; font: 600 13px/18px var(--font-sans); color: var(--ink-muted); }
2336
+ .alt-msg-content { font: 400 15px/23px var(--font-sans); color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
2337
+
2338
+ /* ---- Terminal (#273) ----
2339
+ A command's output in CodeBlock's frame: flush on the ground, a 1px rim, mono. Running, it isn't the record yet, so
2340
+ a dashed line-control rule runs down the left; finished, the rule goes solid (critical when the exit is non-zero)
2341
+ with one afterglow. stderr lines carry a critical mark in the gutter. "Jump to latest output" floats at level 3. */
2342
+ .alt-term { position: relative; border-left-width: 2px; }
2343
+ .alt-term[data-status="running"] { border-left-style: dashed; border-left-color: var(--line-control); }
2344
+ .alt-term-cmd { min-width: 0; font: 500 13px/18px var(--font-mono); color: var(--ink); overflow-wrap: anywhere; }
2345
+ .alt-term-prompt { color: var(--ink-muted); }
2346
+ .alt-term-viewport { position: relative; }
2347
+ .alt-term-body { max-height: 320px; overflow: auto; scrollbar-width: thin; outline: none; }
2348
+ .alt-term-body:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
2349
+ .alt-term-pre { margin: 0; padding: var(--space-3) var(--space-4); font: 400 13px/20px var(--font-mono); color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 4; }
2350
+ .alt-term-line { position: relative; display: block; min-height: 20px; }
2351
+ .alt-term-line[data-stream="stderr"]::before { content: ""; position: absolute; left: calc(-1 * var(--space-4) + 6px); top: 3px; bottom: 3px; width: 3px; border-radius: 2px; background: var(--critical); }
2352
+ .alt-term [data-tone="critical"] { color: var(--critical); }
2353
+ .alt-term [data-tone="caution"] { color: var(--caution); }
2354
+ .alt-term [data-tone="positive"] { color: var(--positive); }
2355
+ .alt-term [data-tone="accent"] { color: var(--accent-ink); }
2356
+ .alt-term [data-tone="muted"] { color: var(--ink-muted); }
2357
+ .alt-term [data-bold] { font-weight: 600; }
2358
+ .alt-term-more { display: block; margin: var(--space-2) var(--space-4) 0; padding: 0; border: 0; background: none; color: var(--accent-ink); font: 500 12.5px/24px var(--font-sans); text-decoration: underline; cursor: pointer; }
2359
+ .alt-term-exit { display: flex; align-items: center; gap: 6px; padding: 8px var(--space-4); border-top: 1px solid var(--line); font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
2360
+ .alt-term-exit[data-tone="critical"] { color: var(--critical); }
2361
+ [data-contrast="more"] .alt-term { border-color: var(--line-control); }
2362
+ @media (forced-colors: active) {
2363
+ .alt-term { border-color: CanvasText; }
2364
+ .alt-term[data-status="running"] { border-left-style: dotted; }
2365
+ .alt-term-line[data-stream="stderr"]::before { background: CanvasText; }
2366
+ }
2367
+
2368
+ /* ---- Suggestions (#272) ----
2369
+ Prompts to start from or follow up with are things you press: small secondary buttons, raised at level 1 with their
2370
+ wall, lifting to 2 when pointed at, never the view's level-2 primary (that's the composer's Send). Follow-ups merely
2371
+ became available, so they cross-fade in under the answer; they don't rise. */
2372
+ .alt-suggestions { display: flex; flex-wrap: wrap; gap: 6px; }
2373
+ .alt-suggestions[data-arrive] { animation: alt-fade-in var(--dur-onset) var(--ease-rise) both; }
2374
+ .alt-suggestion { max-width: 100%; }
2375
+ .alt-suggestion-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2376
+ .alt-msg .alt-suggestions { margin-top: 4px; }
2377
+ .alt-empty .alt-suggestions { justify-content: center; }
2378
+ @media (prefers-reduced-motion: reduce) { .alt-suggestions[data-arrive] { animation: none; } }
2379
+ [data-motion="reduced"] .alt-suggestions[data-arrive] { animation: none; }
2380
+
2381
+ /* ---- Markdown (#268) ----
2382
+ An answer's text set as the record: everything sits on the ground at level 0 and nothing here adds height. Code is
2383
+ CodeBlock, links are Link, [n] markers are InlineCitation (the only part that lifts, when pointed at), tables use
2384
+ Table's flush rows with line dividers, and an image waits in a sunk imprint (-1) and rises out of it. Headings are
2385
+ the heading style, never the title (which belongs to the page). Rims and rules are line; under contrast="more"
2386
+ they're line-control, and in forced colours CanvasText. */
2387
+ .alt-md { font: 400 15px/23px var(--font-sans); color: var(--ink); white-space: normal; overflow-wrap: anywhere; }
2388
+ .alt-md :is(p, .alt-md-list, .alt-md-quote, .alt-md-pre, .alt-md-table, .alt-md-html) { margin: 0 0 12px; }
2389
+ /* After the block margins: at equal specificity the later rule wins, so the last block doesn't carry 12px out of the answer. */
2390
+ .alt-md > :first-child { margin-top: 0; }
2391
+ .alt-md > :last-child { margin-bottom: 0; }
2392
+ .alt-md :is(li, blockquote) > :last-child { margin-bottom: 0; }
2393
+ .alt-md-h { margin: 20px 0 8px; color: var(--ink); font: 600 15px/23px var(--font-sans); }
2394
+ .alt-md-h[data-depth="1"] { font-size: 19px; line-height: 26px; letter-spacing: -0.01em; }
2395
+ .alt-md-h[data-depth="2"] { font-size: 17px; line-height: 24px; }
2396
+ .alt-md-list { padding-inline-start: 1.5em; }
2397
+ .alt-md-list li + li { margin-top: 4px; }
2398
+ .alt-md-list .alt-md-list { margin: 4px 0 0; }
2399
+ .alt-md-list li::marker { color: var(--ink-muted); }
2400
+ .alt-md-task-item { list-style: none; display: flex; gap: 8px; align-items: flex-start; margin-inline-start: -1.5em; }
2401
+ .alt-md-task { flex: none; display: inline-grid; place-items: center; width: 16px; height: 16px; margin-top: 3.5px; border-radius: 4px; box-shadow: inset 0 0 0 1px var(--line-control); color: var(--on-accent); }
2402
+ .alt-md-task[data-done] { background: var(--accent); box-shadow: none; }
2403
+ .alt-md-task-body { min-width: 0; }
2404
+ .alt-md-code { padding: 1px 5px; border-radius: var(--radius-sm); box-shadow: inset 0 0 0 1px var(--line); font: 400 0.87em/1 var(--font-mono); overflow-wrap: anywhere; }
2405
+ .alt-md-quote { margin-inline: 0; padding-inline-start: 12px; border-inline-start: 2px solid var(--line); color: var(--ink-muted); }
2406
+ .alt-md-hr { margin: 16px 0; }
2407
+ .alt-md-table { overflow-x: auto; outline: none; scrollbar-width: thin; }
2408
+ .alt-md-table:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--radius-md); }
2409
+ .alt-md-table .alt-table { width: auto; min-width: 100%; }
2410
+ .alt-md-table .alt-th, .alt-md-table .alt-td { padding: 8px 12px; }
2411
+ .alt-md del { color: var(--ink-muted); }
2412
+ .alt-md-img { position: relative; display: inline-block; max-width: 100%; vertical-align: top; }
2413
+ .alt-md-img img { display: block; max-width: 100%; height: auto; border-radius: var(--radius-md); transition: opacity var(--dur-travel-1) var(--ease-rise), translate var(--dur-travel-1) var(--ease-rise); }
2414
+ .alt-md-img[data-state="wait"] { width: min(100%, 320px); aspect-ratio: 16 / 9; }
2415
+ .alt-md-img[data-state="wait"] img { position: absolute; inset: 0; opacity: 0; translate: 0 2px; }
2416
+ .alt-md-img-imprint { position: absolute; inset: 0; border-radius: var(--radius-md); }
2417
+ .alt-md-img-failed { color: var(--ink-muted); font-style: italic; }
2418
+ @media (prefers-reduced-motion: reduce) { .alt-md-img img { transition: none; translate: none; } }
2419
+ [data-contrast="more"] .alt-md-code { box-shadow: inset 0 0 0 1px var(--line-control); }
2420
+ [data-contrast="more"] .alt-md-quote { border-inline-start-color: var(--line-control); }
2421
+ @media (forced-colors: active) {
2422
+ .alt-md-code, .alt-md-task { box-shadow: none; border: 1px solid CanvasText; }
2423
+ .alt-md-quote { border-inline-start-color: CanvasText; }
2424
+ .alt-md-task[data-done] { background: Highlight; color: HighlightText; }
2425
+ }
2426
+ .alt-msg-caret { display: inline-block; width: 7px; height: 1.05em; margin-left: 2px; vertical-align: text-bottom; background: var(--ink-muted); animation: alt-caret 1s steps(1) infinite; }
2427
+ @keyframes alt-caret { 50% { opacity: 0; } }
2428
+ .alt-msg-caption { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2429
+ .alt-msg-caption[data-tone="critical"] { color: var(--critical); }
2430
+ .alt-msg-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; margin-left: -6px; }
2431
+ .alt-msg-actions .alt-tgroup { gap: 2px; }
2432
+ .alt-msg-versions { display: inline-flex; align-items: center; gap: 2px; margin-right: 4px; }
2433
+ .alt-msg-version { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
2434
+ /* Editing a sent message (#275): the text stays where it is while a Textarea well opens around it, inside the sheet.
2435
+ Editing is local input, not a pending write, so nothing hovers; "Send edit" is secondary, leaving the composer's
2436
+ Send the view's one primary. */
2437
+ .alt-msg[data-from="user"][data-editing] { width: min(72ch, 85%); }
2438
+ .alt-msg-editor { display: flex; flex-direction: column; gap: 8px; }
2439
+ .alt-msg-editor .alt-textarea, .alt-msg-editor textarea { width: 100%; font: 400 15px/23px var(--font-sans); }
2440
+ .alt-msg-editor-actions { display: flex; justify-content: flex-end; gap: 6px; }
2441
+ .alt-msg[data-from="user"] .alt-msg-actions { justify-content: flex-end; margin-left: 0; margin-right: -6px; }
2442
+ .alt-msg[data-from="user"] .alt-msg-edited { justify-content: flex-end; }
2443
+ /* The queue (#276): written but not dispatched, so it doesn't hover; the person's sheet on the ground with a dashed
2444
+ line-control rim, the way Altitude draws what isn't in the record yet. It appears in place: it's the person's own act. */
2445
+ .alt-convo-queue { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; margin: 0; padding: 0; list-style: none; }
2446
+ .alt-queued { width: fit-content; max-width: min(72ch, 85%); padding: 8px 12px 4px; border: 1px dashed var(--line-control); border-radius: var(--radius-md); background: var(--accent-soft); color: var(--ink); }
2447
+ .alt-queued[data-editing] { width: min(72ch, 85%); padding-bottom: 8px; }
2448
+ .alt-queued-text { margin: 0; font: 400 15px/23px var(--font-sans); white-space: pre-wrap; overflow-wrap: anywhere; }
2449
+ .alt-queued-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 4px 12px; }
2450
+ .alt-queued-state { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
2451
+ .alt-queued-actions { display: inline-flex; gap: 2px; margin-right: -6px; }
2452
+ @media (forced-colors: active) { .alt-queued { border-color: CanvasText; } }
2453
+ /* Checkpoints (#277): a divider with a word between its lines, on the ground. While restoring, its label hovers as
2454
+ pending (after dur-grace); restored, it settles once; failed, it sinks into a trench. Restore is a ghost button. */
2455
+ .alt-checkpoint { display: flex; align-items: center; gap: var(--space-3); margin: 4px 0; font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2456
+ .alt-checkpoint-line { flex: 1; min-width: 16px; height: 1px; background: var(--line); }
2457
+ .alt-checkpoint-label { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0 6px; min-width: 0; padding: 2px 8px; border-radius: var(--radius-sm); }
2458
+ .alt-checkpoint-label > * + *::before { content: "·"; margin-right: 6px; color: var(--ink-muted); }
2459
+ .alt-checkpoint-word { color: var(--ink); }
2460
+ .alt-checkpoint-name { overflow-wrap: anywhere; }
2461
+ .alt-checkpoint time { font-variant-numeric: tabular-nums; }
2462
+ .alt-checkpoint-actions { display: inline-flex; gap: 2px; }
2463
+ .alt-checkpoint-actions:empty { display: none; }
2464
+ .alt-checkpoint[data-phase="failed"] .alt-checkpoint-word { color: var(--critical); }
2465
+ [data-contrast="more"] .alt-checkpoint-line { background: var(--line-control); }
2466
+ @media (forced-colors: active) {
2467
+ .alt-checkpoint-line { background: CanvasText; }
2468
+ .alt-checkpoint-label[data-status="pending"] { outline: 1px dotted CanvasText; }
2469
+ .alt-checkpoint-label[data-status="failed"] { outline: 3px dashed CanvasText; }
2470
+ }
2471
+ .alt-convo-jump, .alt-term-jump { position: absolute; left: 50%; bottom: var(--space-3); translate: -50% 0; z-index: 1; --alt-bg: var(--surface-floating); --alt-sh: var(--elev-3); --alt-sh-up: var(--elev-3); animation: alt-emerge var(--dur-travel-2) var(--ease-rise); --alt-from-y: 6px; }
2472
+ @media (prefers-reduced-motion: reduce) { .alt-msg-caret { animation: none; } }
2473
+ [data-motion="reduced"] .alt-msg-caret { animation: none; }
2474
+ [data-contrast="more"] .alt-msg[data-stream] { box-shadow: inset 2px 0 0 0 var(--line-control); }
2475
+ [data-contrast="more"] .alt-msg[data-stream="streaming"] { box-shadow: none; background-size: 2px 8px; }
2476
+ @media (forced-colors: active) {
2477
+ .alt-msg[data-stream="streaming"] { background-image: none; border-left: 2px dotted CanvasText; }
2478
+ .alt-msg[data-stream="complete"], .alt-msg[data-stream="stopped"] { border-left: 2px solid CanvasText; }
2479
+ .alt-msg[data-from="user"] { border: 1px solid CanvasText; }
2480
+ .alt-msg-caret { forced-color-adjust: none; background: CanvasText; }
2481
+ }
2482
+
2483
+ /* ── Confirmation: a proposal from an agent, not yet dispatched. It's status that asks for action, so it's raised
2484
+ (level 1, Alert's raised rule), never pending: hovering would say the action is already in flight. Approving turns it
2485
+ into a pending write (hovers after dur-grace), which lands with the afterglow or drops to a trench. Rejected and
2486
+ expired are flush and muted, kept in the record. ── */
2487
+ .alt-confirm {
2488
+ display: flex; flex-direction: column; gap: 6px; padding: var(--space-4); border-radius: var(--radius-lg);
2489
+ background: var(--surface-ground); box-shadow: inset 0 0 0 1px var(--line); max-width: 560px;
2490
+ transition: background var(--dur-travel-2) var(--ease-rise), box-shadow var(--dur-travel-2) var(--ease-rise);
2491
+ }
2492
+ .alt-confirm[data-state="requested"] { background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1); }
2493
+ .alt-confirm[data-state="rejected"], .alt-confirm[data-state="expired"] { color: var(--ink-muted); }
2494
+ .alt-confirm[data-state="rejected"] .alt-confirm-title, .alt-confirm[data-state="expired"] .alt-confirm-title { color: var(--ink-muted); }
2495
+ .alt-confirm-line { display: flex; align-items: center; gap: 6px; margin: 0; font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2496
+ .alt-confirm-line[data-tone="accent"] { color: var(--accent-ink); }
2497
+ .alt-confirm-line[data-tone="critical"] { color: var(--critical); }
2498
+ .alt-confirm-title { margin: 0; font: 600 15px/21px var(--font-sans); color: var(--ink); }
2499
+ .alt-confirm-consequence { margin: 0; font: 400 14px/20px var(--font-sans); color: inherit; }
2500
+ .alt-confirm-details { margin-top: var(--space-2); }
2501
+ .alt-confirm-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }
2502
+ .alt-confirm-who { margin-right: auto; font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2503
+ [data-contrast="more"] .alt-confirm[data-state="requested"] { box-shadow: 0 3px 0 0 var(--ink), var(--elev-1); }
2504
+ /* Editing a proposal (#281): its fields open in place inside the raised card (wells), as EditRegion does; Reject and
2505
+ Approve stay; an edited proposal carries an "Edited" plate by its title. */
2506
+ .alt-confirm-edit { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-2); }
2507
+ .alt-confirm-edit-actions { display: flex; justify-content: flex-end; gap: var(--space-2); }
2508
+ .alt-confirm-edited { display: inline-flex; align-items: center; height: 20px; margin-left: 8px; padding: 0 6px; border-radius: var(--radius-sm); vertical-align: 2px;
2509
+ background: color-mix(in oklab, var(--surface-ground), var(--surface-well) 70%); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--ink) 9%, transparent); font: 550 11.5px/1 var(--font-sans); color: var(--ink-muted); }
2510
+ .alt-confirm-scope { display: flex; flex-direction: column; gap: 1px; padding-block: 6px; white-space: normal; }
2511
+ .alt-menu:has(.alt-confirm-scope) { min-width: min(300px, calc(100vw - 16px)); }
2512
+ .alt-menu-item:has(.alt-confirm-scope) { height: auto; min-height: var(--size-row-md, 32px); }
2513
+ .alt-confirm-scope-desc { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2514
+ @media (forced-colors: active) { .alt-confirm { border: 1px solid CanvasText; } }
2515
+ /* Question (#270): Confirmation's raised card asking for an answer rather than approval. Options are trenches (radio or
2516
+ checkbox), "Your own answer" a well; Answer is the one primary. Answered, it presses to the ground as one muted line. */
2517
+ .alt-question-options { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 0; }
2518
+ .alt-question-other { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
2519
+ .alt-question-other-label { font: 500 13px/18px var(--font-sans); color: var(--ink); }
2520
+ .alt-question[data-state="answered"], .alt-question[data-state="expired"] { padding-block: 8px; box-shadow: none; background: none; }
2521
+ .alt-question-record { display: flex; align-items: flex-start; gap: 6px; margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); }
2522
+ .alt-question-record svg { flex: none; margin-top: 2.5px; }
2523
+ .alt-question-asked { color: var(--ink); }
2524
+ .alt-question-asked::after { content: " ·"; color: var(--ink-muted); }
2525
+ .alt-question-set { display: flex; flex-direction: column; gap: var(--space-3); max-width: 560px; }
2526
+ .alt-question-set > .alt-confirm { max-width: none; }
2527
+
2528
+ /* ── Trace: an agent's steps as a vertical Stepper. Done steps are flush with a stamped check, the running step stands
2529
+ raised on its wall (no spinner: a small steady lamp in its mark), planned steps are imprints. A failed step drops to a
2530
+ trench with a critical rim and Retry. A step waiting for approval holds a Confirmation. Tool calls and reasoning sit
2531
+ behind Accordion lids under their step, indented to the text. ── */
2532
+ .alt-trace .alt-step { width: 100%; }
2533
+ .alt-trace .alt-step[data-state="failed"] { background: var(--surface-well); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); color: var(--ink); }
2534
+ .alt-trace .alt-step[data-state="failed"] .alt-step-mark { background: var(--critical); color: var(--on-critical); }
2535
+ .alt-trace-live { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--accent); }
2536
+ .alt-trace-error { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-top: 4px; font: 400 13px/18px var(--font-sans); color: var(--ink); }
2537
+ /* Indented to the step's text where there's room; in a narrow trace (under 480px) one small step in, so tool calls and
2538
+ approvals keep the width (a percentage margin reads the step's width: 40px at 480px, 12px from 452px down). */
2539
+ .alt-trace-detail { display: flex; flex-direction: column; gap: var(--space-2); margin: 6px 0 0 clamp(var(--space-3), 100% - 440px, 40px); }
2540
+ /* Subagents (#278): a nested run is a nested list behind an Accordion lid that says how it's going; steps that run at
2541
+ once sit in a labelled group with a 1px line-control bracket, several raised at once because each is in play. */
2542
+ .alt-trace-nested-title { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
2543
+ .alt-trace-nested-sum { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2544
+ .alt-trace-nested .alt-trace { padding: 4px 0 0; }
2545
+ .alt-trace-parallel { margin: 6px 0 0; padding: 2px 0 2px 12px; border-left: 1px solid var(--line-control); }
2546
+ .alt-trace-parallel-head { display: flex; flex-wrap: wrap; gap: 2px 8px; margin: 0 0 6px; font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2547
+ .alt-trace-parallel-head > :first-child { color: var(--ink); }
2548
+ @media (forced-colors: active) { .alt-trace-parallel { border-left-color: CanvasText; } }
2549
+ .alt-trace-acc .alt-acc-trigger { padding-block: 6px; font-size: 13px; }
2550
+ .alt-trace-tool code { font: 500 12.5px/18px var(--font-mono); }
2551
+ .alt-trace-io { display: flex; flex-direction: column; gap: var(--space-2); }
2552
+ .alt-trace-wait, .alt-trace-reasoning { margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); white-space: pre-wrap; }
2553
+ /* Reasoning while it streams (#279): not yet the record, like a streaming answer: "Thinking…" on the lid, and a dashed
2554
+ line-control rule down the open compartment. No shimmer, no pulsing dots, no ticking counter; the lid's words
2555
+ cross-fade when it completes. */
2556
+ .alt-trace-thought-title { animation: alt-fade-in var(--dur-travel-1) var(--ease-rise) both; }
2557
+ .alt-trace-thought[data-status="streaming"] .alt-trace-reasoning { max-height: 240px; overflow-y: auto; padding-left: 10px; border-left: 1px dashed var(--line-control); }
2558
+ .alt-trace-reasoning-hidden { margin: 0; padding: 4px 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); }
2559
+ @media (prefers-reduced-motion: reduce) { .alt-trace-thought-title { animation: none; } }
2560
+ [data-motion="reduced"] .alt-trace-thought-title { animation: none; }
2561
+ @media (forced-colors: active) { .alt-trace-thought[data-status="streaming"] .alt-trace-reasoning { border-left: 1px dotted CanvasText; } }
2562
+ .alt-trace-item:last-child .alt-step-line { display: none; }
2563
+ @media (forced-colors: active) { .alt-step[data-state="failed"] { border: 3px dashed CanvasText; } }
2564
+
2565
+ /* ── AILabel: marks what AI made. A label is part of the record, so it's a flush Badge plate with a small static icon
2566
+ and a word: no gradient, no glow, no shimmer (the light never moves, and a gradient rim reads as focus). It acts, so
2567
+ it lifts one level when pointed at, and opens a Popover at level 3 saying how the content was produced. ── */
2568
+ .alt-ailabel { border: 0; cursor: pointer; font-family: var(--font-sans); transition: transform var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1); }
2569
+ .alt-ailabel svg { flex: none; color: var(--ink-muted); }
2570
+ .alt-ailabel:focus-visible, .alt-ailabel[aria-expanded="true"] { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
2571
+ @media (hover: hover) { .alt-ailabel:hover { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
2572
+ .alt-ailabel:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
2573
+ .alt-ailabel-pop { width: min(320px, calc(100vw - 32px)); }
2574
+ .alt-ailabel-body { display: flex; flex-direction: column; gap: var(--space-2); }
2575
+ .alt-ailabel-title { margin: 0; font: 600 14px/20px var(--font-sans); }
2576
+ .alt-ailabel-sub { margin: var(--space-1) 0 0; font: 600 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2577
+ .alt-ailabel-facts { display: grid; gap: 4px; margin: 0; font: 400 13px/18px var(--font-sans); }
2578
+ .alt-ailabel-facts div { display: grid; grid-template-columns: 96px 1fr; gap: var(--space-2); }
2579
+ .alt-ailabel-facts dt { color: var(--ink-muted); }
2580
+ .alt-ailabel-facts dd { margin: 0; color: var(--ink); }
2581
+ .alt-ailabel-sources { margin: 0; padding-left: 1.3em; font: 400 13px/19px var(--font-sans); }
2582
+ .alt-ailabel-more { font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
2583
+ [data-contrast="more"] .alt-ailabel { box-shadow: inset 0 0 0 1px var(--line-control); }
2584
+ @media (forced-colors: active) { .alt-ailabel { border: 1px solid ButtonText; } }
2585
+
2586
+ /* ── InlineCitation: a numbered source marker in generated text. A citation is part of the record, so it's a small flush
2587
+ plate (ChartAnnotation's build) with a mono number, raised a little off the baseline like a superscript; it acts, so it
2588
+ lifts one level when pointed at, and a HoverCard previews the source at level 3. Sources is a flush ordered list. ── */
2589
+ .alt-cite {
2590
+ display: inline-flex; align-items: center; gap: 2px; min-width: 16px; height: 16px; padding: 0 4px; margin: 0 1px; border-radius: var(--radius-xs);
2591
+ vertical-align: 0.35em; background: color-mix(in oklab, var(--surface-ground), var(--surface-well) 70%); color: var(--ink-muted);
2592
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--ink) 9%, transparent); font: 600 10.5px/1 var(--font-mono); text-decoration: none; justify-content: center;
2593
+ transition: transform var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1), color var(--dur-travel-1);
2594
+ }
2595
+ .alt-cite:focus-visible { background: var(--surface-raised); color: var(--accent-ink); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
2596
+ @media (hover: hover) { .alt-cite:hover { background: var(--surface-raised); color: var(--accent-ink); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
2597
+ .alt-cite:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
2598
+ .alt-cite[data-unverified] svg { color: var(--caution); }
2599
+ .alt-cite-card { width: min(300px, calc(100vw - 32px)); }
2600
+ .alt-cite-preview { display: flex; flex-direction: column; gap: 4px; }
2601
+ .alt-cite-meta { margin: 0; font: 500 12px/16px var(--font-sans); color: var(--ink-muted); }
2602
+ .alt-cite-title { margin: 0; font: 600 14px/20px var(--font-sans); }
2603
+ .alt-cite-excerpt { margin: 4px 0 0; padding-left: 10px; box-shadow: inset 2px 0 0 var(--line-control); font: 400 13px/19px var(--font-sans); color: var(--ink-muted); }
2604
+ .alt-cite-warn { display: inline-flex; align-items: center; gap: 4px; margin-left: 6px; color: var(--ink); font: 500 12px/16px var(--font-sans); }
2605
+ .alt-cite-meta .alt-cite-warn { margin-left: 0; }
2606
+ .alt-cite-warn svg { color: var(--caution); }
2607
+ .alt-sources { margin-top: var(--space-3); }
2608
+ .alt-sources-heading { margin: 0 0 6px; font: 600 13px/18px var(--font-sans); color: var(--ink-muted); }
2609
+ .alt-sources-list { margin: 0; padding-left: 1.4em; font: 400 13.5px/20px var(--font-sans); color: var(--ink); }
2610
+ .alt-sources-list li { scroll-margin: 40px; border-radius: var(--radius-xs); outline: none; }
2611
+ .alt-sources-list li:target, .alt-sources-list li:focus { background: var(--accent-soft); }
2612
+ .alt-sources-domain { color: var(--ink-muted); }
2613
+ [data-contrast="more"] .alt-cite { box-shadow: inset 0 0 0 1px var(--line-control); }
2614
+ @media (forced-colors: active) { .alt-cite { border: 1px solid LinkText; } }
2615
+
1097
2616
  /* ── Pile: thickness says roughly how many (ordinal), the badge says exactly. Click a pile that has items and they
1098
2617
  slide out from under the top card, floating over the page below (level 3) without moving anything. Click the top,
1099
2618
  press Escape or click away and they slide back under. ── */
@@ -1109,8 +2628,10 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1109
2628
  }
1110
2629
  .alt-pile-live .alt-pile-top { cursor: pointer; }
1111
2630
  /* Hover: the top lifts and the sheets beneath fan a little, a preview of what's underneath. */
1112
- .alt-pile-live .alt-pile-top:hover, .alt-pile-live .alt-pile-top:focus-visible { box-shadow: var(--elev-2); transform: translateY(calc(-3px * var(--alt-travel))); }
1113
- .alt-pile-live:has(.alt-pile-top:hover) .alt-pile-sheet, .alt-pile-live:has(.alt-pile-top:focus-visible) .alt-pile-sheet { --fan: var(--alt-travel); }
2631
+ .alt-pile-live .alt-pile-top:focus-visible { box-shadow: var(--elev-2); transform: translateY(calc(-3px * var(--alt-travel))); }
2632
+ @media (hover: hover) { .alt-pile-live .alt-pile-top:hover { box-shadow: var(--elev-2); transform: translateY(calc(-3px * var(--alt-travel))); } }
2633
+ .alt-pile-live:has(.alt-pile-top:focus-visible) .alt-pile-sheet { --fan: var(--alt-travel); }
2634
+ @media (hover: hover) { .alt-pile-live:has(.alt-pile-top:hover) .alt-pile-sheet { --fan: var(--alt-travel); } }
1114
2635
  .alt-pile-live .alt-pile-top:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1115
2636
  .alt-pile-count {
1116
2637
  position: absolute; z-index: 5; top: -8px; right: -8px; min-width: 22px; height: 22px; padding: 0 6px; display: grid; place-items: center;
@@ -1140,9 +2661,12 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1140
2661
  opacity: 1; transform: none;
1141
2662
  transition: transform var(--dur-travel-3) var(--ease-settle) calc(var(--i) * 22ms), opacity var(--dur-travel-2) var(--ease-rise) calc(var(--i) * 22ms), box-shadow var(--dur-travel-1) var(--ease-rise);
1142
2663
  }
1143
- .alt-pile-list[data-state="open"] .alt-pile-item:hover, .alt-pile-list[data-state="open"] .alt-pile-item:focus-visible {
2664
+ .alt-pile-list[data-state="open"] .alt-pile-item:focus-visible {
1144
2665
  box-shadow: var(--elev-3); transform: translateY(calc(-2px * var(--alt-travel))); transition-delay: 0ms;
1145
2666
  }
2667
+ @media (hover: hover) { .alt-pile-list[data-state="open"] .alt-pile-item:hover {
2668
+ box-shadow: var(--elev-3); transform: translateY(calc(-2px * var(--alt-travel))); transition-delay: 0ms;
2669
+ } }
1146
2670
  .alt-pile-list[data-state="open"] .alt-pile-item:active { box-shadow: var(--elev-0); transform: translateY(calc(1px * var(--alt-travel))); transition-duration: var(--dur-press); transition-delay: 0ms; }
1147
2671
  .alt-pile-item:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1148
2672
 
@@ -1153,7 +2677,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1153
2677
  transition: box-shadow var(--dur-travel-2) var(--ease-rise);
1154
2678
  }
1155
2679
  .alt-scroll[data-top] .alt-scroll-header { box-shadow: var(--elev-2); }
1156
- .alt-scroll-body { overflow: auto; overscroll-behavior: contain; outline: none; scrollbar-width: thin; }
2680
+ .alt-scroll-body { overflow: auto; outline: none; scrollbar-width: thin; }
1157
2681
  .alt-scroll-body:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1158
2682
  .alt-scroll-lip { position: absolute; left: 0; right: 0; height: 24px; pointer-events: none; opacity: 0; transition: opacity var(--dur-travel-2) var(--ease-rise); z-index: 1; }
1159
2683
  .alt-scroll-lip[data-edge="top"] { top: 0; box-shadow: var(--lip-top); }
@@ -1163,12 +2687,12 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1163
2687
 
1164
2688
  /* ── DropZone: flush until a file is in flight anywhere, then it opens; over it, it deepens. ── */
1165
2689
  .alt-drop {
1166
- display: grid; place-items: center; gap: var(--space-2); min-height: 132px; padding: var(--space-5); text-align: center;
2690
+ display: grid; place-items: center; gap: var(--space-2); min-height: 132px; padding: var(--alt-inset-box); text-align: center;
1167
2691
  border: 1.5px dashed var(--line-control); border-radius: var(--radius-lg); background: var(--surface-ground); box-shadow: var(--elev-0);
1168
2692
  color: var(--ink-muted); font: 500 14px/20px var(--font-sans); cursor: pointer;
1169
2693
  transition: background-color var(--dur-travel-2) var(--ease-rise), box-shadow var(--dur-travel-2) var(--ease-rise), border-color var(--dur-travel-2), transform var(--dur-travel-2) var(--ease-settle);
1170
2694
  }
1171
- .alt-drop:hover { border-color: var(--ink-muted); }
2695
+ @media (hover: hover) { .alt-drop:hover { border-color: var(--ink-muted); } }
1172
2696
  .alt-drop:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1173
2697
  .alt-drop[data-drop="armed"] { background: var(--surface-well); box-shadow: var(--well); border-color: var(--ink-muted); }
1174
2698
  .alt-drop[data-drop="over"] { background: var(--accent-soft); box-shadow: var(--trench); border-style: solid; border-color: var(--accent-ink); color: var(--ink); transform: scale(calc(1 - 0.01 * var(--alt-travel))); }
@@ -1180,7 +2704,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1180
2704
  It sinks in when it appears (flush to well, dur-travel-1, ease-sink), the inverse of content arriving lifted,
1181
2705
  and it never pulses. ── */
1182
2706
  .alt-empty {
1183
- display: flex; flex-direction: column; align-items: center; gap: var(--space-2); padding: var(--space-8) var(--space-6); text-align: center;
2707
+ display: flex; flex-direction: column; align-items: center; gap: var(--space-2); padding: var(--space-8) var(--alt-inset-page); text-align: center;
1184
2708
  border: 1.5px dashed var(--line-control); border-radius: var(--radius-lg); background: var(--surface-well); box-shadow: var(--well);
1185
2709
  color: var(--ink-muted); font: 400 15px/23px var(--font-sans);
1186
2710
  animation: alt-empty-sink var(--dur-travel-1) var(--ease-sink);
@@ -1192,7 +2716,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1192
2716
  .alt-empty-title { margin: 0; font: 600 19px/26px var(--font-sans); letter-spacing: -0.01em; color: var(--ink); text-wrap: balance; }
1193
2717
  .alt-empty-desc { margin: 0; max-width: 46ch; color: var(--ink-muted); text-wrap: pretty; }
1194
2718
  .alt-empty-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); margin-top: var(--space-3); }
1195
- .alt-empty:has(.alt-empty-actions > :first-child:is(:hover, :focus-visible):not(:disabled)) { box-shadow: var(--trench); border-color: var(--ink-muted); }
2719
+ @media (hover: hover) { .alt-empty:has(.alt-empty-actions > :first-child:is(:hover, :focus-visible):not(:disabled)) { box-shadow: var(--trench); border-color: var(--ink-muted); } }
1196
2720
  .alt-empty[data-size="sm"] { gap: var(--space-1); padding: var(--space-5) var(--space-4); border-radius: var(--radius-md); font: 400 14px/20px var(--font-sans); }
1197
2721
  .alt-empty[data-size="sm"] .alt-empty-icon > svg { width: 18px; height: 18px; }
1198
2722
  .alt-empty[data-size="sm"] .alt-empty-title { font: 500 14px/20px var(--font-sans); letter-spacing: 0; }
@@ -1203,21 +2727,29 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1203
2727
  .alt-sort-item {
1204
2728
  position: relative; z-index: 1; display: flex; align-items: center; gap: var(--space-3); padding: 10px var(--space-4) 10px var(--space-3);
1205
2729
  border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--elev-1); color: var(--ink);
1206
- font: 500 14px/20px var(--font-sans); user-select: none; touch-action: none; cursor: grab; outline: none;
2730
+ font: 500 14px/20px var(--font-sans); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; touch-action: manipulation; cursor: grab; outline: none;
1207
2731
  transition: transform var(--dur-travel-2) var(--ease-settle), box-shadow var(--dur-travel-2) var(--ease-rise), background-color var(--dur-travel-1);
1208
2732
  }
1209
- .alt-sort-item:hover { box-shadow: var(--elev-2); }
2733
+ @media (hover: hover) { .alt-sort-item:hover { box-shadow: var(--elev-2); } }
1210
2734
  .alt-sort-item:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1211
2735
  .alt-sort-item[data-held] { z-index: 3; cursor: grabbing; background: var(--surface-floating); box-shadow: var(--elev-3); }
1212
2736
  .alt-sort-item[data-held]:not([data-following]) { transform: translateY(calc(-2px * var(--alt-travel))) rotate(calc(-0.5deg * var(--alt-travel))); }
1213
2737
  .alt-sort-item[data-following] { transition: box-shadow var(--dur-travel-2) var(--ease-rise), background-color var(--dur-travel-1); rotate: calc(-0.6deg * var(--alt-travel)); scale: calc(1 + 0.015 * var(--alt-travel)); }
1214
2738
  .alt-sort-grip { display: inline-flex; color: var(--ink-muted); flex: none; }
2739
+ /* On touch a swipe on the row scrolls and a press-and-hold picks it up; the grip picks it up at once, with a finger-sized hit area (#256). */
2740
+ .alt-sort-grip { position: relative; touch-action: none; }
2741
+ @media (pointer: coarse) { .alt-sort-grip::after { content: ""; position: absolute; inset: -14px -14px; } }
1215
2742
  .alt-sort-body { flex: 1; min-width: 0; }
1216
2743
  .alt-sort-slot {
1217
2744
  position: absolute; left: 0; right: 0; z-index: 0; border-radius: var(--radius-md); background: var(--surface-well); box-shadow: var(--well);
1218
2745
  border: 1.5px dashed var(--line-control); transition: top var(--dur-travel-2) var(--ease-settle);
1219
2746
  }
1220
2747
 
2748
+ /* ── Breadcrumb and Drilldown share one staircase (CrumbStep): every step stands on the ground line, each deeper one a
2749
+ taller slab. Breadcrumb's steps are links (or your router's), the current page a step that doesn't press, and a deep
2750
+ path's middle waits behind a "…" step that opens a menu. A route change re-renders it without motion. ── */
2751
+ .alt-breadcrumb ol { padding-top: 14px; padding-bottom: 4px; }
2752
+
1221
2753
  /* ── Drilldown: hierarchy as a stack. Each place you go into is laid on top of the last: the crumbs climb a
1222
2754
  staircase (each one step higher, its wall one pixel thicker), and the view sits on a pile as thick as you are deep. ── */
1223
2755
  .alt-drill { display: flex; flex-direction: column; gap: var(--space-4); }
@@ -1226,10 +2758,10 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1226
2758
  higher, so all the contact shadows line up. Face and wall share one hairline so the top edge always reads.
1227
2759
  Shadow list: [face ring, wall, wall ring, contact, ambient]. */
1228
2760
  .alt-crumbs li { position: relative; display: flex; --rise: calc(var(--d) * 3px); }
1229
- .alt-crumbs button {
2761
+ .alt-crumbs .alt-crumb-link {
1230
2762
  --wd: calc(2px + var(--rise)); --wall: var(--wd); --wall-c: color-mix(in oklab, var(--surface-trench), var(--ink) 6%);
1231
- position: relative; height: 30px; padding: 0 12px; border: 0; border-radius: var(--radius-sm);
1232
- font: 500 13px/18px var(--font-sans); color: var(--ink-muted); white-space: nowrap; cursor: pointer;
2763
+ position: relative; display: inline-flex; align-items: center; height: var(--size-control-sm); padding: 0 12px; border: 0; border-radius: var(--radius-sm);
2764
+ font: 500 13px/18px var(--font-sans); color: var(--ink-muted); white-space: nowrap; text-decoration: none; cursor: pointer;
1233
2765
  background: var(--surface-raised); background-image: var(--alt-sheen);
1234
2766
  box-shadow: 0 0 0 1px var(--alt-ring), 0 var(--wd) 0 0 var(--wall-c), 0 var(--wd) 0 1px var(--alt-ring),
1235
2767
  0 calc(var(--wd) + 1px) 2px 0 var(--alt-cast), 0 calc(var(--wd) + 3px) 8px -3px var(--alt-cast);
@@ -1237,12 +2769,15 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1237
2769
  transition: transform var(--dur-travel-2) var(--ease-settle), box-shadow var(--dur-travel-2) var(--ease-settle),
1238
2770
  color var(--dur-travel-1), background-color var(--dur-travel-1);
1239
2771
  }
1240
- .alt-crumbs li:not([data-current]) button:hover, .alt-crumbs li:not([data-current]) button:focus-visible {
2772
+ .alt-crumbs li:not([data-current]) .alt-crumb-link:focus-visible {
1241
2773
  --wd: calc(3px + var(--rise)); color: var(--ink); background-color: var(--surface-lifted);
1242
2774
  }
1243
- .alt-crumbs li:not([data-current]) button:active { --wd: 0px; background-image: none; transition-duration: var(--dur-press); transition-timing-function: ease-out; }
1244
- .alt-crumbs li[data-current] button { color: var(--ink); font-weight: 600; cursor: default; }
1245
- .alt-crumbs li[data-new] button { animation: alt-crumb-rise var(--dur-travel-3) var(--ease-settle); }
2775
+ @media (hover: hover) { .alt-crumbs li:not([data-current]) .alt-crumb-link:hover {
2776
+ --wd: calc(3px + var(--rise)); color: var(--ink); background-color: var(--surface-lifted);
2777
+ } }
2778
+ .alt-crumbs li:not([data-current]) .alt-crumb-link:active { --wd: 0px; background-image: none; transition-duration: var(--dur-press); transition-timing-function: ease-out; }
2779
+ .alt-crumbs li[data-current] .alt-crumb-link { color: var(--ink); font-weight: 600; cursor: default; }
2780
+ .alt-crumbs li[data-new] .alt-crumb-link { animation: alt-crumb-rise var(--dur-travel-3) var(--ease-settle); }
1246
2781
  /* A new step is built: it rises out of the ground to its height. */
1247
2782
  @keyframes alt-crumb-rise {
1248
2783
  from { transform: none; box-shadow: 0 0 0 1px var(--alt-ring), 0 0 0 0 var(--wall-c), 0 0 0 1px var(--alt-ring), 0 1px 2px 0 var(--alt-cast), 0 3px 8px -3px var(--alt-cast); }
@@ -1266,7 +2801,8 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1266
2801
  background: transparent; color: var(--ink); text-align: left; cursor: pointer; box-shadow: var(--elev-0);
1267
2802
  transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-2) var(--ease-settle);
1268
2803
  }
1269
- .alt-drill-row:hover, .alt-drill-row:focus-visible { background: var(--surface-lifted); box-shadow: var(--elev-2); transform: translateY(calc(-1px * var(--alt-travel))); }
2804
+ .alt-drill-row:focus-visible { background: var(--surface-lifted); box-shadow: var(--elev-2); transform: translateY(calc(-1px * var(--alt-travel))); }
2805
+ @media (hover: hover) { .alt-drill-row:hover { background: var(--surface-lifted); box-shadow: var(--elev-2); transform: translateY(calc(-1px * var(--alt-travel))); } }
1270
2806
  .alt-drill-row:active { box-shadow: var(--elev-0); transform: translateY(calc(1px * var(--alt-travel))); transition-duration: var(--dur-press); }
1271
2807
  .alt-drill-row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1272
2808
  .alt-drill-row-text { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
@@ -1284,7 +2820,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1284
2820
  change, rows that survive travel to their new places (FLIP, ease-glide) and new ones rise 4px in, at most four at a
1285
2821
  time (riseDelay) and only near the top; the rest cross-fade. On opening, predicted items are there at once and the
1286
2822
  rest fade in over dur-onset. ── */
1287
- .alt-cmd-root { position: fixed; inset: 0; z-index: var(--z-modal); display: grid; justify-items: center; align-items: start; padding: max(var(--space-6), 12vh) var(--space-4) var(--space-6); }
2823
+ .alt-cmd-root { position: fixed; inset: 0; top: var(--alt-vv-top, 0px); bottom: auto; height: var(--alt-vvh, 100dvh); z-index: var(--z-modal); display: grid; justify-items: center; align-items: start; padding: max(var(--space-6), calc(var(--alt-vvh, 100dvh) * 0.12)) var(--space-4) var(--space-6); }
1288
2824
  .alt-cmd-root[data-state="closed"] .alt-scrim { opacity: 0; transition-duration: calc(var(--dur-travel-5) * 0.7); transition-timing-function: var(--ease-sink); }
1289
2825
  .alt-cmd {
1290
2826
  position: relative; display: flex; flex-direction: column; width: min(100%, 600px); max-height: 100%; overflow: hidden; outline: none;
@@ -1299,7 +2835,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1299
2835
  .alt-cmd .alt-cmd-field { height: 48px; padding: 0 var(--space-3) 0 var(--space-4); gap: var(--space-3); border-radius: calc(var(--radius-xl) - var(--space-3)); }
1300
2836
  .alt-cmd .alt-cmd-field input { font-size: 16px; }
1301
2837
  .alt-cmd .alt-cmd-field:focus-within, .alt-cmd .alt-cmd-field:has(input:not(:placeholder-shown)) { background: var(--surface-modal); }
1302
- .alt-cmd-viewport { position: relative; flex: 0 1 auto; min-height: 0; max-height: min(380px, 52vh); overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
2838
+ .alt-cmd-viewport { position: relative; flex: 0 1 auto; min-height: 0; max-height: min(380px, 52vh); max-height: min(380px, calc(var(--alt-vvh, 100dvh) * 0.52)); overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
1303
2839
  .alt-cmd-viewport[data-ready] { transition: height var(--dur-travel-2) var(--ease-glide); }
1304
2840
  .alt-cmd-list { position: relative; padding: 0 var(--space-2) var(--space-2); outline: none; }
1305
2841
  .alt-cmd-group + .alt-cmd-group { margin-top: var(--space-1); }
@@ -1310,7 +2846,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1310
2846
  }
1311
2847
  .alt-cmd-hl[data-tone="critical"] { background: var(--critical-soft); }
1312
2848
  .alt-cmd-item {
1313
- position: relative; display: flex; align-items: center; gap: 10px; min-height: 40px; padding: 0 10px; border-radius: var(--radius-sm);
2849
+ position: relative; display: flex; align-items: center; gap: 10px; min-height: var(--size-field-md); padding: 0 10px; border-radius: var(--radius-sm);
1314
2850
  font: 500 14px/20px var(--font-sans); color: var(--ink); cursor: pointer; user-select: none;
1315
2851
  }
1316
2852
  .alt-cmd-item[data-tone="critical"] { color: var(--critical); }
@@ -1338,7 +2874,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1338
2874
  8px clear of its edge so its own edges, and the shadow it casts below them, read as height. While content scrolls
1339
2875
  under its header the header rises; where content continues below, the footer's top edge curves away. A bottom
1340
2876
  sheet has a finger pull: a small recess (an opening, like every place you put something into) to drag it down by. ── */
1341
- .alt-sheet-root { position: fixed; inset: 0; z-index: var(--z-overlay); pointer-events: none; }
2877
+ .alt-sheet-root { position: fixed; inset: 0; top: var(--alt-vv-top, 0px); bottom: auto; height: var(--alt-vvh, 100dvh); z-index: var(--z-overlay); pointer-events: none; }
1342
2878
  .alt-sheet-root > .alt-scrim { pointer-events: auto; }
1343
2879
  .alt-sheet-root[data-state="closed"] > .alt-scrim { opacity: 0; transition-duration: calc(var(--dur-travel-4) * 0.7); transition-timing-function: var(--ease-sink); }
1344
2880
  .alt-sheet {
@@ -1349,10 +2885,10 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1349
2885
  }
1350
2886
  .alt-sheet[data-size="sm"] { --w: 360px; }
1351
2887
  .alt-sheet[data-size="lg"] { --w: 560px; }
1352
- .alt-sheet[data-side="right"], .alt-sheet[data-side="left"] { top: var(--edge); bottom: var(--edge); width: min(calc(100% - 2 * var(--edge)), var(--w)); }
1353
- .alt-sheet[data-side="right"] { right: var(--edge); transform-origin: 100% 50%; }
1354
- .alt-sheet[data-side="left"] { left: var(--edge); transform-origin: 0 50%; }
1355
- .alt-sheet[data-side="bottom"] { --w: 720px; left: 0; right: 0; bottom: var(--edge); width: min(calc(100% - 2 * var(--edge)), var(--w)); max-height: calc(100% - 2 * var(--space-8)); margin: 0 auto; transform-origin: 50% 100%; }
2888
+ .alt-sheet[data-side="right"], .alt-sheet[data-side="left"] { top: calc(var(--edge) + env(safe-area-inset-top, 0px)); bottom: calc(var(--edge) + env(safe-area-inset-bottom, 0px)); width: min(calc(100% - 2 * var(--edge)), var(--w)); }
2889
+ .alt-sheet[data-side="right"] { right: calc(var(--edge) + env(safe-area-inset-right, 0px)); transform-origin: 100% 50%; }
2890
+ .alt-sheet[data-side="left"] { left: calc(var(--edge) + env(safe-area-inset-left, 0px)); transform-origin: 0 50%; }
2891
+ .alt-sheet[data-side="bottom"] { --w: 720px; left: 0; right: 0; bottom: calc(var(--edge) + env(safe-area-inset-bottom, 0px)); width: min(calc(100% - 2 * var(--edge)), var(--w)); max-height: calc(100% - 2 * var(--space-8)); margin: 0 auto; transform-origin: 50% 100%; }
1356
2892
  .alt-sheet[data-side="bottom"][data-size="sm"] { --w: 480px; }
1357
2893
  .alt-sheet[data-side="bottom"][data-size="lg"] { --w: 960px; }
1358
2894
  .alt-sheet-root[data-state="closed"] .alt-sheet { opacity: 0; box-shadow: var(--elev-1); }
@@ -1364,19 +2900,19 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1364
2900
  transition-duration: calc(var(--dur-travel-4) * 0.7); transition-timing-function: var(--ease-sink);
1365
2901
  }
1366
2902
  .alt-sheet-scroll { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
1367
- .alt-sheet-inner { display: flex; flex-direction: column; gap: var(--space-4); min-height: 100%; padding: 0 var(--space-6); }
2903
+ .alt-sheet-inner { display: flex; flex-direction: column; gap: var(--space-4); min-height: 100%; padding: 0 var(--alt-inset-page); }
1368
2904
  .alt-sheet-header {
1369
- position: sticky; top: 0; z-index: 2; display: flex; flex-direction: column; gap: 6px; margin: 0 calc(-1 * var(--space-6));
1370
- padding: var(--space-6) calc(var(--space-6) + 36px) var(--space-2) var(--space-6); background: var(--surface-overlay); box-shadow: var(--elev-0);
2905
+ position: sticky; top: 0; z-index: 2; display: flex; flex-direction: column; gap: 6px; margin: 0 calc(-1 * var(--alt-inset-page));
2906
+ padding: var(--alt-inset-page) calc(var(--alt-inset-page) + 36px) var(--space-2) var(--alt-inset-page); background: var(--surface-overlay); box-shadow: var(--elev-0);
1371
2907
  transition: box-shadow var(--dur-travel-2) var(--ease-rise);
1372
2908
  }
1373
2909
  .alt-sheet[data-top] .alt-sheet-header { box-shadow: var(--elev-2); }
1374
2910
  .alt-sheet-title { margin: 0; font: 600 19px/26px var(--font-sans); letter-spacing: -0.01em; }
1375
2911
  .alt-sheet-desc { margin: 0; font: 400 14px/21px var(--font-sans); color: var(--ink-muted); }
1376
- .alt-sheet-inner > :last-child:not(.alt-sheet-footer) { margin-bottom: var(--space-6); }
2912
+ .alt-sheet-inner > :last-child:not(.alt-sheet-footer) { margin-bottom: var(--alt-inset-page); }
1377
2913
  .alt-sheet-footer {
1378
- position: sticky; bottom: 0; z-index: 2; display: flex; justify-content: flex-end; gap: var(--space-2); margin: auto calc(-1 * var(--space-6)) 0;
1379
- padding: var(--space-4) var(--space-6) var(--space-6); background: var(--surface-overlay);
2914
+ position: sticky; bottom: 0; z-index: 2; display: flex; justify-content: flex-end; gap: var(--space-2); margin: auto calc(-1 * var(--alt-inset-page)) 0;
2915
+ padding: var(--space-4) var(--alt-inset-page) var(--alt-inset-page); background: var(--surface-overlay);
1380
2916
  }
1381
2917
  .alt-sheet-footer::before { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 24px; pointer-events: none; box-shadow: var(--lip-bottom); opacity: 0; transition: opacity var(--dur-travel-2) var(--ease-rise); }
1382
2918
  .alt-sheet[data-bottom] .alt-sheet-footer::before { opacity: 1; }
@@ -1491,9 +3027,12 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1491
3027
  .alt-step-desc { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
1492
3028
  .alt-step[role="button"] { cursor: pointer; }
1493
3029
  .alt-step[role="button"][data-state="done"] { --wall: 0px; }
1494
- .alt-step[role="button"][data-state="done"]:hover, .alt-step[role="button"][data-state="done"]:focus-visible {
3030
+ .alt-step[role="button"][data-state="done"]:focus-visible {
1495
3031
  --wall: var(--wall-1); --st-ring: var(--alt-ring); --st-cast: var(--alt-cast); background: color-mix(in oklab, var(--surface-raised) 60%, transparent);
1496
3032
  }
3033
+ @media (hover: hover) { .alt-step[role="button"][data-state="done"]:hover {
3034
+ --wall: var(--wall-1); --st-ring: var(--alt-ring); --st-cast: var(--alt-cast); background: color-mix(in oklab, var(--surface-raised) 60%, transparent);
3035
+ } }
1497
3036
  .alt-step[role="button"][data-state="done"]:active { --wall: 0px; transition-duration: var(--dur-press); transition-timing-function: ease-out; }
1498
3037
  .alt-step[role="button"]:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1499
3038
  .alt-stepper[data-orientation="vertical"] { flex-direction: column; align-items: stretch; flex-wrap: nowrap; padding: 4px 0 0; }
@@ -1511,17 +3050,31 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1511
3050
  level 3, tilted, over a slot that opens as a well. Cells can't share one shadow, so each row's height is drawn as a
1512
3051
  single slab by its first cell, as wide as the table (--alt-row-w, measured). ── */
1513
3052
  .alt-data-table { min-width: 0; }
1514
- .alt-table-tools { display: flex; align-items: center; gap: var(--space-3); min-height: 36px; margin-bottom: var(--space-2); font: 500 14px/20px var(--font-sans); color: var(--ink); }
1515
- .alt-table-selection { display: inline-flex; align-items: center; gap: var(--space-2); --alt-from-y: 4px; animation: alt-emerge var(--dur-travel-1) var(--ease-rise); }
3053
+ .alt-table-tools { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); min-height: 36px; margin-bottom: var(--space-2); font: 500 14px/20px var(--font-sans); color: var(--ink); }
3054
+ .alt-table-selection { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); --alt-from-y: 4px; animation: alt-emerge var(--dur-travel-1) var(--ease-rise); }
1516
3055
  .alt-table-selection .alt-badge[data-variant="count"] { position: static; }
1517
- .alt-table-tools-end { display: inline-flex; align-items: center; gap: var(--space-2); margin-left: auto; }
1518
- .alt-table-wrap { position: relative; isolation: isolate; overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; outline: none; }
3056
+ .alt-table-tools-end { display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); min-width: 0; max-width: 100%; margin-left: auto; }
3057
+ /* At narrow widths the toolbar wraps (WCAG 1.4.10 Reflow): a field in it may shrink rather than push the row off the page. */
3058
+ .alt-table-tools-end > .alt-input { flex: 0 1 auto; min-width: 0; }
3059
+ .alt-table-tools-end > .alt-input input { min-width: 0; }
3060
+ .alt-table-wrap { position: relative; isolation: isolate; overflow: auto; scrollbar-width: thin; outline: none; }
1519
3061
  .alt-table-wrap:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--radius-md); }
1520
3062
  .alt-table-wrap::after { content: ""; position: sticky; z-index: 4; bottom: 0; display: block; height: 24px; margin-top: -24px; pointer-events: none; box-shadow: var(--lip-bottom); opacity: 0; transition: opacity var(--dur-travel-2) var(--ease-rise); }
1521
3063
  .alt-table-wrap[data-bottom]::after { opacity: 1; }
1522
3064
  .alt-table { --alt-row-w: 100%; width: 100%; border-collapse: separate; border-spacing: 0; font: 400 14px/20px var(--font-sans); color: var(--ink); font-variant-numeric: tabular-nums; }
1523
3065
  .alt-th, .alt-td { padding: 10px 12px; text-align: left; vertical-align: middle; border-bottom: 1px solid var(--line); transition: border-color var(--dur-travel-1); }
1524
3066
  .alt-th[data-align="end"], .alt-td[data-align="end"] { text-align: right; }
3067
+ /* Narrow (#260): columns keep a readable width and the table scrolls inside its frame. A header never wraps, and a
3068
+ cell's min-width (from minWidth, or 6rem when the table is narrow) stops a column being squeezed to "Des…". */
3069
+ .alt-table-wrap { container-type: inline-size; }
3070
+ @container (max-width: 480px) { .alt-table { table-layout: auto; width: max-content; min-width: 100%; } .alt-th, .alt-td:not([colspan]) { min-width: 6rem; } .alt-table :is(.alt-td-check, .alt-td-expand, .alt-td-grip, .alt-td-edit, .alt-th-check, .alt-th-expand, .alt-th-grip, .alt-th-edit, .alt-th:empty) { min-width: 0; } }
3071
+ /* A sticky column stays put over a flush 1px edge, on the ground surface (no shadow: it isn't live). */
3072
+ .alt-table .alt-th[data-sticky], .alt-table .alt-td[data-sticky] { position: sticky; left: 0; z-index: 2; background: var(--surface-ground); box-shadow: inset -1px 0 0 var(--line); }
3073
+ .alt-table .alt-th[data-sticky] { z-index: 5; }
3074
+ /* The edge with more columns fades; the start edge only without a sticky column, which already marks it. */
3075
+ .alt-table-wrap[data-more-end] { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); }
3076
+ .alt-table-wrap[data-more-start]:not(:has([data-sticky])) { -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent); }
3077
+ .alt-table-wrap[data-more-start][data-more-end]:not(:has([data-sticky])) { -webkit-mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent); }
1525
3078
  .alt-th { position: sticky; top: 0; z-index: 3; padding-block: 9px; font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); white-space: nowrap; }
1526
3079
  .alt-td:first-child { position: relative; }
1527
3080
  .alt-tr { --row-bg: transparent; --row-sh: var(--elev-0); transition: transform var(--dur-travel-1) var(--ease-rise), color var(--dur-travel-2) var(--ease-rise); }
@@ -1533,17 +3086,18 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1533
3086
  .alt-table-wrap[data-top] .alt-table-head .alt-tr { --row-bg: var(--surface-raised); --row-sh: var(--elev-2); }
1534
3087
  .alt-table-wrap[data-top] .alt-th { border-bottom-color: transparent; }
1535
3088
  .alt-tr[data-interactive] { cursor: pointer; }
1536
- .alt-tr[data-interactive]:not([data-row-held]):hover, .alt-tr[data-interactive]:not([data-row-held]):has(.alt-table-rowbtn:focus-visible) { --row-bg: var(--surface-raised); --row-sh: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
3089
+ .alt-tr[data-interactive]:not([data-row-held]):has(.alt-table-rowbtn:focus-visible) { --row-bg: var(--surface-raised); --row-sh: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
3090
+ @media (hover: hover) { .alt-tr[data-interactive]:not([data-row-held]):hover { --row-bg: var(--surface-raised); --row-sh: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
1537
3091
  .alt-tr[data-interactive]:not([data-row-held]):active { --row-sh: var(--elev-0); transform: none; transition-duration: var(--dur-press); }
1538
3092
  .alt-tr[data-row-relevance="match"] { --row-bg: var(--surface-raised); --row-sh: var(--elev-1); }
1539
3093
  .alt-tr[data-row-relevance="miss"] { --row-bg: var(--surface-well); --row-sh: var(--well); color: var(--ink-muted); }
1540
3094
  .alt-tr[data-selected] { --row-bg: var(--accent-soft); --row-sh: var(--elev-1); }
1541
- .alt-tr[data-selected][data-interactive]:not([data-row-held]):hover { --row-bg: var(--accent-soft); --row-sh: var(--elev-2); }
3095
+ @media (hover: hover) { .alt-tr[data-selected][data-interactive]:not([data-row-held]):hover { --row-bg: var(--accent-soft); --row-sh: var(--elev-2); } }
1542
3096
  .alt-tr[data-row-held] { position: relative; z-index: 5; --row-bg: var(--surface-floating); --row-sh: var(--elev-3); rotate: calc(-0.35deg * var(--alt-travel)); transition: none; }
1543
3097
  .alt-tr[data-row-held][data-selected] { --row-bg: color-mix(in oklab, var(--accent-soft) 70%, var(--surface-floating)); }
1544
3098
  .alt-tr[data-row-held]:not([data-following]) { transform: translateY(calc(-3px * var(--alt-travel))); }
1545
- .alt-tr:is([data-interactive]:hover, [data-selected], [data-row-relevance], [data-row-held]) > .alt-td,
1546
3099
  .alt-tr:has(+ .alt-tr:is([data-selected], [data-row-relevance], [data-row-held])) > .alt-td { border-bottom-color: transparent; }
3100
+ @media (hover: hover) { .alt-tr:is([data-interactive]:hover, [data-selected], [data-row-relevance], [data-row-held]) > .alt-td { border-bottom-color: transparent; } }
1547
3101
  .alt-tr[data-row-settle="a"] > .alt-td:first-child::before { animation: alt-afterglow-a var(--dur-settle) linear var(--alt-rise-delay, 0ms) backwards; }
1548
3102
  .alt-tr[data-row-settle="b"] > .alt-td:first-child::before { animation: alt-afterglow-b var(--dur-settle) linear var(--alt-rise-delay, 0ms) backwards; }
1549
3103
  .alt-table[data-dragging] .alt-tr:not([data-following]) { transition: transform var(--dur-travel-2) var(--ease-settle); }
@@ -1552,11 +3106,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1552
3106
  background: none; color: inherit; font: inherit; cursor: pointer; box-shadow: var(--elev-0);
1553
3107
  transition: background-color var(--dur-travel-1), color var(--dur-travel-1), box-shadow var(--dur-travel-1) var(--ease-rise);
1554
3108
  }
1555
- .alt-table-sort:hover { color: var(--ink); background: var(--surface-raised); box-shadow: var(--elev-1); }
3109
+ @media (hover: hover) { .alt-table-sort:hover { color: var(--ink); background: var(--surface-raised); box-shadow: var(--elev-1); } }
1556
3110
  .alt-table-sort:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1557
3111
  .alt-table-sort:not([data-sort="none"]) { color: var(--ink); }
1558
3112
  .alt-table-sort-icon { opacity: 0; transition: transform var(--dur-travel-2) var(--ease-settle), opacity var(--dur-travel-1); }
1559
- .alt-table-sort:hover .alt-table-sort-icon { opacity: 0.45; }
3113
+ @media (hover: hover) { .alt-table-sort:hover .alt-table-sort-icon { opacity: 0.45; } }
1560
3114
  .alt-table-sort:not([data-sort="none"]) .alt-table-sort-icon { opacity: 1; }
1561
3115
  .alt-table-sort[data-sort="descending"] .alt-table-sort-icon { transform: rotate(180deg); }
1562
3116
  .alt-th[data-align="end"] .alt-table-sort { flex-direction: row-reverse; }
@@ -1564,7 +3118,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1564
3118
  .alt-th-check .alt-check, .alt-td-check .alt-check { display: inline-flex; vertical-align: middle; }
1565
3119
  .alt-th-grip, .alt-td-grip { width: 24px; padding-right: 0; }
1566
3120
  .alt-table-grip { display: inline-grid; place-items: center; width: 24px; height: 28px; margin: -4px 0 -4px -6px; padding: 0; border: 0; border-radius: var(--radius-xs); background: none; color: var(--ink-muted); cursor: grab; touch-action: none; }
1567
- .alt-table-grip:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 7%, transparent); }
3121
+ @media (hover: hover) { .alt-table-grip:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 7%, transparent); } }
1568
3122
  .alt-table-grip:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
1569
3123
  .alt-tr[data-row-held] .alt-table-grip { cursor: grabbing; color: var(--ink); }
1570
3124
  .alt-table-slot { position: absolute; z-index: -2; left: 0; width: var(--alt-row-w); border-radius: var(--radius-md); background: var(--surface-well); box-shadow: var(--well); outline: 1.5px dashed var(--line-control); outline-offset: -3px; pointer-events: none; transition: top var(--dur-travel-2) var(--ease-settle); }
@@ -1600,11 +3154,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1600
3154
  .alt-board-empty { display: grid; place-items: center; min-height: 72px; border-radius: var(--radius-md); font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
1601
3155
  .alt-board-card {
1602
3156
  position: relative; padding: 10px 12px; border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--elev-1);
1603
- color: var(--ink); font: 500 14px/20px var(--font-sans); cursor: grab; user-select: none; outline: none;
3157
+ color: var(--ink); font: 500 14px/20px var(--font-sans); cursor: grab; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; touch-action: manipulation; outline: none;
1604
3158
  transition: box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1), transform var(--dur-travel-2) var(--ease-settle),
1605
3159
  rotate var(--dur-travel-2) var(--ease-settle), scale var(--dur-travel-2) var(--ease-settle);
1606
3160
  }
1607
- .alt-board-card:hover:not([data-carried]):not([data-held]) { box-shadow: var(--elev-2); }
3161
+ @media (hover: hover) { .alt-board-card:hover:not([data-carried]):not([data-held]) { box-shadow: var(--elev-2); } }
1608
3162
  .alt-board-card:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1609
3163
  .alt-board-card[data-carried] { z-index: 10; cursor: grabbing; background: var(--surface-floating); box-shadow: var(--elev-3); rotate: calc(-1.2deg * var(--alt-travel)); scale: calc(1 + 0.015 * var(--alt-travel)); }
1610
3164
  .alt-board-card[data-carried]:not([data-following]) { transform: translateY(calc(-3px * var(--alt-travel))); }
@@ -1632,6 +3186,565 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1632
3186
  .alt-menu[data-ephemeral][data-state="open"] .alt-menu-item:not([data-predicted]) { transition: opacity var(--dur-onset) linear, transform var(--dur-onset) var(--ease-rise); transition-delay: 0ms; }
1633
3187
  .alt-menu[data-ephemeral][data-state="open"] .alt-menu-item[data-predicted], .alt-menu[data-ephemeral][data-state="open"] .alt-menu-label { transition-duration: var(--dur-travel-1); transition-delay: 0ms; }
1634
3188
 
3189
+ /* ════════════════════ Charts ════════════════════ */
3190
+
3191
+ /* ── ChartContainer: the frame every chart shares. A chart is the record drawn as a shape, so everything in the frame
3192
+ lies flush on whatever it sits on, the page or a Card's surface-raised: no level, no shadow, no wall, no sheen. Amounts
3193
+ are length and position on a flat plot standing on the ground line (1px ink-muted, as under Stepper and Drilldown),
3194
+ never height. Gridlines are decorative line hairlines, horizontal only; tick labels are caption text in ink-muted
3195
+ with tabular figures. Marks wear data-* fills and strokes and nothing else. On resize it lays out again without
3196
+ animating. ── */
3197
+ .alt-chart { margin: 0; min-width: 0; display: flex; flex-direction: column; gap: var(--space-3); color: var(--ink); }
3198
+ .alt-chart-head { display: flex; flex-direction: column; gap: 2px; }
3199
+ .alt-chart-title { font: 600 15px/22px var(--font-sans); color: var(--ink); }
3200
+ .alt-chart-desc { margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); }
3201
+ /* A takeaway (#187) is the chart's heading: text-heading in ink. The title over it becomes a label, caption-sized
3202
+ ink-muted, so the chart stays named. A changed takeaway cross-fades in place, like a Stat's value. */
3203
+ .alt-chart-takeaway { display: grid; margin: 0; font: 600 19px/26px var(--font-sans); letter-spacing: -0.01em; color: var(--ink); text-wrap: balance; }
3204
+ .alt-chart-head[data-takeaway] .alt-chart-title { font: 500 12.5px/17px var(--font-sans); letter-spacing: 0.005em; color: var(--ink-muted); }
3205
+ .alt-chart-head[data-takeaway] .alt-chart-desc { margin-top: 2px; }
3206
+ .alt-chart-takeaway-text { grid-area: 1 / 1; }
3207
+ .alt-chart-takeaway-text[data-in] { animation: alt-stat-in var(--dur-travel-2) var(--ease-glide) both; }
3208
+ .alt-chart-takeaway-text[data-out] { animation: alt-stat-out var(--dur-travel-2) var(--ease-glide) both; }
3209
+ .alt-chart-plot { position: relative; width: 100%; min-width: 0; }
3210
+ .alt-chart-svg { position: absolute; inset: 0; display: block; overflow: visible; }
3211
+ .alt-chart-grid line { stroke: var(--line); stroke-width: 1; shape-rendering: crispEdges; }
3212
+ .alt-chart-ground { stroke: var(--ink-muted); stroke-width: 1; shape-rendering: crispEdges; }
3213
+ .alt-chart-floor { stroke: var(--line); stroke-width: 1; shape-rendering: crispEdges; }
3214
+ /* Axis text is a caption in weight 400 (#193): the frame's words rank below the marks, the ground line and references.
3215
+ Inline y labels sit just above their gridlines on a halo of the plot's colour, as reference labels do. */
3216
+ .alt-chart-axis text { fill: var(--ink-muted); font: 400 13px/19px var(--font-sans); font-variant-numeric: tabular-nums; }
3217
+ .alt-chart-axis[data-inline] text { stroke: var(--alt-chart-bg); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; }
3218
+ .alt-chart-summary { margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); text-wrap: pretty; }
3219
+ /* ── BarChart: amounts as lengths standing on the ground line. Bars are the record, so they're flat data-* fills,
3220
+ square where they meet the ground and rounded (radius-xs) only at the data end; neighbours and stacked segments
3221
+ are parted by a real gap, never a shadow. A negative bar hangs below the ground line: that's position, not depth,
3222
+ so it has no inner wall and doesn't read as an opening. A bar never lifts, on hover or anywhere else. Value labels
3223
+ are selective and wear ink, never a series colour. ── */
3224
+ .alt-bar-label { fill: var(--ink); font: 500 12.5px/17px var(--font-sans); font-variant-numeric: tabular-nums; }
3225
+ @media (forced-colors: active) { .alt-bar-label { fill: CanvasText; } }
3226
+
3227
+ /* ── Not yet the record (#67): a period still being counted or a forecast. A pending write hovers, but a pending mark
3228
+ can't, since lifting a bar misstates it; so it's drawn with pattern and a word instead. Bars are hatched at 45° in
3229
+ their colour on the plot's own, with no solid fill (a projection also keeps its outline); lines are dashed from the
3230
+ last settled point and their points hollow; areas are hatched. The record ends at a 1px line-control rule named in
3231
+ words ("Today"). A partial period that completes cross-fades from hatch to solid over dur-travel-3: it's been pressed
3232
+ into the record. An uncertainty band is a flat, low-opacity fill: no gradient, no error bars. ── */
3233
+ .alt-bar-mark, .alt-bar-pending { transition: fill-opacity var(--dur-travel-3) var(--ease-glide), stroke-opacity var(--dur-travel-3) var(--ease-glide), fill var(--dur-travel-1) var(--ease-glide); }
3234
+ /* Focus (#188): what a chart isn't about is drawn in data-context, one flat neutral, and its words recede to ink-muted.
3235
+ A change of focus cross-fades fills and strokes in place over dur-travel-1; under reduced motion it changes at once.
3236
+ Under forced colours context is GrayText at full strength, apart from a filter's misses, which also recede to 35%. */
3237
+ .alt-line-stroke, .alt-area-fill, .alt-area-line, .alt-scatter-dot { transition: fill var(--dur-travel-1) var(--ease-glide), stroke var(--dur-travel-1) var(--ease-glide); }
3238
+ .alt-line-label[data-context], .alt-area-label[data-context] { fill: var(--ink-muted); }
3239
+ /* The record before (#189): both settled, both flat on the ground, the earlier one quieter. On bars, a 2px ink tick
3240
+ across the bar at the earlier value, 4px past it each side (the bullet graph's comparative measure); on lines, a
3241
+ 1.5px solid ink-muted trace under the series, named at its end. Never dotted: under forced colours dots mean pending. */
3242
+ .alt-bar-compare { stroke: var(--ink); stroke-width: 2; shape-rendering: crispEdges; }
3243
+ /* A reading set down (#190): the readout lands on the record as a plate, from level 3 to the ground over dur-travel-3 on
3244
+ ease-settle, then takes the afterglow once. A pin icon tells it from an annotation; its ghost remove button lifts
3245
+ when pointed at, as a ghost button does. Two pins say their difference under the x axis on a line-control bracket. */
3246
+ .alt-chart-note[data-pin] { display: inline-flex; align-items: center; gap: 4px; padding-right: 2px;
3247
+ animation: alt-pin-land var(--dur-travel-3) var(--ease-settle) both, alt-pin-glow var(--dur-settle) var(--ease-decay) var(--dur-travel-3) both; }
3248
+ .alt-chart-pin-icon { flex: none; color: var(--ink-muted); }
3249
+ .alt-chart-pin-remove { display: inline-grid; place-items: center; width: 18px; height: 18px; margin: 0; padding: 0; border: 0; border-radius: var(--radius-xs); background: transparent; color: var(--ink-muted); cursor: pointer; box-shadow: var(--elev-0); transition: background-color var(--dur-travel-1) var(--ease-glide), box-shadow var(--dur-travel-1) var(--ease-glide), transform var(--dur-travel-1) var(--ease-glide); }
3250
+ .alt-chart-pin-remove:focus-visible { background: var(--surface-raised); box-shadow: var(--elev-1); color: var(--ink); transform: translateY(calc(-1px * var(--alt-travel))); }
3251
+ @media (hover: hover) { .alt-chart-pin-remove:hover { background: var(--surface-raised); box-shadow: var(--elev-1); color: var(--ink); transform: translateY(calc(-1px * var(--alt-travel))); } }
3252
+ @keyframes alt-pin-land { from { box-shadow: var(--elev-3); transform: translateY(calc(-6px * var(--alt-travel))); } }
3253
+ @keyframes alt-pin-glow { 0%, 12% { outline: 2px solid var(--accent-soft); outline-offset: 1px; } 100% { outline: 2px solid transparent; outline-offset: 1px; } }
3254
+ .alt-chart-pins-strip path { stroke: var(--line-control); stroke-width: 1; shape-rendering: crispEdges; }
3255
+ .alt-chart-pins-strip text { fill: var(--ink); font: 500 12.5px/17px var(--font-sans); font-variant-numeric: tabular-nums; }
3256
+ @media (forced-colors: active) { .alt-chart-pins-strip path { stroke: CanvasText; } .alt-chart-pins-strip text { fill: CanvasText; } .alt-chart-pin-icon, .alt-chart-pin-remove { color: CanvasText; } }
3257
+ .alt-line-compare { stroke: var(--ink-muted); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
3258
+ @media (forced-colors: active) { .alt-bar-compare { stroke: CanvasText; } .alt-line-compare { stroke: CanvasText; stroke-width: 1; } }
3259
+ /* ── DotPlot (#192): values where zero isn't part of the story, as position, never height. The values are the record,
3260
+ so dots lie flat on the ground as a ScatterChart's do: at least 8px, a marker per series, ringed 2px in the plot's
3261
+ colour. With no zero there's no ground line, only the plot's foot. Each row has a 1px line guide from its label. A
3262
+ spread is a flat band of the series colour at 16% behind the dot, never error bars. A dumbbell joins a hollow "from"
3263
+ dot to a filled "to" dot with a 2px ink-muted link. A mark that acts gets a 2px ink outline, never a lift. ── */
3264
+ .alt-dot-guide { stroke: var(--line); stroke-width: 1; shape-rendering: crispEdges; }
3265
+ .alt-dot-range { fill-opacity: 0.16; }
3266
+ .alt-dot-link { stroke: var(--ink-muted); stroke-width: 2; stroke-linecap: round; }
3267
+ .alt-dot-hover { fill: none; stroke: var(--ink); stroke-width: 2; }
3268
+ .alt-chart-plot[data-interactive] .alt-dot-row { cursor: pointer; }
3269
+ @media (prefers-contrast: more) { .alt-dot-guide { stroke: var(--line-control); } }
3270
+ [data-contrast="more"] .alt-dot-guide { stroke: var(--line-control); }
3271
+ @media (forced-colors: active) {
3272
+ .alt-dot-guide { stroke: GrayText; }
3273
+ .alt-dot-range { fill: GrayText; fill-opacity: 0.4; }
3274
+ .alt-dot-link, .alt-dot-hover { stroke: CanvasText; }
3275
+ .alt-dot-mark[data-from] { stroke: CanvasText; fill: Canvas; }
3276
+ }
3277
+ /* Changed since a snapshot (#191): a changed bar's old value is the comparison's tick; a changed line point's old place
3278
+ is a small hollow ink-muted ring. The caption says how many changed, in a switch that recedes the rest. */
3279
+ .alt-line-since { fill: var(--alt-chart-bg); stroke: var(--ink-muted); stroke-width: 1.5; }
3280
+ .alt-chart-since { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-2); border-radius: var(--radius-sm); font: 400 13px/19px var(--font-sans); color: var(--ink-muted); }
3281
+ @media (forced-colors: active) { .alt-line-since { stroke: CanvasText; fill: Canvas; } }
3282
+ .alt-legend-key[data-context] { color: var(--ink-muted); }
3283
+ @media (prefers-reduced-motion: reduce) { .alt-bar-mark, .alt-line-stroke, .alt-area-fill, .alt-area-line, .alt-scatter-dot { transition-property: fill-opacity, stroke-opacity; } }
3284
+ [data-motion="reduced"] .alt-bar-mark, [data-motion="reduced"] .alt-line-stroke, [data-motion="reduced"] .alt-area-fill, [data-motion="reduced"] .alt-area-line, [data-motion="reduced"] .alt-scatter-dot { transition-property: fill-opacity, stroke-opacity; }
3285
+ .alt-chart-boundary line { stroke: var(--line-control); stroke-width: 1; shape-rendering: crispEdges; }
3286
+ .alt-chart-boundary-label { fill: var(--ink-muted); stroke: var(--alt-chart-bg); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; font: 500 12.5px/17px var(--font-sans); }
3287
+ .alt-line-hollow { fill: var(--alt-chart-bg); stroke-width: 2; }
3288
+ .alt-bar-label-status { fill: var(--ink-muted); }
3289
+ .alt-chart-readout-note { margin-left: 6px; color: var(--ink); font-weight: 600; }
3290
+ .alt-chart-readout-note::before { content: "· "; }
3291
+ /* ── Filtered in place (#70): misses recede to 35% and keep their place and their colour slot, so positions survive
3292
+ the filter. Relevance elsewhere raises matches, but marks can't rise, so in a chart it's spent as contrast only.
3293
+ Misses cross-fade over dur-travel-1. The frame says how many match, in words. ── */
3294
+ .alt-chart-rel { transition: opacity var(--dur-travel-1) var(--ease-glide); }
3295
+ .alt-bar-label-miss { fill: var(--ink-muted); }
3296
+ .alt-chart-count { margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
3297
+ @media (forced-colors: active) {
3298
+ .alt-chart-rel[data-miss] { opacity: 1 !important; }
3299
+ .alt-chart-rel[data-miss] .alt-bar-mark:not([fill^="url"]), .alt-chart-rel[data-miss] .alt-area-fill:not([fill^="url"]) { fill: GrayText; }
3300
+ .alt-chart-rel[data-miss] [fill^="url"] { opacity: 0.45; }
3301
+ .alt-chart-rel[data-miss] .alt-line-stroke, .alt-chart-rel[data-miss] .alt-area-line { stroke: GrayText; }
3302
+ .alt-chart-rel[data-miss] circle, .alt-chart-rel[data-miss] rect, .alt-chart-rel[data-miss] path:not([class]) { fill: GrayText; stroke: GrayText; }
3303
+ .alt-bar-label-miss { fill: GrayText; }
3304
+ .alt-chart-count { color: CanvasText; }
3305
+ }
3306
+ /* ── ChartBrush (#74): an overview you slide a window along. The overview is an opening, the series flat in a trench;
3307
+ the window is a raised lens over it (level 1, accent rim, clear face), level 2 on hover and 3 while held, whose
3308
+ grips are a range Slider's thumbs, whose date bubbles hang below, clear of the plot's axis. The series is a flat line spanning its own low to high, as a Sparkline's is;
3309
+ outside the window it recedes to 35%. ── */
3310
+ .alt-brush { display: flex; flex-direction: column; gap: 6px; }
3311
+ .alt-brush-slide { position: relative; touch-action: none; user-select: none; cursor: pointer; padding: 2px 0; }
3312
+ .alt-brush-track { position: relative; border-radius: var(--radius-sm); background: var(--surface-trench); box-shadow: var(--trench); overflow: hidden; }
3313
+ .alt-brush-svg { display: block; }
3314
+ .alt-brush-line { stroke-width: 1.5px; stroke-linejoin: round; stroke-linecap: round; }
3315
+ .alt-brush-lens {
3316
+ position: absolute; top: 0; bottom: 0; box-sizing: border-box; border: 2px solid var(--accent); border-radius: var(--radius-sm);
3317
+ box-shadow: var(--elev-1); cursor: grab;
3318
+ transition: left var(--dur-travel-1) var(--ease-glide), width var(--dur-travel-1) var(--ease-glide), box-shadow var(--dur-travel-1) var(--ease-rise);
3319
+ }
3320
+ @media (hover: hover) { .alt-brush-lens:hover { box-shadow: var(--elev-2); } }
3321
+ .alt-brush-lens[data-grabbed] { box-shadow: var(--elev-3); cursor: grabbing; }
3322
+ .alt-brush-lens:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3323
+ .alt-brush-grip {
3324
+ position: absolute; top: 50%; width: 10px; height: 24px; translate: -50% -50%; box-sizing: border-box;
3325
+ border: 2px solid var(--accent); border-radius: var(--radius-full); background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1);
3326
+ cursor: ew-resize; transition: left var(--dur-travel-1) var(--ease-glide), box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-2) var(--ease-settle);
3327
+ }
3328
+ @media (hover: hover) { .alt-brush-grip:hover { box-shadow: var(--elev-2); } }
3329
+ .alt-brush-grip[data-grabbed] { box-shadow: var(--elev-3); transform: scale(calc(1 + 0.12 * var(--alt-travel))); }
3330
+ .alt-brush-grip:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3331
+ .alt-brush[data-dragging] :is(.alt-brush-lens, .alt-brush-grip) { transition-property: box-shadow, transform; }
3332
+ .alt-brush-bubble {
3333
+ position: absolute; left: 50%; top: calc(100% + 8px); translate: -50% 0; padding: 3px 8px; text-align: center;
3334
+ border-radius: var(--radius-sm); background: var(--surface-floating); box-shadow: var(--elev-1); color: var(--ink);
3335
+ font: 500 12px/16px var(--font-mono); white-space: nowrap; pointer-events: none; z-index: 1;
3336
+ opacity: 0; transform: translateY(calc(-4px * var(--alt-travel))) scale(calc(1 - 0.1 * var(--alt-travel))); transform-origin: 50% 0;
3337
+ transition: opacity var(--dur-travel-1) var(--ease-sink), transform var(--dur-travel-1) var(--ease-sink);
3338
+ }
3339
+ .alt-brush-grip[data-edge="start"] .alt-brush-bubble { left: 50%; translate: 0 0; transform-origin: 0 0; }
3340
+ .alt-brush-grip[data-edge="end"] .alt-brush-bubble { left: auto; right: 50%; translate: 0 0; transform-origin: 100% 0; }
3341
+ .alt-brush-grip[data-grabbed] .alt-brush-bubble, .alt-brush-grip:focus-visible .alt-brush-bubble {
3342
+ opacity: 1; transform: none; box-shadow: var(--elev-3);
3343
+ transition: opacity var(--dur-travel-3) var(--ease-rise), transform var(--dur-travel-3) var(--ease-rise), box-shadow var(--dur-travel-3) var(--ease-rise);
3344
+ }
3345
+ .alt-brush-range { margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
3346
+ [data-contrast="more"] .alt-brush-lens { border-color: var(--ink); }
3347
+ @media (forced-colors: active) {
3348
+ .alt-brush-track { border: 2px dashed CanvasText; }
3349
+ .alt-brush-lens { border: 1px solid Highlight; box-shadow: none; }
3350
+ .alt-brush-lens[data-grabbed] { border-width: 2px; }
3351
+ .alt-brush-grip { border: 1px solid CanvasText; }
3352
+ .alt-brush-line { forced-color-adjust: none; stroke: CanvasText; }
3353
+ .alt-brush-range { color: CanvasText; }
3354
+ }
3355
+ @media (prefers-reduced-motion: reduce) {
3356
+ .alt-brush-lens, .alt-brush-grip { transition-property: box-shadow; }
3357
+ }
3358
+
3359
+
3360
+ /* ── Marks you can select or drill into (#69). Hover is a promise: only where a click does something does the plot take
3361
+ a pointer cursor and outline the record under it in 2px ink (appearing over dur-travel-1), and the readout names the
3362
+ action. A mark can't lift without misstating its value, so in a chart the hover cue is the outline, the cursor and
3363
+ the readout; height stays with the readout. The selection keeps its colour and the rest recede, as a filter's do. ── */
3364
+ .alt-chart-plot[data-interactive] { cursor: pointer; }
3365
+ .alt-chart-hover path, .alt-chart-hover circle { fill: none; stroke: var(--ink); stroke-width: 2; pointer-events: none; animation: alt-chart-hover-in var(--dur-travel-1) var(--ease-glide); }
3366
+ @keyframes alt-chart-hover-in { from { stroke-opacity: 0; } }
3367
+ .alt-chart-readout-action { margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--line); color: var(--ink-muted); font-weight: 500; }
3368
+ .alt-chart-selection { display: flex; align-items: center; gap: var(--space-2); min-height: 0; font: 500 13px/19px var(--font-sans); color: var(--ink); }
3369
+ .alt-chart-selection:empty { display: none; }
3370
+ @media (forced-colors: active) {
3371
+ .alt-chart-hover path, .alt-chart-hover circle { stroke: Highlight; }
3372
+ .alt-chart-readout-action { border-top-color: CanvasText; }
3373
+ }
3374
+
3375
+ /* ── Live charts (#66): show the new state at once and leave a fading trace (phosphor). A new or changed mark carries
3376
+ a 2px ink outline (an outline, not height or a glow) that holds for the first second and decays over dur-settle on
3377
+ ease-decay, with its value beside it for that first second. A whole refresh is one change: the "Updated" caption
3378
+ takes a ground-level tint instead. Afterglows keep under reduced motion: they're tint, not travel. ── */
3379
+ .alt-chart-glow path, .alt-chart-glow circle { fill: none; stroke: var(--ink); stroke-width: 2; animation: alt-chart-glow var(--dur-settle) linear var(--alt-rise-delay, 0ms) both; }
3380
+ .alt-chart-glow text { fill: var(--ink); stroke: var(--alt-chart-bg); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; font: 600 12.5px/17px var(--font-sans); font-variant-numeric: tabular-nums; animation: alt-chart-glow-label var(--dur-settle) linear var(--alt-rise-delay, 0ms) both; }
3381
+ @keyframes alt-chart-glow { 0%, 12% { stroke-opacity: 1; animation-timing-function: var(--ease-decay); } 100% { stroke-opacity: 0; } }
3382
+ @keyframes alt-chart-glow-label { 0%, 12% { opacity: 1; } 14%, 100% { opacity: 0; } }
3383
+ .alt-chart-live { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-3); }
3384
+ .alt-chart-updated { padding: 1px 6px; margin-left: -6px; border-radius: var(--radius-sm); font: 400 13px/19px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
3385
+ .alt-chart-updated[data-settle]::before { animation-name: alt-chart-tint-a; }
3386
+ .alt-chart-updated[data-settle="b"]::before { animation-name: alt-chart-tint-b; }
3387
+ @keyframes alt-chart-tint-a { 0%, 12% { background-color: var(--accent-soft); animation-timing-function: var(--ease-decay); } 100% { background-color: transparent; } }
3388
+ @keyframes alt-chart-tint-b { 0%, 12% { background-color: var(--accent-soft); animation-timing-function: var(--ease-decay); } 100% { background-color: transparent; } }
3389
+ @media (forced-colors: active) {
3390
+ .alt-chart-glow path, .alt-chart-glow circle { stroke: Highlight; }
3391
+ .alt-chart-glow text { fill: CanvasText; stroke: Canvas; }
3392
+ .alt-chart-updated[data-settle]::before { border: 1px solid Highlight; }
3393
+ }
3394
+
3395
+ /* ── References and annotations (#68): what a chart is read against. A reference line is 1px ink-muted and solid,
3396
+ because dashes mean projected; a status threshold wears caution or critical, with its icon in the label. A band is a
3397
+ flat line-coloured fill (a decorative token, so it isn't read as data or as an opening), under the marks. Labels are
3398
+ caption text on a halo of the plot's colour, kept apart in rows. An annotation is a Badge plate flush at the top of
3399
+ the plot (a label is part of the record) with a 1px line-control leader to its x; one that acts lifts one level. ── */
3400
+ .alt-chart-ref { stroke: var(--ink-muted); stroke-width: 1; shape-rendering: crispEdges; }
3401
+ .alt-chart-ref[data-tone="caution"] { stroke: var(--caution); }
3402
+ .alt-chart-ref[data-tone="critical"] { stroke: var(--critical); }
3403
+ .alt-chart-ref-label text { fill: var(--ink-muted); stroke: var(--alt-chart-bg); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; font: 500 12.5px/17px var(--font-sans); font-variant-numeric: tabular-nums; }
3404
+ .alt-chart-ref-label[data-tone] text { fill: var(--ink); }
3405
+ .alt-chart-ref-icon { color: var(--critical); }
3406
+ .alt-chart-ref-label[data-tone="caution"] .alt-chart-ref-icon { color: var(--caution); }
3407
+ .alt-chart-note-leader { stroke: var(--line-control); stroke-width: 1; shape-rendering: crispEdges; }
3408
+ .alt-chart-note { position: absolute; z-index: 1; justify-content: center; box-sizing: border-box; }
3409
+ button.alt-chart-note { border: 0; cursor: pointer; box-shadow: var(--elev-0); transition: transform var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1) var(--ease-rise); }
3410
+ @media (hover: hover) { button.alt-chart-note:hover { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
3411
+ button.alt-chart-note:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3412
+ @media (forced-colors: active) {
3413
+ .alt-bar-pending { stroke: CanvasText; stroke-dasharray: 2 2; }
3414
+ .alt-chart-ref, .alt-chart-ref[data-tone] { stroke: CanvasText; }
3415
+ .alt-chart-ref-label text, .alt-chart-ref-label[data-tone] text { fill: CanvasText; stroke: Canvas; }
3416
+ .alt-chart-ref-icon, .alt-chart-ref-label[data-tone="caution"] .alt-chart-ref-icon { color: CanvasText; }
3417
+ .alt-chart-note-leader { stroke: CanvasText; }
3418
+ .alt-chart-note, button.alt-chart-note { border: 1px solid CanvasText; }
3419
+ button.alt-chart-note:focus-visible { outline-color: Highlight; }
3420
+ .alt-chart-boundary line { stroke: CanvasText; }
3421
+ .alt-chart-boundary-label { fill: CanvasText; stroke: Canvas; }
3422
+ .alt-line-hollow { fill: Canvas; }
3423
+ .alt-bar-label-status { fill: CanvasText; }
3424
+ }
3425
+
3426
+ /* ── The table view: the same record as a sortable DataTable, behind a segmented Chart | Table control (one raised
3427
+ pill gliding between two views of one record). Both views sit in one box, so switching doesn't move the page: the
3428
+ table scrolls inside the chart's size, and the two cross-fade over dur-travel-2 rather than slide. The unchosen
3429
+ view stays mounted but inert. ── */
3430
+ .alt-chart-tabs { display: contents; }
3431
+ .alt-chart-bar { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }
3432
+ .alt-chart-bar .alt-tabs-list { flex: none; }
3433
+ .alt-chart-views { display: grid; min-width: 0; }
3434
+ .alt-chart-view { grid-area: 1 / 1; min-width: 0; transition: opacity var(--dur-travel-2) var(--ease-glide); }
3435
+ .alt-chart-view.alt-tabs-content { animation: none; }
3436
+ .alt-chart-view[data-state="inactive"] { opacity: 0; pointer-events: none; }
3437
+ .alt-chart-view[data-view="table"] { position: relative; }
3438
+ .alt-chart-tablebox { position: absolute; inset: 0; overflow: auto; }
3439
+
3440
+ /* ── Loading, refetch and nothing: an imprint in the chart's shape while data is on its way (surface-well bars or a
3441
+ band pressed into the page, no labels, after dur-grace like every imprint); data lands by fading in place, never
3442
+ rising, because a rising bar would leave the ground line. A refetch keeps the chart and, after dur-grace, drops its
3443
+ marks to half strength. Nothing to show is an opening: an EmptyState in the plot's own box, with no axes. ── */
3444
+ .alt-chart-imprint { display: flex; align-items: flex-end; gap: 8%; padding: 8px 4% 26px; box-sizing: border-box; }
3445
+ .alt-chart-imprint > .alt-skeleton { flex: 1; border-radius: var(--radius-xs) var(--radius-xs) 0 0; }
3446
+ .alt-chart-imprint[data-shape="line"] { padding: 0; }
3447
+ .alt-chart-imprint-line { fill: none; stroke: var(--surface-well); stroke-width: 10px; stroke-linecap: round; vector-effect: non-scaling-stroke; }
3448
+ .alt-chart-await[data-phase="reveal"] > .alt-await-face { --alt-from-y: 0px; }
3449
+ .alt-chart-plot[data-refreshing] .alt-chart-marks { opacity: 0.5; transition: opacity var(--dur-travel-1) var(--ease-glide) var(--dur-grace); }
3450
+ .alt-chart-marks { transition: opacity var(--dur-travel-1) var(--ease-glide); }
3451
+ .alt-chart-empty { position: absolute; inset: 0; display: grid; }
3452
+ .alt-chart-empty > .alt-empty { margin: 0; justify-content: center; }
3453
+ @media (forced-colors: active) { .alt-chart-imprint-line { stroke: GrayText; stroke-dasharray: 6 4; stroke-width: 2px; } .alt-chart-imprint ~ .alt-chart-legend rect { fill: CanvasText; } }
3454
+
3455
+ /* ── The keyboard path: a layer of focusable nodes over the aria-hidden SVG, one level at a time (summary, series,
3456
+ points) with a roving tabindex, so the plot is one Tab stop. A node is invisible until focused; then it's the focus
3457
+ ring itself, 2px accent-ink at a 2px offset, round on a point. It moves instantly, because it's focus, and the
3458
+ readout opens on the focused point. Nodes take no pointer events, so the plot's hover still works through them. ── */
3459
+ .alt-chart-nav { position: absolute; inset: 0; pointer-events: none; }
3460
+ .alt-chart-node { position: absolute; box-sizing: border-box; border-radius: var(--radius-sm); outline: none; }
3461
+ .alt-chart-node[data-level="point"] { border-radius: var(--radius-full); }
3462
+ .alt-chart-node:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3463
+ @media (forced-colors: active) { .alt-chart-node:focus-visible { outline-color: Highlight; } }
3464
+
3465
+ /* ── LineChart: change over time as 2px strokes lying on the plot, round joins, no fill, no glow. A missing value is a
3466
+ gap in the line, never bridged. The last point wears a marker and each line is named at its end in ink, so the
3467
+ lines are their own legend. ── */
3468
+ .alt-line-label { fill: var(--ink); font: 500 12.5px/17px var(--font-sans); }
3469
+ @media (forced-colors: active) { .alt-line-label { fill: CanvasText; } }
3470
+ .alt-line-active { stroke: var(--alt-chart-bg); stroke-width: 2; paint-order: stroke; }
3471
+
3472
+ /* ── Sparkline: a word-sized trend, the record in the row it sits in. It lies flush on the ground like every mark: a
3473
+ 1.5px stroke (2px under contrast="more"), round joins, no fill under it, no axes, no gridlines, no readout, and a 4px
3474
+ marker on the last point. Bars are flat fills parted by 1px, standing on zero. A normal range is a flat band of
3475
+ line behind the marks. Its height is one line of the text around it (1lh) and it sits on that text's baseline. The
3476
+ colour is context (ink-muted) unless it's the series (data-1) or a status. While its data loads it's an imprint one
3477
+ line high and its own width. ── */
3478
+ .alt-sparkline { --spark: var(--ink-muted); display: inline-block; vertical-align: middle; height: 1.4em; height: 1lh; min-width: 0; }
3479
+ .alt-sparkline > svg { display: block; overflow: visible; }
3480
+ .alt-sparkline[data-tone="data"] { --spark: var(--data-1); }
3481
+ .alt-sparkline[data-tone="positive"] { --spark: var(--positive); }
3482
+ .alt-sparkline[data-tone="caution"] { --spark: var(--caution); }
3483
+ .alt-sparkline[data-tone="critical"] { --spark: var(--critical); }
3484
+ .alt-sparkline-line { fill: none; stroke: var(--spark); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
3485
+ .alt-sparkline-end, .alt-sparkline-bar { fill: var(--spark); }
3486
+ .alt-sparkline-band { fill: var(--line); }
3487
+ .alt-sparkline-imprint { display: inline-block; vertical-align: middle; height: 0.6em; border-radius: var(--radius-xs); }
3488
+ @media (prefers-contrast: more) { .alt-sparkline-line { stroke-width: 2; } }
3489
+ [data-contrast="more"] .alt-sparkline-line { stroke-width: 2; }
3490
+ @media (forced-colors: active) {
3491
+ .alt-sparkline, .alt-sparkline[data-tone] { --spark: CanvasText; }
3492
+ .alt-sparkline-band { fill: none; stroke: GrayText; stroke-dasharray: 2 2; }
3493
+ }
3494
+
3495
+ /* ── Stat: a headline number. A number is the record, so a stat lies flush on what it sits on (a Card's surface, or
3496
+ the page): no level, no shadow. The label is text-label in ink-muted; the value is text-title (text-display when
3497
+ size="hero", once per view) in ink, weight 600, tabular figures. A change with no stated good direction is
3498
+ ink-muted text with an arrow; with one, it's a Badge plate whose lamp is positive or critical, so the word, the
3499
+ arrow and the colour each say it. A changed value cross-fades (dur-travel-2) and the stat takes the afterglow; it
3500
+ never counts up. While the value loads it's an imprint of a number's width under a label already in place. ── */
3501
+ .alt-stat { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; border-radius: var(--radius-md); }
3502
+ .alt-stat-label { font: 500 14px/20px var(--font-sans); color: var(--ink-muted); }
3503
+ .alt-stat-main { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
3504
+ .alt-stat-value { display: inline-grid; font: 600 28px/34px var(--font-sans); letter-spacing: -0.018em; color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
3505
+ .alt-stat[data-size="hero"] .alt-stat-value { font: 600 44px/48px var(--font-sans); letter-spacing: -0.025em; }
3506
+ .alt-stat-text { grid-area: 1 / 1; }
3507
+ .alt-stat-text[data-in] { animation: alt-stat-in var(--dur-travel-2) var(--ease-glide) both; }
3508
+ .alt-stat-text[data-out] { animation: alt-stat-out var(--dur-travel-2) var(--ease-glide) both; }
3509
+ @keyframes alt-stat-in { from { opacity: 0; } }
3510
+ @keyframes alt-stat-out { to { opacity: 0; } }
3511
+ .alt-stat-trend { display: inline-flex; align-items: center; min-width: 0; font: 400 15px/23px var(--font-sans); }
3512
+ .alt-stat-imprint { display: inline-block; vertical-align: middle; width: 4.5em; height: 0.6em; border-radius: var(--radius-xs); }
3513
+ .alt-stat-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); font: 500 12.5px/17px var(--font-sans); letter-spacing: 0.005em; color: var(--ink-muted); }
3514
+ .alt-stat-change { gap: 4px; font-variant-numeric: tabular-nums; }
3515
+ .alt-stat-change[data-neutral] { display: inline-flex; align-items: center; color: var(--ink-muted); }
3516
+ .alt-stat-arrow { display: block; flex: none; }
3517
+ .alt-stat-help { flex-basis: 100%; font-weight: 400; }
3518
+ .alt-stat-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: var(--space-5) var(--space-6); }
3519
+ @media (forced-colors: active) { .alt-stat-change.alt-badge { border: 1px solid CanvasText; } }
3520
+
3521
+ /* ── Meter: where a reading sits in a known range. A reading is the record, so unlike Progress (a task under way, live,
3522
+ a raised fill rising out of a trench) a meter lies on the ground: a flush track of line with the reading drawn
3523
+ across it as flat ink, data-1 by default, no shadow, wall or sheen. Thresholds are 1px ink-muted notches standing
3524
+ at the track's rim. Past `high` the fill turns caution and past the limit critical, cross-fading, and the state is
3525
+ said in a Badge whose lamp relights. Segments are flat data-* fills parted by 2px, with a legend of their values.
3526
+ The fill glides on ease-glide (dur-travel-3); under reduced motion it changes in place. ── */
3527
+ .alt-meter { --meter-fill: var(--data-1); --meter-bg: var(--surface-ground); display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
3528
+ .alt-card .alt-meter { --meter-bg: var(--surface-raised); }
3529
+ .alt-meter[data-state="high"] { --meter-fill: var(--caution); }
3530
+ .alt-meter[data-state="critical"] { --meter-fill: var(--critical); }
3531
+ .alt-meter-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); font: 500 14px/20px var(--font-sans); color: var(--ink); }
3532
+ .alt-meter-value { margin-left: auto; font-variant-numeric: tabular-nums; color: var(--ink-muted); }
3533
+ .alt-meter-state { order: 3; }
3534
+ .alt-meter-track { position: relative; height: 8px; border-radius: var(--radius-full); background: var(--line); }
3535
+ .alt-meter-fill {
3536
+ position: absolute; top: 0; bottom: 0; border-radius: inherit; background: var(--meter-fill);
3537
+ transition: width calc(var(--dur-travel-3) * var(--alt-travel)) var(--ease-glide), left calc(var(--dur-travel-3) * var(--alt-travel)) var(--ease-glide), background-color var(--dur-travel-3) var(--ease-glide);
3538
+ }
3539
+ /* A segment after the first is parted from the one before by a 2px cut in the surface's own colour, and only the ends are rounded. */
3540
+ .alt-meter-fill[data-slot] { border-radius: 0; }
3541
+ .alt-meter-fill[data-slot] + .alt-meter-fill[data-slot] { border-left: 2px solid var(--meter-bg); background-clip: padding-box; }
3542
+ .alt-meter-fill[data-slot]:first-child { border-radius: var(--radius-full) 0 0 var(--radius-full); }
3543
+ .alt-meter-notch { position: absolute; top: -3px; bottom: -3px; width: 1px; margin-left: -0.5px; background: var(--ink-muted); }
3544
+ .alt-meter-legend { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-4); margin: 0; padding: 0; list-style: none; font: 500 13px/18px var(--font-sans); color: var(--ink); }
3545
+ .alt-meter-legend-value { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
3546
+ .alt-meter-imprint { height: 8px; border-radius: var(--radius-full); }
3547
+ @media (prefers-contrast: more) { .alt-meter-track { box-shadow: inset 0 0 0 1px var(--ink); } .alt-meter-notch { background: var(--ink); } }
3548
+ [data-contrast="more"] .alt-meter-track { box-shadow: inset 0 0 0 1px var(--ink); }
3549
+ [data-contrast="more"] .alt-meter-notch { background: var(--ink); }
3550
+ @media (forced-colors: active) {
3551
+ .alt-meter-track { background: Canvas; border: 1px solid CanvasText; }
3552
+ .alt-meter-fill { background: Highlight; forced-color-adjust: none; }
3553
+ .alt-meter-fill[data-slot] { background: Highlight !important; }
3554
+ .alt-meter-fill[data-slot] + .alt-meter-fill[data-slot] { border-left-color: Canvas; background-clip: border-box; }
3555
+ .alt-meter-notch { background: CanvasText; }
3556
+ .alt-meter-legend rect { fill: CanvasText; }
3557
+ }
3558
+
3559
+ /* ── BarList: a ranked list whose rows are their own bars. Rows are the record, flush on the ground with line
3560
+ dividers, like a table's. Each row's bar is a flat tint of data-1 (22%, so ink holds 4.5:1 over it) behind its label,
3561
+ its length the row's value against the largest; the label wraps rather than being cut, and the value sits at the
3562
+ row's end in tabular figures. A row that does something (a link or a button) lifts one level when pointed at, as
3563
+ Table's rows do, and its bar stays flat on it; a row that doesn't never moves. "Other" wears data-other. Filtering
3564
+ in place raises matches and sinks misses. Re-ranked rows are carried to their places (FLIP, dur-travel-3,
3565
+ ease-glide), and bars glide to their new lengths; under reduced motion both change in place. ── */
3566
+ .alt-barlist-wrap { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); min-width: 0; }
3567
+ .alt-barlist { align-self: stretch; margin: 0; padding: 0; list-style: none; font: 500 14px/20px var(--font-sans); color: var(--ink); }
3568
+ .alt-barlist-row { --bar: color-mix(in oklab, var(--data-1) 22%, transparent); position: relative; border-bottom: 1px solid var(--line); }
3569
+ .alt-barlist-row:last-child { border-bottom-color: transparent; }
3570
+ .alt-barlist-row[data-other] { --bar: color-mix(in oklab, var(--data-other) 22%, transparent); }
3571
+ .alt-barlist-item {
3572
+ position: relative; display: flex; align-items: center; gap: var(--space-2); width: 100%; min-height: 36px;
3573
+ margin: 0; padding: 3px var(--space-2) 3px 0; box-sizing: border-box; border: 0; border-radius: var(--radius-sm);
3574
+ background: transparent; font: inherit; color: inherit; text-align: start; text-decoration: none;
3575
+ }
3576
+ button.alt-barlist-item, a.alt-barlist-item {
3577
+ cursor: pointer; -webkit-tap-highlight-color: transparent; box-shadow: var(--elev-0);
3578
+ transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-1) var(--ease-rise);
3579
+ }
3580
+ @media (hover: hover) { button.alt-barlist-item:hover, a.alt-barlist-item:hover { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
3581
+ button.alt-barlist-item:active, a.alt-barlist-item:active { box-shadow: var(--elev-0); transform: none; transition-duration: var(--dur-press); }
3582
+ button.alt-barlist-item:focus-visible, a.alt-barlist-item:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3583
+ .alt-barlist-zone { position: relative; isolation: isolate; flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--space-3); min-height: 30px; padding: 4px var(--space-2); }
3584
+ .alt-barlist-bar {
3585
+ position: absolute; z-index: -1; left: 0; top: 0; bottom: 0; border-radius: var(--radius-xs); background: var(--bar);
3586
+ transition: width calc(var(--dur-travel-3) * var(--alt-travel)) var(--ease-glide);
3587
+ }
3588
+ .alt-barlist-name { flex: 1; min-width: 0; overflow-wrap: break-word; }
3589
+ .alt-barlist-value { flex: none; font-variant-numeric: tabular-nums; }
3590
+ .alt-barlist-share { flex: none; min-width: 3.2em; text-align: end; font-variant-numeric: tabular-nums; color: var(--ink-muted); }
3591
+ .alt-barlist-row[data-other] .alt-barlist-name { color: var(--ink-muted); }
3592
+ .alt-barlist-imprint { display: block; height: 0.6em; margin: 14px 0; }
3593
+ .alt-barlist-row[data-relevance="miss"] .alt-barlist-name, .alt-barlist-row[data-relevance="miss"] .alt-barlist-value { color: var(--ink-muted); }
3594
+ @media (prefers-contrast: more) { .alt-barlist-bar { box-shadow: inset 0 0 0 1px var(--ink); } }
3595
+ [data-contrast="more"] .alt-barlist-bar { box-shadow: inset 0 0 0 1px var(--ink); }
3596
+ @media (forced-colors: active) {
3597
+ .alt-barlist-bar { forced-color-adjust: none; background: color-mix(in srgb, Highlight 25%, Canvas); border: 1px solid Highlight; }
3598
+ button.alt-barlist-item:hover, a.alt-barlist-item:hover { outline: 1px solid CanvasText; }
3599
+ .alt-barlist-row { border-bottom-color: GrayText; }
3600
+ .alt-barlist-row[data-relevance="match"] { outline: 1px solid CanvasText; outline-offset: -1px; }
3601
+ .alt-barlist-row[data-relevance="miss"] { border-bottom-style: dashed; }
3602
+ }
3603
+
3604
+ /* ── AreaChart: a total over time and what it's made of. Bands are the record: flat data-* fills stacked on the ground
3605
+ line in config order, parted by a 2px line in the plot's own colour (the gap a stacked bar has), with a 2px top line
3606
+ on the topmost. A single area is a flat data-1 fill at reduced opacity under its 2px line. No gradient fill, ever:
3607
+ the only face gradient in Altitude is the sheen, and it means "press it". Bands are named inside themselves in ink,
3608
+ on a halo of the plot's colour, where they're tall enough. ── */
3609
+ .alt-area-sep { stroke: var(--alt-chart-bg); stroke-width: 2; }
3610
+ .alt-area-label { fill: var(--ink); stroke: var(--alt-chart-bg); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; font: 500 12.5px/17px var(--font-sans); pointer-events: none; }
3611
+ .alt-chart-imprint[data-shape="area"] { padding: 0; }
3612
+ .alt-chart-imprint-area { fill: var(--surface-well); }
3613
+ @media (prefers-contrast: more) { .alt-area-sep { stroke: var(--ink); stroke-width: 3; } }
3614
+ [data-contrast="more"] .alt-area-sep { stroke: var(--ink); stroke-width: 3; }
3615
+ @media (forced-colors: active) {
3616
+ .alt-area-sep { stroke: Canvas; }
3617
+ .alt-area-label { fill: CanvasText; stroke: Canvas; }
3618
+ .alt-chart-imprint-area { fill: none; stroke: GrayText; stroke-dasharray: 6 4; stroke-width: 2px; vector-effect: non-scaling-stroke; }
3619
+ }
3620
+
3621
+ /* ── Heatmap (#61): a value per cell across two dimensions. Cells are the record, so they lie flat on the plot: a
3622
+ data-seq step (data-div around a midpoint for polarity), parted by a 2px gap of the plot's own colour and rounded
3623
+ at radius-xs (less on small cells), never raised, shaded or glowing. "No data" isn't an opening, since it isn't
3624
+ waiting for anything: no fill and a 1px line outline. Rows and columns are named in ink-muted like an axis; the
3625
+ legend is the steps as swatches with their ranges in ink. With no crosshair in a grid, the cell under the readout
3626
+ wears a 2px ink outline, which never lifts it. A value change cross-fades the cell's fill; under reduced motion it's
3627
+ instant. Under forced colours and print each step adds a pattern density (none, sparse dots, dense dots, hatching,
3628
+ crosshatch), and contrast="more" gives every cell a 1px ink edge. ── */
3629
+ .alt-heat-labels text { fill: var(--ink-muted); font: 500 12.5px/17px var(--font-sans); font-variant-numeric: tabular-nums; }
3630
+ .alt-heat-legend text { fill: var(--ink); font: 500 12.5px/17px var(--font-sans); font-variant-numeric: tabular-nums; }
3631
+ .alt-heat-fill { transition: fill var(--dur-travel-1) var(--ease-glide); }
3632
+ .alt-heat-none { fill: none; stroke: var(--line); stroke-width: 1; }
3633
+ .alt-heat-pattern { pointer-events: none; }
3634
+ .alt-heat-active { fill: none; stroke: var(--ink); stroke-width: 2; pointer-events: none; }
3635
+ .alt-chart-node[data-shape="cell"] { border-radius: var(--radius-xs); }
3636
+ .alt-chart-imprint[data-shape="grid"] { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); grid-template-rows: repeat(7, minmax(0, 1fr)); gap: 3px; padding: 8px 0 26px; }
3637
+ .alt-chart-imprint[data-shape="grid"] > .alt-skeleton { border-radius: var(--radius-xs); }
3638
+ @media (prefers-reduced-motion: reduce) { .alt-heat-fill { transition: none; } }
3639
+ [data-motion="reduced"] .alt-heat-fill { transition: none; }
3640
+ @media (prefers-contrast: more) { .alt-heat-none { stroke: var(--line-control); } }
3641
+ [data-contrast="more"] .alt-heat-none { stroke: var(--line-control); }
3642
+ @media (forced-colors: active) {
3643
+ .alt-heat-labels text, .alt-heat-legend text { fill: CanvasText; }
3644
+ .alt-heat-none { stroke: GrayText; }
3645
+ .alt-heat-active { stroke: Highlight; }
3646
+ }
3647
+
3648
+ /* ── ScatterChart (#62): two measures per record. Dots are the record, so they lie on the plot: at least 8px, a data-*
3649
+ fill and a marker shape per group (circle, square, triangle), with a 2px ring of the plot's own colour so dots that
3650
+ overlap stay apart, and never a shadow, sheen or glow. At most three groups, the palette's first three slots, since
3651
+ any two dots can be neighbours. The dot under the readout is drawn again on top, so an overlapped dot is seen whole;
3652
+ it doesn't lift. Labelled outliers are ink on a halo with a 1px ink-muted leader; the trend is a 1px ink-muted
3653
+ line, named in words. Under forced colours the shapes carry the groups; contrast="more" rings each dot in 1px ink. ── */
3654
+ .alt-scatter-label text { fill: var(--ink); stroke: var(--alt-chart-bg); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; font: 500 12.5px/17px var(--font-sans); }
3655
+ .alt-scatter-label line { stroke: var(--ink-muted); stroke-width: 1; }
3656
+ .alt-scatter-trend line { stroke: var(--ink-muted); stroke-width: 1; }
3657
+ .alt-scatter-axis-name text { fill: var(--ink-muted); stroke: var(--alt-chart-bg); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; font: 600 12.5px/17px var(--font-sans); }
3658
+ .alt-scatter-trend text { fill: var(--ink-muted); stroke: var(--alt-chart-bg); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; font: 500 12.5px/17px var(--font-sans); }
3659
+ .alt-chart-imprint[data-shape="dots"] { display: block; position: relative; padding: 0; }
3660
+ .alt-chart-imprint-dot { position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: var(--radius-full); }
3661
+ @media (forced-colors: active) {
3662
+ .alt-scatter-label text, .alt-scatter-trend text, .alt-scatter-axis-name text { fill: CanvasText; stroke: Canvas; }
3663
+ .alt-scatter-label line, .alt-scatter-trend line { stroke: CanvasText; }
3664
+ }
3665
+
3666
+ /* ── ChartTooltip: reading exact values. The crosshair is a mark on the ground (1px line-control, snapped to the nearest
3667
+ x); the readout is live, a level-3 floating surface like Tooltip. It rises 4px once as its shadow grows from elev-1
3668
+ to elev-3, then steps between x positions with no easing (it follows the data, not each pixel of the pointer), flips
3669
+ sides so it never covers the point, and leaves in 70% of the time. It shares Tooltip's 400ms warm-up. It's
3670
+ aria-hidden: the same values are in each datum's name, the keyboard path and the table view. ── */
3671
+ .alt-chart { --alt-chart-bg: var(--surface-ground); }
3672
+ .alt-card .alt-chart { --alt-chart-bg: var(--surface-raised); }
3673
+ .alt-chart-plot { touch-action: pan-y; }
3674
+ .alt-chart-crosshair { stroke: var(--line-control); stroke-width: 1; shape-rendering: crispEdges; }
3675
+ .alt-chart-readout {
3676
+ position: absolute; z-index: var(--z-floating); pointer-events: none; display: grid; gap: 3px; min-width: 112px;
3677
+ padding: 8px 10px; border-radius: var(--radius-lg); background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--ink);
3678
+ font: 400 12.5px/17px var(--font-sans); white-space: nowrap;
3679
+ transition: opacity var(--dur-travel-3) var(--ease-rise), transform var(--dur-travel-3) var(--ease-rise), box-shadow var(--dur-travel-3) var(--ease-rise);
3680
+ }
3681
+ .alt-chart-readout[data-state="closed"] {
3682
+ opacity: 0; box-shadow: var(--elev-1); transform: translateY(calc(4px * var(--alt-travel)));
3683
+ transition-timing-function: var(--ease-sink); transition-duration: calc(var(--dur-travel-3) * 0.7);
3684
+ }
3685
+ .alt-chart-readout[data-warm] { transition-duration: var(--dur-travel-1); }
3686
+ .alt-chart-readout[data-side="right"] { translate: 14px 0; }
3687
+ .alt-chart-readout[data-side="left"] { translate: calc(-100% - 14px) 0; }
3688
+ .alt-chart-readout[data-side="below"] { translate: 0 14px; }
3689
+ .alt-chart-readout[data-side="above"] { translate: 0 calc(-100% - 14px); }
3690
+ .alt-chart-readout-label { font-weight: 500; color: var(--ink-muted); margin-bottom: 1px; }
3691
+ .alt-chart-readout-row { display: grid; grid-template-columns: auto auto 1fr; align-items: center; gap: 6px; }
3692
+ .alt-chart-readout-key { display: block; }
3693
+ .alt-chart-readout-value { font-weight: 500; font-variant-numeric: tabular-nums; color: var(--ink); }
3694
+ .alt-chart-readout-name { color: var(--ink-muted); }
3695
+ /* ── ChartLegend: names the series. A legend that only labels is settled: keys flush on the ground, a swatch in the
3696
+ series colour and its name in ink. A legend that hides and shows series is a row of choices, so each key is built
3697
+ like a Checkbox: a ghost button that lifts one level when pointed at (clicking it does something), holding a trench
3698
+ that the series colour pours into while the series is shown and drains out of when it's hidden. The checked fill
3699
+ is the series colour, not accent, because the swatch is the key. Hidden series keep their colour slots. ── */
3700
+ .alt-chart-legend { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); font: 500 13px/18px var(--font-sans); color: var(--ink); }
3701
+ .alt-chart-legend[data-toggle] { gap: var(--space-1); margin-left: calc(-1 * var(--space-2)); }
3702
+ .alt-legend-key { display: inline-flex; align-items: center; gap: var(--space-2); }
3703
+ .alt-legend-mark { display: block; flex: none; }
3704
+ button.alt-legend-key {
3705
+ appearance: none; border: 0; margin: 0; padding: 5px var(--space-2); border-radius: var(--radius-sm); font: inherit; color: var(--ink);
3706
+ background: transparent; box-shadow: var(--elev-0); cursor: pointer; -webkit-tap-highlight-color: transparent;
3707
+ transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-1) var(--ease-rise), color var(--dur-travel-1);
3708
+ }
3709
+ button.alt-legend-key:focus-visible { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
3710
+ @media (hover: hover) { button.alt-legend-key:hover:not([aria-disabled]) { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
3711
+ button.alt-legend-key:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3712
+ button.alt-legend-key[aria-pressed="false"] { color: var(--ink-muted); }
3713
+ button.alt-legend-key[aria-disabled] { cursor: default; }
3714
+ .alt-legend-swatch {
3715
+ display: inline-grid; place-items: center; width: 22px; height: 16px; box-sizing: border-box; border-radius: 5px;
3716
+ border: 1px solid var(--line-control); background: var(--surface-trench); box-shadow: var(--trench);
3717
+ }
3718
+ .alt-legend-swatch .alt-legend-mark {
3719
+ opacity: 0; clip-path: circle(0% at 50% 50%);
3720
+ transition: clip-path var(--dur-travel-1) var(--ease-sink), opacity var(--dur-travel-1) var(--ease-sink);
3721
+ }
3722
+ .alt-legend-swatch[data-shown] { border-color: transparent; }
3723
+ .alt-legend-swatch .alt-legend-mark { width: 22px; height: 16px; }
3724
+ .alt-legend-swatch[data-shown] .alt-legend-mark { opacity: 1; clip-path: circle(75% at 50% 50%); transition-timing-function: var(--ease-rise); }
3725
+ @media (prefers-contrast: more) { .alt-legend-swatch { box-shadow: inset 0 1px 0 0 var(--ink); } }
3726
+ [data-contrast="more"] .alt-legend-swatch { box-shadow: inset 0 1px 0 0 var(--ink); }
3727
+ @media (forced-colors: active) {
3728
+ .alt-chart-readout { border: 2px solid CanvasText; }
3729
+ button.alt-legend-key:hover:not([aria-disabled]) { border: 1px solid CanvasText; padding: 4px 7px; }
3730
+ .alt-legend-swatch { border-color: CanvasText; }
3731
+ .alt-legend-swatch:not([data-shown]) { border-style: dashed; }
3732
+ .alt-chart-crosshair { stroke: CanvasText; }
3733
+ .alt-line-active { stroke: Canvas; }
3734
+ }
3735
+ @media (prefers-contrast: more) {
3736
+ .alt-chart-grid line, .alt-chart-floor { stroke: var(--line-control); }
3737
+ .alt-chart-ground { stroke: var(--ink); }
3738
+ }
3739
+ [data-contrast="more"] .alt-chart-grid line, [data-contrast="more"] .alt-chart-floor { stroke: var(--line-control); }
3740
+ [data-contrast="more"] .alt-chart-ground { stroke: var(--ink); }
3741
+ @media (forced-colors: active) {
3742
+ .alt-chart-axis text { fill: CanvasText; }
3743
+ .alt-chart-axis[data-inline] text { stroke: Canvas; }
3744
+ .alt-chart-grid line, .alt-chart-floor { stroke: GrayText; }
3745
+ .alt-chart-ground { stroke: CanvasText; }
3746
+ }
3747
+
1635
3748
  /* ════════════════════ Accessible elevation ════════════════════ */
1636
3749
 
1637
3750
  /* High contrast: elevation becomes a hard slab whose thickness is the height (1 level = 1px more),
@@ -1649,7 +3762,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1649
3762
  --line: var(--line-control); --alt-sheen: none;
1650
3763
  --alt-ring: var(--ink); --alt-cast: transparent; --alt-cast-deep: transparent; --alt-wall-in: var(--ink-muted); --alt-lip: transparent; --alt-rim-held: var(--ink);
1651
3764
  }
1652
- .alt-btn, .alt-kbd, .alt-crumbs button { --wall-c: var(--ink); }
3765
+ .alt-btn, .alt-kbd, .alt-crumbs .alt-crumb-link { --wall-c: var(--ink); }
1653
3766
  .alt-empty { border-style: solid; }
1654
3767
  }
1655
3768
  [data-contrast="more"], [data-contrast="more"] [data-theme] {
@@ -1664,7 +3777,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1664
3777
  --line: var(--line-control); --alt-sheen: none;
1665
3778
  --alt-ring: var(--ink); --alt-cast: transparent; --alt-cast-deep: transparent; --alt-wall-in: var(--ink-muted); --alt-lip: transparent; --alt-rim-held: var(--ink);
1666
3779
  }
1667
- [data-contrast="more"] .alt-btn, [data-contrast="more"] .alt-kbd, [data-contrast="more"] .alt-crumbs button { --wall-c: var(--ink); }
3780
+ [data-contrast="more"] .alt-btn, [data-contrast="more"] .alt-kbd, [data-contrast="more"] .alt-crumbs .alt-crumb-link { --wall-c: var(--ink); }
1668
3781
  [data-contrast="more"] .alt-empty { border-style: solid; }
1669
3782
 
1670
3783
  /* Forced colours (Windows High Contrast) drop every box-shadow, which erases elevation entirely
@@ -1688,7 +3801,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1688
3801
  .alt-tabs[data-variant="folder"] .alt-tabs-trigger[aria-selected="true"] { border: 2px solid CanvasText; border-bottom: 0; }
1689
3802
  .alt-tabs[data-variant="folder"] > .alt-tabs-content { border: 1px solid CanvasText; }
1690
3803
  .alt-acc-item[data-state="open"] { border: 1px solid CanvasText; } .alt-acc-content { border: 1px dashed CanvasText; }
1691
- .alt-crumbs button { border: 1px solid CanvasText; } .alt-crumbs li[data-current] button { border-width: 2px; }
3804
+ .alt-crumbs .alt-crumb-link { border: 1px solid CanvasText; } .alt-crumbs li[data-current] .alt-crumb-link { border-width: 2px; }
1692
3805
  .alt-cmd { border: 4px double CanvasText; } .alt-cmd-hl { display: none; } .alt-cmd-item[aria-selected="true"] { outline: 2px solid Highlight; outline-offset: -2px; }
1693
3806
  .alt-sheet { border: 2px solid CanvasText; } .alt-sheet-pull { border: 1px dashed CanvasText; }
1694
3807
  .alt-input[data-filled]:not([data-invalid]) { border-style: solid !important; }
@@ -1697,5 +3810,769 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1697
3810
  .alt-board-card { border: 1px solid CanvasText; } .alt-board-card[data-carried] { border-width: 2px; } .alt-board[data-carrying] .alt-board-col { border: 1px dashed CanvasText; } .alt-board[data-carrying] .alt-board-col[data-over] { border: 2px dashed Highlight; } .alt-board-slot { outline-color: CanvasText; }
1698
3811
  .alt-tr[data-row-held] > .alt-td { border-block: 2px solid CanvasText; } .alt-table-slot { outline-color: CanvasText; } .alt-table-wrap[data-top] .alt-th { border-bottom: 1px solid CanvasText; }
1699
3812
  .alt-step[data-state="current"] { border: 1px solid CanvasText; } .alt-step[data-state="upcoming"] { border: 1px dashed CanvasText; } .alt-step-mark { border: 1px solid CanvasText; } .alt-step-line[data-done] { background: Highlight; }
1700
- .alt-avatar[data-status="offline"] .alt-avatar-face { border: 1px dashed CanvasText; } .alt-avatar[data-status="active"] .alt-avatar-face { border: 2px solid CanvasText; }
3813
+ .alt-avatar:is([data-status="offline"], [data-more]) .alt-avatar-face { border-color: GrayText; } .alt-avatar[data-live] .alt-avatar-face { border-width: 2px; }
3814
+ .alt-avatar[data-status="active"] .alt-avatar-lamp { forced-color-adjust: none; background: CanvasText; }
3815
+ }
3816
+
3817
+ /* Stationary dialogs keep their context still; the dimming scrim and modal tier remain. */
3818
+ .alt-dialog-root[data-background="stationary"] > .alt-scrim,
3819
+ [data-modal-stationary] > .alt-scrim {
3820
+ backdrop-filter: none;
3821
+ -webkit-backdrop-filter: none;
3822
+ }
3823
+
3824
+ /* ── InlineEdit: one value edited where it sits. The value's text lives in a slot with the control's own inset (12px
3825
+ and a transparent 1px border, as .alt-input and .alt-select-trigger have) and font, at the control's height, and the
3826
+ line reaches 13px past its column on each side so the text itself lines up with the page. Opening puts the control in
3827
+ the same slot: the text stays exactly where it was and only the well (floor, walls, border) fades in around it. Save
3828
+ and Cancel take the Edit button's place on the same row; notes under the line (an error, the key hint, "Choose to
3829
+ save") arrive as the line's glide reaches them. The status is announced, never shown. ── */
3830
+ .alt-inline-edit { min-width: 0; }
3831
+ .alt-inline-edit-surface { min-width: 0; border-radius: var(--radius-md); }
3832
+ .alt-inline-edit-line { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); min-width: 0; margin-inline: -13px; }
3833
+ .alt-inline-edit-slot, .alt-inline-edit-slot-box { flex: 0 1 auto; min-width: 0; }
3834
+ .alt-inline-edit[data-state]:not([data-state="read"]) .alt-inline-edit-slot-box { flex: 1 1 14rem; }
3835
+ .alt-inline-edit-slot {
3836
+ position: relative; box-sizing: border-box; display: flex; align-items: center; max-width: 100%; min-height: max(40px, calc(1lh + 18px));
3837
+ padding: 0 12px; border: 1px solid transparent; border-radius: var(--radius-md);
3838
+ }
3839
+ .alt-inline-edit-value { overflow-wrap: anywhere; min-width: 0; }
3840
+ .alt-inline-edit-slot-box > .alt-input:not([data-multiline]) { font: inherit; height: max(40px, calc(1lh + 18px)); }
3841
+ .alt-inline-edit-slot-box .alt-input input, .alt-inline-edit-slot-box .alt-input textarea, .alt-inline-edit-slot-box .alt-select-trigger { font: inherit; }
3842
+ .alt-inline-edit-slot-box .alt-select > .alt-input { font: inherit; height: max(40px, calc(1lh + 18px)); }
3843
+ .alt-inline-edit .alt-input, .alt-inline-edit .alt-select { min-width: 0; }
3844
+ .alt-inline-edit .alt-input input { width: 100%; min-width: 0; }
3845
+ /* The well opens under text that stays put: only its floor, walls and border fade in. */
3846
+ .alt-inline-edit-slot-box > .alt-input, .alt-inline-edit-slot-box .alt-select > .alt-input, .alt-edit-region-form[data-layout="in-place"] :is(.alt-input, .alt-select > .alt-input) { animation: alt-well-open var(--dur-travel-2) var(--ease-sink); }
3847
+ @keyframes alt-well-open { from { background-color: transparent; border-color: transparent; box-shadow: none; } }
3848
+ .alt-inline-edit-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-2); margin-inline-start: auto; }
3849
+ .alt-inline-edit-actions .alt-btn { max-width: 100%; white-space: normal; }
3850
+ .alt-inline-edit-note { margin: var(--space-2) 0 0; }
3851
+ .alt-inline-edit-current { color: var(--ink-muted); font: 400 12.5px/17px var(--font-sans); overflow-wrap: anywhere; }
3852
+ /* Multiline: the slot takes the textarea's inset and the whole width; Edit, then Save and Cancel, sit under it on the right. */
3853
+ .alt-inline-edit[data-multiline] .alt-inline-edit-slot, .alt-inline-edit[data-multiline] .alt-inline-edit-slot-box { flex: 1 1 100%; }
3854
+ .alt-inline-edit[data-multiline] .alt-inline-edit-slot { align-items: flex-start; min-height: 0; padding: var(--space-2) 12px; }
3855
+ .alt-inline-edit[data-multiline] .alt-inline-edit-value { white-space: pre-wrap; }
3856
+ .alt-inline-edit[data-multiline] textarea { flex: none; min-height: 0; resize: none; }
3857
+ .alt-inline-edit[data-multiline] .alt-inline-edit-actions { margin-inline-start: 0; justify-content: flex-start; }
3858
+ .alt-inline-edit-keys { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 2px; }
3859
+
3860
+ /* The value as its own button ('pencil', 'field'): the same slot, so its text sits where it always does. */
3861
+ .alt-inline-edit-slot-button { width: auto; margin: 0; font: inherit; color: inherit; text-align: start; background: transparent; cursor: pointer;
3862
+ transition: background-color var(--dur-travel-1) var(--ease-rise), border-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise); }
3863
+ .alt-inline-edit-slot-button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
3864
+ .alt-inline-edit-slot-button:disabled { cursor: default; }
3865
+ .alt-inline-edit-slot-button[data-empty] .alt-inline-edit-value { color: var(--ink-muted); }
3866
+ /* Pencil: a faint well tint and the pencil, whose space is always reserved, so showing it moves nothing. Touch screens
3867
+ can't hover, so they always show it. */
3868
+ .alt-inline-edit-pencil { flex: none; margin-inline-start: var(--space-2); color: var(--ink-muted); opacity: 0; transition: opacity var(--dur-travel-1) var(--ease-rise); }
3869
+ @media (hover: hover) { .alt-inline-edit[data-trigger="pencil"] .alt-inline-edit-slot-button:hover:not(:disabled) { background: var(--surface-well); } }
3870
+ .alt-inline-edit-slot-button:focus-visible .alt-inline-edit-pencil { opacity: 1; }
3871
+ @media (hover: hover) { .alt-inline-edit-slot-button:hover:not(:disabled) .alt-inline-edit-pencil { opacity: 1; } }
3872
+ @media (hover: none) { .alt-inline-edit-pencil { opacity: 1; } }
3873
+ /* Field: pointing at the value draws the filled input's border and floor around it, so the click only focuses it. */
3874
+ .alt-inline-edit[data-trigger="field"] .alt-inline-edit-slot-button { cursor: text; }
3875
+ @media (hover: hover) { .alt-inline-edit[data-trigger="field"] .alt-inline-edit-slot-button:is(:hover, :focus-visible):not(:disabled) { background: var(--surface-raised); border-color: var(--line-control); box-shadow: 0 1px 0 0 var(--alt-lip); } }
3876
+ .alt-inline-edit[data-trigger="field"] .alt-inline-edit-slot-button:focus-visible { outline-color: var(--alt-rim-held); }
3877
+ /* It was drawn on hover already, so the real field doesn't fade in again. */
3878
+ .alt-inline-edit[data-trigger="field"] .alt-inline-edit-slot-box > .alt-input { animation: none; }
3879
+ /* hint="seam": at rest, a hairline groove is scored under the value where its well will open (a line-control cut with
3880
+ the well's light lip beneath it), so a value whose trigger only shows on hover still reads as something to point at.
3881
+ The field trigger's border replaces it on hover. Flat in contrast="more" and forced colours. */
3882
+ .alt-inline-edit [data-hint="seam"]::after {
3883
+ content: ""; position: absolute; left: 12px; right: 12px; bottom: 6px; height: 1px; border-radius: 1px; pointer-events: none;
3884
+ background: color-mix(in oklab, var(--line-control) 40%, transparent); box-shadow: 0 1px 0 0 var(--alt-lip);
3885
+ transition: opacity var(--dur-travel-1) var(--ease-rise);
3886
+ }
3887
+ .alt-inline-edit[data-trigger="pencil"] [data-hint="seam"]::after { right: calc(12px + 15px + var(--space-2)); }
3888
+ .alt-inline-edit-plate[data-hint="seam"] { position: relative; }
3889
+ .alt-inline-edit-plate[data-hint="seam"]::after { left: 12px; right: 12px; bottom: 3px; }
3890
+ @media (hover: hover) { .alt-inline-edit[data-trigger="field"] [data-hint="seam"]:is(:hover, :focus-visible)::after { opacity: 0; } }
3891
+ .alt-inline-edit [data-hint="seam"][data-empty]::after { display: none; }
3892
+ [data-contrast="more"] .alt-inline-edit [data-hint="seam"]::after { background: var(--line-control); box-shadow: none; }
3893
+ @media (forced-colors: active) {
3894
+ .alt-inline-edit [data-hint="seam"]::after { background: none; border-bottom: 1px dashed CanvasText; box-shadow: none; }
3895
+ .alt-inline-edit[data-trigger="field"] .alt-inline-edit-slot-button:is(:hover, :focus-visible) { border-color: CanvasText; }
3896
+ }
3897
+
3898
+ /* ── EditRegion: several fields of one record, saved together. Read mode is the record on the ground (level 0) with a
3899
+ visible Edit button. Editing lifts the region into a static raised card (level 1, no hover lift: it's being worked
3900
+ in, not offered), as an Accordion lid lifts over its compartment; the fields inside are openings (Input, Select…),
3901
+ and Surface's own signals carry the write: pending hovers after dur-grace, a known failure drops to a trench, an
3902
+ accepted save settles back to the ground with one afterglow for the whole region. The height glides open; the
3903
+ fields fade in together, never staggered. Forced colours and contrast="more" come from Surface and the controls. ── */
3904
+ .alt-edit-region { min-width: 0; --alt-er-pad: var(--space-3); }
3905
+ .alt-edit-region[data-layout="in-place"] { --alt-er-pad: calc(var(--space-2) + 13px); }
3906
+ /* The editor's padding and border are there in read mode too, under a matching negative margin, so lifting it changes
3907
+ colour and shadow but never moves the record; the afterglow fills that padded box, so it never sits against the text. */
3908
+ .alt-edit-region-surface { min-width: 0; margin: calc(-1 * (var(--space-2) + 1px)) calc(-1 * (var(--alt-er-pad) + 1px)); padding: var(--space-2) var(--alt-er-pad); border: 1px solid transparent; border-radius: var(--radius-lg);
3909
+ transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), border-color var(--dur-travel-1) var(--ease-rise); }
3910
+ .alt-edit-region:not([data-state="read"]) .alt-edit-region-surface { border-color: var(--line-control); }
3911
+ .alt-edit-region-view { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-2) var(--space-3); flex-wrap: wrap; }
3912
+ .alt-edit-region-values { flex: 1 1 14rem; min-width: 0; overflow-wrap: anywhere; }
3913
+ .alt-edit-region-form { display: grid; gap: var(--space-4); min-width: 0; --alt-from-y: 0px; animation: alt-emerge var(--dur-travel-1) var(--ease-rise); }
3914
+ .alt-edit-region-fields { display: grid; gap: var(--space-3); min-width: 0; }
3915
+ .alt-edit-region-fields .alt-field, .alt-edit-region-fields .alt-input, .alt-edit-region-fields .alt-select { min-width: 0; }
3916
+ .alt-edit-region-fields .alt-input input { width: 100%; min-width: 0; }
3917
+ .alt-edit-region-chrome { display: grid; gap: var(--space-3); }
3918
+ .alt-edit-region-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
3919
+ .alt-edit-region-actions .alt-btn { max-width: 100%; white-space: normal; }
3920
+ /* In place: the form doesn't fade in as a whole; fields whose text lands where their value was simply appear, the
3921
+ rest fade in, and nothing slides. Controls take the value's font and reach 13px past their column on each side. */
3922
+ .alt-edit-region-form[data-layout="in-place"] { gap: var(--space-3); animation: none; }
3923
+ /* The fields sit where the values were, so the form adds no spacing of its own between them. */
3924
+ .alt-edit-region-form[data-layout="in-place"] .alt-edit-region-fields { display: block; }
3925
+ .alt-edit-region-form[data-layout="in-place"] :is(.alt-input:not(.alt-select > .alt-input), .alt-select) { margin-inline: -13px; }
3926
+ .alt-edit-region-form[data-layout="in-place"] :is(.alt-input input, .alt-input textarea, .alt-select-trigger, .alt-switch-row) { font: inherit; }
3927
+ .alt-edit-region-form[data-layout="in-place"] .alt-input:not([data-multiline]), .alt-edit-region-form[data-layout="in-place"] .alt-select > .alt-input { font: inherit; height: max(40px, calc(1lh + 18px)); }
3928
+ .alt-edit-region-form[data-layout="in-place"] .alt-edit-region-actions { justify-content: flex-end; }
3929
+ /* The record around the fields names them (a row's label, the value's place), so a Field's label is announced, not shown. */
3930
+ .alt-edit-region-form[data-layout="in-place"] .alt-field { gap: var(--space-1); }
3931
+ .alt-edit-region-form[data-layout="in-place"] .alt-field-label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
3932
+ /* A reserved value already has its control's height and inset, so in labelled rows Edit moves nothing. */
3933
+ .alt-edit-region-value[data-reserve] { box-sizing: border-box; display: flex; align-items: center; min-height: max(40px, calc(1lh + 18px)); margin-inline: -13px; padding: 0 12px; border: 1px solid transparent; overflow-wrap: anywhere; }
3934
+ /* ── TagInput: a set of short values in one field. The field is an opening, built like Input; once it holds a tag its
3935
+ floor rises flush (filled). Each tag is a label, part of the record, so it's a Badge plate stamped flush into the
3936
+ floor, and stamped again (down 3px, settling) when it arrives. A first Backspace arms the last tag: it lifts one
3937
+ level on a critical tint; a second lifts it off (up 8px, fading) and the gap closes. A tag waiting on onAdd or
3938
+ onRemove hovers at level 2 after dur-grace; a rejected one drops to a trench with a critical rim. Suggestions are the
3939
+ Combobox's list at level 3. ── */
3940
+ .alt-tag-input { position: relative; display: block; min-width: 0; }
3941
+ .alt-tag-input > .alt-input { height: auto; min-height: var(--size-field-md); flex-wrap: wrap; align-items: center; row-gap: 4px; padding-block: 5px; }
3942
+ .alt-tag-input > .alt-input[data-size="sm"] { min-height: var(--size-field-sm); padding-block: 3px; }
3943
+ .alt-tag-input > .alt-input > input { flex: 1 1 6ch; min-width: 6ch; height: 28px; }
3944
+ .alt-tag-input > .alt-input:not([data-filled]):not([data-invalid]):not([data-disabled]) { --w: 4px; --n: calc(var(--near) * var(--alt-travel)); background: var(--surface-well); }
3945
+ .alt-tag-list { display: contents; list-style: none; margin: 0; padding: 0; }
3946
+ .alt-tag {
3947
+ --t-bg: color-mix(in oklab, var(--surface-ground), var(--surface-well) 70%);
3948
+ position: relative; display: inline-flex; align-items: center; max-width: 100%; height: 24px; border-radius: var(--radius-sm);
3949
+ background: var(--t-bg); color: var(--ink); font: 550 12.5px/1 var(--font-sans); vertical-align: middle;
3950
+ box-shadow: inset 0 1px 1.5px color-mix(in oklab, var(--ink) 16%, transparent), inset 0 0 0 1px color-mix(in oklab, var(--ink) 9%, transparent), 0 1px 0 0 var(--alt-lip);
3951
+ transition: transform var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1) var(--ease-rise);
3952
+ }
3953
+ .alt-tag-face { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 24ch; height: 100%; margin: 0; padding: 0 8px; border: 0; border-radius: inherit; background: none; color: inherit; font: inherit; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: default; }
3954
+ .alt-tag-face:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
3955
+ .alt-tag:has(.alt-tag-remove) .alt-tag-face { padding-right: 2px; }
3956
+ .alt-tag-remove { display: inline-grid; place-items: center; width: 20px; height: 20px; margin-right: 2px; padding: 0; border: 0; border-radius: var(--radius-sm); background: none; color: var(--ink-muted); cursor: pointer; transition: background-color var(--dur-travel-1), box-shadow var(--dur-travel-1); }
3957
+ @media (hover: hover) { .alt-tag-remove:hover { background: var(--surface-raised); color: var(--ink); box-shadow: var(--elev-1); } }
3958
+ .alt-tag[data-stamp] { animation: alt-stamp-a var(--dur-travel-2) var(--ease-settle); }
3959
+ .alt-tag[data-armed] { --t-bg: var(--critical-soft); background: var(--critical-soft); transform: translateY(calc(-1px * var(--alt-travel))); box-shadow: var(--elev-1), inset 0 0 0 1px color-mix(in oklab, var(--critical) 40%, transparent); }
3960
+ .alt-tag[data-status="pending"] { animation: alt-tag-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
3961
+ @keyframes alt-tag-pending { to { background: var(--surface-lifted); box-shadow: var(--elev-2); transform: translateY(calc(-2px * var(--alt-travel))); } }
3962
+ .alt-tag[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
3963
+ .alt-tag[data-leaving] { animation: alt-lifted-off 140ms var(--ease-rise) forwards; }
3964
+ .alt-tag-edit { display: inline-flex; }
3965
+ .alt-tag-edit-input { height: 24px; padding: 0 6px; border: 1px solid var(--line-control); border-radius: var(--radius-sm); background: var(--surface-well); color: var(--ink); font: 500 12.5px/1 var(--font-sans); box-shadow: var(--well); outline: none; }
3966
+ .alt-tag-edit-input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
3967
+ .alt-tag-edit-input[aria-invalid] { background: var(--critical-soft); border-color: var(--critical); }
3968
+ .alt-tag-hint { display: block; margin-top: var(--space-1); color: var(--ink-muted); font: 400 12.5px/17px var(--font-sans); }
3969
+ .alt-tag-error { margin: var(--space-1) 0 0; }
3970
+ .alt-tag-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
3971
+ .alt-tag-standalone { margin: 2px; }
3972
+ [data-contrast="more"] .alt-tag[data-armed] { box-shadow: 0 2px 0 0 var(--ink), inset 0 0 0 1px var(--critical); }
3973
+ @media (forced-colors: active) {
3974
+ .alt-tag { border: 1px solid CanvasText; } .alt-tag[data-armed] { border: 1px solid Highlight; outline: 1px solid Highlight; }
3975
+ .alt-tag[data-status="pending"] { border-style: dotted; } .alt-tag[data-status="failed"] { border: 2px dashed CanvasText; }
3976
+ .alt-tag-input > .alt-input:not([data-filled]) { border-style: dashed; }
3977
+ }
3978
+
3979
+ /* ── DatePicker, DateRangePicker, Calendar: the field is an opening built like Select. Day, month and year are typed
3980
+ segments; a partial date isn't a value, so the floor rises flush only once a whole, allowed date is held. The
3981
+ calendar floats at level 3 like a Popover. Days are flush (at this density a trench per day would be noise); the
3982
+ chosen day carries the one raised accent mark, the RadioGroup thumb's rule, and the mark is carried to a new day in
3983
+ the same month. Hovering a day shows the mark's ghost (feedforward). A range is the Slider's build: a sunk
3984
+ accent-soft track between two raised ends. A month change is lateral, so it cross-fades. Calendar on its own lies
3985
+ on the page at level 0. ── */
3986
+ .alt-date-picker { position: relative; display: block; min-width: 0; }
3987
+ .alt-date-picker > .alt-input { gap: 0; padding-right: 4px; cursor: text; }
3988
+ .alt-date-segs { display: inline-flex; align-items: center; min-width: 0; font: 400 14px/20px var(--font-sans); font-variant-numeric: tabular-nums; transform: translateY(calc(var(--wd) * 0.5)); transition: transform var(--dur-travel-2) var(--ease-settle); }
3989
+ .alt-date-seg { padding: 1px 2px; border-radius: 3px; outline: none; caret-color: transparent; }
3990
+ .alt-date-seg[data-placeholder] { color: var(--ink-muted); }
3991
+ .alt-date-seg:focus { background: var(--accent); color: var(--on-accent); }
3992
+ .alt-date-sep { padding: 0 1px; color: var(--ink-muted); }
3993
+ .alt-date-to { padding: 0 var(--space-2); color: var(--ink-muted); transform: translateY(calc(var(--wd) * 0.5)); }
3994
+ .alt-date-button { display: inline-grid; place-items: center; width: var(--size-control-sm); height: var(--size-control-sm); margin-left: auto; padding: 0; border: 0; border-radius: var(--radius-sm); background: none; color: var(--ink-muted); cursor: pointer; transition: background-color var(--dur-travel-1), box-shadow var(--dur-travel-1), color var(--dur-travel-1); }
3995
+ @media (hover: hover) { .alt-date-button:hover:not(:disabled) { background: var(--surface-raised); color: var(--ink); box-shadow: var(--elev-1); } }
3996
+ .alt-date-button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
3997
+ .alt-date-picker > .alt-input:not([data-filled]):not([data-invalid]):not([data-disabled]) { --w: 4px; --n: calc(var(--near) * var(--alt-travel)); background: var(--surface-well); }
3998
+ .alt-date-picker > .alt-input:not([data-filled]):not([data-invalid]):not([data-disabled]):focus-within { --n: var(--alt-travel); }
3999
+ .alt-date-error { margin: var(--space-1) 0 0; }
4000
+ .alt-date-panel { padding: var(--space-3); min-width: 0; }
4001
+ .alt-date-step { margin: 0 0 var(--space-2); color: var(--ink-muted); font: 500 12.5px/17px var(--font-sans); }
4002
+ .alt-cal { width: max-content; color: var(--ink); font: 400 13.5px/1 var(--font-sans); font-variant-numeric: tabular-nums; }
4003
+ .alt-cal-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); margin-bottom: var(--space-2); }
4004
+ .alt-cal-title { margin: 0; font: 600 14px/20px var(--font-sans); }
4005
+ .alt-cal-nav { display: inline-grid; place-items: center; width: var(--size-control-sm); height: var(--size-control-sm); padding: 0; border: 0; border-radius: var(--radius-sm); background: none; color: var(--ink-muted); cursor: pointer; transition: background-color var(--dur-travel-1), box-shadow var(--dur-travel-1); }
4006
+ @media (hover: hover) { .alt-cal-nav:hover:not(:disabled) { background: var(--surface-raised); color: var(--ink); box-shadow: var(--elev-1); } }
4007
+ .alt-cal-nav:disabled { opacity: 0.4; cursor: default; }
4008
+ .alt-cal-nav:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4009
+ [dir="rtl"] .alt-cal-chevron { scale: -1 1; }
4010
+ .alt-cal-body { position: relative; }
4011
+ .alt-cal-grid { border-collapse: separate; border-spacing: 0 2px; animation: alt-cal-fade var(--dur-travel-1) ease-out; }
4012
+ @keyframes alt-cal-fade { from { opacity: 0; } }
4013
+ .alt-cal-grid th { width: 36px; height: 24px; padding: 0; color: var(--ink-muted); font: 500 11.5px/1 var(--font-sans); text-align: center; }
4014
+ .alt-cal-grid td { padding: 0; text-align: center; }
4015
+ .alt-cal-day {
4016
+ position: relative; z-index: 1; display: inline-grid; place-items: center; width: 36px; height: 34px; padding: 0; border: 0; border-radius: var(--radius-sm);
4017
+ background: none; color: inherit; font: inherit; cursor: pointer; transition: color var(--dur-travel-2) var(--ease-settle);
4018
+ }
4019
+ .alt-cal-day::before { content: ""; position: absolute; inset: 2px; z-index: -1; border-radius: var(--radius-sm); background: var(--accent); opacity: 0; scale: 0.8; transition: opacity var(--dur-travel-1) var(--ease-sink), scale var(--dur-travel-2) var(--ease-settle); }
4020
+ @media (hover: hover) { .alt-cal-day:hover:not([aria-disabled]):not([data-chosen])::before { opacity: 0.16; scale: 1; } }
4021
+ .alt-cal-day:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
4022
+ .alt-cal-day[data-today]::after { content: ""; position: absolute; bottom: 4px; left: 50%; width: 4px; height: 4px; margin-left: -2px; border-radius: 50%; background: var(--accent-ink); }
4023
+ .alt-cal-day[aria-disabled] { color: var(--ink-faint); cursor: default; }
4024
+ .alt-cal-day[data-chosen] { color: var(--on-accent); font-weight: 600; }
4025
+ .alt-cal-day[data-chosen][data-today]::after { background: var(--on-accent); }
4026
+ .alt-cal-mark {
4027
+ position: absolute; left: 0; top: 0; z-index: 0; border-radius: var(--radius-sm); pointer-events: none; opacity: 0;
4028
+ background: var(--accent); background-image: var(--alt-sheen); box-shadow: var(--elev-1), inset 0 -2px 0 0 color-mix(in oklab, var(--accent-ink) 55%, transparent);
4029
+ transition: transform var(--dur-travel-2) var(--ease-settle), width var(--dur-travel-2) var(--ease-settle), height var(--dur-travel-2) var(--ease-settle);
4030
+ }
4031
+ /* Presets: a column of rows beside the calendar (above it when narrow), built like Select's options at level 3 inside
4032
+ the popover: one gliding highlight, the chosen row wearing the tick. A chosen preset fills the field with its name in
4033
+ ink and its dates in ink-muted, and settles with the value's afterglow. */
4034
+ .alt-date-panes { display: flex; align-items: flex-start; gap: var(--space-3); }
4035
+ .alt-date-presets { position: relative; flex: none; min-width: 168px; padding: 0 var(--space-3) 0 0; border-right: 1px solid var(--line); }
4036
+ .alt-date-presets .alt-menu-hl { left: 0; right: var(--space-3); }
4037
+ .alt-date-presets:focus { outline: none; }
4038
+ .alt-date-custom { min-width: 0; }
4039
+ @media (max-width: 480px) {
4040
+ .alt-date-panes { flex-direction: column; }
4041
+ .alt-date-presets { align-self: stretch; padding: 0 0 var(--space-2); border-right: 0; border-bottom: 1px solid var(--line); }
4042
+ .alt-date-presets .alt-menu-hl { right: 0; }
4043
+ }
4044
+ .alt-date-preset {
4045
+ display: flex; align-items: baseline; gap: 0.35em; flex: 1; min-width: 0; margin: 0; padding: 1px 2px; border: 0; border-radius: 3px; background: none;
4046
+ font: 400 14px/20px var(--font-sans); color: var(--ink); text-align: start; white-space: nowrap; cursor: pointer;
4047
+ transform: translateY(calc(var(--wd) * 0.5)); transition: transform var(--dur-travel-2) var(--ease-settle);
4048
+ }
4049
+ .alt-date-preset:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4050
+ .alt-date-preset-name { flex: none; font-weight: 500; }
4051
+ .alt-date-preset-dates { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
4052
+ .alt-date-preset[data-glow="a"] { animation: alt-value-a calc(var(--dur-settle) / 5) var(--ease-decay); }
4053
+ .alt-date-preset[data-glow="b"] { animation: alt-value-b calc(var(--dur-settle) / 5) var(--ease-decay); }
4054
+ @media (forced-colors: active) { .alt-date-presets { border-color: CanvasText; } .alt-date-presets .alt-select-option[data-active] { outline: 2px solid Highlight; outline-offset: -2px; } }
4055
+ /* A range: the sunk track, then the two raised ends. */
4056
+ .alt-cal[data-range] .alt-cal-day[data-span]:not([data-chosen])::before { opacity: 1; scale: 1; inset: 2px -1px; border-radius: 0; background: var(--accent-soft); box-shadow: var(--well); }
4057
+ .alt-cal[data-range] .alt-cal-day[data-span-start]:not([data-chosen])::before { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
4058
+ .alt-cal[data-range] .alt-cal-day[data-span-end]:not([data-chosen])::before { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
4059
+ .alt-cal[data-range] .alt-cal-day[data-chosen]::before { opacity: 1; scale: 1; background: var(--accent); background-image: var(--alt-sheen); box-shadow: var(--elev-1); }
4060
+ .alt-calendar { display: inline-block; }
4061
+ [data-contrast="more"] .alt-cal-mark, [data-contrast="more"] .alt-cal[data-range] .alt-cal-day[data-chosen]::before { box-shadow: 0 2px 0 0 var(--ink); }
4062
+ @media (forced-colors: active) {
4063
+ .alt-date-panel { border: 2px solid CanvasText; }
4064
+ .alt-date-picker > .alt-input:not([data-filled]) { border-style: dashed; }
4065
+ .alt-cal-mark, .alt-cal[data-range] .alt-cal-day[data-chosen]::before { forced-color-adjust: none; background: Highlight; }
4066
+ .alt-cal-day[data-chosen] { forced-color-adjust: none; color: HighlightText; }
4067
+ .alt-cal[data-range] .alt-cal-day[data-span]:not([data-chosen])::before { border: 1px dashed CanvasText; background: Canvas; }
4068
+ .alt-cal-day[data-today] { text-decoration: underline; }
4069
+ .alt-date-seg:focus { forced-color-adjust: none; background: Highlight; color: HighlightText; }
4070
+ }
4071
+ @media (prefers-reduced-motion: reduce) { .alt-cal-mark { transition: none; } }
4072
+
4073
+ /* ── Tree: a hierarchy seen all at once. Nodes are the record: flush rows on the ground, indented space-4 per level,
4074
+ like Table rows. Only a node that acts lifts on hover; the selected node is tinted accent-soft and raised one level.
4075
+ Expanding is disclosure of existing records, so it's a chevron turn, not a rise. Renaming sinks the label into a
4076
+ well (InlineEdit's field); a write hovers while pending and drops to a trench when it fails. A carried node floats
4077
+ at level 3, tilted, with a count of what it carries; the slot it would land in is a thin well at the target's
4078
+ depth, and a node it would go into opens to a trench with an accent rim (a dashed critical one if it can't). ── */
4079
+ .alt-tree-wrap { position: relative; min-width: 0; }
4080
+ .alt-tree { margin: 0; padding: 0; list-style: none; color: var(--ink); font: 400 14px/20px var(--font-sans); }
4081
+ .alt-tree-row {
4082
+ --indent: calc((var(--level, 1) - 1) * var(--space-4));
4083
+ position: relative; display: flex; align-items: center; gap: 4px; min-height: var(--size-row-md); padding: 2px var(--space-2) 2px 4px; border-radius: var(--radius-sm);
4084
+ outline: none; cursor: default; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; touch-action: pan-y;
4085
+ transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-1) var(--ease-rise);
4086
+ }
4087
+ .alt-tree-indent { flex: none; width: var(--indent); }
4088
+ .alt-tree-chevron { display: inline-grid; place-items: center; flex: none; width: 20px; height: 20px; border-radius: var(--radius-sm); color: var(--ink-muted); cursor: pointer; }
4089
+ .alt-tree-chevron svg { rotate: -90deg; transition: rotate var(--dur-travel-2) var(--ease-settle); }
4090
+ .alt-tree-chevron[data-open] svg { rotate: 0deg; }
4091
+ [dir="rtl"] .alt-tree-chevron:not([data-open]) svg { rotate: 90deg; }
4092
+ .alt-tree-leaf { flex: none; width: 20px; }
4093
+ .alt-tree-label { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4094
+ @media (hover: hover) { .alt-tree-row:hover:not([data-carried]) { background: var(--surface-raised); box-shadow: var(--elev-1); } }
4095
+ .alt-tree-row[aria-selected="true"] { background: var(--accent-soft); box-shadow: var(--elev-1); }
4096
+ .alt-tree-row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
4097
+ .alt-tree-row[data-status="pending"] { animation: alt-tree-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
4098
+ @keyframes alt-tree-pending { to { background: var(--surface-lifted); box-shadow: var(--elev-2); transform: translateY(calc(-1px * var(--alt-travel))); } }
4099
+ .alt-tree-row[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4100
+ .alt-tree-row[data-carried="keys"] { z-index: 2; background: var(--surface-floating); box-shadow: var(--elev-3); rotate: calc(-1.2deg * var(--alt-travel)); }
4101
+ .alt-tree-row[data-carried="pointer"] { opacity: 0.4; }
4102
+ .alt-tree-row[data-into] { background: var(--surface-trench); box-shadow: var(--trench), inset 0 0 0 2px var(--accent); }
4103
+ .alt-tree-row[data-into="blocked"] { box-shadow: var(--trench), inset 0 0 0 2px var(--critical); }
4104
+ .alt-tree-slot { height: 6px; margin: -3px 0 -3px calc((var(--level, 1) - 1) * var(--space-4) + 28px); border-radius: var(--radius-full); background: var(--surface-well); box-shadow: var(--well), inset 0 0 0 1px var(--accent); list-style: none; }
4105
+ .alt-tree-count { flex: none; margin-left: auto; padding: 1px 6px; border-radius: var(--radius-full); background: var(--surface-floating); box-shadow: var(--elev-2); font: 650 11px/16px var(--font-sans); font-variant-numeric: tabular-nums; }
4106
+ .alt-tree-ghost {
4107
+ position: fixed; z-index: var(--z-floating); display: flex; align-items: center; gap: var(--space-2); min-height: 32px; padding: 2px var(--space-3); border-radius: var(--radius-sm);
4108
+ background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--ink); font: 400 14px/20px var(--font-sans); pointer-events: none;
4109
+ rotate: calc(-1.2deg * var(--alt-travel)); scale: calc(1 + 0.015 * var(--alt-travel));
4110
+ }
4111
+ .alt-tree-wrap[data-carrying="pointer"] { cursor: grabbing; }
4112
+ .alt-tree-name { position: relative; display: inline-flex; flex-direction: column; flex: 1; min-width: 0; }
4113
+ .alt-tree-name-input {
4114
+ width: 100%; min-width: 8ch; height: 26px; padding: 0 6px; border: 1px solid var(--line-control); border-radius: var(--radius-sm); outline: none;
4115
+ background: var(--surface-well); box-shadow: var(--well); color: var(--ink); font: inherit; animation: alt-tree-sink var(--dur-travel-1) var(--ease-sink);
4116
+ }
4117
+ @keyframes alt-tree-sink { from { background: var(--surface-ground); box-shadow: none; } }
4118
+ .alt-tree-name-input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4119
+ .alt-tree-name[data-invalid] .alt-tree-name-input { background: var(--critical-soft); border-color: var(--critical); box-shadow: var(--trench); }
4120
+ .alt-tree-error { display: inline-flex; align-items: center; gap: 4px; margin-top: 2px; color: var(--critical); font: 400 12px/16px var(--font-sans); white-space: normal; }
4121
+ .alt-tree-new { cursor: default; }
4122
+ .alt-tree-imprint { width: 40%; height: 12px; border-radius: var(--radius-sm); }
4123
+ .alt-tree-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4124
+ [data-contrast="more"] .alt-tree-row[data-carried="keys"], [data-contrast="more"] .alt-tree-ghost { box-shadow: var(--elev-3), 0 3px 0 0 var(--ink); }
4125
+ @media (forced-colors: active) {
4126
+ .alt-tree-row[aria-selected="true"] { border: 1px solid Highlight; } .alt-tree-row[data-carried="keys"], .alt-tree-ghost { border: 2px solid CanvasText; }
4127
+ .alt-tree-slot { background: none; border-top: 2px dashed CanvasText; height: 0; } .alt-tree-row[data-into] { border: 3px dashed Highlight; }
4128
+ .alt-tree-row[data-status="pending"] { border: 1px dotted CanvasText; } .alt-tree-row[data-status="failed"] { border: 2px dashed CanvasText; }
4129
+ }
4130
+
4131
+ /* ── Checklist: a list you write into. Each item is the record, flush on the ground, and its line is the editor: no
4132
+ raised editor and no Save, the line commits when you leave it. The empty line at the end is an opening (a well
4133
+ while it's focused or holds text). The checkbox is the Checkbox's choice; a done item is pressed into the record,
4134
+ muted with a strike drawn left to right, and stays in place. A write hovers after dur-grace and a failure drops to
4135
+ a trench with Retry on the line. Nesting indents by space-6 and glides sideways, a change of hierarchy. ── */
4136
+ .alt-checklist { position: relative; min-width: 0; color: var(--ink); font: 400 14px/20px var(--font-sans); }
4137
+ .alt-checklist-list { margin: 0; padding: 0; list-style: none; }
4138
+ .alt-checklist-list .alt-checklist-list { padding-left: var(--space-6); }
4139
+ .alt-checklist-item { min-width: 0; border-radius: var(--radius-sm); outline: none; }
4140
+ .alt-checklist-item:focus-visible > .alt-checklist-line { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
4141
+ .alt-checklist-line { position: relative; display: flex; align-items: center; gap: var(--space-2); min-height: 34px; padding: 0 var(--space-1); border-radius: var(--radius-sm); transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise); }
4142
+ .alt-checklist-line > .alt-check { flex: none; }
4143
+ .alt-checklist-text { flex: 1; min-width: 0; height: 28px; margin: 0; padding: 0 var(--space-1); border: 0; border-radius: var(--radius-sm); outline: none; background: none; color: inherit; font: inherit; }
4144
+ .alt-checklist-text:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-ink); }
4145
+ .alt-checklist-item[data-done] > .alt-checklist-line .alt-checklist-text {
4146
+ color: var(--ink-muted); text-decoration: line-through; text-decoration-color: var(--ink-muted);
4147
+ animation: alt-checklist-strike var(--dur-travel-2) var(--ease-rise);
4148
+ }
4149
+ @keyframes alt-checklist-strike { from { text-decoration-color: transparent; color: var(--ink); } }
4150
+ .alt-checklist-item[data-arrived] > .alt-checklist-line { animation: alt-checklist-open var(--dur-travel-1) var(--ease-sink); }
4151
+ @keyframes alt-checklist-open { from { background: var(--surface-well); box-shadow: var(--well); transform: translateY(calc(-4px * var(--alt-travel))); } }
4152
+ .alt-checklist-item[data-status="pending"] > .alt-checklist-line { animation: alt-checklist-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
4153
+ @keyframes alt-checklist-pending { to { background: var(--surface-lifted); box-shadow: var(--elev-2); } }
4154
+ .alt-checklist-item[data-status="failed"] > .alt-checklist-line { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4155
+ .alt-checklist-failed { display: inline-flex; flex: none; align-items: center; gap: 4px; color: var(--critical); font: 400 12.5px/17px var(--font-sans); }
4156
+ .alt-checklist-new .alt-checklist-line { color: var(--ink-muted); }
4157
+ .alt-checklist-plus { display: inline-grid; place-items: center; flex: none; width: 20px; font: 500 16px/1 var(--font-sans); }
4158
+ .alt-checklist-new .alt-checklist-text::placeholder { color: var(--ink-muted); opacity: 1; }
4159
+ .alt-checklist-new .alt-checklist-line:focus-within, .alt-checklist-new .alt-checklist-line:has(input:not(:placeholder-shown)) { background: var(--surface-well); box-shadow: var(--well); color: var(--ink); }
4160
+ .alt-checklist-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4161
+ [data-contrast="more"] .alt-checklist-new .alt-checklist-line:focus-within { box-shadow: inset 0 2px 0 0 var(--ink); }
4162
+ @media (forced-colors: active) {
4163
+ .alt-checklist-new .alt-checklist-line { border: 1px dashed CanvasText; }
4164
+ .alt-checklist-item[data-status="pending"] > .alt-checklist-line { border: 1px dotted CanvasText; }
4165
+ .alt-checklist-item[data-status="failed"] > .alt-checklist-line { border: 2px dashed CanvasText; }
4166
+ }
4167
+
4168
+ /* ── FilterBar: a query as a row of conditions. Applied filters are part of what you're looking at, so each condition
4169
+ is a plate stamped flush into the bar like a Badge, split into segments (field · condition · value · remove). Each
4170
+ segment opens a picker, so it lifts one level when pointed at (hover is a promise). A condition still waiting for
4171
+ a value keeps that segment as an opening (a dashed well), and so does Add filter, deepening under the pointer.
4172
+ Pickers float at level 3 as Select's list does. A changed condition is re-stamped (down 3px, settling). ── */
4173
+ .alt-filter-bar { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); min-width: 0; color: var(--ink); font: 400 13px/18px var(--font-sans); }
4174
+ .alt-filter-token {
4175
+ display: inline-flex; align-items: stretch; max-width: 100%; min-height: 28px; border-radius: var(--radius-sm);
4176
+ background: color-mix(in oklab, var(--surface-ground), var(--surface-well) 70%);
4177
+ box-shadow: inset 0 1px 1.5px color-mix(in oklab, var(--ink) 16%, transparent), inset 0 0 0 1px color-mix(in oklab, var(--ink) 9%, transparent), 0 1px 0 0 var(--alt-lip);
4178
+ }
4179
+ .alt-filter-token[data-stamp] { animation: alt-stamp-a var(--dur-travel-2) var(--ease-settle); }
4180
+ .alt-filter-anchor { position: relative; display: inline-flex; min-width: 0; }
4181
+ .alt-filter-seg {
4182
+ display: inline-flex; align-items: center; gap: 4px; min-width: 0; min-height: 28px; margin: 0; padding: 0 8px; border: 0; border-radius: var(--radius-sm);
4183
+ background: none; color: inherit; font: inherit; white-space: nowrap; cursor: pointer;
4184
+ transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-1) var(--ease-rise);
4185
+ }
4186
+ .alt-filter-seg[aria-expanded="true"] { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
4187
+ @media (hover: hover) { .alt-filter-seg:hover { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
4188
+ .alt-filter-seg:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -1px; }
4189
+ .alt-filter-field { font-weight: 600; }
4190
+ .alt-filter-operator { color: var(--ink-muted); }
4191
+ .alt-filter-value { font-weight: 550; overflow: hidden; text-overflow: ellipsis; max-width: 24ch; }
4192
+ .alt-filter-value[data-empty] { margin: 3px; min-height: 22px; border: 1px dashed var(--line-control); background: var(--surface-well); box-shadow: var(--well); color: var(--ink-muted); font-weight: 400; }
4193
+ .alt-filter-remove { width: 28px; justify-content: center; padding: 0; color: var(--ink-muted); }
4194
+ .alt-filter-add { border: 1px dashed var(--line-control); background: var(--surface-well); box-shadow: var(--well); color: var(--ink-muted); }
4195
+ @media (hover: hover) { .alt-filter-add:hover { background: var(--surface-trench); box-shadow: var(--trench); transform: none; color: var(--ink); } }
4196
+ .alt-filter-plus { font: 500 15px/1 var(--font-sans); }
4197
+ .alt-filter-match { color: var(--ink-muted); }
4198
+ .alt-filter-match strong { color: var(--ink); font-weight: 600; }
4199
+ .alt-filter-count { color: var(--ink-muted); font: 400 12.5px/17px var(--font-sans); }
4200
+ .alt-filter-picker { min-width: 200px; max-width: min(320px, calc(100vw - 32px)); padding: var(--space-1); }
4201
+ .alt-filter-list { max-height: 260px; overflow-y: auto; outline: none; }
4202
+ .alt-filter-option { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-height: var(--size-row-md); padding: 0 var(--space-2); border-radius: var(--radius-sm); cursor: pointer; font: 400 13.5px/18px var(--font-sans); }
4203
+ .alt-filter-option[data-active] { background: var(--accent-soft); }
4204
+ .alt-filter-empty { padding: var(--space-2); color: var(--ink-muted); }
4205
+ .alt-filter-search, .alt-filter-input {
4206
+ width: 100%; min-width: 0; height: 32px; margin-bottom: var(--space-1); padding: 0 var(--space-2); border: 1px solid var(--line-control); border-radius: var(--radius-sm);
4207
+ background: var(--surface-well); box-shadow: var(--well); color: var(--ink); font: 400 13.5px/18px var(--font-sans); outline: none; box-sizing: border-box;
4208
+ }
4209
+ .alt-filter-search:focus-visible, .alt-filter-input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4210
+ .alt-filter-form { display: grid; gap: var(--space-2); padding: var(--space-2); }
4211
+ .alt-filter-form label { display: grid; gap: 2px; color: var(--ink-muted); font: 500 12px/16px var(--font-sans); }
4212
+ .alt-filter-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4213
+ @media (hover: hover) { [data-contrast="more"] .alt-filter-seg:hover { box-shadow: 0 2px 0 0 var(--ink); } }
4214
+ @media (forced-colors: active) {
4215
+ .alt-filter-token { border: 1px solid CanvasText; } .alt-filter-value[data-empty], .alt-filter-add { border: 1px dashed CanvasText; }
4216
+ .alt-filter-picker { border: 2px solid CanvasText; } .alt-filter-option[data-active] { forced-color-adjust: none; background: Highlight; color: HighlightText; }
4217
+ }
4218
+
4219
+ /* ── Schedule: time you handle directly. The grid is ground, with line rules on the hour. Events are live work items
4220
+ raised one level like BoardCards (a tinted left edge), lifting on hover. Time opens only while something could land
4221
+ in it: while an event is carried or a span drawn, the days sink into wells; the slot under a carried event deepens
4222
+ to a trench with an accent rim, as long as the event (critical rim where it can't go). A carried event floats at
4223
+ level 3, tilted. Drawing makes a well the length of the drag, which rises into an event to be named. Pending writes
4224
+ hover after dur-grace; failures drop to a trench. The now line is accent-ink: it isn't status. ── */
4225
+ .alt-schedule { --cols: var(--days); position: relative; min-width: 0; color: var(--ink); font: 400 12.5px/16px var(--font-sans); }
4226
+ .alt-schedule-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2); margin-bottom: var(--space-2); }
4227
+ .alt-schedule-period { margin: 0; font: 600 15px/22px var(--font-sans); }
4228
+ .alt-schedule-nav { display: inline-flex; gap: 2px; }
4229
+ .alt-schedule-grid { display: grid; grid-template-columns: 52px repeat(var(--cols), minmax(0, 1fr)); min-width: 0; overflow-x: auto; animation: alt-schedule-fade var(--dur-travel-1) ease-out; }
4230
+ @keyframes alt-schedule-fade { from { opacity: 0; } }
4231
+ .alt-schedule-row, .alt-schedule-body { display: contents; }
4232
+ .alt-schedule-corner, .alt-schedule-day { position: sticky; top: 0; z-index: 3; padding: 6px 4px; background: var(--surface-ground); border-bottom: 1px solid var(--line); color: var(--ink-muted); font-weight: 500; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
4233
+ .alt-schedule-day[data-today] { color: var(--accent-ink); font-weight: 650; }
4234
+ .alt-schedule-row[aria-rowindex="2"] .alt-schedule-time { transform: none; }
4235
+ .alt-schedule-time { height: var(--slot); padding-right: 6px; color: var(--ink-muted); font-variant-numeric: tabular-nums; text-align: right; transform: translateY(-7px); }
4236
+ .alt-schedule-cell { position: relative; height: var(--slot); border-left: 1px solid var(--line); outline: none; transition: background-color var(--dur-travel-1) var(--ease-sink), box-shadow var(--dur-travel-1) var(--ease-sink); }
4237
+ .alt-schedule-row[data-hour] > .alt-schedule-cell { border-top: 1px solid var(--line); }
4238
+ .alt-schedule-cell:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-ink); z-index: 2; }
4239
+ .alt-schedule[data-carrying] .alt-schedule-cell { background: var(--surface-well); }
4240
+ .alt-schedule-event-box { position: absolute; z-index: 1; display: flex; }
4241
+ .alt-schedule-event {
4242
+ position: relative; display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 1px; width: 100%; min-width: 0; margin: 0; padding: 3px 6px 3px 8px; overflow: hidden;
4243
+ border: 0; border-radius: var(--radius-sm); background: var(--surface-raised); box-shadow: var(--elev-1), inset 3px 0 0 0 var(--accent); color: var(--ink); font: inherit; text-align: left; cursor: grab;
4244
+ transition: box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-1) var(--ease-rise);
4245
+ }
4246
+ .alt-schedule-event > * { flex: none; }
4247
+ .alt-schedule-event[data-short] { flex-direction: row; align-items: center; gap: 6px; padding-block: 0; }
4248
+ .alt-schedule-event[data-short] small { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
4249
+ .alt-schedule-event strong { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
4250
+ .alt-schedule-event small { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
4251
+ @media (hover: hover) { .alt-schedule-event:hover { box-shadow: var(--elev-2), inset 3px 0 0 0 var(--accent); } }
4252
+ .alt-schedule-event:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; z-index: 2; }
4253
+ .alt-schedule-event[data-carried] { z-index: 5; background: var(--surface-floating); box-shadow: var(--elev-3), inset 3px 0 0 0 var(--accent); rotate: calc(-1.2deg * var(--alt-travel)); cursor: grabbing; }
4254
+ .alt-schedule-event[data-status="pending"] { animation: alt-schedule-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
4255
+ @keyframes alt-schedule-pending { to { background: var(--surface-lifted); box-shadow: var(--elev-2), inset 3px 0 0 0 var(--accent); transform: translateY(calc(-1px * var(--alt-travel))); } }
4256
+ .alt-schedule-event[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4257
+ .alt-schedule-event[data-held] { cursor: not-allowed; box-shadow: var(--elev-3), 0 0 0 2px var(--presence-1); }
4258
+ .alt-schedule-event[data-held="2"] { box-shadow: var(--elev-3), 0 0 0 2px var(--presence-2); }
4259
+ .alt-schedule-event[data-held="3"] { box-shadow: var(--elev-3), 0 0 0 2px var(--presence-3); }
4260
+ .alt-schedule-event[data-held="2"] .alt-schedule-holder { background: var(--presence-2); }
4261
+ .alt-schedule-event[data-held="3"] .alt-schedule-holder { background: var(--presence-3); }
4262
+ .alt-schedule-holder { position: absolute; right: 2px; bottom: 2px; padding: 0 4px; border-radius: var(--radius-sm); background: var(--presence-1); color: var(--on-presence); font: 600 10.5px/14px var(--font-sans); }
4263
+ .alt-schedule-handle { position: absolute; left: 25%; right: 25%; bottom: -3px; z-index: 2; height: 6px; border-radius: var(--radius-full); background: var(--surface-raised); box-shadow: var(--elev-1); cursor: ns-resize; opacity: 0; transition: opacity var(--dur-travel-1); }
4264
+ .alt-schedule-event-box:focus-within .alt-schedule-handle { opacity: 1; }
4265
+ @media (hover: hover) { .alt-schedule-event-box:hover .alt-schedule-handle { opacity: 1; } }
4266
+ .alt-schedule-slot { position: absolute; left: 2px; right: 2px; z-index: 0; border-radius: var(--radius-sm); background: var(--surface-trench); box-shadow: var(--trench), inset 0 0 0 2px var(--accent); }
4267
+ .alt-schedule-slot[data-blocked] { box-shadow: var(--trench), inset 0 0 0 2px var(--critical); }
4268
+ .alt-schedule-drawn { width: 100%; padding: 3px 6px; border-radius: var(--radius-sm); background: var(--surface-trench); box-shadow: var(--trench), inset 0 0 0 1px var(--accent); color: var(--ink-muted); }
4269
+ .alt-schedule-draft { display: flex; flex-direction: column; gap: 2px; width: 100%; padding: 3px; border-radius: var(--radius-sm); background: var(--surface-raised); box-shadow: var(--elev-1), inset 3px 0 0 0 var(--accent); animation: alt-emerge var(--dur-travel-2) var(--ease-rise); }
4270
+ .alt-schedule-draft small { padding-left: 4px; color: var(--ink-muted); }
4271
+ .alt-schedule-field { display: flex; flex-direction: column; min-width: 0; }
4272
+ .alt-schedule-event-box > .alt-schedule-field { position: absolute; inset: 2px; z-index: 3; }
4273
+ .alt-schedule-title-input { width: 100%; min-width: 0; height: 24px; padding: 0 6px; border: 1px solid var(--line-control); border-radius: var(--radius-sm); outline: none; background: var(--surface-well); box-shadow: var(--well); color: var(--ink); font: 500 12.5px/16px var(--font-sans); box-sizing: border-box; }
4274
+ .alt-schedule-title-input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4275
+ .alt-schedule-title-input[aria-invalid] { background: var(--critical-soft); border-color: var(--critical); }
4276
+ .alt-schedule-error { display: inline-flex; align-items: center; gap: 3px; color: var(--critical); font: 400 11px/14px var(--font-sans); }
4277
+ .alt-schedule-now { position: absolute; left: -4px; right: 0; z-index: 4; height: 2px; background: var(--accent-ink); pointer-events: none; }
4278
+ .alt-schedule-now::before { content: ""; position: absolute; left: 0; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-ink); }
4279
+ .alt-schedule-failure { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-top: var(--space-2); padding: var(--space-1) var(--space-1) var(--space-1) var(--space-3); border-radius: var(--radius-md); background: var(--critical-soft); color: var(--ink); font: 400 13px/18px var(--font-sans); }
4280
+ .alt-schedule-failure > svg { color: var(--critical); }
4281
+ .alt-schedule-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4282
+ [data-contrast="more"] .alt-schedule-event { box-shadow: 0 2px 0 0 var(--ink), inset 3px 0 0 0 var(--accent); }
4283
+ [data-contrast="more"] .alt-schedule-event[data-carried] { box-shadow: 0 3px 0 0 var(--ink); }
4284
+ @media (forced-colors: active) {
4285
+ .alt-schedule-event { border: 1px solid CanvasText; } .alt-schedule-event[data-carried] { border-width: 2px; }
4286
+ .alt-schedule-slot, .alt-schedule-drawn { border: 2px dashed CanvasText; } .alt-schedule-now { background: Highlight; }
4287
+ .alt-schedule-event[data-status="pending"] { border-style: dotted; } .alt-schedule-event[data-status="failed"] { border: 2px dashed CanvasText; }
4288
+ }
4289
+
4290
+ /* ── DataGrid: a table edited cell by cell. Cells are the record, flush in Table's rows; the active cell is a focus
4291
+ ring, not a height, and a range is an accent-soft tint (choosing a target isn't live content). Editable columns
4292
+ carry a pencil in the header instead of a hover lift: a click selects rather than acts. The cell being edited opens
4293
+ into a well clipped to the cell, with the held rim; an invalid value deepens to a trench with its message beside it.
4294
+ A saving cell holds a small raised plate after dur-grace; a failed cell drops to a trench with a critical mark; a
4295
+ landed value settles, and a pasted range settles as one change. ── */
4296
+ .alt-data-grid { min-width: 0; }
4297
+ .alt-data-grid .alt-table { table-layout: fixed; }
4298
+ .alt-grid-head { display: inline-flex; align-items: center; gap: 4px; }
4299
+ .alt-grid-pencil { display: inline-flex; color: var(--ink-faint); }
4300
+ .alt-grid-cell { position: relative; outline: none; cursor: cell; overflow: visible; white-space: nowrap; }
4301
+ .alt-grid-value { display: block; overflow: hidden; text-overflow: ellipsis; }
4302
+ .alt-grid-cell[data-range] { background: var(--accent-soft); }
4303
+ .alt-grid-cell[data-active] { box-shadow: inset 0 0 0 2px var(--accent-ink); }
4304
+ .alt-grid-cell:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-ink); }
4305
+ .alt-grid-cell[data-editing] { padding: 0; background: var(--surface-well); box-shadow: var(--well), inset 0 0 0 2px var(--alt-rim-held, var(--accent)); animation: alt-grid-sink var(--dur-travel-1) var(--ease-sink); }
4306
+ @keyframes alt-grid-sink { from { background: transparent; box-shadow: none; } }
4307
+ .alt-grid-cell[data-editing]:has([aria-invalid="true"]) { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 2px var(--critical); }
4308
+ .alt-grid-cell[data-invalid]:not([data-editing]) { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4309
+ .alt-grid-cell[data-invalid][data-active]:not([data-editing]) { box-shadow: var(--trench), inset 0 0 0 2px var(--accent-ink); }
4310
+ @media (forced-colors: active) { .alt-grid-cell[data-invalid]:not([data-editing]) { outline: 2px dashed CanvasText; outline-offset: -3px; } }
4311
+ .alt-grid-input { display: block; width: 100%; height: 100%; min-height: 40px; padding: 10px 12px; border: 0; outline: none; background: none; color: var(--ink); font: inherit; box-sizing: border-box; }
4312
+ .alt-grid-cell[data-align="end"] .alt-grid-input { text-align: right; }
4313
+ .alt-grid-editor { display: block; padding: 3px; }
4314
+ .alt-grid-tip {
4315
+ position: absolute; left: 0; top: calc(100% + 4px); z-index: 6; max-width: 260px; padding: 4px 8px; border-radius: var(--radius-sm);
4316
+ background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--critical); font: 400 12px/16px var(--font-sans); white-space: normal;
4317
+ }
4318
+ .alt-grid-unsaved { color: var(--ink-muted); }
4319
+ .alt-grid-cell[data-status="pending"] .alt-grid-plate { position: absolute; right: 6px; top: 50%; width: 8px; height: 8px; margin-top: -4px; border-radius: 2px; background: var(--surface-lifted); box-shadow: var(--elev-2); animation: alt-onset var(--dur-travel-1) ease-out var(--dur-grace) backwards; }
4320
+ .alt-grid-cell[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4321
+ .alt-grid-fail { position: absolute; right: 6px; top: 50%; translate: 0 -50%; display: inline-flex; color: var(--critical); }
4322
+ .alt-grid-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4323
+ [data-contrast="more"] .alt-grid-cell[data-editing] { box-shadow: inset 0 2px 0 0 var(--ink), inset 0 0 0 2px var(--accent); }
4324
+ @media (forced-colors: active) {
4325
+ .alt-grid-cell[data-active] { outline: 2px solid Highlight; outline-offset: -2px; } .alt-grid-cell[data-range] { forced-color-adjust: none; background: Highlight; color: HighlightText; }
4326
+ .alt-grid-cell[data-editing] { outline: 2px dashed CanvasText; } .alt-grid-cell[data-editing]:has([aria-invalid="true"]) { outline: 3px dashed CanvasText; }
4327
+ .alt-grid-cell[data-status="pending"] { outline: 1px dotted CanvasText; } .alt-grid-cell[data-status="failed"] { outline: 2px dashed CanvasText; }
4328
+ }
4329
+
4330
+ /* ── ItemGroup: a list changed in the list itself. Slats are the record, flush on the ground with line dividers, as
4331
+ Table rows. Editing lifts one slat at a time into its EditRegion (a static raised card) while its neighbours stay
4332
+ flush and readable. The add slat is an opening: a dashed well that deepens to a trench under the pointer. A slat
4333
+ picked up to reorder floats at level 3, tilted, over a slot that opens as a well. A recoverable removal leaves its
4334
+ RemovalReceipt in the slat's place, then glides the gap shut; a confirmed one asks in a stationary Dialog, then
4335
+ glides the slat shut. ── */
4336
+ .alt-item-group { position: relative; display: grid; gap: var(--space-2); min-width: 0; color: var(--ink); container-type: inline-size; }
4337
+ .alt-item-list { margin: 0; padding: 0; list-style: none; outline: none; }
4338
+ .alt-item-slat { position: relative; display: flex; align-items: flex-start; gap: var(--space-2); padding: var(--space-3) 0; border-bottom: 1px solid var(--line); border-radius: 0; transition: transform var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1); }
4339
+ .alt-item-slat[data-editing] { border-bottom-color: transparent; }
4340
+ .alt-item-body { flex: 1; min-width: 0; }
4341
+ .alt-item-slat[data-carried] { z-index: 2; padding-inline: var(--space-2); border-radius: var(--radius-md); background: var(--surface-floating); box-shadow: var(--elev-3); rotate: calc(-0.5deg * var(--alt-travel)); border-bottom-color: transparent; }
4342
+ .alt-item-slat[data-status="pending"] { animation: alt-item-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
4343
+ @keyframes alt-item-pending { to { background: var(--surface-lifted); box-shadow: var(--elev-2); } }
4344
+ .alt-item-slot { position: absolute; left: 0; right: 0; top: -3px; height: 6px; border-radius: var(--radius-full); background: var(--surface-well); box-shadow: var(--well); }
4345
+ .alt-item-grip { display: inline-grid; place-items: center; flex: none; width: 28px; height: 28px; margin-top: 2px; padding: 0; border: 0; border-radius: var(--radius-sm); background: none; color: var(--ink-muted); cursor: grab; }
4346
+ @media (hover: hover) { .alt-item-grip:hover { background: var(--surface-raised); box-shadow: var(--elev-1); } }
4347
+ .alt-item-grip[aria-pressed="true"] { cursor: grabbing; color: var(--ink); }
4348
+ .alt-item-grip:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4349
+ .alt-item { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
4350
+ .alt-item-media { display: inline-grid; place-items: center; flex: none; width: 36px; height: 36px; border-radius: var(--radius-md); background: var(--surface-well); color: var(--ink-muted); }
4351
+ .alt-item-media[data-variant="avatar"] { background: none; }
4352
+ .alt-item-content { flex: 1; min-width: 0; }
4353
+ .alt-item-title { font: 500 14.5px/21px var(--font-sans); overflow-wrap: anywhere; }
4354
+ .alt-item-description { margin: 2px 0 0; color: var(--ink-muted); font: 400 13px/18px var(--font-sans); overflow-wrap: anywhere; }
4355
+ .alt-item-actions { display: flex; flex-wrap: wrap; gap: var(--space-1); flex: none; }
4356
+ .alt-item-description[data-truncate] { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: var(--lines, 1); line-clamp: var(--lines, 1); overflow: hidden; }
4357
+ /* Narrow (#267): under 480px of the group's width, actions take their own row, lined up with the content (after the
4358
+ media), so a row's title keeps the width; narrow="inline" keeps them beside it. */
4359
+ @container (max-width: 480px) {
4360
+ .alt-item:has(> .alt-item-actions[data-narrow="below"]) { flex-wrap: wrap; }
4361
+ .alt-item-actions[data-narrow="below"] { flex-basis: 100%; }
4362
+ .alt-item:has(> .alt-item-media) > .alt-item-actions[data-narrow="below"] { padding-left: calc(36px + var(--space-3)); }
4363
+ }
4364
+ .alt-item-separator { margin: 0; border: 0; border-top: 1px solid var(--line); }
4365
+ .alt-item-add {
4366
+ display: inline-flex; align-items: center; gap: var(--space-2); width: 100%; min-height: 44px; padding: 0 var(--space-3); border: 1px dashed var(--line-control); border-radius: var(--radius-md);
4367
+ background: var(--surface-well); box-shadow: var(--well); color: var(--ink-muted); font: 500 14px/20px var(--font-sans); cursor: pointer;
4368
+ transition: background-color var(--dur-travel-1) var(--ease-sink), box-shadow var(--dur-travel-1) var(--ease-sink), color var(--dur-travel-1);
4369
+ }
4370
+ .alt-item-add:focus-visible { background: var(--surface-trench); box-shadow: var(--trench); color: var(--ink); }
4371
+ @media (hover: hover) { .alt-item-add:hover { background: var(--surface-trench); box-shadow: var(--trench); color: var(--ink); } }
4372
+ .alt-item-add:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
4373
+ .alt-item-plus { font: 500 17px/1 var(--font-sans); }
4374
+ .alt-item-new { animation: alt-emerge var(--dur-travel-2) var(--ease-rise); }
4375
+ .alt-item-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4376
+ [data-contrast="more"] .alt-item-slat[data-carried] { box-shadow: var(--elev-3), 0 3px 0 0 var(--ink); }
4377
+ @media (forced-colors: active) {
4378
+ .alt-item-add, .alt-item-slot { border: 1px dashed CanvasText; } .alt-item-slat[data-carried] { border: 2px solid CanvasText; }
4379
+ .alt-item-slat[data-status="pending"] { outline: 1px dotted CanvasText; }
4380
+ }
4381
+
4382
+ /* ── Board, edited in place. A column's add slot is an opening: a dashed well that deepens to a trench under the
4383
+ pointer. Activating it rises into a new card (static, level 1) holding a title field; the next slot is ready after
4384
+ each add. Renaming turns a card's title into a well; a card being edited can't be picked up. A pending card hovers
4385
+ after dur-grace; a failed one drops to a trench with its title kept and Retry. A fuller editor is an EditRegion in
4386
+ the card's place. ── */
4387
+ .alt-board-add-wrap { margin-top: var(--space-2); }
4388
+ .alt-board-add {
4389
+ display: flex; align-items: center; gap: var(--space-2); width: 100%; min-height: 40px; padding: 0 var(--space-3); border: 1px dashed var(--line-control); border-radius: var(--radius-md);
4390
+ background: var(--surface-well); box-shadow: var(--well); color: var(--ink-muted); font: 500 13.5px/18px var(--font-sans); cursor: pointer;
4391
+ transition: background-color var(--dur-travel-1) var(--ease-sink), box-shadow var(--dur-travel-1) var(--ease-sink), color var(--dur-travel-1);
4392
+ }
4393
+ .alt-board-add:focus-visible { background: var(--surface-trench); box-shadow: var(--trench); color: var(--ink); }
4394
+ @media (hover: hover) { .alt-board-add:hover { background: var(--surface-trench); box-shadow: var(--trench); color: var(--ink); } }
4395
+ .alt-board-add:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
4396
+ .alt-board-add > span { font: 500 16px/1 var(--font-sans); }
4397
+ .alt-board-new { display: grid; gap: var(--space-1); cursor: default; animation: alt-emerge var(--dur-travel-2) var(--ease-rise); }
4398
+ .alt-board-new[data-status="pending"], .alt-board-rename[data-status="pending"] { animation: alt-board-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
4399
+ @keyframes alt-board-pending { to { box-shadow: var(--elev-2); transform: translateY(calc(-2px * var(--alt-travel))); } }
4400
+ .alt-board-new[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4401
+ .alt-board-add-hint { color: var(--ink-muted); font: 400 12px/16px var(--font-sans); }
4402
+ .alt-board-add-error { display: inline-flex; align-items: center; gap: 4px; color: var(--critical); font: 400 12px/16px var(--font-sans); }
4403
+ .alt-board-rename { display: grid; gap: var(--space-1); }
4404
+ .alt-board-card[data-editing] { cursor: default; }
4405
+ .alt-board-card-tools { position: absolute; top: 4px; right: 4px; display: inline-flex; gap: 2px; opacity: 0; transition: opacity var(--dur-travel-1); }
4406
+ .alt-board-card:focus-within .alt-board-card-tools, .alt-board-card:focus-visible .alt-board-card-tools { opacity: 1; }
4407
+ @media (hover: hover) { .alt-board-card:hover .alt-board-card-tools { opacity: 1; } }
4408
+ /* Without hover the tools are always there, or a finger could never find them (#254). */
4409
+ @media (hover: none) { .alt-board-card-tools { opacity: 1; } }
4410
+ .alt-board-card-editor { min-width: 0; }
4411
+ @media (forced-colors: active) { .alt-board-add { border: 1px dashed CanvasText; } .alt-board-new[data-status="pending"] { outline: 1px dotted CanvasText; } .alt-board-new[data-status="failed"] { outline: 2px dashed CanvasText; } }
4412
+
4413
+ /* ── DataTable, expanded and edited in its row. Read-only detail is a reveal of the record, so it stays on the ground:
4414
+ a full-width region under the row, inset by space-4, with a line rule, and the disclosure chevron turns. Editing is
4415
+ live, so it rises: the row and its region become one raised slab (elev-1, drawn by the first cell as for selected
4416
+ rows), holding a static EditRegion; the cells above keep the record identified. Sorting and reordering wait. ── */
4417
+ .alt-td-expand, .alt-th-expand { width: 40px; padding-inline: 6px; }
4418
+ /* Density: compact rows keep every target at least 24px tall (WCAG 2.5.8). Showing, hiding or reordering a column is a
4419
+ change of layout, not of the record, so the table doesn't animate it. */
4420
+ .alt-table[data-density="compact"] .alt-td { padding-block: 4px; }
4421
+ .alt-table[data-density="compact"] .alt-th { padding-block: 6px; }
4422
+ .alt-table-hidden { color: var(--ink-muted); font-weight: 400; }
4423
+ /* Paging: the footer holds Pagination (or "Load more") under a line. While the next page loads the current rows stay on
4424
+ the ground at half strength, as a refetching chart does, and the page thumb is held pressed. */
4425
+ .alt-table-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--line); margin-top: -1px; }
4426
+ .alt-table[data-refetching] tbody { opacity: 0.5; transition: opacity var(--dur-travel-2) var(--ease-sink); }
4427
+ /* Selecting past the page: once a page is selected, a flush accent-soft row offers every matching record; a selection of
4428
+ "all matching" is said in words. With actionsPlacement floating (or auto, once the toolbar scrolls away), the count and
4429
+ actions dock at the bottom edge as a bar at level 3: a selection is a set of records in your hand. */
4430
+ /* Saved views: folder Tabs whose active tab is one raised sheet with the filters and table beneath it. A view you've
4431
+ changed is a draft: a dot on its tab (and "modified" in its name), and Save view, Save as new view and Reset beside
4432
+ the tabs, as a SaveBar does for a page. */
4433
+ .alt-views { position: relative; }
4434
+ .alt-views-actions { position: absolute; top: 0; right: 0; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-2); height: 38px; align-items: center; }
4435
+ .alt-views-dot { display: inline-block; width: 6px; height: 6px; margin-left: 6px; border-radius: var(--radius-full); background: var(--accent); vertical-align: 2px; }
4436
+ .alt-views-sheet { display: flex; flex-direction: column; gap: var(--space-3); }
4437
+ .alt-views-new { display: flex; flex-direction: column; gap: var(--space-3); width: 240px; max-width: 100%; }
4438
+ .alt-views-new .alt-btn { align-self: flex-end; }
4439
+ @media (max-width: 640px) { .alt-views-actions { position: static; height: auto; margin-bottom: var(--space-2); justify-content: flex-start; } }
4440
+ @media (forced-colors: active) { .alt-views-dot { background: CanvasText; forced-color-adjust: none; } }
4441
+ .alt-table-allrow > .alt-td { padding: var(--space-2) var(--space-3); background: var(--accent-soft); text-align: center; font: 400 13px/18px var(--font-sans); color: var(--ink); }
4442
+ .alt-table-allrow .alt-link { padding: 0; border: 0; background: none; font: inherit; font-weight: 600; }
4443
+ .alt-table-floatbar {
4444
+ position: sticky; bottom: var(--space-3); z-index: 6; display: flex; justify-content: center; margin-top: var(--space-3); pointer-events: none;
4445
+ }
4446
+ .alt-table-floatbar > .alt-table-selection {
4447
+ pointer-events: auto; display: inline-flex; align-items: center; gap: var(--space-2); padding: 6px 8px 6px 10px; border-radius: var(--radius-lg);
4448
+ background: var(--surface-floating); box-shadow: var(--elev-3); font: 500 14px/20px var(--font-sans);
4449
+ animation: alt-emerge var(--dur-travel-3) var(--ease-rise); --alt-from-y: 12px;
4450
+ }
4451
+ @media (forced-colors: active) { .alt-table-floatbar > .alt-table-selection { border: 2px solid CanvasText; } }
4452
+ .alt-table-columns { display: flex; flex-direction: column; gap: var(--space-2); width: 240px; max-width: 100%; }
4453
+ .alt-table-columns-title { margin: 0; font: 600 12.5px/17px var(--font-sans); color: var(--ink-muted); }
4454
+ .alt-table-columns-title + * { margin-bottom: var(--space-2); }
4455
+ .alt-table-columns .alt-sort-item { padding: 6px 8px; }
4456
+ .alt-td-edit, .alt-th-edit { width: 1%; white-space: nowrap; }
4457
+ .alt-table-disclose { display: inline-grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: var(--radius-sm); background: none; color: var(--ink-muted); cursor: pointer; transition: background-color var(--dur-travel-1), box-shadow var(--dur-travel-1); }
4458
+ @media (hover: hover) { .alt-table-disclose:hover { background: var(--surface-raised); box-shadow: var(--elev-1); color: var(--ink); } }
4459
+ .alt-table-disclose:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4460
+ .alt-table-disclose svg { rotate: -90deg; transition: rotate var(--dur-travel-2) var(--ease-settle); }
4461
+ .alt-table-disclose[aria-expanded="true"] svg { rotate: 0deg; }
4462
+ [dir="rtl"] .alt-table-disclose:not([aria-expanded="true"]) svg { rotate: 90deg; }
4463
+ .alt-table-detail > .alt-td { padding: var(--space-3) var(--space-4) var(--space-4); white-space: normal; animation: alt-emerge var(--dur-travel-2) var(--ease-glide); --alt-from-y: -4px; }
4464
+ .alt-tr[data-expanded]:not([data-editing]) > .alt-td { border-bottom-color: transparent; }
4465
+ .alt-tr[data-editing] { --row-bg: var(--surface-raised); --row-sh: var(--elev-1); }
4466
+ .alt-tr[data-editing]:not(.alt-table-editrow) > .alt-td { border-bottom-color: transparent; }
4467
+ .alt-tr[data-editing]:not(.alt-table-editrow) > .alt-td:first-child::before { bottom: -2px; border-bottom-left-radius: 0; border-bottom-right-radius: 0; clip-path: inset(-12px -12px 0 -12px); }
4468
+ .alt-table-editrow > .alt-td:first-child::before { border-top-left-radius: 0; border-top-right-radius: 0; clip-path: inset(0 -12px -12px -12px); }
4469
+ .alt-table-editrow > .alt-td { padding: var(--space-2) var(--space-4) var(--space-4); white-space: normal; }
4470
+ .alt-table-editrow .alt-edit-region-surface { margin: 0; border: 0; padding: 0; background: none; box-shadow: none; }
4471
+ /* ItemGroup swaps a slat for its editor, so the editor keeps to the list's column rather than reaching past it. */
4472
+ .alt-item-group .alt-edit-region-surface { margin-inline: 0; }
4473
+ @media (forced-colors: active) { .alt-tr[data-editing] > .alt-td { border-block: 1px solid CanvasText; } }
4474
+
4475
+ /* ── PropertyList: one record's properties. They're the record, on the ground. An editable value is one plate (the
4476
+ InlineEdit trigger) that lifts one level when pointed at, because clicking it opens its editor; read-only values
4477
+ never lift, so editability shows without a pencil per row. An unset value is a small opening: a dashed well, the
4478
+ empty field's treatment at plate size, so a half-filled record reads at a glance. Edit all lifts the list into one
4479
+ raised EditRegion. ── */
4480
+ .alt-property-list { position: relative; min-width: 0; color: var(--ink); container-type: inline-size; }
4481
+ .alt-property-tools { display: flex; justify-content: flex-end; margin-bottom: var(--space-1); }
4482
+ .alt-property-dl { display: grid; gap: var(--space-3); margin: 0; }
4483
+ .alt-property { display: grid; gap: 2px; min-width: 0; }
4484
+ .alt-property-list[data-layout="inline"] .alt-property-dl { gap: 0; }
4485
+ .alt-property-list[data-layout="inline"] .alt-property { grid-template-columns: minmax(8rem, 30%) 1fr; align-items: center; gap: var(--space-3); padding: var(--space-2) 0; border-bottom: 1px solid var(--line); }
4486
+ .alt-property-label { color: var(--ink-muted); font: 500 12.5px/17px var(--font-sans); }
4487
+ .alt-property-value { min-width: 0; margin: 0; font: 400 14px/20px var(--font-sans); overflow-wrap: anywhere; }
4488
+ .alt-property-empty { color: var(--ink-muted); }
4489
+ .alt-property .alt-inline-edit-read { display: block; }
4490
+ /* Proposed changes: a field with a proposal is status that asks for action, so it's raised one level (as Confirmation),
4491
+ showing the current value struck beside the proposed one (DiffView's property change), with Reject and Accept. Nothing
4492
+ touches the record until it's accepted; accepting hovers as pending, then settles with the afterglow. The summary
4493
+ offers Accept all and Reject all. */
4494
+ .alt-property-proposals { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); padding: 6px 6px 6px var(--space-3); border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--elev-1); }
4495
+ .alt-property-proposals-text { display: inline-flex; align-items: center; gap: 6px; margin-right: auto; font: 500 13px/18px var(--font-sans); color: var(--ink); }
4496
+ .alt-property[data-proposed] { margin: 4px calc(-1 * var(--space-2)); padding: var(--space-2); border-radius: var(--radius-md); background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1); }
4497
+ .alt-property-list[data-layout="inline"] .alt-property[data-proposed] { align-items: start; padding: var(--space-2); border-bottom-color: transparent; }
4498
+ .alt-property-list[data-layout="inline"] .alt-property[data-proposed] .alt-property-label { padding-top: 6px; }
4499
+ .alt-property-proposal { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 2px var(--space-2); }
4500
+ .alt-property-change { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
4501
+ .alt-property-proposal-by { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
4502
+ .alt-property-proposal-error { display: inline-flex; align-items: center; gap: 4px; color: var(--critical); }
4503
+ .alt-property-proposal-by { grid-column: 1; }
4504
+ .alt-property-proposal-actions { display: inline-flex; gap: 4px; grid-column: 2; grid-row: 1 / span 2; }
4505
+ /* Narrow (#258): under the narrow breakpoint, an inline list stacks each label over its value, and a proposal stacks its
4506
+ change, who proposed it and its actions, so the values are never squeezed into a column a few letters wide. */
4507
+ @container (max-width: 480px) {
4508
+ .alt-property-list[data-layout="inline"] .alt-property { grid-template-columns: minmax(0, 1fr); gap: 2px; }
4509
+ .alt-property-list[data-layout="inline"] .alt-property[data-proposed] .alt-property-label { padding-top: 0; }
4510
+ .alt-property-proposal { grid-template-columns: minmax(0, 1fr); }
4511
+ .alt-property-proposal-actions { grid-column: 1; grid-row: auto; margin-top: var(--space-1); }
4512
+ }
4513
+ @media (forced-colors: active) { .alt-property[data-proposed], .alt-property-proposals { border: 1px solid CanvasText; } }
4514
+ .alt-inline-edit-plate {
4515
+ display: inline-flex; align-items: center; min-height: 30px; max-width: 100%; padding: 2px 12px; border: 1px solid transparent; border-radius: var(--radius-sm);
4516
+ background: none; color: inherit; font: inherit; text-align: left; cursor: pointer;
4517
+ transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-1) var(--ease-rise);
4518
+ }
4519
+ @media (hover: hover) { .alt-inline-edit-plate:hover:not(:disabled) { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
4520
+ .alt-inline-edit-plate:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4521
+ .alt-inline-edit-plate:disabled { cursor: default; }
4522
+ .alt-inline-edit-plate[data-empty] { margin-left: 13px; padding-inline: 8px; border: 1px dashed var(--line-control); background: var(--surface-well); box-shadow: var(--well); color: var(--ink-muted); font-size: 13px; }
4523
+ @media (hover: hover) { .alt-inline-edit-plate[data-empty]:hover:not(:disabled) { background: var(--surface-trench); box-shadow: var(--trench); transform: none; color: var(--ink); } }
4524
+ @media (hover: hover) { [data-contrast="more"] .alt-inline-edit-plate:hover:not(:disabled) { box-shadow: 0 2px 0 0 var(--ink); } }
4525
+ @media (forced-colors: active) { .alt-inline-edit-plate[data-empty] { border: 1px dashed CanvasText; } .alt-inline-edit-plate:hover { outline: 1px solid CanvasText; } }
4526
+
4527
+ /* Workflow feedback uses the existing invalid trench and readable ground signals. */
4528
+ .alt-error-summary { border: 2px solid var(--critical); border-radius: var(--radius-lg); background: var(--critical-soft); color: var(--ink); padding: var(--space-4); overflow-wrap: anywhere; }
4529
+ .alt-error-summary h2 { margin: 0 0 var(--space-2); font: 600 var(--text-heading)/1.3 var(--font-sans); }
4530
+ .alt-error-summary ul { display: grid; gap: var(--space-1); margin: 0; padding-inline-start: var(--space-5); }
4531
+ /* Each link is a 24px target (WCAG 2.2 SC 2.5.8) whatever line-height the host page sets. */
4532
+ .alt-error-summary a { display: inline-flex; align-items: center; min-block-size: 24px; color: var(--ink); text-decoration: underline; }
4533
+ .alt-error-summary:focus { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
4534
+ @media (forced-colors: active) { .alt-error-summary { border-color: CanvasText; } .alt-error-summary a { color: LinkText; } }
4535
+
4536
+ /* ── Field text on touch (#250): under a coarse pointer, text you type is at least 16px, the size below which iOS
4537
+ Safari zooms the page when a field is focused (and leaves it zoomed). max(16px, 1em) keeps a field that inherits
4538
+ larger text (an in-place title) at its own size. It's a platform minimum, so it wins over every field's own font
4539
+ (the only !important in the stylesheet's sizing). Labels stay text-label; only the text you type grows. Never
4540
+ maximum-scale=1 instead: that stops pinch zoom and fails WCAG 1.4.4. ── */
4541
+ @media (pointer: coarse) {
4542
+ :is([class*="alt-"] :is(input, textarea, select), :is(input, textarea, select)[class*="alt-"]):not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"], [type="hidden"]) {
4543
+ font-size: max(16px, 1em) !important;
4544
+ }
4545
+ }
4546
+
4547
+ /* ── Hit areas (#261): a small face keeps its size and its tier, and a transparent ::after centred on it reaches
4548
+ size-target-min, 24px (WCAG 2.5.8) or 44px under a coarse pointer, so a finger finds it. It has no colour, border
4549
+ or shadow, so forced colours and every elevation are untouched. Date segments sit side by side, so theirs stops at
4550
+ 24px rather than covering a neighbour. ── */
4551
+ :is(.alt-tag-remove, .alt-toast-close, .alt-cite, .alt-ailabel, .alt-filter-operator, .alt-date-seg, .alt-numfield-step, .alt-table-sort, .alt-link, .alt-reltime, .alt-chart-pin-remove) { position: relative; }
4552
+ :is(.alt-numfield-step, .alt-slider-thumb, .alt-brush-grip, .alt-tag-remove, .alt-toast-close, .alt-cite, .alt-ailabel, button.alt-chart-note, .alt-alert-more, .alt-filter-operator, .alt-table-sort, .alt-chart-pin-remove)::after {
4553
+ content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, var(--size-target-min)); height: max(100%, var(--size-target-min)); transform: translate(-50%, -50%);
4554
+ }
4555
+ .alt-numfield .alt-numfield-step::after { width: 100%; }
4556
+ /* Links and relative times sit in rows of text: they reach 24px tall, never wider, so neighbours stay apart. */
4557
+ :is(.alt-link, .alt-reltime):not(:is(p, li, dd, td) > *)::after { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: max(100%, 24px); transform: translateY(-50%); }
4558
+ .alt-date-seg::after { content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, 24px); height: max(100%, 24px); transform: translate(-50%, -50%); }
4559
+
4560
+ /* ── A menu as a sheet (#249): on a narrow touch screen DropdownMenu and Select open at the bottom edge, where the thumb
4561
+ is, rather than wherever the trigger happened to be. It's still the menu, at level 3 (surface-floating, elev-3):
4562
+ changing edges doesn't climb a tier. It clears the safe areas, holds at most 70% of the visible height, has a head
4563
+ with its title and Close, rows at size-row-md, and dims the page at half the scrim's strength. It rises from the
4564
+ edge over dur-travel-3 on ease-rise, as a Sheet does, and fades under reduced motion. ── */
4565
+ .alt-pop[data-presentation="sheet"] {
4566
+ position: fixed; top: auto; left: calc(var(--space-2) + env(safe-area-inset-left, 0px)); right: calc(var(--space-2) + env(safe-area-inset-right, 0px));
4567
+ bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px)); translate: none; width: auto; min-width: 0; max-width: none;
4568
+ max-height: min(70vh, 560px); max-height: min(calc(var(--alt-vvh, 100dvh) * 0.7), 560px); overflow-y: auto; overscroll-behavior: contain;
4569
+ padding: 0 var(--space-1) var(--space-1); transform-origin: 50% 100%;
4570
+ box-shadow: var(--elev-3), 0 0 0 100vmax color-mix(in oklab, var(--scrim) 50%, transparent);
1701
4571
  }
4572
+ .alt-pop[data-presentation="sheet"][data-state="closed"] { transform: translateY(calc(24px * var(--alt-travel))); }
4573
+ .alt-menu-sheet .alt-menu { position: relative; min-width: 0; padding: 0; }
4574
+ .alt-menu-sheet :is(.alt-menu-item, .alt-select-option) { min-height: var(--size-row-md); }
4575
+ .alt-menu-sheet-head { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
4576
+ margin: 0 calc(-1 * var(--space-1)); padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4); background: var(--surface-floating); }
4577
+ .alt-menu-sheet-title { min-width: 0; font: 600 15px/22px var(--font-sans); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4578
+ @media (forced-colors: active) { .alt-pop[data-presentation="sheet"] { border: 2px solid CanvasText; box-shadow: none; } }