altitude-design 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/README.md +41 -6
  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 +18792 -1925
  104. package/dist/index.cjs.map +4 -4
  105. package/dist/index.js +18792 -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
package/dist/altitude.css CHANGED
@@ -1,6 +1,6 @@
1
1
  @import url('https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400..700&family=JetBrains+Mono:wght@400;500&display=swap');
2
2
 
3
- :root, [data-theme="daylight"] {
3
+ :root, [data-theme="daylight"], [data-theme="mesa"] {
4
4
  --surface-trench: #e1e4ea;
5
5
  --surface-well: #eaedf1;
6
6
  --surface-ground: #f3f4f7;
@@ -29,6 +29,28 @@
29
29
  --presence-2: #0b7285;
30
30
  --presence-3: #7046c9;
31
31
  --on-presence: #ffffff;
32
+ --data-1: #5d7bff;
33
+ --data-2: #e65000;
34
+ --data-3: #0498b9;
35
+ --data-4: #2a8f00;
36
+ --data-5: #643980;
37
+ --data-6: #c56f99;
38
+ --data-other: #31343c;
39
+ --data-context: #898a8e;
40
+ --data-seq-1: #e4ebff;
41
+ --data-seq-2: #c1cdef;
42
+ --data-seq-3: #a0b0de;
43
+ --data-seq-4: #8093cd;
44
+ --data-seq-5: #6176bb;
45
+ --data-seq-6: #4559a9;
46
+ --data-seq-7: #2b3c97;
47
+ --data-div-neg-3: #503000;
48
+ --data-div-neg-2: #966112;
49
+ --data-div-neg-1: #c6a178;
50
+ --data-div-mid: #e6e8ed;
51
+ --data-div-pos-1: #97a7d6;
52
+ --data-div-pos-2: #5569ad;
53
+ --data-div-pos-3: #1d2a84;
32
54
  --scrim: #12161f52;
33
55
  --trench: inset 0 1px 3px #0f17292e, 0 0 0 0 #0f172900, 0 0 0 0 #0f172900, 0 0 0 0 #0f172900;
34
56
  --well: inset 0 1px 2px #0f17291a, 0 0 0 0 #0f172900, 0 0 0 0 #0f172900, 0 0 0 0 #0f172900;
@@ -41,7 +63,7 @@
41
63
  --lip-top: inset 0 18px 18px -18px #0f17293d;
42
64
  --lip-bottom: inset 0 -18px 18px -18px #0f17293d;
43
65
  }
44
- [data-theme="nightfall"] {
66
+ [data-theme="nightfall"], [data-theme="alpine"] {
45
67
  --surface-trench: #08090c;
46
68
  --surface-well: #0c0e12;
47
69
  --surface-ground: #121419;
@@ -70,88 +92,28 @@
70
92
  --presence-2: #3fc1d0;
71
93
  --presence-3: #b39dff;
72
94
  --on-presence: #0b0d12;
73
- --scrim: #00000080;
74
- --trench: inset 0 2px 4px #000000b3, 0 0 0 0 #00000000, 0 0 0 0 #00000000, 0 0 0 0 #00000000;
75
- --well: inset 0 1px 2px #00000099, 0 0 0 0 #00000000, 0 0 0 0 #00000000, 0 0 0 0 #00000000;
76
- --elev-0: inset 0 1px 0 #ffffff00, 0 0 0 0 #00000000, 0 0 0 0 #00000000, 0 0 0 0 #00000000;
77
- --elev-1: inset 0 1px 0 #ffffff12, 0 0 0 1px #ffffff0a, 0 1px 2px #00000080, 0 2px 6px -1px #00000052;
78
- --elev-2: inset 0 1px 0 #ffffff17, 0 0 0 1px #ffffff0d, 0 2px 4px -1px #00000073, 0 10px 20px -6px #00000066;
79
- --elev-3: inset 0 1px 0 #ffffff1c, 0 0 0 1px #ffffff0f, 0 4px 8px -2px #00000073, 0 18px 36px -10px #00000080;
80
- --elev-4: inset 0 1px 0 #ffffff21, 0 0 0 1px #ffffff12, 0 8px 16px -4px #00000080, 0 30px 60px -16px #0000008c;
81
- --elev-5: inset 0 1px 0 #ffffff26, 0 0 0 1px #ffffff14, 0 12px 24px -6px #0000008c, 0 48px 96px -24px #00000099;
82
- --lip-top: inset 0 18px 18px -18px #000000cc;
83
- --lip-bottom: inset 0 -18px 18px -18px #000000cc;
84
- }
85
- [data-theme="mesa"] {
86
- --surface-trench: #e5ddd2;
87
- --surface-well: #eee7dd;
88
- --surface-ground: #f6f1ea;
89
- --surface-raised: #fffcf8;
90
- --surface-lifted: #fffdfa;
91
- --surface-floating: #ffffff;
92
- --surface-overlay: #ffffff;
93
- --surface-modal: #ffffff;
94
- --ink: #221b14;
95
- --ink-muted: #62564a;
96
- --ink-faint: #a09484;
97
- --line: #e6ded3;
98
- --line-control: #807467;
99
- --accent: #b4441b;
100
- --on-accent: #ffffff;
101
- --accent-ink: #a63e17;
102
- --accent-soft: #f8e4d9;
103
- --positive: #2c6e3f;
104
- --positive-soft: #e3efdc;
105
- --caution: #8a5000;
106
- --caution-soft: #fbeccf;
107
- --critical: #b3261e;
108
- --critical-soft: #f9e1dc;
109
- --on-critical: #ffffff;
110
- --presence-1: #a8256a;
111
- --presence-2: #0b7285;
112
- --presence-3: #6e44c2;
113
- --on-presence: #ffffff;
114
- --scrim: #221b144d;
115
- --trench: inset 0 1px 3px #2a1a0c2e, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00;
116
- --well: inset 0 1px 2px #2a1a0c1a, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00;
117
- --elev-0: inset 0 1px 0 #ffffff00, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00, 0 0 0 0 #2a1a0c00;
118
- --elev-1: inset 0 1px 0 #ffffffb3, 0 0 0 1px #2a1a0c17, 0 1px 2px #2a1a0c12, 0 2px 4px -1px #2a1a0c0f;
119
- --elev-2: inset 0 1px 0 #ffffffcc, 0 0 0 1px #2a1a0c12, 0 2px 4px -1px #2a1a0c14, 0 8px 16px -6px #2a1a0c24;
120
- --elev-3: inset 0 1px 0 #ffffffe6, 0 0 0 1px #2a1a0c0f, 0 4px 8px -2px #2a1a0c14, 0 16px 32px -10px #2a1a0c33;
121
- --elev-4: inset 0 1px 0 #ffffff, 0 0 0 1px #2a1a0c0d, 0 8px 16px -4px #2a1a0c17, 0 28px 56px -16px #2a1a0c3d;
122
- --elev-5: inset 0 1px 0 #ffffff, 0 0 0 1px #2a1a0c0a, 0 12px 24px -6px #2a1a0c1a, 0 48px 96px -24px #2a1a0c4d;
123
- --lip-top: inset 0 18px 18px -18px #2a1a0c3d;
124
- --lip-bottom: inset 0 -18px 18px -18px #2a1a0c3d;
125
- }
126
- [data-theme="alpine"] {
127
- --surface-trench: #060a09;
128
- --surface-well: #0a0f0e;
129
- --surface-ground: #0f1513;
130
- --surface-raised: #151e1b;
131
- --surface-lifted: #1a2522;
132
- --surface-floating: #202c28;
133
- --surface-overlay: #25332f;
134
- --surface-modal: #2a3935;
135
- --ink: #eaf3ef;
136
- --ink-muted: #9db0a9;
137
- --ink-faint: #5d6c67;
138
- --line: #222c29;
139
- --line-control: #62736d;
140
- --accent: #5fd6b2;
141
- --on-accent: #04140f;
142
- --accent-ink: #6fdcbc;
143
- --accent-soft: #13302a;
144
- --positive: #8ed67a;
145
- --positive-soft: #1a2e17;
146
- --caution: #efbd5b;
147
- --caution-soft: #2f2712;
148
- --critical: #ff8a70;
149
- --critical-soft: #3a1a14;
150
- --on-critical: #1a0905;
151
- --presence-1: #f07ab8;
152
- --presence-2: #5cc8e0;
153
- --presence-3: #b9a3ff;
154
- --on-presence: #06100d;
95
+ --data-1: #455ee1;
96
+ --data-2: #989902;
97
+ --data-3: #983dbc;
98
+ --data-4: #ce3401;
99
+ --data-5: #0397d2;
100
+ --data-6: #227702;
101
+ --data-other: #676a73;
102
+ --data-context: #86888b;
103
+ --data-seq-1: #2d3242;
104
+ --data-seq-2: #404966;
105
+ --data-seq-3: #54618c;
106
+ --data-seq-4: #687ab3;
107
+ --data-seq-5: #7e94dc;
108
+ --data-seq-6: #97b0ff;
109
+ --data-seq-7: #bfd0ff;
110
+ --data-div-neg-3: #ffc580;
111
+ --data-div-neg-2: #c68e48;
112
+ --data-div-neg-1: #816038;
113
+ --data-div-mid: #36383c;
114
+ --data-div-pos-1: #576590;
115
+ --data-div-pos-2: #8097df;
116
+ --data-div-pos-3: #bfd0ff;
155
117
  --scrim: #00000080;
156
118
  --trench: inset 0 2px 4px #000000b3, 0 0 0 0 #00000000, 0 0 0 0 #00000000, 0 0 0 0 #00000000;
157
119
  --well: inset 0 1px 2px #00000099, 0 0 0 0 #00000000, 0 0 0 0 #00000000, 0 0 0 0 #00000000;
@@ -179,6 +141,14 @@
179
141
  --radius-lg: 14px;
180
142
  --radius-xl: 20px;
181
143
  --radius-full: 9999px;
144
+ --size-target-min: 24px;
145
+ --size-control-sm: 30px;
146
+ --size-control-md: 36px;
147
+ --size-field-sm: 34px;
148
+ --size-field-md: 40px;
149
+ --size-row-md: 32px;
150
+ --breakpoint-narrow: 480px;
151
+ --breakpoint-compact: 640px;
182
152
  --dur-press: 90ms;
183
153
  --dur-travel-1: 140ms;
184
154
  --dur-travel-2: 180ms;
@@ -186,11 +156,16 @@
186
156
  --dur-travel-4: 260ms;
187
157
  --dur-travel-5: 300ms;
188
158
  --dur-sweep: 1600ms;
159
+ --dur-breath: 4800ms;
160
+ --dur-wave: 1800ms;
189
161
  --dur-grace: 300ms;
190
162
  --dur-onset: 500ms;
191
163
  --dur-hold: 1000ms;
192
164
  --dur-hover: 1800ms;
165
+ --dur-linger: 2000ms;
193
166
  --dur-settle: 8000ms;
167
+ --dur-arrive: 1100ms;
168
+ --dur-plate: 600ms;
194
169
  --ease-rise: cubic-bezier(0.2, 0.9, 0.25, 1);
195
170
  --ease-sink: cubic-bezier(0.45, 0, 0.8, 0.35);
196
171
  --ease-settle: cubic-bezier(0.3, 1.3, 0.45, 1);
@@ -205,6 +180,31 @@
205
180
  --font-sans: "Instrument Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
206
181
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
207
182
  }
183
+ @media (pointer: coarse) {
184
+ :root {
185
+ --size-target-min: 44px;
186
+ --size-control-sm: 40px;
187
+ --size-control-md: 44px;
188
+ --size-field-sm: 44px;
189
+ --size-field-md: 48px;
190
+ --size-row-md: 44px;
191
+ }
192
+ }
193
+ [data-density="comfortable"] {
194
+ --size-target-min: 44px;
195
+ --size-control-sm: 40px;
196
+ --size-control-md: 44px;
197
+ --size-field-sm: 44px;
198
+ --size-field-md: 48px;
199
+ --size-row-md: 44px;
200
+ }
201
+ [data-density="compact"] {
202
+ --size-control-sm: 30px;
203
+ --size-control-md: 36px;
204
+ --size-field-sm: 34px;
205
+ --size-field-md: 40px;
206
+ --size-row-md: 32px;
207
+ }
208
208
 
209
209
 
210
210
  /* Altitude — component styles. Every value comes from tokens.css; the only
@@ -215,6 +215,13 @@
215
215
  --alt-travel: 1;
216
216
  /* Convexity cue: a 2–5 L* light-to-dark face gradient on things you press (measured; see Research). Pressing flattens it. */
217
217
  --alt-sheen: linear-gradient(to bottom, rgb(255 255 255 / 0.06), rgb(0 0 0 / 0.03));
218
+ /* Insets that step down on a phone (Mobile, M11), so a small screen doesn't stack a desktop's padding at every level
219
+ of nesting. Each is full from the compact breakpoint (640px) up and eases to space-4 below it: the page's gutter and
220
+ an overlay's inset from space-6 (16px by 427px), a card's from space-5 (16px by 512px). They follow the viewport,
221
+ like a density, so a narrow card in a desktop grid keeps its padding; what changes form in a narrow slot still
222
+ follows its container. */
223
+ --alt-inset-page: clamp(var(--space-4), 3.75vw, var(--space-6));
224
+ --alt-inset-box: clamp(var(--space-4), 3.125vw, var(--space-5));
218
225
  }
219
226
  :root, [data-theme] {
220
227
  /* The hairline that outlines a slab, face and wall together, so its edges read on light grounds. */
@@ -231,7 +238,7 @@
231
238
  in use stays calm. 80% ink keeps a 3:1 change from line-control in every scheme. */
232
239
  --alt-rim-held: color-mix(in oklab, var(--ink) 80%, var(--line-control));
233
240
  }
234
- [data-theme="mesa"] { --alt-cast: rgb(42 26 12 / 0.22); --alt-cast-deep: rgb(42 26 12 / 0.36); }
241
+ /* alpine is a retired name for nightfall (#324). */
235
242
  [data-theme="nightfall"], [data-theme="alpine"] {
236
243
  --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);
237
244
  --alt-wall-in: color-mix(in oklab, var(--surface-trench), #000 55%); --alt-lip: rgb(255 255 255 / 0.035);
@@ -274,10 +281,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
274
281
  cursor: pointer;
275
282
  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);
276
283
  }
277
- .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))); }
284
+ .alt-lift:focus-visible { background: var(--alt-bg-up); box-shadow: var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel))); }
285
+ @media (hover: hover) { .alt-lift:hover { background: var(--alt-bg-up); box-shadow: var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel))); } }
278
286
  .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; }
279
287
  .alt-lift:focus-visible, .alt-tabs-trigger:focus-visible, .alt-acc-trigger:focus-visible,
280
- .alt-slider-thumb:focus-visible, .alt-switch:focus-visible, .alt-tabs-content:focus-visible, .alt-toast-close:focus-visible {
288
+ .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 {
281
289
  outline: 2px solid var(--accent-ink); outline-offset: 2px;
282
290
  }
283
291
 
@@ -291,7 +299,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
291
299
  --wall: var(--wall-1); --wall-c: var(--surface-trench);
292
300
  --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);
293
301
  position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none;
294
- height: 36px; padding: 0 var(--space-4); border: 0; border-radius: var(--radius-md);
302
+ height: var(--size-control-md); padding: 0 var(--space-4); border: 0; border-radius: var(--radius-md);
295
303
  font: 500 14px/20px var(--font-sans); letter-spacing: -0.003em; white-space: nowrap; user-select: none; cursor: pointer;
296
304
  background: var(--alt-bg); background-image: var(--alt-sheen); color: var(--alt-fg);
297
305
  /* A button stays exactly where it is until the pointer is on it: no approach, no lean. */
@@ -304,14 +312,14 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
304
312
  outline 2px off a box that covers the face and the wall, because an outline never adds to the page's scrollable
305
313
  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
306
314
  alone, so only a keyboard-focused button's wall (the box's bottom edge, at most 5px) reaches past its own box. */
307
- .alt-btn:focus-visible, .alt-crumbs button:focus-visible { outline: none; }
308
- .alt-btn::after, .alt-crumbs button::after {
315
+ .alt-btn:focus-visible, .alt-crumbs .alt-crumb-link:focus-visible { outline: none; }
316
+ .alt-btn::after, .alt-crumbs .alt-crumb-link::after {
309
317
  content: ""; position: absolute; pointer-events: none; opacity: 0;
310
318
  inset: 0; outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--radius-md);
311
319
  transition: opacity var(--dur-travel-1) var(--ease-rise), bottom var(--dur-press) ease-out;
312
320
  }
313
- .alt-btn[data-size="sm"]::after, .alt-crumbs button::after { border-radius: var(--radius-sm); }
314
- .alt-btn:focus-visible::after, .alt-crumbs button:focus-visible::after { opacity: 1; bottom: calc(-1 * var(--wall, 0px) - 1px); }
321
+ .alt-btn[data-size="sm"]::after, .alt-crumbs .alt-crumb-link::after { border-radius: var(--radius-sm); }
322
+ .alt-btn:focus-visible::after, .alt-crumbs .alt-crumb-link:focus-visible::after { opacity: 1; bottom: calc(-1 * var(--wall, 0px) - 1px); }
315
323
  .alt-btn:active::after, .alt-btn[data-loading]::after { bottom: 0; }
316
324
  .alt-btn-label { display: inline-flex; align-items: center; gap: var(--space-2); transition: opacity var(--dur-travel-1) var(--ease-rise); }
317
325
  .alt-btn-label > svg { flex: none; margin-inline: -2px; }
@@ -324,16 +332,21 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
324
332
  --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);
325
333
  }
326
334
  .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; }
327
- .alt-btn[data-variant="ghost"]:hover:not(:disabled), .alt-btn[data-variant="ghost"]:focus-visible { --wall: var(--wall-1); background-image: var(--alt-sheen); }
328
- .alt-btn[data-size="sm"] { height: 30px; padding: 0 var(--space-3); font-size: 13px; border-radius: var(--radius-sm); }
335
+ .alt-btn[data-variant="ghost"]:focus-visible { --wall: var(--wall-1); background-image: var(--alt-sheen); }
336
+ @media (hover: hover) { .alt-btn[data-variant="ghost"]:hover:not(:disabled) { --wall: var(--wall-1); background-image: var(--alt-sheen); } }
337
+ .alt-btn[data-size="sm"] { height: var(--size-control-sm); padding: 0 var(--space-3); font-size: 13px; border-radius: var(--radius-sm); }
329
338
  .alt-btn[data-size="lg"] { height: 44px; padding: 0 var(--space-5); font-size: 15px; }
330
- .alt-btn[data-square] { width: 36px; padding: 0; }
331
- .alt-btn[data-square][data-size="sm"] { width: 30px; }
339
+ .alt-btn[data-square] { width: var(--size-control-md); padding: 0; }
340
+ .alt-btn[data-square][data-size="sm"] { width: var(--size-control-sm); }
332
341
  .alt-btn[data-square][data-size="lg"] { width: 44px; }
333
- .alt-btn:hover:not(:disabled):not([data-loading]), .alt-btn:focus-visible {
342
+ .alt-btn:focus-visible {
334
343
  background: var(--alt-bg-up); background-image: var(--alt-sheen);
335
344
  box-shadow: 0 calc(var(--wall) + 1px) 0 0 var(--wall-c), var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel)));
336
345
  }
346
+ @media (hover: hover) { .alt-btn:hover:not(:disabled):not([data-loading]) {
347
+ background: var(--alt-bg-up); background-image: var(--alt-sheen);
348
+ box-shadow: 0 calc(var(--wall) + 1px) 0 0 var(--wall-c), var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel)));
349
+ } }
337
350
  .alt-btn:active:not(:disabled):not([data-loading]) {
338
351
  box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); background-image: none; transform: translateY(var(--wall));
339
352
  transition-duration: var(--dur-press); transition-timing-function: ease-out;
@@ -350,13 +363,14 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
350
363
  background: var(--alt-bg); box-shadow: var(--alt-sh); border-radius: var(--radius-lg); color: var(--ink);
351
364
  display: flex; flex-direction: column;
352
365
  }
353
- .alt-card.alt-lift:hover, .alt-card.alt-lift:focus-visible { transform: translateY(calc(-2px * var(--alt-travel))); }
354
- .alt-card-header { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-5) var(--space-5) 0; }
366
+ .alt-card.alt-lift:focus-visible { transform: translateY(calc(-2px * var(--alt-travel))); }
367
+ @media (hover: hover) { .alt-card.alt-lift:hover { transform: translateY(calc(-2px * var(--alt-travel))); } }
368
+ .alt-card-header { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--alt-inset-box) var(--alt-inset-box) 0; }
355
369
  .alt-card-title { margin: 0; font: 600 17px/24px var(--font-sans); letter-spacing: -0.01em; }
356
370
  .alt-card-desc { margin: 0; font: 400 14px/20px var(--font-sans); color: var(--ink-muted); }
357
- .alt-card-content { padding: var(--space-4) var(--space-5) var(--space-5); font: 400 15px/23px var(--font-sans); }
358
- .alt-card-header + .alt-card-footer { padding-top: var(--space-5); }
359
- .alt-card-footer { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2); padding: 0 var(--space-5) var(--space-5); }
371
+ .alt-card-content { padding: var(--space-4) var(--alt-inset-box) var(--alt-inset-box); font: 400 15px/23px var(--font-sans); }
372
+ .alt-card-header + .alt-card-footer { padding-top: var(--alt-inset-box); }
373
+ .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); }
360
374
 
361
375
  /* ── Input / Textarea: a field is an opening cut into the page, and its depth says whether it still needs filling.
362
376
  One camera for everything: under a raised face you see its front wall; inside an opening you see its back wall,
@@ -371,15 +385,15 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
371
385
  .alt-input {
372
386
  --w: 4px; --wc: var(--alt-wall-in); --halo: 0px; --halo-c: transparent;
373
387
  --wd: calc(var(--w) + var(--n) * 2px);
374
- position: relative; isolation: isolate; display: flex; align-items: center; gap: var(--space-2); height: 40px; padding: 0 var(--space-3);
388
+ position: relative; isolation: isolate; display: flex; align-items: center; gap: var(--space-2); height: var(--size-field-md); padding: 0 var(--space-3);
375
389
  border: 1px solid var(--line-control); border-radius: var(--radius-md);
376
390
  background: var(--surface-well); color: var(--ink); cursor: text;
377
391
  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);
378
392
  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);
379
393
  outline: 1px solid transparent; outline-offset: 0;
380
394
  }
381
- .alt-input[data-size="sm"] { --w: 3px; height: 34px; border-radius: var(--radius-sm); }
382
- .alt-input:hover { border-color: var(--ink-muted); }
395
+ .alt-input[data-size="sm"] { --w: 3px; height: var(--size-field-sm); border-radius: var(--radius-sm); }
396
+ @media (hover: hover) { .alt-input:hover { border-color: var(--ink-muted); } }
383
397
  /* Filled: the floor rises flush with the page. Nothing left to receive, so approach no longer deepens it. */
384
398
  .alt-input:has(input:not(:placeholder-shown)), .alt-input:has(textarea:not(:placeholder-shown)) { --w: 0px; --n: 0; background: var(--surface-raised); }
385
399
  /* Focused: the floor lights up, and an empty field stays open (at the depth approach previewed) until something is put
@@ -397,6 +411,19 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
397
411
  .alt-input[data-multiline] { height: auto; padding: var(--space-2) var(--space-3); align-items: stretch; flex-direction: column; overflow: hidden; }
398
412
  .alt-input textarea { min-height: 84px; resize: vertical; line-height: 21px; }
399
413
  .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); }
414
+ /* Suggested: a value proposed for the field is a ghost of the value in ink-faint, the feedforward vocabulary (the ghost
415
+ of the check a box would get). It isn't the value, so an empty field with a ghost stays an opening; Tab accepts,
416
+ raising the floor flush with one afterglow. The ghost appears without motion: nothing has arrived in the record. */
417
+ .alt-input-field { position: relative; flex: 1; min-width: 0; display: flex; align-self: stretch; }
418
+ .alt-input-field[data-multiline] { flex-direction: column; }
419
+ .alt-input-ghost {
420
+ position: absolute; inset: 0; display: flex; align-items: center; overflow: hidden; white-space: pre; pointer-events: none;
421
+ font: 400 14px/20px var(--font-sans); color: var(--ink-faint);
422
+ transform: translateY(calc(var(--wd) * 0.5)); transition: transform var(--dur-travel-2) var(--ease-settle);
423
+ }
424
+ .alt-input-ghost[data-multiline] { display: block; white-space: pre-wrap; overflow-wrap: break-word; line-height: 21px; }
425
+ .alt-input-ghost-typed { color: transparent; }
426
+ @media (forced-colors: active) { .alt-input-ghost { color: GrayText; } }
400
427
  /* Fill line: with a maxLength, the well fills from the floor as the text approaches the limit. */
401
428
  .alt-input-gauge {
402
429
  --g: var(--accent-ink);
@@ -449,18 +476,18 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
449
476
  transition: transform var(--dur-travel-2) var(--ease-settle), filter var(--dur-travel-1) var(--ease-rise), color var(--dur-travel-1);
450
477
  }
451
478
  /* Hover is feedforward: an unchecked box shows a ghost of the check it would get; a checked one lifts its mark. */
452
- .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; }
453
- .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); }
454
- .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)); }
455
- .alt-check input:checked + .alt-check-track, .alt-check input:indeterminate + .alt-check-track { border-color: var(--accent); }
456
- .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%); }
457
- .alt-check:hover:not([data-disabled]) input:not(:checked):not(:indeterminate) + .alt-check-track { border-color: var(--ink-muted); }
479
+ @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; } }
480
+ @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); } }
481
+ @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)); } }
482
+ .alt-check input:checked + .alt-check-track, .alt-check input[type="checkbox"]:indeterminate + .alt-check-track { border-color: var(--accent); }
483
+ .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%); }
484
+ @media (hover: hover) { .alt-check:hover:not([data-disabled]) input:not(:checked):not(:indeterminate) + .alt-check-track { border-color: var(--ink-muted); } }
458
485
  .alt-check:not(.alt-radio):active:not([data-disabled]) .alt-check-track { box-shadow: var(--trench); }
459
486
  .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); }
460
487
  .alt-check input:focus-visible + .alt-check-track { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
461
488
  .alt-check-mark path { stroke-dasharray: 16; stroke-dashoffset: 16; transition: stroke-dashoffset var(--dur-travel-1) var(--ease-sink); }
462
489
  .alt-check input:checked:not(:indeterminate) + .alt-check-track .alt-check-tick,
463
- .alt-check input:indeterminate + .alt-check-track .alt-check-dash { stroke-dashoffset: 0; transition: stroke-dashoffset var(--dur-travel-2) var(--ease-rise) 60ms; }
490
+ .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; }
464
491
  .alt-check-text { display: flex; flex-direction: column; gap: 2px; padding-top: 1px; }
465
492
  .alt-check-label { font: 500 14px/20px var(--font-sans); }
466
493
  .alt-check-desc { font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
@@ -481,7 +508,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
481
508
  transition: box-shadow var(--dur-travel-1) var(--ease-rise), scale var(--dur-travel-2) var(--ease-settle);
482
509
  }
483
510
  .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); }
484
- .alt-radio-group:has(.alt-radio:hover input:checked) .alt-radio-thumb { box-shadow: var(--elev-2); }
511
+ @media (hover: hover) { .alt-radio-group:has(.alt-radio:hover input:checked) .alt-radio-thumb { box-shadow: var(--elev-2); } }
485
512
  .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)); }
486
513
  /* Feedforward: hovering an unchosen option shows a ghost of the thumb where it would land. */
487
514
  .alt-radio .alt-check-track::after {
@@ -489,7 +516,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
489
516
  background: var(--surface-raised); box-shadow: var(--elev-1); opacity: 0; scale: 0.5; pointer-events: none;
490
517
  transition: opacity var(--dur-travel-1) var(--ease-sink), scale var(--dur-travel-2) var(--ease-settle);
491
518
  }
492
- .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); }
519
+ @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); } }
493
520
  .alt-radio-group[data-disabled] .alt-radio-thumb { background: var(--surface-well); background-image: none; box-shadow: var(--elev-0); }
494
521
 
495
522
  /* ── Switch: a trench with a raised thumb; the accent pours in behind the thumb. ── */
@@ -512,7 +539,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
512
539
  .alt-switch[aria-checked="true"] { border-color: var(--accent); }
513
540
  .alt-switch[aria-checked="true"] .alt-switch-fill { opacity: 1; clip-path: inset(0 0 0 0 round 999px); }
514
541
  .alt-switch[aria-checked="true"] .alt-switch-thumb { transform: translateX(calc(var(--track) - var(--tw))); }
515
- .alt-switch:hover:not(:disabled) .alt-switch-thumb { box-shadow: var(--elev-2); }
542
+ @media (hover: hover) { .alt-switch:hover:not(:disabled) .alt-switch-thumb { box-shadow: var(--elev-2); } }
516
543
  .alt-switch:active:not(:disabled) { --tw: calc(18px + 5px * var(--alt-travel)); }
517
544
  .alt-switch:disabled { cursor: not-allowed; border-color: var(--line); background: var(--surface-ground); box-shadow: var(--elev-0); }
518
545
  .alt-switch:disabled .alt-switch-thumb { box-shadow: var(--elev-0); background: var(--surface-well); }
@@ -527,7 +554,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
527
554
  variant="segmented": the options sit in a well and one raised pill glides between them. ── */
528
555
  .alt-tabs { display: flex; flex-direction: column; gap: var(--space-4); }
529
556
  .alt-tabs-list {
530
- position: relative; display: inline-flex; align-self: flex-start; gap: 2px; padding: var(--space-1);
557
+ 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);
531
558
  border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-well); box-shadow: var(--well);
532
559
  }
533
560
  .alt-tabs-indicator {
@@ -536,21 +563,30 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
536
563
  }
537
564
  .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); }
538
565
  .alt-tabs-trigger {
539
- position: relative; z-index: 1; height: 30px; padding: 0 var(--space-3); border: 0; border-radius: calc(var(--radius-md) - var(--space-1));
566
+ 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));
540
567
  background: none; color: var(--ink-muted); font: 500 14px/20px var(--font-sans); cursor: pointer; white-space: nowrap;
541
568
  transition: color var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1) var(--ease-rise);
542
569
  }
543
- .alt-tabs-trigger:hover, .alt-tabs-trigger[aria-selected="true"] { color: var(--ink); }
544
- .alt-tabs[data-variant="segmented"] .alt-tabs-trigger:hover:not([aria-selected="true"]) { background: color-mix(in oklab, var(--surface-raised) 40%, transparent); }
570
+ .alt-tabs-trigger[aria-selected="true"], .alt-tabs-trigger[aria-checked="true"] { color: var(--ink); }
571
+ @media (hover: hover) { .alt-tabs-trigger:hover { color: var(--ink); } }
572
+ @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); } }
573
+ /* Narrow (#259): the list scrolls within itself; an edge with more tabs past it fades out, so you can see there's more. */
574
+ .alt-tabs-list::-webkit-scrollbar { display: none; }
575
+ .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); }
576
+ .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); }
577
+ .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); }
545
578
  .alt-tabs-content { animation: alt-emerge var(--dur-travel-2) var(--ease-rise); border-radius: var(--radius-sm); }
546
579
 
547
580
  .alt-tabs[data-variant="folder"] { gap: 0; }
581
+ /* The list makes no layer of its own, so each tab stacks against the panel: only the chosen tab lies over it (covering its
582
+ hairline); the others, and a tab dropping back, sit behind the sheet. */
548
583
  .alt-tabs[data-variant="folder"] > .alt-tabs-list {
549
- --r: 10px; align-self: stretch; z-index: 2; gap: var(--space-1); margin-bottom: -1px; padding: 0 var(--space-5);
584
+ /* It scrolls when narrow (#259), so the tab's shadow above and its 1px reach into the panel sit in its padding. */
585
+ --r: 10px; align-self: stretch; z-index: auto; gap: var(--space-1); margin: -6px 0 -2px; padding: 6px var(--alt-inset-box) 1px;
550
586
  border: 0; border-radius: 0; background: none; box-shadow: none;
551
587
  }
552
588
  .alt-tabs[data-variant="folder"] .alt-tabs-indicator { display: none; }
553
- .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; }
589
+ .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; }
554
590
  .alt-tabs[data-variant="folder"] .alt-tabs-trigger:focus-visible { outline-offset: -5px; }
555
591
  /* The tab: the top edge of its sheet. It reaches 1px into the panel to cover the panel's top hairline. */
556
592
  .alt-tabs[data-variant="folder"] .alt-tabs-trigger::before {
@@ -567,17 +603,18 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
567
603
  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;
568
604
  opacity: 0; transition: opacity var(--dur-travel-1) var(--ease-sink);
569
605
  }
606
+ .alt-tabs[data-variant="folder"] .alt-tabs-trigger[aria-selected="true"] { z-index: 2; }
570
607
  .alt-tabs[data-variant="folder"] .alt-tabs-trigger[aria-selected="true"]::before {
571
608
  opacity: 1; transform: none; transition: opacity var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-3) var(--ease-settle);
572
609
  }
573
610
  .alt-tabs[data-variant="folder"] .alt-tabs-trigger[aria-selected="true"]::after { opacity: 1; transition: opacity var(--dur-travel-2) var(--ease-rise) 60ms; }
574
- .alt-tabs[data-variant="folder"] .alt-tabs-trigger:hover:not([aria-selected="true"])::before {
611
+ @media (hover: hover) { .alt-tabs[data-variant="folder"] .alt-tabs-trigger:hover:not([aria-selected="true"])::before {
575
612
  opacity: 1; transform: translateY(calc(5px * var(--alt-travel))); clip-path: inset(-8px -8px calc(1px + 5px * var(--alt-travel)) -8px);
576
613
  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);
577
614
  transition: opacity var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-2) var(--ease-settle);
578
- }
615
+ } }
579
616
  .alt-tabs[data-variant="folder"] > .alt-tabs-content {
580
- position: relative; z-index: 1; padding: var(--space-5); border-radius: var(--radius-lg);
617
+ position: relative; z-index: 1; padding: var(--alt-inset-box); border-radius: var(--radius-lg);
581
618
  background: var(--surface-raised); box-shadow: var(--elev-2); color: var(--ink);
582
619
  animation: alt-sheet-surface var(--dur-travel-3) var(--ease-rise);
583
620
  }
@@ -593,9 +630,9 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
593
630
  border: 2px solid var(--accent); border-radius: var(--radius-full); background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1);
594
631
  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);
595
632
  }
596
- .alt-slider:hover .alt-slider-thumb { box-shadow: var(--elev-2); }
633
+ @media (hover: hover) { .alt-slider:hover .alt-slider-thumb { box-shadow: var(--elev-2); } }
597
634
  .alt-slider[data-dragging] .alt-slider-fill, .alt-slider[data-dragging] .alt-slider-thumb { transition-property: box-shadow, transform; }
598
- .alt-slider[data-dragging] .alt-slider-thumb { box-shadow: var(--elev-3); transform: scale(calc(1 + 0.12 * var(--alt-travel))); cursor: grabbing; }
635
+ .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; }
599
636
  .alt-slider-bubble {
600
637
  position: absolute; left: 50%; bottom: calc(100% + 10px); translate: -50% 0; min-width: 32px; padding: 3px 8px; text-align: center;
601
638
  border-radius: var(--radius-sm); background: var(--surface-floating); box-shadow: var(--elev-1); color: var(--ink);
@@ -603,13 +640,23 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
603
640
  opacity: 0; transform: translateY(calc(4px * var(--alt-travel))) scale(calc(1 - 0.1 * var(--alt-travel))); transform-origin: 50% 100%;
604
641
  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);
605
642
  }
606
- .alt-slider[data-dragging] .alt-slider-bubble, .alt-slider-thumb:focus-visible .alt-slider-bubble {
643
+ .alt-slider[data-dragging] .alt-slider-thumb:not([data-rest]) .alt-slider-bubble, .alt-slider-thumb:focus-visible .alt-slider-bubble {
607
644
  opacity: 1; transform: none; box-shadow: var(--elev-3);
608
645
  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);
609
646
  }
610
647
  .alt-slider[data-disabled] { cursor: not-allowed; }
611
648
  .alt-slider[data-disabled] .alt-slider-fill { background: var(--line-control); box-shadow: none; }
612
649
  .alt-slider[data-disabled] .alt-slider-thumb { border-color: var(--line-control); box-shadow: var(--elev-0); }
650
+ /* A range: two thumbs with the raised fill between them. Only the thumb under the pointer lifts, and the one held
651
+ floats; the other stays at rest. The fill lifts on hover too, because dragging it moves both thumbs. */
652
+ .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); }
653
+ @media (hover: hover) { .alt-slider[data-range]:not([data-disabled]):hover .alt-slider-thumb { box-shadow: var(--elev-1); } }
654
+ .alt-slider-fill[data-grabbed] { box-shadow: var(--elev-2); }
655
+ @media (hover: hover) { .alt-slider[data-range]:not([data-disabled]) :is(.alt-slider-thumb:hover, .alt-slider-fill:hover) { box-shadow: var(--elev-2); } }
656
+ .alt-slider[data-range][data-dragging] .alt-slider-thumb[data-grabbed] { box-shadow: var(--elev-3); }
657
+ .alt-slider[data-range][data-dragging] .alt-slider-fill { transition-property: box-shadow; }
658
+ .alt-slider-fill[data-grabbed] { cursor: grabbing; }
659
+ .alt-slider[data-range][data-disabled] .alt-slider-fill { cursor: not-allowed; box-shadow: none; }
613
660
 
614
661
  /* ── Progress ── */
615
662
  .alt-progress { position: relative; height: 8px; border-radius: var(--radius-full); background: var(--surface-trench); box-shadow: var(--trench); overflow: hidden; }
@@ -663,71 +710,76 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
663
710
  @media (prefers-reduced-motion: reduce) { .alt-badge-lamp[data-pulse] { animation: none; } }
664
711
  [data-motion="reduced"] .alt-badge-lamp[data-pulse] { animation: none; }
665
712
 
666
- /* ── Avatar: presence as height, drawn the way the eye judges height above a floor: by the gap between a thing and
667
- its shadow (Kersten, Mamassian & Knill 1997). Every face is a disc with a cast shadow on the ground beneath it.
668
- Active people hover: the face lifts off and its shadow shrinks and fades. Idle people rest: a tight contact
669
- shadow right under them. Offline people lie flat in the page, grey, with no shadow at all. Someone live
670
- (speaking, typing, presenting) drifts gently up and down. --hr is the height as a fraction of the face. ── */
713
+ /* ── 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
714
+ its contact shadow; when they're not (offline) it's part of the record, a plate set flush into the page, grey. Faces
715
+ are neutral: status hues never stand for a person, and colour stays in the lamp, as on a Badge (filled when active,
716
+ an empty ring when idle). Only someone live (speaking, typing, presenting), or an avatar you can press under the
717
+ pointer, leaves the ground: the face lifts and a shadow appears on the page beneath it, smaller and fainter the
718
+ higher it goes. A detached shadow is what makes an object read as floating rather than resting (Kersten, Mamassian
719
+ & Knill 1997). --hr is the height as a fraction of the face. ── */
671
720
  @property --hr { syntax: "<number>"; inherits: true; initial-value: 0; }
672
721
  .alt-avatar {
673
722
  --size: 36px; --hr: 0; --lift: calc(var(--size) * var(--hr) * var(--alt-travel));
723
+ --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);
674
724
  position: relative; isolation: isolate; display: inline-grid; flex: none; width: var(--size); height: var(--size); vertical-align: middle;
675
725
  transition: --hr var(--dur-travel-3) var(--ease-settle);
676
726
  }
677
727
  .alt-avatar-face {
678
- position: relative; z-index: 1; display: grid; place-items: center; width: 100%; height: 100%; overflow: hidden; border-radius: var(--radius-full);
679
- background-color: var(--surface-raised); background-image: radial-gradient(120% 90% at 50% 0%, rgb(255 255 255 / 0.2), transparent 60%);
680
- box-shadow: inset 0 0 0 1px var(--alt-ring); color: var(--ink);
681
- font: 600 calc(var(--size) * 0.36) / 1 var(--font-sans); letter-spacing: 0.01em; user-select: none;
728
+ position: relative; z-index: 1; display: grid; place-items: center; width: 100%; height: 100%; overflow: hidden; border-radius: var(--av-r, var(--radius-full));
729
+ background: var(--surface-raised); box-shadow: var(--av-sh); color: var(--ink);
730
+ font: 600 calc(var(--size) * 0.36) / 1 var(--font-sans); user-select: none;
682
731
  transform: translateY(calc(-1 * var(--lift)));
683
- transition: background-color var(--dur-travel-2), color var(--dur-travel-2), filter var(--dur-travel-2), box-shadow var(--dur-travel-2);
732
+ transition: background-color var(--dur-travel-2), color var(--dur-travel-2), box-shadow var(--dur-travel-2) var(--ease-rise);
684
733
  }
685
734
  .alt-avatar-face img { width: 100%; height: 100%; object-fit: cover; }
686
- /* The cast shadow. The higher the face, the smaller and fainter it gets — that gap is what reads as height. */
735
+ /* One letter has room to be bigger: small faces (under 28px) show only the first. */
736
+ .alt-avatar-face[data-one] { font-size: calc(var(--size) * 0.46); }
737
+ /* A team, an organisation, a bot or an agent is a square tile; its corners (--av-r) grow with its size, from
738
+ radius-xs to radius-lg. Its shadow, lamp and states are a person's. */
739
+ .alt-avatar[data-shape="square"] .alt-avatar-lamp { inset-inline-end: calc(var(--lamp) * -0.3); bottom: calc(var(--lamp) * -0.3); }
740
+ .alt-avatar[data-shape="square"] .alt-avatar-shadow { left: 8%; right: 8%; border-radius: var(--radius-sm); }
741
+ /* 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
742
+ as the face leaves the ground, then shrinks and fades the higher the face goes: that gap is what reads as height. */
687
743
  .alt-avatar-shadow {
688
- position: absolute; left: 7%; right: 7%; top: 100%; height: calc(var(--size) * 0.2); margin-top: calc(var(--size) * -0.1);
689
- border-radius: 50%; background: radial-gradient(closest-side, var(--alt-cast-deep), var(--alt-cast-deep) 25%, transparent); pointer-events: none;
690
- scale: calc(1 - var(--hr) * 1.15); opacity: calc(1 - var(--hr) * 1.7);
691
- }
692
- .alt-avatar[data-tint="accent"] .alt-avatar-face { background-color: var(--accent-soft); }
693
- .alt-avatar[data-tint="positive"] .alt-avatar-face { background-color: var(--positive-soft); }
694
- .alt-avatar[data-tint="caution"] .alt-avatar-face { background-color: var(--caution-soft); }
695
- .alt-avatar[data-tint="critical"] .alt-avatar-face { background-color: var(--critical-soft); }
696
- .alt-avatar[data-status="active"] { --hr: 0.15; }
697
- .alt-avatar[data-status="offline"] .alt-avatar-face, .alt-avatar[data-tint="more"] .alt-avatar-face {
698
- 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);
699
- }
700
- .alt-avatar[data-status="offline"] .alt-avatar-face { filter: grayscale(1); }
701
- .alt-avatar[data-status="offline"] .alt-avatar-face img { opacity: 0.6; }
702
- .alt-avatar[data-status="offline"] .alt-avatar-shadow, .alt-avatar[data-tint="more"] .alt-avatar-shadow { opacity: 0; }
703
- .alt-avatar[data-tint="more"] .alt-avatar-face { font-size: calc(var(--size) * 0.32); }
704
- .alt-avatar[data-live] { animation: alt-drift var(--dur-hover) ease-in-out infinite alternate; }
705
- @keyframes alt-drift { from { --hr: 0.15; } to { --hr: 0.24; } }
706
- @media (prefers-reduced-motion: reduce) { .alt-avatar[data-live] { animation: none; --hr: 0.2; } }
707
- [data-motion="reduced"] .alt-avatar[data-live] { animation: none; --hr: 0.2; }
708
- .alt-avatar[data-presence] .alt-avatar-face { outline: 2px solid var(--alt-presence); outline-offset: 1px; }
744
+ position: absolute; left: 14%; right: 14%; top: 100%; height: calc(var(--size) * 0.14); margin-top: calc(var(--size) * -0.07);
745
+ border-radius: 50%; pointer-events: none;
746
+ background: radial-gradient(closest-side, var(--alt-cast-deep), color-mix(in oklab, var(--alt-cast-deep) 55%, transparent) 50%, transparent);
747
+ opacity: calc(clamp(0, var(--hr) * 12, 1) * (1 - var(--hr) * 1.4)); scale: calc(1 - var(--hr) * 1.1);
748
+ }
749
+ .alt-avatar:is([data-status="offline"], [data-more]) .alt-avatar-face {
750
+ 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);
751
+ }
752
+ .alt-avatar[data-status="offline"] .alt-avatar-face img { filter: grayscale(1); opacity: 0.5; }
753
+ .alt-avatar[data-more] .alt-avatar-face { font-size: calc(var(--size) * 0.32); font-weight: 550; font-variant-numeric: tabular-nums; }
754
+ .alt-avatar[data-live] { --av-sh: var(--elev-2); animation: alt-drift var(--dur-hover) ease-in-out infinite alternate; }
755
+ @keyframes alt-drift { from { --hr: 0.14; } to { --hr: 0.22; } }
756
+ @media (prefers-reduced-motion: reduce) { .alt-avatar[data-live] { animation: none; --hr: 0.18; } }
757
+ [data-motion="reduced"] .alt-avatar[data-live] { animation: none; --hr: 0.18; }
758
+ /* Presence colour: a 2px ring drawn tight around the face. */
759
+ .alt-avatar[data-presence] .alt-avatar-face { outline: 2px solid var(--alt-presence); outline-offset: 0; }
760
+ /* The lamp is a Badge lamp, cut out of the face by a ring of ground: filled when active, an empty ring when idle. */
709
761
  .alt-avatar-lamp {
710
- --b-lamp: var(--positive);
711
- 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);
762
+ --b-lamp: var(--positive); --lamp: clamp(8px, calc(var(--size) * 0.2), 12px);
763
+ position: absolute; z-index: 2; inset-inline-end: 0; bottom: 0; width: var(--lamp); height: var(--lamp); border-radius: var(--radius-full);
712
764
  background: var(--b-lamp); box-shadow: 0 0 0 2px var(--surface-ground); transform: translateY(calc(-1 * var(--lift)));
713
765
  }
714
- .alt-avatar[data-status="idle"] .alt-avatar-lamp { --b-lamp: var(--caution); }
715
- .alt-avatar[data-interactive]:hover, .alt-avatar[data-interactive]:focus-visible { --hr: 0.3; }
766
+ .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); }
716
767
  .alt-avatar[data-interactive] { cursor: pointer; outline: none; }
768
+ .alt-avatar[data-interactive]:focus-visible { --hr: 0.26; --av-sh: var(--elev-2); }
769
+ @media (hover: hover) { .alt-avatar[data-interactive]:hover { --hr: 0.26; --av-sh: var(--elev-2); } }
770
+ .alt-avatar[data-interactive]:active { --hr: 0; --av-sh: var(--elev-0); transition-duration: var(--dur-press); }
717
771
  .alt-avatar[data-interactive]:focus-visible .alt-avatar-face { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
718
- .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); }
772
+ /* Group: every face stands on one ground line, cut out of the one before it by a ring of ground. Later faces are
773
+ laid on top, overlapping by 15% of a face, which keeps the initials beneath whole. */
774
+ .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); }
719
775
  .alt-avatar-group .alt-avatar { isolation: auto; }
720
- /* Faces overlap by a fifth. An avatar that waited for its data sits inside its loading wrapper (display: contents at
721
- rest), so the overlap is counted from the group's own children either way, imprints included. */
776
+ /* An avatar that waited for its data sits inside its loading wrapper (display: contents at rest), so the overlap is
777
+ counted from the group's own children either way, imprints included. */
722
778
  .alt-avatar-group > .alt-avatar:not(:first-child),
723
- .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); }
724
- /* Overlapping faces are cut out of each other with a ring of ground; higher faces sit in front. */
725
- .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); }
726
- .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); }
727
- .alt-avatar-group .alt-avatar[data-status="active"] .alt-avatar-face { z-index: 3; }
728
- .alt-avatar-group .alt-avatar[data-status="idle"] .alt-avatar-face { z-index: 2; }
729
- .alt-avatar-group .alt-avatar:not([data-tint="more"]):hover { --hr: 0.32; }
730
- .alt-avatar-group .alt-avatar:not([data-tint="more"]):hover .alt-avatar-face { z-index: 5; }
779
+ .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); }
780
+ .alt-avatar-group .alt-avatar .alt-avatar-face { box-shadow: 0 0 0 2px var(--surface-ground), var(--av-sh); }
781
+ .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); }
782
+ @media (hover: hover) { .alt-avatar-group .alt-avatar:is([data-live], [data-interactive]:hover, [data-interactive]:focus-visible) .alt-avatar-face { z-index: 5; } }
731
783
  .alt-avatar-group .alt-avatar-lamp { display: none; }
732
784
 
733
785
  /* ── Skeleton: an imprint of the content to come, pressed into the page. Light passes over it while it waits; when the
@@ -775,8 +827,77 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
775
827
  /* Text imprints are as tall as the text they stand for: one line-height of the surrounding type per line. */
776
828
  .alt-await-lines { display: flex; flex-direction: column; justify-content: space-around; height: calc(var(--lines, 1) * 1lh); max-width: 100%; }
777
829
  .alt-await-lines > .alt-skeleton { flex: none; height: 0.6em; }
778
- .alt-await-card { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-5); }
830
+ .alt-await-card { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--alt-inset-box); }
779
831
  .alt-await-card-body { font: 400 15px/23px var(--font-sans); }
832
+
833
+ /* ── Breath (AltitudeProvider loading="breath", #343). Instead of light sweeping over it, the imprint breathes: its floor
834
+ deepens from well to trench over 40% of dur-breath and releases over the rest, slower and less even than a pending
835
+ write's breath. In Nightfall, where well and trench are nearly one tone, the back wall (lip-top) breathes instead.
836
+ Every imprint takes its phase from one page clock, lagged by its height in the view (--alt-breath-delay, set by
837
+ useBreath), so they change in step and a swell crosses the view in dur-wave. Ten seconds in, it calms to a shallow,
838
+ slower breath. Depth and tone only: nothing travels. Charts, stats and meters hold still (their imprints stand for
839
+ amounts). When content lands: raised content (settle="raised") rises 8px to level 2 and settles to its rest over
840
+ dur-arrive; flush content fills past the ground (its floor passes just above it, then fills in flush); content that
841
+ arrives into a view already showing gets a plate that dissolves over dur-plate, no tint, so it isn't an afterglow. ── */
842
+ [data-loading="breath"] .alt-skeleton::after { content: none; }
843
+ [data-loading="breath"] .alt-skeleton { animation: alt-breath var(--dur-breath) linear var(--alt-breath-delay, 0ms) infinite; }
844
+ [data-loading="breath"][data-theme="nightfall"] .alt-skeleton, [data-theme="nightfall"] [data-loading="breath"] .alt-skeleton { animation-name: alt-breath-wall; }
845
+ [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; }
846
+ [data-loading="breath"] :is(.alt-chart-imprint .alt-skeleton, .alt-stat-imprint, .alt-sparkline-imprint, .alt-meter-imprint) { animation: none; }
847
+ @keyframes alt-breath {
848
+ 0% { background-color: var(--surface-well); box-shadow: var(--well); animation-timing-function: ease-in-out; }
849
+ 40% { background-color: var(--surface-trench); box-shadow: var(--trench); animation-timing-function: ease-in-out; }
850
+ 100% { background-color: var(--surface-well); box-shadow: var(--well); }
851
+ }
852
+ @keyframes alt-breath-wall {
853
+ 0% { box-shadow: var(--well), inset 0 0 0 0 transparent; animation-timing-function: ease-in-out; }
854
+ 40% { box-shadow: var(--well), var(--lip-top); animation-timing-function: ease-in-out; }
855
+ 100% { box-shadow: var(--well), inset 0 0 0 0 transparent; }
856
+ }
857
+ @keyframes alt-breath-calm { to { background-color: color-mix(in oklab, var(--surface-well), var(--surface-trench) 45%); } }
858
+ [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; }
859
+ [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; }
860
+ [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; }
861
+ [data-loading="breath"] .alt-await[data-phase="reveal"][data-arrival="late"]:not([data-rest="raised"]) > .alt-await-face::before {
862
+ content: ""; position: absolute; inset: -3px -6px; z-index: -1; border-radius: var(--radius-sm); pointer-events: none;
863
+ background: var(--surface-raised); box-shadow: var(--elev-1); animation: alt-plate var(--dur-plate) linear var(--alt-rise-delay, 0ms) both;
864
+ }
865
+ [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; }
866
+ [data-loading="breath"] .alt-await[data-phase="reveal"][data-rest="raised"] > .alt-await-face::before {
867
+ content: ""; position: absolute; inset: 0; z-index: -1; border-radius: var(--alt-await-radius, var(--radius-lg)); pointer-events: none;
868
+ box-shadow: var(--elev-2); animation: alt-plate var(--dur-arrive) linear var(--alt-rise-delay, 0ms) both;
869
+ }
870
+ [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; }
871
+ [data-loading="breath"] .alt-reveal[data-reveal] { animation: alt-reveal-past calc(var(--dur-travel-3) * 2) linear var(--alt-rise-delay, 0ms) backwards; }
872
+ /* A chart's marks still fade in place (Charts: "fade, don't rise"), under breath too. */
873
+ [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; }
874
+ [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; }
875
+ [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; }
876
+ @keyframes alt-fill-ride {
877
+ 0% { opacity: 0; transform: translateY(calc(3px * var(--alt-travel))); animation-timing-function: var(--ease-rise); }
878
+ 50% { opacity: 1; transform: translateY(calc(-1.5px * var(--alt-travel))); animation-timing-function: var(--ease-decay); }
879
+ 100% { transform: none; }
880
+ }
881
+ @keyframes alt-fill-past {
882
+ 0% { background-color: var(--surface-well); box-shadow: var(--well); animation-timing-function: var(--ease-rise); }
883
+ 50% { opacity: 1; background-color: var(--surface-raised); box-shadow: var(--elev-1); animation-timing-function: var(--ease-decay); }
884
+ 100% { opacity: 0; background-color: var(--surface-ground); box-shadow: var(--elev-0); }
885
+ }
886
+ @keyframes alt-reveal-past {
887
+ 0% { background-color: var(--surface-well); box-shadow: var(--well); animation-timing-function: var(--ease-rise); }
888
+ 50% { background-color: var(--surface-raised); box-shadow: var(--elev-1); animation-timing-function: var(--ease-decay); }
889
+ 100% { background-color: transparent; box-shadow: var(--elev-0); }
890
+ }
891
+ @keyframes alt-arrive {
892
+ 0% { opacity: 0; transform: translateY(calc(8px * var(--alt-travel))); animation-timing-function: var(--ease-rise); }
893
+ 20% { opacity: 1; transform: translateY(calc(-3px * var(--alt-travel))); animation-timing-function: var(--ease-decay); }
894
+ 100% { transform: none; }
895
+ }
896
+ @keyframes alt-plate { 0% { opacity: 0; } 20% { opacity: 1; animation-timing-function: var(--ease-decay); } 100% { opacity: 0; } }
897
+ /* Reduced motion and forced colours: the breath stops, as a pending write's does; the imprint holds still at well depth. */
898
+ @media (prefers-reduced-motion: reduce) { [data-loading="breath"] :is(.alt-skeleton, [data-calm] .alt-skeleton, .alt-skeleton[data-calm]) { animation: none; } }
899
+ [data-motion="reduced"] :is(.alt-skeleton, [data-calm] .alt-skeleton, .alt-skeleton[data-calm]) { animation: none; }
900
+ @media (forced-colors: active) { [data-loading="breath"] :is(.alt-skeleton, [data-calm] .alt-skeleton, .alt-skeleton[data-calm]) { animation: none; } }
780
901
  .alt-avatar-imprint { display: inline-block; vertical-align: middle; }
781
902
  .alt-badge-imprint { display: inline-block; vertical-align: middle; width: 64px; height: 22px; }
782
903
  .alt-badge-imprint[data-variant="count"] { width: 22px; border-radius: var(--radius-full); }
@@ -822,8 +943,38 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
822
943
  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);
823
944
  background: none; color: var(--ink-muted); cursor: pointer; transition: background-color var(--dur-travel-1), color var(--dur-travel-1);
824
945
  }
825
- .alt-alert-close:hover { background: color-mix(in oklab, var(--ink) 8%, transparent); color: var(--ink); }
946
+ @media (hover: hover) { .alt-alert-close:hover { background: color-mix(in oklab, var(--ink) 8%, transparent); color: var(--ink); } }
826
947
  .alt-alert-close:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
948
+ .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; }
949
+ /* 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
950
+ no side edges, only its bottom edge and contact shadow. It informs (flat, ground) or asks for action (raised), as
951
+ any Alert does. Sticky, it stays at the top while the page scrolls. Its text wraps; its padding follows the page's. */
952
+ .alt-banner[data-sticky] { position: sticky; top: 0; z-index: 30; }
953
+ .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); }
954
+ .alt-alert[data-placement="page"][data-variant="raised"] { box-shadow: var(--elev-1); }
955
+ .alt-alert[data-placement="page"] .alt-alert-inner { flex-wrap: wrap; padding: var(--space-3) var(--alt-page-pad, var(--space-4)); }
956
+ .alt-alert[data-placement="page"] .alt-alert-body { min-width: min(100%, 240px); }
957
+ .alt-alert-stack[data-placement="page"] .alt-alert-sheet { border-radius: 0; left: 0; right: 0; }
958
+
959
+ /* ── RemovalReceipt. Left in place, then closed: the result shows in the item's slot, at its height, on the ground
960
+ (a trace of what happened, not a live surface). It lifts off as an Alert does, and only then does the gap glide shut. ── */
961
+ .alt-receipt-item { display: contents; }
962
+ /* The receipt lays itself out, whatever the item's own layout was (a row stacked on a phone included); at the item's
963
+ height, its line sits in the middle of the slot. */
964
+ .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); }
965
+ .alt-receipt-note:empty { display: none; }
966
+ .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);
967
+ animation: alt-receipt-in var(--dur-travel-2) ease-out both; }
968
+ .alt-receipt-icon { display: inline-flex; flex: none; }
969
+ .alt-receipt[data-tone="critical"] .alt-receipt-note { color: var(--ink); }
970
+ .alt-receipt[data-tone="critical"] .alt-receipt-icon { color: var(--critical); }
971
+ .alt-receipt-action { display: flex; flex-wrap: wrap; gap: var(--space-2); animation: alt-receipt-in var(--dur-travel-2) ease-out both; }
972
+ @keyframes alt-receipt-in { from { opacity: 0; } }
973
+ /* Like every pending surface, it only shows as pending after dur-grace, so a fast write never flickers. */
974
+ .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; }
975
+ .alt-receipt[data-leaving] { pointer-events: none; }
976
+ .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; }
977
+ @media (forced-colors: active) { .alt-receipt[data-removed] { outline: 1px solid CanvasText; outline-offset: -1px; } }
827
978
 
828
979
  .alt-alert-stack { position: relative; display: grid; gap: var(--space-2); }
829
980
  .alt-alert-stack[data-sheets="1"] { --sheets: 1; } .alt-alert-stack[data-sheets="2"] { --sheets: 2; }
@@ -845,11 +996,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
845
996
  background: var(--surface-floating); color: var(--ink); box-shadow: var(--elev-2); font: 600 12px/1 var(--font-sans); cursor: pointer;
846
997
  transition: box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-2) var(--ease-settle);
847
998
  }
848
- .alt-alert-more:hover { box-shadow: var(--elev-3); transform: translateY(calc(-1px * var(--alt-travel))); }
999
+ @media (hover: hover) { .alt-alert-more:hover { box-shadow: var(--elev-3); transform: translateY(calc(-1px * var(--alt-travel))); } }
849
1000
  .alt-alert-more:active { box-shadow: var(--elev-1); transform: none; transition-duration: var(--dur-press); }
850
1001
  .alt-alert-more:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
851
1002
  .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; }
852
- .alt-alert-stack[data-expanded] .alt-alert-more:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 6%, transparent); }
1003
+ @media (hover: hover) { .alt-alert-stack[data-expanded] .alt-alert-more:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 6%, transparent); } }
853
1004
  .alt-alert-stack-item { animation: alt-fan var(--dur-travel-3) var(--ease-settle) calc(var(--i) * 30ms) backwards; }
854
1005
  .alt-alert-stack-item:first-child { animation: none; }
855
1006
  @keyframes alt-fan { from { opacity: 0; transform: translateY(calc(var(--i) * -12px * var(--alt-travel))) scale(calc(1 - 0.02 * var(--alt-travel))); } }
@@ -869,8 +1020,8 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
869
1020
  .alt-acc-item[data-state="open"] { background: var(--surface-raised); box-shadow: var(--elev-2); margin: var(--space-2) 0; }
870
1021
  .alt-acc-item[data-state="open"]:first-child { margin-top: 0; }
871
1022
  .alt-acc-item[data-state="open"]:last-child { margin-bottom: 0; }
872
- .alt-acc-item[data-state="open"]::before, .alt-acc-item[data-state="open"] + .alt-acc-item::before,
873
- .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; }
1023
+ .alt-acc-item[data-state="open"]::before, .alt-acc-item[data-state="open"] + .alt-acc-item::before { opacity: 0; }
1024
+ @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; } }
874
1025
  .alt-acc-heading { margin: 0; }
875
1026
  .alt-acc-trigger {
876
1027
  display: flex; width: 100%; align-items: center; justify-content: space-between; gap: var(--space-3);
@@ -878,11 +1029,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
878
1029
  font: 500 15px/22px var(--font-sans); text-align: left; cursor: pointer;
879
1030
  transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise);
880
1031
  }
881
- .alt-acc-item[data-state="closed"] .alt-acc-trigger:hover {
1032
+ @media (hover: hover) { .alt-acc-item[data-state="closed"] .alt-acc-trigger:hover {
882
1033
  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);
883
- }
1034
+ } }
884
1035
  .alt-acc-chevron { flex: none; color: var(--ink-muted); transition: transform var(--dur-travel-3) var(--ease-settle); }
885
- .alt-acc-trigger:hover .alt-acc-chevron { transform: translateY(calc(2px * var(--alt-travel))); }
1036
+ @media (hover: hover) { .alt-acc-trigger:hover .alt-acc-chevron { transform: translateY(calc(2px * var(--alt-travel))); } }
886
1037
  .alt-acc-item[data-state="open"] .alt-acc-chevron { transform: rotate(180deg); }
887
1038
  .alt-acc-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--dur-travel-3) var(--ease-glide); }
888
1039
  .alt-acc-item[data-state="open"] .alt-acc-panel { grid-template-rows: 1fr; }
@@ -920,15 +1071,19 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
920
1071
  .alt-tip {
921
1072
  left: 50%; bottom: calc(100% + 8px); translate: -50% 0; transform-origin: 50% 100%;
922
1073
  padding: 5px 10px; border-radius: var(--radius-sm); background: var(--surface-floating); box-shadow: var(--elev-3);
923
- font: 500 12.5px/17px var(--font-sans); white-space: nowrap; pointer-events: none;
1074
+ font: 500 12.5px/17px var(--font-sans); width: max-content; max-width: min(320px, calc(100vw - 16px)); pointer-events: none;
924
1075
  }
925
1076
  .alt-tip[data-side="bottom"] { bottom: auto; top: calc(100% + 8px); transform-origin: 50% 0; }
926
1077
  .alt-tip[data-state="closed"] { transform: translateY(calc(4px * var(--alt-travel))) scale(calc(1 - 0.04 * var(--alt-travel))); }
927
1078
  .alt-tip[data-side="bottom"][data-state="closed"] { transform: translateY(calc(-4px * var(--alt-travel))) scale(calc(1 - 0.04 * var(--alt-travel))); }
1079
+ .alt-tip[data-side="right"] { left: calc(100% + 8px); bottom: auto; top: 50%; translate: 0 -50%; transform-origin: 0 50%; }
1080
+ .alt-tip[data-side="right"][data-state="closed"] { transform: translateX(calc(-4px * var(--alt-travel))) scale(calc(1 - 0.04 * var(--alt-travel))); }
928
1081
  .alt-tip[data-warm] { transition-duration: var(--dur-travel-1); }
929
1082
 
1083
+ /* On screen at any width (#248): a panel is capped at the viewport's width, and useFitFloating shifts it sideways to
1084
+ stay 8px clear of the edges when it opens. */
930
1085
  .alt-pop {
931
- top: calc(100% + 8px); left: 0; transform-origin: 0 0; min-width: 220px; padding: var(--space-4);
1086
+ 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);
932
1087
  border-radius: var(--radius-lg); background: var(--surface-floating); box-shadow: var(--elev-3); outline: none;
933
1088
  }
934
1089
  .alt-pop[data-align="end"] { left: auto; right: 0; transform-origin: 100% 0; }
@@ -938,14 +1093,14 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
938
1093
  .alt-pop[data-state="closed"] { transform: translateY(calc(-6px * var(--alt-travel))) scale(calc(1 - 0.04 * var(--alt-travel))); }
939
1094
  .alt-pop[data-side="top"][data-state="closed"] { transform: translateY(calc(6px * var(--alt-travel))) scale(calc(1 - 0.04 * var(--alt-travel))); }
940
1095
 
941
- .alt-menu { padding: var(--space-1); min-width: 228px; }
1096
+ .alt-menu { padding: var(--space-1); min-width: min(228px, calc(100vw - 16px)); }
942
1097
  .alt-menu-hl {
943
1098
  position: absolute; top: 0; left: var(--space-1); right: var(--space-1); border-radius: var(--radius-sm); background: var(--accent-soft);
944
1099
  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);
945
1100
  }
946
1101
  .alt-menu-hl[data-tone="critical"] { background: var(--critical-soft); }
947
1102
  .alt-menu-item, .alt-menu-label {
948
- position: relative; display: flex; align-items: center; gap: 10px; min-height: 34px; padding: 0 10px; border-radius: var(--radius-sm);
1103
+ 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);
949
1104
  font: 500 14px/20px var(--font-sans); color: var(--ink); cursor: pointer; user-select: none;
950
1105
  opacity: 0; transform: translateY(calc(-3px * var(--alt-travel)));
951
1106
  transition: opacity var(--dur-travel-2) var(--ease-rise), transform var(--dur-travel-2) var(--ease-rise);
@@ -961,14 +1116,15 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
961
1116
  .alt-menu-sep { height: 1px; margin: var(--space-1) 6px; background: var(--line); }
962
1117
 
963
1118
  /* ── Dialog: level 5. The page recedes, the scrim veils it, the dialog rises with its shadow growing. ── */
964
- .alt-dialog-root { position: fixed; inset: 0; z-index: var(--z-modal); display: grid; place-items: center; padding: var(--space-6); }
1119
+ /* The modal roots cover the visible viewport (#251), not the largest one, so a footer is never under the keyboard. */
1120
+ .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)); }
965
1121
  .alt-scrim {
966
- position: absolute; inset: 0; background: var(--scrim); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
1122
+ 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 */
967
1123
  transition: opacity var(--dur-travel-5) var(--ease-rise);
968
1124
  }
969
1125
  .alt-dialog {
970
- position: relative; width: min(100%, 460px); max-height: calc(100vh - 48px); overflow: auto; outline: none;
971
- padding: var(--space-6); border-radius: var(--radius-xl); background: var(--surface-modal); box-shadow: var(--elev-5); color: var(--ink);
1126
+ 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;
1127
+ padding: var(--alt-inset-page); border-radius: var(--radius-xl); background: var(--surface-modal); box-shadow: var(--elev-5); color: var(--ink);
972
1128
  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);
973
1129
  }
974
1130
  .alt-dialog[data-size="sm"] { width: min(100%, 380px); }
@@ -989,10 +1145,16 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
989
1145
  .alt-dialog-close { position: absolute; top: var(--space-4); right: var(--space-4); color: var(--ink-muted); }
990
1146
 
991
1147
  /* ── Toast: the stack is a depth stack. Newest in front at level 4; older ones sink behind it. ── */
1148
+ /* Safe areas (#252): every layer fixed to the screen's edge adds env(safe-area-inset-*) to its offset, so in an
1149
+ edge-to-edge page (viewport-fit=cover) it clears the notch, the rounded corners and the home indicator. Without
1150
+ viewport-fit=cover env() is 0 and nothing moves. The mode frame stays at the physical edge: it frames the screen. */
992
1151
  .alt-toaster {
993
- position: fixed; z-index: var(--z-toast); right: var(--space-6); bottom: var(--space-6); width: min(360px, calc(100vw - 32px));
1152
+ 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)));
994
1153
  transition: height var(--dur-travel-4) var(--ease-rise);
995
1154
  }
1155
+ /* On touch (#253): a tap opens the stack and holds it (timers paused) until a tap outside; a horizontal swipe dismisses
1156
+ a toast, so the toast lets vertical scrolling through. */
1157
+ .alt-toast { touch-action: pan-y; }
996
1158
  .alt-toast {
997
1159
  position: absolute; right: 0; bottom: 0; width: 100%; display: flex; align-items: flex-start; gap: var(--space-3);
998
1160
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4); border-radius: var(--radius-lg);
@@ -1013,7 +1175,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1013
1175
  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);
1014
1176
  background: none; color: var(--ink-muted); cursor: pointer; transition: background-color var(--dur-travel-1), color var(--dur-travel-1);
1015
1177
  }
1016
- .alt-toast-close:hover { background: var(--surface-well); color: var(--ink); }
1178
+ @media (hover: hover) { .alt-toast-close:hover { background: var(--surface-well); color: var(--ink); } }
1017
1179
 
1018
1180
 
1019
1181
  /* ════════════════════ Depth signals: what height can tell you ════════════════════ */
@@ -1077,7 +1239,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1077
1239
  /* ── HoldButton: holding pushes it through the ground; the fuse along the bottom shows how far. ── */
1078
1240
  .alt-hold { overflow: visible; }
1079
1241
  .alt-hold .alt-btn-label { position: static; }
1080
- .alt-hold-text { position: relative; }
1242
+ /* All of its labels share one grid cell and only the current one is visible, so the button keeps the width of its
1243
+ longest label: holding and completing never change its size or move what's beside it. */
1244
+ .alt-hold-text { position: relative; display: inline-grid; justify-items: center; }
1245
+ .alt-hold-label { grid-area: 1 / 1; display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
1246
+ .alt-hold-label:not([data-shown]) { visibility: hidden; }
1081
1247
  .alt-hold-fill {
1082
1248
  position: absolute; left: 8px; right: 8px; bottom: 3px; height: 2px; border-radius: var(--radius-full); background: currentColor; opacity: 0.9;
1083
1249
  transform: scaleX(0); transform-origin: left; transition: transform var(--dur-travel-2) var(--ease-sink);
@@ -1089,13 +1255,1366 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1089
1255
  .alt-hold[data-hold="holding"] .alt-hold-fill { transform: scaleX(1); transition: transform var(--alt-hold-dur) linear; }
1090
1256
  .alt-hold[data-hold="done"]:not([data-final]) { animation: alt-land-a calc(var(--dur-travel-3) * 1.6) var(--ease-settle); }
1091
1257
  /* 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. */
1092
- .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 {
1258
+ .alt-btn.alt-hold[data-final], .alt-btn.alt-hold[data-final]:active, .alt-btn.alt-hold[data-final]:focus-visible {
1093
1259
  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;
1094
1260
  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);
1095
1261
  }
1262
+ @media (hover: hover) { .alt-btn.alt-hold[data-final]:hover {
1263
+ 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;
1264
+ 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);
1265
+ } }
1096
1266
 
1097
1267
  .alt-hold[data-final] .alt-hold-fill { opacity: 0; }
1098
1268
 
1269
+ /* ── Separator: a divider is part of the page, so it's 1px of line on the ground: no shadow, no wall, never
1270
+ line-control (that's for the edges of controls). A vertical one stretches to its row. A labelled one ("or") is the
1271
+ word in ink-muted between two lines; its lines are drawn, so only the word is read. ── */
1272
+ .alt-sep { flex: none; margin: 0; border: 0; background: var(--line); }
1273
+ .alt-sep[data-orientation="horizontal"] { width: 100%; height: 1px; }
1274
+ .alt-sep[data-orientation="vertical"] { display: inline-block; width: 1px; min-height: 1em; align-self: stretch; vertical-align: middle; }
1275
+ /* A card's header has no bottom padding (its content brings the gap), so a separator under it brings the same gap. */
1276
+ .alt-card-header + .alt-sep { margin-top: var(--space-4); }
1277
+ .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); }
1278
+ .alt-sep-labelled[data-orientation="vertical"] { flex-direction: column; min-height: 4em; }
1279
+ .alt-sep-labelled::before, .alt-sep-labelled::after { content: ""; flex: 1; min-width: 1px; min-height: 1px; background: var(--line); }
1280
+ @media (prefers-contrast: more) { .alt-sep:not(.alt-sep-labelled), .alt-sep-labelled::before, .alt-sep-labelled::after { background: var(--line-control); } }
1281
+ [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); }
1282
+ @media (forced-colors: active) { .alt-sep:not(.alt-sep-labelled), .alt-sep-labelled::before, .alt-sep-labelled::after { background: CanvasText; forced-color-adjust: none; } }
1283
+
1284
+ /* ── Link: navigation is text in the record, so it never lifts: lifting a word would break its line. It's accent-ink
1285
+ with an underline (never colour alone); hover thickens the underline, focus is the 2px accent-ink ring. An external
1286
+ link ends with an icon and says it opens a new tab. ── */
1287
+ .alt-link {
1288
+ color: var(--accent-ink); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.2em;
1289
+ border-radius: var(--radius-xs); cursor: pointer; transition: text-decoration-thickness var(--dur-travel-1) var(--ease-rise);
1290
+ }
1291
+ @media (hover: hover) { .alt-link:hover { text-decoration-thickness: 2px; } }
1292
+ .alt-link:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1293
+ .alt-link[data-visited]:visited { text-decoration-color: var(--ink-muted); }
1294
+ .alt-link-tail { white-space: nowrap; }
1295
+ .alt-link-ext { display: inline-block; margin-left: 0.2em; vertical-align: -0.1em; }
1296
+ @media (prefers-contrast: more) { .alt-link { text-decoration-thickness: 2px; } }
1297
+ [data-contrast="more"] .alt-link { text-decoration-thickness: 2px; }
1298
+ @media (forced-colors: active) { .alt-link { color: LinkText; } .alt-link:focus-visible { outline-color: Highlight; } }
1299
+
1300
+ /* ── FormattedNumber: a number is the record, so it lies flush in its sentence or cell, in tabular figures. A changed
1301
+ value cross-fades (the old figure fading out over the new) and takes a ground-level tint that decays over dur-settle:
1302
+ the afterglow without the lift, because a word in a line can't rise. It never counts. ── */
1303
+ .alt-num { display: inline-grid; padding: 0 3px; margin: 0 -3px; border-radius: var(--radius-xs); font-variant-numeric: tabular-nums; white-space: nowrap; }
1304
+ .alt-num-text { grid-area: 1 / 1; }
1305
+ .alt-num-text[data-in] { animation: alt-stat-in var(--dur-travel-2) var(--ease-glide) both; }
1306
+ .alt-num-text[data-out] { animation: alt-stat-out var(--dur-travel-2) var(--ease-glide) both; }
1307
+ .alt-num[data-settle]::before { animation-name: alt-chart-tint-a; }
1308
+ .alt-num[data-settle="b"]::before { animation-name: alt-chart-tint-b; }
1309
+ @media (prefers-reduced-motion: reduce) { .alt-num-text[data-in] { animation: none; } .alt-num-text[data-out] { display: none; } }
1310
+ [data-motion="reduced"] .alt-num-text[data-in] { animation: none; }
1311
+ [data-motion="reduced"] .alt-num-text[data-out] { display: none; }
1312
+ @media (forced-colors: active) { .alt-num[data-settle]::before { border: 1px solid Highlight; } }
1313
+
1314
+ /* ── RelativeTime: a timestamp is part of the record, so it's plain text on the ground. Ticking forward ("3 min" to
1315
+ "4 min") isn't a change to the record and gets nothing; a new moment (the record edited again) takes the same
1316
+ ground-level tint as a changed number. It's focusable for its Tooltip, so it carries the focus ring. ── */
1317
+ .alt-reltime { padding: 0 2px; border-radius: var(--radius-xs); font-variant-numeric: tabular-nums; }
1318
+ .alt-reltime:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 0; }
1319
+ .alt-reltime[data-settle]::before { animation-name: alt-chart-tint-a; }
1320
+ .alt-reltime[data-settle="b"]::before { animation-name: alt-chart-tint-b; }
1321
+ @media (forced-colors: active) { .alt-reltime[data-settle]::before { border: 1px solid Highlight; } .alt-reltime:focus-visible { outline-color: Highlight; } }
1322
+
1323
+ /* ── CopyButton: copying is a press that confirms in words. It's a Button (ghost by default); once copied it stays
1324
+ pressed to the ground for dur-linger, its icon cross-fades from copy to a check and its words to "Copied" (all its
1325
+ words share one cell, so its width never changes). No rise and no afterglow: nothing new arrived on the page. A
1326
+ failure says so in words, with the way to copy by hand announced. ── */
1327
+ .alt-copy-icons, .alt-copy-words { display: inline-grid; place-items: center; }
1328
+ .alt-copy-icons > svg, .alt-copy-words > span { grid-area: 1 / 1; transition: opacity var(--dur-travel-1) var(--ease-rise); }
1329
+ .alt-copy-icons > svg:not([data-shown]), .alt-copy-words > span:not([data-shown]) { opacity: 0; visibility: hidden; }
1330
+ .alt-copy-words > span { white-space: nowrap; }
1331
+ .alt-btn.alt-copy[data-copy="copied"], .alt-btn.alt-copy[data-copy="copied"]:focus-visible {
1332
+ box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); background-image: none; transform: translateY(var(--wall));
1333
+ }
1334
+ @media (hover: hover) { .alt-btn.alt-copy[data-copy="copied"]:hover {
1335
+ box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); background-image: none; transform: translateY(var(--wall));
1336
+ } }
1337
+ .alt-btn.alt-copy[data-variant="ghost"][data-copy="copied"] { background: transparent; }
1338
+ .alt-btn.alt-copy[data-copy="failed"] .alt-copy-icons { color: var(--critical); }
1339
+ @media (forced-colors: active) { .alt-btn.alt-copy:hover:not([data-copy="copied"]) { border: 1px solid ButtonText; } }
1340
+
1341
+ /* ── Toggle and ToggleGroup: a button that stays on. On is a choice, so it's built like the Switch, not like a held
1342
+ press (which would read as loading): the face is cut into a shallow well with accent-soft poured in and the label in
1343
+ accent-ink. Off is the ghost (ground) or secondary (level 1) slab it started as. Pointing at an on toggle previews
1344
+ off: the well lightens toward the raised face it will become. Sinking uses ease-sink, rising ease-rise.
1345
+ type="single" is segmented Tabs without panels: options in a well, one raised pill that glides (the same classes).
1346
+ type="multiple" is a row of Toggles. ── */
1347
+ .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) {
1348
+ --wall: 0px; background: var(--accent-soft); background-image: none; color: var(--accent-ink); box-shadow: var(--well); transform: none;
1349
+ 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);
1350
+ }
1351
+ @media (hover: hover) { .alt-btn.alt-toggle[aria-pressed="true"]:hover:not(:disabled):not([data-loading]) {
1352
+ --wall: 0px; background: var(--accent-soft); background-image: none; color: var(--accent-ink); box-shadow: var(--well); transform: none;
1353
+ 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);
1354
+ } }
1355
+ @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)); } }
1356
+ .alt-btn.alt-toggle[aria-pressed="true"]:disabled { background: var(--surface-well); color: var(--ink-faint); }
1357
+ .alt-tgroup[data-type="multiple"] { display: inline-flex; align-items: center; gap: 2px; }
1358
+ .alt-tgroup[data-orientation="vertical"] { flex-direction: column; align-items: stretch; }
1359
+ .alt-tgroup[data-type="single"] .alt-tabs-indicator { transition: none; }
1360
+ .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); }
1361
+ @media (prefers-reduced-motion: reduce) { .alt-tgroup[data-type="single"] .alt-tabs-indicator[data-ready] { transition: opacity var(--dur-travel-1); } }
1362
+ [data-motion="reduced"] .alt-tgroup[data-type="single"] .alt-tabs-indicator[data-ready] { transition: opacity var(--dur-travel-1); }
1363
+ .alt-tgroup .alt-tabs-trigger:disabled { color: var(--ink-faint); cursor: not-allowed; }
1364
+ @media (forced-colors: active) {
1365
+ .alt-btn.alt-toggle[aria-pressed="true"] { border: 1px dashed CanvasText; background: Highlight; color: HighlightText; forced-color-adjust: none; }
1366
+ .alt-btn.alt-toggle[data-variant="secondary"][aria-pressed="false"] { border: 1px solid CanvasText; }
1367
+ }
1368
+
1369
+ /* ── Toolbar: the bar lies on the ground (a line rim when bordered); its tools are ghost buttons that lift one level
1370
+ when pointed at, and latched tools are Toggles. Groups are parted by decorative separators. With overflow="menu" the
1371
+ tools that don't fit are hidden from the end and offered in a "More tools" menu; they don't animate as they go. ── */
1372
+ .alt-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); min-width: 0; }
1373
+ .alt-toolbar[data-overflow="menu"] { flex-wrap: nowrap; }
1374
+ .alt-toolbar[data-orientation="vertical"] { flex-direction: column; align-items: stretch; }
1375
+ .alt-toolbar[data-bordered] { padding: var(--space-1); border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-ground); }
1376
+ .alt-toolbar > [data-overflowed] { display: none; }
1377
+ .alt-toolbar-group { display: inline-flex; align-items: center; gap: 2px; }
1378
+ .alt-toolbar[data-orientation="vertical"] .alt-toolbar-group { flex-direction: column; align-items: stretch; }
1379
+ .alt-toolbar .alt-sep[data-orientation="vertical"] { align-self: center; height: 20px; min-height: 0; margin-inline: var(--space-1); }
1380
+ .alt-toolbar[data-orientation="vertical"] .alt-sep[data-orientation="vertical"] { width: auto; height: 1px; margin: var(--space-1) 0; }
1381
+ .alt-toolbar-more { flex: none; margin-left: auto; }
1382
+ @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; } }
1383
+
1384
+ /* ── SplitButton: one action with its alternatives. Two faces on one slab, sharing one wall: the action and a chevron,
1385
+ parted by a 1px notch (line-control). Each face lifts on its own when pointed at, because each does something
1386
+ different, and each has its own focus ring around its face and wall. A loading action is held pressed while the
1387
+ chevron stays usable. ── */
1388
+ .alt-split { display: inline-flex; align-items: stretch; isolation: isolate; }
1389
+ .alt-split .alt-pop-anchor { display: inline-flex; }
1390
+ .alt-split .alt-split-action { border-top-right-radius: 0; border-bottom-right-radius: 0; }
1391
+ .alt-split .alt-split-menu { border-top-left-radius: 0; border-bottom-left-radius: 0; width: 32px; }
1392
+ .alt-split .alt-split-menu[data-size="sm"] { width: 26px; }
1393
+ .alt-split .alt-split-action::after { border-top-right-radius: 0; border-bottom-right-radius: 0; }
1394
+ .alt-split .alt-split-menu::after { border-top-left-radius: 0; border-bottom-left-radius: 0; }
1395
+ .alt-split .alt-split-menu::before { content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 1px; background: var(--line-control); pointer-events: none; }
1396
+ .alt-split[data-variant="primary"] .alt-split-menu::before { background: color-mix(in oklab, var(--on-accent) 45%, var(--accent)); }
1397
+ .alt-split .alt-split-action:focus-visible, .alt-split .alt-split-menu:focus-visible { z-index: 1; }
1398
+ /* One slab, so it moves as one: pointing at either face lifts both, and pressing either presses both, so the faces
1399
+ 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
1400
+ tint on that face alone, not by height. */
1401
+ @media (hover: hover) {
1402
+ .alt-split:has(:is(.alt-split-action, .alt-split-menu):hover:not(:disabled)) :is(.alt-split-action, .alt-split-menu):not(:disabled) {
1403
+ background: var(--alt-bg-up); background-image: var(--alt-sheen);
1404
+ box-shadow: 0 calc(var(--wall) + 1px) 0 0 var(--wall-c), var(--alt-sh-up); transform: translateY(calc(-1px * var(--alt-travel)));
1405
+ }
1406
+ .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%); }
1407
+ }
1408
+ .alt-split:has(:is(.alt-split-action, .alt-split-menu):focus-visible) :is(.alt-split-action, .alt-split-menu):not(:disabled) {
1409
+ 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)));
1410
+ }
1411
+ .alt-split:has(:is(.alt-split-action, .alt-split-menu):active:not(:disabled)) :is(.alt-split-action, .alt-split-menu):not(:disabled) {
1412
+ box-shadow: 0 0 0 0 var(--wall-c), var(--elev-0); background-image: none; transform: translateY(var(--wall));
1413
+ transition-duration: var(--dur-press); transition-timing-function: ease-out;
1414
+ }
1415
+ @media (forced-colors: active) { .alt-split .alt-split-menu::before { background: CanvasText; } }
1416
+
1417
+ /* ── NumberInput: an opening that fills, like Input, with two small raised steppers in its end cap: you press them,
1418
+ 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
1419
+ limit. The unit sits beside the number in ink-muted. Editing isn't a change to the record, so nothing glows. ── */
1420
+ .alt-numfield { padding-right: 3px; }
1421
+ .alt-numfield input { font-variant-numeric: tabular-nums; }
1422
+ .alt-numfield-unit { font: 400 14px/20px var(--font-sans); }
1423
+ /* Side by side, 24×24 each (#261): stacked, two 15px steppers could never each reach the 24px minimum. Down first. */
1424
+ .alt-numfield-steps { display: flex; flex-direction: row-reverse; gap: 2px; flex: none; align-self: center; }
1425
+ .alt-numfield-step {
1426
+ display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: var(--radius-xs); cursor: pointer;
1427
+ background: var(--surface-raised); background-image: var(--alt-sheen); color: var(--ink-muted);
1428
+ box-shadow: 0 1px 0 0 var(--surface-trench), var(--elev-1);
1429
+ 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);
1430
+ }
1431
+ .alt-numfield[data-size="sm"] .alt-numfield-step { height: 24px; }
1432
+ @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))); } }
1433
+ .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); }
1434
+ .alt-numfield-step:disabled { background: transparent; background-image: none; box-shadow: none; color: var(--ink-faint); cursor: default; }
1435
+ [data-contrast="more"] .alt-numfield-step:not(:disabled) { box-shadow: 0 1px 0 0 var(--ink), var(--elev-1); }
1436
+ @media (forced-colors: active) { .alt-numfield-step:not(:disabled) { border: 1px solid ButtonText; } }
1437
+
1438
+ /* ── InputOTP: a one-time code as a row of openings. One real input lies invisible over the row and drives it; each
1439
+ slot is built like an Input (the same well, wall and floor): empty, its back wall shows; the slot the next character
1440
+ goes into is held (floor lit, the depth approach would preview, the neutral rim when reached by keyboard) with a
1441
+ caret; once its character lands its floor rises flush. A pasted or autofilled code fills left to right, staggered
1442
+ 35ms a slot. A rejected code drops the whole row to a trench on critical-soft. ── */
1443
+ .alt-otp { position: relative; display: inline-flex; align-items: center; gap: clamp(4px, 2vw, var(--space-2)); max-width: 100%; min-width: 0; }
1444
+ .alt-otp-input {
1445
+ position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; outline: 0; opacity: 0;
1446
+ background: transparent; color: transparent; caret-color: transparent; font-size: 16px; cursor: text;
1447
+ }
1448
+ .alt-otp-input:disabled { cursor: not-allowed; }
1449
+ /* Narrow (#266): slots give way from 40px to 28px, and gaps tighten, so an eight-character code fits a 320px screen. */
1450
+ .alt-otp-group { display: inline-flex; gap: clamp(3px, 1.6vw, 6px); min-width: 0; }
1451
+ .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; }
1452
+ .alt-otp-slot[data-filled]:not([data-invalid]):not([data-disabled]) { --w: 0px; --n: 0; background: var(--surface-raised); }
1453
+ .alt-otp-slot[data-active]:not([data-invalid]) { background: var(--surface-raised); --n: var(--alt-travel); }
1454
+ .alt-otp[data-key-focus] .alt-otp-slot[data-active] { border-color: var(--alt-rim-held); outline-color: var(--alt-rim-held); }
1455
+ .alt-otp[data-key-focus] .alt-otp-slot[data-active][data-invalid] { border-color: var(--critical); outline-color: var(--critical); }
1456
+ .alt-otp-char { transform: translateY(calc(var(--wd) * 0.5)); transition: transform var(--dur-travel-2) var(--ease-settle); }
1457
+ .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; }
1458
+ @keyframes alt-otp-blink { 50% { opacity: 0; } }
1459
+ @media (prefers-reduced-motion: reduce) { .alt-otp-caret { animation: none; } }
1460
+ [data-motion="reduced"] .alt-otp-caret { animation: none; }
1461
+ .alt-otp[data-burst] .alt-otp-slot { transition-delay: calc(var(--i) * 35ms); }
1462
+ .alt-otp-sep { flex: none; width: 10px; height: 2px; border-radius: var(--radius-full); background: var(--line-control); }
1463
+ @media (forced-colors: active) { .alt-otp-slot[data-filled]:not([data-invalid]) { border-style: solid !important; } .alt-otp-caret { background: CanvasText; } }
1464
+
1465
+ /* ── TimeInput: a time of day typed by segment, built like DatePicker's field (the same segments and well). It's an
1466
+ opening that fills only when every segment holds a value: a partial time ("09:––") is still a hole. A time zone's
1467
+ short name sits after it in ink-muted, and is spoken with it. ── */
1468
+ .alt-time { width: fit-content; min-width: 132px; max-width: 100%; }
1469
+ .alt-time-icon { margin-left: -2px; }
1470
+ .alt-time-gap { display: inline-block; width: 0.35em; }
1471
+ .alt-time-zone { font: 400 13px/20px var(--font-sans); }
1472
+
1473
+ /* ── PasswordInput: an Input with a reveal Toggle in its end cap (sunk and filled while the password shows). Creating
1474
+ a password, its strength is a reading, so it's a Meter (a flush track with flat ink), never Progress's raised fill,
1475
+ with the word beside its label; each requirement is stamped as met, like a done Checklist item, muted with a check.
1476
+ Caps Lock is a caption with an icon. ── */
1477
+ .alt-pw { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
1478
+ .alt-pw .alt-input { padding-right: 4px; }
1479
+ .alt-pw-reveal.alt-btn[data-square][data-size="sm"] { width: var(--size-control-sm); height: var(--size-control-sm); color: var(--ink-muted); }
1480
+ .alt-pw-caps { display: flex; align-items: center; gap: 6px; margin: 0; font: 500 12.5px/17px var(--font-sans); color: var(--ink); }
1481
+ .alt-pw-caps svg { flex: none; color: var(--caution); }
1482
+ .alt-pw-strength .alt-meter-label { display: inline-flex; gap: 8px; }
1483
+ .alt-pw-word { font-weight: 600; color: var(--ink); }
1484
+ .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); }
1485
+ .alt-pw-reqs li { display: flex; align-items: center; gap: 8px; transition: color var(--dur-travel-1) var(--ease-rise); }
1486
+ .alt-pw-reqs li[data-met] { color: var(--ink-muted); }
1487
+ .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); }
1488
+ .alt-pw-reqs li[data-met] .alt-pw-stamp { border-color: transparent; background: var(--surface-well); box-shadow: var(--well); }
1489
+ @media (forced-colors: active) { .alt-pw-stamp { border-color: CanvasText; } .alt-pw-caps svg { color: CanvasText; } }
1490
+
1491
+ /* ── SecretField: two readings. A new secret is an opening, something to take away: a sunk well (its back wall
1492
+ showing, though it holds text) in mono, with a raised Copy beside it and a caution warning, until you confirm you've
1493
+ saved it; then its floor rises flush and it masks to its tail. A stored secret is settled: flush, masked, with a
1494
+ reveal Toggle (if allowed), Rotate and a held revoke. ── */
1495
+ .alt-secret { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
1496
+ .alt-secret-row { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
1497
+ .alt-secret-field { flex: 1; min-width: 0; cursor: default; }
1498
+ .alt-input.alt-secret-field[data-open] { --w: 4px; background: var(--surface-well); }
1499
+ .alt-input.alt-secret-field[data-filled] { border-color: var(--line); }
1500
+ .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; }
1501
+ .alt-input input.alt-secret-value { font: 400 13.5px/20px var(--font-mono); cursor: text; }
1502
+ .alt-secret-dots { letter-spacing: 0.1em; color: var(--ink-muted); }
1503
+ .alt-secret-warning { display: flex; align-items: flex-start; gap: 6px; margin: 0; font: 500 12.5px/17px var(--font-sans); color: var(--ink); }
1504
+ .alt-secret-warning svg { flex: none; margin-top: 1.5px; color: var(--caution); }
1505
+ .alt-secret-saved:focus { outline: none; }
1506
+ .alt-secret-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
1507
+ @media (forced-colors: active) { .alt-input.alt-secret-field[data-filled] { border-style: solid !important; } .alt-secret-warning svg { color: CanvasText; } }
1508
+
1509
+ /* ── CodeBlock: code shown to read and copy is the record, so it lies flush on the ground: a 1px line rim, mono type,
1510
+ no shadow. The header carries its name, a segmented switch between forms (npm, pnpm) and a ghost CopyButton.
1511
+ Highlighted lines are accent-soft bands behind the text; line numbers sit in an ink-faint gutter. Code still
1512
+ arriving has a dashed line-control rule down its left and says "Writing…". Syntax colours are yours (ink, ink-muted
1513
+ and accent-ink by default, never the data palette) and carry nothing on their own. ── */
1514
+ .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; }
1515
+ .alt-code[data-streaming] { border-left: 2px dashed var(--line-control); }
1516
+ .alt-code-tabs { gap: 0; }
1517
+ .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); }
1518
+ .alt-code-head .alt-tabs-list { padding: 2px; }
1519
+ .alt-code-head .alt-tabs-trigger { height: 26px; padding: 0 var(--space-2); font-size: 13px; }
1520
+ .alt-code-title { font: 500 13px/18px var(--font-sans); color: var(--ink); }
1521
+ .alt-code-title[data-language] { font-family: var(--font-mono); color: var(--ink-muted); }
1522
+ .alt-code-tools { display: inline-flex; align-items: center; gap: var(--space-2); margin-left: auto; }
1523
+ .alt-code-writing { font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); }
1524
+ .alt-code-panel { border-radius: 0; animation-duration: var(--dur-travel-1); --alt-from-y: 0px; }
1525
+ /* Scrolling in the page chains: a region on the ground (CodeBlock, ScrollArea, Table, Conversation, Terminal,
1526
+ streaming reasoning) hands the wheel or swipe to the page once it reaches its edge, so it never traps you. Only layers
1527
+ above the page (the CommandPalette list, Sheet, a Select's list, a menu as a sheet) contain their overscroll, since
1528
+ the page under them shouldn't move. */
1529
+ .alt-code-scroll { overflow: auto; }
1530
+ .alt-code-scroll:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1531
+ .alt-code-grid { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); min-width: max-content; padding: var(--space-3) 0; }
1532
+ .alt-code-grid[data-numbers] { grid-template-columns: auto minmax(0, 1fr); }
1533
+ .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); }
1534
+ .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; }
1535
+ .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; }
1536
+ .alt-code-grid[data-numbers] .alt-code-pre { padding-left: 0; }
1537
+ .alt-code-pre code { font: inherit; }
1538
+ .alt-code .tok-comment, .alt-code .token.comment, .alt-code .hljs-comment { color: var(--ink-muted); font-style: italic; }
1539
+ .alt-code .tok-keyword, .alt-code .token.keyword, .alt-code .hljs-keyword { color: var(--accent-ink); }
1540
+ .alt-code .tok-string, .alt-code .token.string, .alt-code .hljs-string { color: var(--ink); }
1541
+ @media (prefers-contrast: more) { .alt-code { border-color: var(--line-control); } }
1542
+ [data-contrast="more"] .alt-code { border-color: var(--line-control); }
1543
+ @media (forced-colors: active) { .alt-code { border: 1px solid CanvasText; } .alt-code-band { background: Mark; forced-color-adjust: none; box-shadow: none; } }
1544
+
1545
+ /* ── ColorPicker: colours as choices, built like RadioGroup. Each swatch is a trench (line-control rim) the colour
1546
+ pours into; the chosen one carries the raised white mark, one mark that travels between swatches on ease-glide.
1547
+ Pointing at another swatch shows a ghost of the mark (feedforward). The hex field is an Input that fills only when
1548
+ it holds a whole colour. The full picker floats in a Popover: the area and the hue strip are wells, their handles
1549
+ raised. A contrast check is a Badge plate that says in words whether text on the colour passes. ── */
1550
+ .alt-color { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
1551
+ .alt-swatches { position: relative; display: flex; flex-wrap: wrap; gap: 10px; }
1552
+ .alt-swatch {
1553
+ position: relative; flex: none; width: 28px; height: 28px; padding: 0; border: 1px solid var(--line-control); border-radius: var(--radius-full);
1554
+ background: var(--surface-trench); box-shadow: var(--trench); cursor: pointer; -webkit-tap-highlight-color: transparent;
1555
+ }
1556
+ .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); }
1557
+ .alt-swatch:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1558
+ .alt-swatch::after {
1559
+ content: ""; position: absolute; left: 50%; top: 50%; width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: var(--radius-full);
1560
+ background: var(--surface-raised); box-shadow: var(--elev-1); opacity: 0; scale: 0.5; pointer-events: none;
1561
+ transition: opacity var(--dur-travel-1) var(--ease-sink), scale var(--dur-travel-2) var(--ease-settle);
1562
+ }
1563
+ @media (hover: hover) { .alt-swatch:hover:not([aria-checked="true"])::after { opacity: 0.5; scale: 0.85; } }
1564
+ .alt-swatch-mark {
1565
+ 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;
1566
+ background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1);
1567
+ }
1568
+ .alt-swatch-mark[data-ready] { transition: transform var(--dur-travel-3) var(--ease-glide), opacity var(--dur-travel-1); }
1569
+ @media (prefers-reduced-motion: reduce) { .alt-swatch-mark[data-ready] { transition: opacity var(--dur-travel-1); } }
1570
+ [data-motion="reduced"] .alt-swatch-mark[data-ready] { transition: opacity var(--dur-travel-1); }
1571
+ .alt-color-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
1572
+ .alt-color-hex { width: 150px; }
1573
+ .alt-color-hex input { font-family: var(--font-mono); text-transform: lowercase; }
1574
+ .alt-input.alt-color-partial:has(input:not(:placeholder-shown)):not(:focus-within) { --w: 4px; background: var(--surface-well); }
1575
+ .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); }
1576
+ .alt-color-checks { display: flex; flex-wrap: wrap; gap: var(--space-2); }
1577
+ .alt-color-custom { display: flex; flex-direction: column; gap: 12px; width: 240px; max-width: 100%; padding: var(--space-3); }
1578
+ .alt-color-area, .alt-color-hue { position: relative; touch-action: none; cursor: crosshair; box-shadow: var(--well); }
1579
+ .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%)); }
1580
+ /* tells-ignore: CO1 the hue track is the whole spectrum, purple included */
1581
+ .alt-color-hue { height: 14px; border-radius: var(--radius-full); background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); }
1582
+ .alt-color-area:focus-visible, .alt-color-hue:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1583
+ .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; }
1584
+ [data-contrast="more"] .alt-swatch[aria-checked="true"] { outline: 2px solid var(--ink); outline-offset: 2px; }
1585
+ @media (forced-colors: active) {
1586
+ .alt-swatch-fill, .alt-color-area, .alt-color-hue, .alt-color-chip { forced-color-adjust: none; }
1587
+ .alt-swatch { border: 1px solid CanvasText; } .alt-swatch[aria-checked="true"] { border-width: 2px; } .alt-swatch-mark { border: 1px solid CanvasText; }
1588
+ }
1589
+
1590
+ /* ── Pagination: the page numbers sit in a trench (surface-well) and the current page is one raised thumb that
1591
+ travels to it on ease-glide, as RadioGroup's does. Pointing at another page shows a ghost of the thumb there. While
1592
+ the next page loads the thumb is held pressed. Previous and Next are ghost buttons, flush and faint at the ends. ── */
1593
+ .alt-pager { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); }
1594
+ .alt-pager-pages { display: inline-flex; align-items: center; gap: 2px; margin: 0; padding: 0; list-style: none; }
1595
+ .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); }
1596
+ .alt-pager-track li { display: flex; }
1597
+ .alt-pager-page {
1598
+ 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));
1599
+ background: none; color: var(--ink-muted); font: 500 13px/18px var(--font-sans); font-variant-numeric: tabular-nums; text-decoration: none; cursor: pointer;
1600
+ transition: color var(--dur-travel-1) var(--ease-rise);
1601
+ }
1602
+ .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); }
1603
+ @media (hover: hover) { .alt-pager-page:hover:not([aria-current])::after { opacity: 0.45; transition-timing-function: var(--ease-rise); } }
1604
+ .alt-pager-page[aria-current] { color: var(--ink); }
1605
+ @media (hover: hover) { .alt-pager-page:hover { color: var(--ink); } }
1606
+ .alt-pager-page[aria-current] { font-weight: 600; cursor: default; }
1607
+ .alt-pager-page:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1608
+ .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; }
1609
+ .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); }
1610
+ .alt-pager-thumb[data-loading] { box-shadow: var(--elev-0); background-image: none; }
1611
+ .alt-pager-gap { display: inline-grid; place-items: center; min-width: 24px; color: var(--ink-faint); font: 500 13px/18px var(--font-sans); }
1612
+ .alt-pager-step { gap: 4px; text-decoration: none; }
1613
+ .alt-pager-step[aria-disabled] { color: var(--ink-faint); cursor: default; pointer-events: none; }
1614
+ .alt-pager-summary { font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
1615
+ @media (max-width: 480px) { .alt-pager-step-text { display: none; } }
1616
+ @media (prefers-reduced-motion: reduce) { .alt-pager-thumb[data-ready] { transition: opacity var(--dur-travel-1); } }
1617
+ [data-motion="reduced"] .alt-pager-thumb[data-ready] { transition: opacity var(--dur-travel-1); }
1618
+ [data-contrast="more"] .alt-pager-thumb { box-shadow: 0 2px 0 0 var(--ink), var(--elev-1); }
1619
+ @media (forced-colors: active) { .alt-pager-track { border: 1px dashed CanvasText; } .alt-pager-thumb { border: 1px solid CanvasText; } }
1620
+
1621
+ /* ── PageHeader: the page's name is the record, so the block lies on the ground; only its actions are live, with one
1622
+ slot for the primary (level 2) and the rest secondary or ghost. Sticky, it rises one level (surface-raised, elev-1)
1623
+ when content passes under it, as ScrollArea's header does. ── */
1624
+ /* --alt-page-pad is the page's side padding (AppShell sets it): the header reaches through it, so a sticky header's
1625
+ rise spans the page edge to edge. */
1626
+ .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); }
1627
+ .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); }
1628
+ .alt-page-head[data-under] { background: var(--surface-raised); box-shadow: var(--elev-1); }
1629
+ .alt-page-head-sentinel { display: block; height: 1px; margin-bottom: -1px; }
1630
+ .alt-page-head-crumbs .alt-crumbs ol { padding-top: 6px; padding-bottom: 0; }
1631
+ .alt-page-head-main { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--space-3) var(--space-5); }
1632
+ .alt-page-head-text { display: flex; flex-direction: column; gap: var(--space-1); min-width: min(100%, 240px); flex: 1; }
1633
+ .alt-page-head-title { margin: 0; font: 600 24px/30px var(--font-sans); letter-spacing: -0.015em; color: var(--ink); }
1634
+ .alt-page-head-desc { margin: 0; font: 400 14px/21px var(--font-sans); color: var(--ink-muted); max-width: 70ch; }
1635
+ .alt-page-head-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
1636
+ .alt-page-head-tabs .alt-tabs { gap: 0; }
1637
+ @media (forced-colors: active) { .alt-page-head[data-under] { border-bottom: 1px solid CanvasText; } }
1638
+
1639
+ /* ── AppShell and Sidebar: where you are is part of the record, so the frame lies on the ground: sidebar, top bar
1640
+ and page on surface-ground, divided by line. Only what acts is live: a destination lifts one level when pointed at,
1641
+ and the current one sits on a raised pill (segmented Tabs' build) that glides between destinations. Only a count
1642
+ floats. Collapsing to a rail changes width on ease-glide, not level. The top bar rises one level while content
1643
+ passes under it. On narrow screens the sidebar is a Sheet drawer. ── */
1644
+ .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); }
1645
+ .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%); }
1646
+ .alt-skip:focus { transform: none; outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1647
+ .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); }
1648
+ .alt-sidebar[data-rail] { width: var(--alt-rail-w); }
1649
+ .alt-sidebar-drawer .alt-sidebar { width: auto; border: 0; background: none; margin-inline: calc(-1 * var(--space-2)); }
1650
+ .alt-sidebar-header, .alt-sidebar-footer { flex: none; padding: var(--space-2); }
1651
+ .alt-sidebar-header { border-bottom: 1px solid var(--line); }
1652
+ .alt-sidebar-footer { border-top: 1px solid var(--line); }
1653
+ .alt-sidebar-content { position: relative; flex: 1; min-height: 0; overflow: auto; padding: var(--space-2); }
1654
+ .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; }
1655
+ .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); }
1656
+ .alt-sidebar-group + .alt-sidebar-group { margin-top: var(--space-3); }
1657
+ .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; }
1658
+ button.alt-sidebar-label { cursor: pointer; border-radius: var(--radius-sm); }
1659
+ button.alt-sidebar-label:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1660
+ .alt-sidebar-chev { margin-left: auto; transition: transform var(--dur-travel-2) var(--ease-settle); }
1661
+ .alt-sidebar-label[aria-expanded="false"] .alt-sidebar-chev { transform: rotate(-90deg); }
1662
+ .alt-sidebar[data-rail] .alt-sidebar-label { visibility: hidden; height: 12px; }
1663
+ .alt-sidebar-menu { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
1664
+ .alt-sidebar-item { position: relative; display: flex; align-items: center; }
1665
+ .alt-sidebar-button {
1666
+ 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);
1667
+ font: 500 14px/20px var(--font-sans); color: var(--ink-muted); text-decoration: none; white-space: nowrap; cursor: pointer;
1668
+ transition: background-color var(--dur-travel-1) var(--ease-rise), box-shadow var(--dur-travel-1) var(--ease-rise), color var(--dur-travel-1);
1669
+ }
1670
+ @media (hover: hover) { .alt-sidebar-button:hover:not([aria-current]) { background: var(--surface-raised); box-shadow: var(--elev-1); color: var(--ink); } }
1671
+ .alt-sidebar-button[aria-current] { color: var(--ink); font-weight: 600; }
1672
+ .alt-sidebar-button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1673
+ .alt-sidebar-icon { display: inline-flex; flex: none; width: 20px; justify-content: center; }
1674
+ .alt-sidebar-text { overflow: hidden; text-overflow: ellipsis; transition: opacity var(--dur-travel-2) var(--ease-rise); }
1675
+ /* Artifact (#271): what an agent is making, in a pane on the ground beside the conversation. Being written isn't the
1676
+ record yet: a dashed line-control rule at the pane's edge; complete goes solid with one afterglow; failed, critical.
1677
+ Viewing an earlier version is a mode, so it's a flush strip with a line-control border, not a lift. The reference in
1678
+ a message is a plate that acts. */
1679
+ .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; }
1680
+ .alt-artifact[data-status="writing"] { border-left-style: dashed; border-left-color: var(--line-control); }
1681
+ .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); }
1682
+ .alt-artifact-titles { flex: 1; min-width: 0; }
1683
+ .alt-artifact-title { margin: 0; font: 600 15px/21px var(--font-sans); color: var(--ink); overflow-wrap: anywhere; outline: none; }
1684
+ .alt-artifact-title:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--radius-sm); }
1685
+ .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); }
1686
+ .alt-artifact-state[data-tone="critical"] { color: var(--critical); }
1687
+ .alt-artifact-tools { display: inline-flex; gap: 2px; }
1688
+ .alt-artifact-versions { display: flex; align-items: center; gap: 2px; padding: 4px var(--space-3); }
1689
+ .alt-artifact-version { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
1690
+ .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); }
1691
+ .alt-artifact-earlier > span { margin-right: auto; }
1692
+ .alt-artifact-views { display: flex; flex-direction: column; flex: 1; min-height: 0; }
1693
+ .alt-artifact-views > .alt-tabs-list { align-self: flex-start; margin: var(--space-2) var(--space-3); }
1694
+ .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; }
1695
+ .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); }
1696
+ .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;
1697
+ 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);
1698
+ 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); }
1699
+ @media (hover: hover) { .alt-artifact-ref:hover { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
1700
+ .alt-artifact-ref:active { transform: none; box-shadow: none; transition-duration: var(--dur-press); }
1701
+ .alt-artifact-ref:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1702
+ .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); }
1703
+ .alt-artifact-ref-text { display: flex; flex-direction: column; min-width: 0; }
1704
+ .alt-artifact-ref-title { font: 600 13.5px/19px var(--font-sans); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1705
+ .alt-artifact-ref-meta { font: 400 12px/16px var(--font-sans); color: var(--ink-muted); }
1706
+ @media (prefers-reduced-motion: reduce) { .alt-artifact-body { animation: none; } }
1707
+ [data-contrast="more"] .alt-artifact { border-left-color: var(--line-control); }
1708
+ @media (forced-colors: active) {
1709
+ .alt-artifact { border-left-color: CanvasText; }
1710
+ .alt-artifact[data-status="writing"] { border-left-style: dotted; }
1711
+ .alt-artifact-ref { border: 1px solid CanvasText; }
1712
+ }
1713
+ /* ThreadList (#269): conversations as Sidebar destinations, each with its state in words. Working isn't pending, so it
1714
+ 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).
1715
+ Unread is a dot and weight 600. Renaming opens a well in the row; deleting leaves a receipt in its slot. */
1716
+ .alt-threads { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
1717
+ .alt-threads-search { margin: 0 var(--space-2); }
1718
+ .alt-thread .alt-thread-link { height: auto; min-height: 44px; padding-block: 5px; align-items: flex-start; }
1719
+ .alt-thread .alt-sidebar-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; width: 100%; }
1720
+ .alt-thread-main { display: flex; align-items: center; gap: 6px; min-width: 0; }
1721
+ .alt-thread-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1722
+ .alt-thread[data-unread] .alt-thread-title { font-weight: 600; color: var(--ink); }
1723
+ .alt-thread-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
1724
+ .alt-thread-meta { display: flex; align-items: center; gap: 6px; min-width: 0; font: 400 12px/16px var(--font-sans); color: var(--ink-muted); }
1725
+ .alt-thread-plate { flex: none; }
1726
+ .alt-thread-time { white-space: nowrap; }
1727
+ .alt-thread-more { position: absolute; right: 4px; top: 6px; opacity: 0; transition: opacity var(--dur-travel-1); }
1728
+ .alt-thread:focus-within .alt-thread-more, .alt-thread-more[aria-expanded="true"] { opacity: 1; }
1729
+ @media (hover: hover) { .alt-thread:hover .alt-thread-more { opacity: 1; } }
1730
+ @media (hover: none) { .alt-thread-more { opacity: 1; } }
1731
+ .alt-thread:has(.alt-thread-more) .alt-thread-main { padding-right: 28px; }
1732
+ .alt-thread-rename { display: block; width: 100%; padding: 4px; }
1733
+ .alt-threads-new { width: 100%; justify-content: center; }
1734
+ /* AgentFleet: every agent in a run of work, grouped by what they need from you. Rows are the record, flush, with their
1735
+ state in words on a plate (the lamp only repeats it); working never hovers. The open agent is selected: tinted and
1736
+ raised one level, as a table's selected row. Finished agents are stamped: a check and muted ink. Agents an agent
1737
+ started hang under it in a 1px line-control bracket, as TraceParallel's subagents do. */
1738
+ .alt-fleet { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
1739
+ .alt-fleet-summary { margin: 0; font: 500 13px/18px var(--font-sans); color: var(--ink-muted); }
1740
+ .alt-fleet-group { display: flex; flex-direction: column; gap: 2px; }
1741
+ .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; }
1742
+ button.alt-fleet-group-head { cursor: pointer; border-radius: var(--radius-sm); }
1743
+ button.alt-fleet-group-head:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1744
+ .alt-fleet-chevron { transform: rotate(-90deg); transition: transform var(--dur-travel-1) var(--ease-glide); }
1745
+ .alt-fleet-group[data-open] .alt-fleet-chevron { transform: none; }
1746
+ .alt-fleet-group-count { font-variant-numeric: tabular-nums; font-weight: 500; }
1747
+ .alt-fleet-empty { margin: 0; padding: var(--space-1) var(--space-2); font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
1748
+ .alt-fleet-list, .alt-agent-children { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
1749
+ .alt-fleet-list[hidden] { display: none; }
1750
+ .alt-agent-children { margin: 2px 0 2px 18px; padding-left: 8px; border-left: 1px solid var(--line-control); }
1751
+ .alt-agent-line { position: relative; }
1752
+ .alt-agent-row { --row-bg: transparent; --row-sh: var(--elev-0);
1753
+ display: flex; flex-direction: column; gap: 3px; width: 100%; min-width: 0; padding: 8px var(--space-2); border: 0; border-radius: var(--radius-md);
1754
+ background: var(--row-bg); box-shadow: var(--row-sh); color: var(--ink); font: 400 13px/18px var(--font-sans); text-align: start;
1755
+ 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); }
1756
+ button.alt-agent-row { cursor: pointer; }
1757
+ @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))); } }
1758
+ button.alt-agent-row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1759
+ .alt-agent[data-selected] > .alt-agent-line > .alt-agent-row { --row-bg: var(--accent-soft); --row-sh: var(--elev-1); }
1760
+ .alt-agent-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
1761
+ .alt-agent-line:has(.alt-agent-output) .alt-agent-head { padding-right: 64px; }
1762
+ .alt-agent-mark { display: inline-flex; flex: none; width: 14px; justify-content: center; color: var(--ink-muted); }
1763
+ .alt-agent-mark i { width: 7px; height: 7px; border-radius: 50%; border: 1.5px solid currentColor; }
1764
+ .alt-agent[data-agent-state="working"] > .alt-agent-line .alt-agent-mark { color: var(--accent); }
1765
+ .alt-agent[data-agent-state="working"] > .alt-agent-line .alt-agent-mark i { background: currentColor; }
1766
+ .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); }
1767
+ .alt-agent[data-agent-state="needs-input"] > .alt-agent-line .alt-agent-mark i { background: currentColor; }
1768
+ .alt-agent[data-agent-state="failed"] > .alt-agent-line .alt-agent-mark { color: var(--critical); }
1769
+ .alt-agent[data-agent-state="done"] > .alt-agent-line .alt-agent-mark { color: var(--positive); }
1770
+ .alt-agent-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
1771
+ .alt-agent-time { flex: none; font: 400 12px/16px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
1772
+ .alt-agent-activity { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; padding-left: 22px; color: var(--ink-muted); }
1773
+ .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; }
1774
+ .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); }
1775
+ .alt-agent-conflict svg { flex: none; margin-top: 1px; color: var(--caution); }
1776
+ .alt-agent-conflict code { font: 400 11.5px/16px var(--font-code); }
1777
+ .alt-agent-output { position: absolute; top: 7px; right: var(--space-2); }
1778
+ .alt-agent:is([data-agent-state="done"], [data-agent-state="stopped"]) > .alt-agent-line .alt-agent-name { font-weight: 500; color: var(--ink-muted); }
1779
+ @media (forced-colors: active) {
1780
+ .alt-agent[data-selected] > .alt-agent-line > .alt-agent-row { outline: 1px solid Highlight; }
1781
+ .alt-agent-children { border-left-color: CanvasText; }
1782
+ .alt-agent-conflict { border: 1px dashed CanvasText; }
1783
+ }
1784
+ /* AgentRequests: the decisions agents are waiting on, oldest first. Each asks for action, so it's a raised card (level
1785
+ 1) that arrives lifted and settles; nothing in it has been dispatched, so nothing hovers. Its answers are secondary
1786
+ and ghost: the view's one primary stays where it is. With nothing waiting it says so, flush. */
1787
+ .alt-requests { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
1788
+ .alt-requests-head { display: flex; align-items: center; gap: 8px; min-height: 28px; }
1789
+ .alt-requests-title { margin: 0; font: 600 14px/20px var(--font-sans); color: var(--ink); }
1790
+ .alt-requests-oldest { margin-left: auto; font: 400 12px/16px var(--font-sans); color: var(--ink-muted); }
1791
+ .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); }
1792
+ .alt-requests-empty svg { flex: none; color: var(--positive); }
1793
+ .alt-requests-list { display: flex; flex-direction: column; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
1794
+ .alt-request { display: flex; flex-direction: column; gap: 6px; padding: var(--space-3); border-radius: var(--radius-md); }
1795
+ .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); }
1796
+ .alt-request-agent { font-weight: 600; color: var(--ink); }
1797
+ .alt-request-time { margin-left: auto; font-variant-numeric: tabular-nums; }
1798
+ .alt-request-title { margin: 0; font: 600 14px/20px var(--font-sans); color: var(--ink); }
1799
+ .alt-request-detail { margin: 0; font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
1800
+ .alt-request-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-top: 2px; }
1801
+ .alt-request-actions > .alt-btn[data-variant="ghost"]:last-child { margin-left: auto; }
1802
+ /* AgentLanes: where each agent's time went, on one clock. A chart, so nothing here has height (Charts): stretches are
1803
+ equal bars lying on each lane, told apart by fill and pattern and named in the key. Working is data-1; waiting on you
1804
+ is caution hatched at 45°; waiting on another agent is a 1px dashed outline; failed is critical. "Now" is a 1px
1805
+ line-control rule. The selected lane's name is accent-ink with aria-pressed; lanes never lift. */
1806
+ .alt-lanes { --lane-name: 132px; display: flex; flex-direction: column; gap: var(--space-2); margin: 0; min-width: 0; }
1807
+ .alt-lanes-takeaway { font: 500 14px/20px var(--font-sans); color: var(--ink); }
1808
+ .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); }
1809
+ .alt-lanes-legend li { display: inline-flex; align-items: center; gap: 6px; }
1810
+ .alt-lane-swatch { display: inline-block; width: 14px; height: 10px; border-radius: 2px; }
1811
+ .alt-lanes-plot { position: relative; display: flex; flex-direction: column; gap: 6px; }
1812
+ .alt-lane { display: grid; grid-template-columns: var(--lane-name) 1fr; align-items: center; gap: var(--space-2); }
1813
+ .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; }
1814
+ button.alt-lane-name { cursor: pointer; text-decoration: underline transparent; text-underline-offset: 3px; border-radius: var(--radius-xs); }
1815
+ @media (hover: hover) { button.alt-lane-name:hover { text-decoration-color: currentColor; } }
1816
+ button.alt-lane-name:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
1817
+ .alt-lane[data-selected] .alt-lane-name { color: var(--accent-ink); font-weight: 650; }
1818
+ .alt-lane-track { position: relative; height: 14px; border-bottom: 1px solid var(--line); }
1819
+ .alt-lane-bar { position: absolute; top: 1px; bottom: 1px; min-width: 2px; border-radius: 2px; }
1820
+ .alt-lane-swatch[data-lane="working"], .alt-lane-bar[data-lane="working"] { background: var(--data-1); }
1821
+ .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); }
1822
+ .alt-lane-swatch[data-lane="blocked"], .alt-lane-bar[data-lane="blocked"] { background: transparent; box-shadow: none; border: 1px dashed var(--ink-muted); }
1823
+ .alt-lane-swatch[data-lane="failed"], .alt-lane-bar[data-lane="failed"] { background: var(--critical); }
1824
+ .alt-lanes-axis { display: grid; grid-template-columns: var(--lane-name) 1fr; gap: var(--space-2); }
1825
+ .alt-lanes-ticks { position: relative; height: 16px; font: 400 11.5px/16px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
1826
+ .alt-lanes-ticks > span { position: absolute; transform: translateX(-50%); white-space: nowrap; }
1827
+ .alt-lanes-ticks > span[data-edge="start"] { transform: none; }
1828
+ .alt-lanes-ticks > span[data-edge="end"] { transform: translateX(-100%); color: var(--ink); }
1829
+ .alt-lanes-now { position: absolute; top: 0; bottom: 18px; right: 0; width: 1px; background: var(--line-control); }
1830
+ @media (forced-colors: active) {
1831
+ .alt-lane-bar, .alt-lane-swatch { forced-color-adjust: none; }
1832
+ .alt-lane-bar[data-lane="working"], .alt-lane-swatch[data-lane="working"] { background: CanvasText; }
1833
+ .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; }
1834
+ .alt-lane-bar[data-lane="blocked"], .alt-lane-swatch[data-lane="blocked"] { border-color: CanvasText; }
1835
+ .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; }
1836
+ .alt-lanes-now { background: CanvasText; }
1837
+ }
1838
+ @media (max-width: 480px) { .alt-lanes { --lane-name: 88px; } }
1839
+ @media (pointer: coarse) { button.alt-fleet-group-head, button.alt-lane-name { min-height: 44px; } }
1840
+ /* In the rail only icons show: each destination is a square, and the rail lets its tooltips out to the right. */
1841
+ .alt-sidebar[data-rail], .alt-sidebar[data-rail] .alt-sidebar-content { overflow: visible; }
1842
+ .alt-sidebar[data-rail] .alt-sidebar-item .alt-tip-anchor { display: flex; flex: 1; }
1843
+ .alt-sidebar[data-rail] .alt-sidebar-button { justify-content: center; padding: 0; }
1844
+ .alt-sidebar[data-rail] .alt-sidebar-text { display: none; }
1845
+ .alt-sidebar[data-rail] .alt-sidebar-header .alt-sidebar-text { display: none; }
1846
+ .alt-sidebar-badge { position: absolute; right: var(--space-2); z-index: 2; pointer-events: none; }
1847
+ .alt-sidebar[data-rail] .alt-sidebar-badge { right: 2px; top: 0; transform: scale(0.8); }
1848
+ .alt-shell-page { position: relative; flex: 1; min-width: 0; min-height: 0; overflow: auto; }
1849
+ .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); }
1850
+ .alt-topbar[data-under] { background: var(--surface-raised); box-shadow: var(--elev-1); }
1851
+ .alt-shell-sentinel { display: block; height: 1px; margin-bottom: -1px; }
1852
+ .alt-inset { --alt-page-pad: var(--alt-inset-page); display: block; padding: 0 var(--alt-page-pad) var(--alt-page-pad); outline: none; }
1853
+ @media (prefers-reduced-motion: reduce) { .alt-sidebar { transition: none; } .alt-sidebar-pill[data-ready] { transition: opacity var(--dur-travel-1); } }
1854
+ [data-motion="reduced"] .alt-sidebar { transition: none; }
1855
+ [data-motion="reduced"] .alt-sidebar-pill[data-ready] { transition: opacity var(--dur-travel-1); }
1856
+ @media (prefers-contrast: more) { .alt-sidebar, .alt-sidebar-header, .alt-sidebar-footer, .alt-topbar { border-color: var(--line-control); } }
1857
+ [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); }
1858
+ [data-contrast="more"] .alt-sidebar-pill { box-shadow: 0 2px 0 0 var(--ink), var(--elev-1); }
1859
+ @media (forced-colors: active) { .alt-sidebar-pill { border: 1px solid CanvasText; } .alt-sidebar-button:hover:not([aria-current]) { border: 1px solid CanvasText; } }
1860
+
1861
+ /* ── Resizable: panes beside each other on the ground, parted by 1px of line. The panes never lift; only the grip is
1862
+ live: a small line-control plate, flush at rest, lifting one level when pointed at or focused, floating at level 3
1863
+ while held (whatever is in a hand floats). Collapsing a pane changes its size on ease-glide, not its level. ── */
1864
+ .alt-resizable { display: flex; width: 100%; height: 100%; min-width: 0; min-height: 0; }
1865
+ .alt-resizable[data-direction="vertical"] { flex-direction: column; }
1866
+ .alt-resizable-panel { flex: 0 0 auto; min-width: 0; min-height: 0; overflow: auto; background: var(--surface-ground); }
1867
+ .alt-resizable-panel[data-collapsed] { overflow: hidden; }
1868
+ .alt-resizable[data-animate] .alt-resizable-panel { transition: flex-basis var(--dur-travel-3) var(--ease-glide); }
1869
+ .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; }
1870
+ .alt-resizable[data-direction="vertical"] > .alt-resizable-handle { width: auto; height: 1px; cursor: row-resize; }
1871
+ .alt-resizable-handle::before { content: ""; position: absolute; inset: 0 calc((var(--size-target-min) - 1px) / -2); }
1872
+ .alt-resizable[data-direction="vertical"] > .alt-resizable-handle::before { inset: calc((var(--size-target-min) - 1px) / -2) 0; }
1873
+ .alt-resizable-grip {
1874
+ position: relative; width: 8px; height: 28px; border-radius: var(--radius-full); border: 1px solid var(--line-control); background: var(--surface-ground);
1875
+ 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);
1876
+ }
1877
+ .alt-resizable[data-direction="vertical"] .alt-resizable-grip { width: 28px; height: 8px; }
1878
+ .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; }
1879
+ @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; } }
1880
+ .alt-resizable-handle:focus-visible .alt-resizable-grip { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1881
+ .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); }
1882
+ @media (prefers-reduced-motion: reduce) { .alt-resizable[data-animate] .alt-resizable-panel { transition: none; } }
1883
+ [data-motion="reduced"] .alt-resizable[data-animate] .alt-resizable-panel { transition: none; }
1884
+ @media (prefers-contrast: more) { .alt-resizable-handle { background: var(--line-control); } }
1885
+ [data-contrast="more"] .alt-resizable-handle { background: var(--line-control); }
1886
+ [data-contrast="more"] .alt-resizable-handle[data-held] .alt-resizable-grip { box-shadow: 0 2px 0 0 var(--ink), var(--elev-3); }
1887
+ @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; } }
1888
+
1889
+ /* ── ContextMenu: DropdownMenu's menu (level 3, the gliding highlight), opened where you right-click or long-press, or
1890
+ at the thing with Shift+F10. The thing shows it's the subject with a 2px accent-ink outline: a ring, never a height,
1891
+ so a flush row doesn't lift out of its table. ── */
1892
+ .alt-context { position: fixed; z-index: var(--z-floating); }
1893
+ .alt-context .alt-context-menu { position: relative; top: auto; left: auto; right: auto; bottom: auto; margin: 0; }
1894
+ [data-context-open] { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1895
+ @media (forced-colors: active) { [data-context-open] { outline-color: Highlight; } }
1896
+
1897
+ /* ── App mode (AltitudeProvider mode): test mode, viewing as a customer, read-only, a preview. The page is the ground,
1898
+ so height can't say "this whole ground is different"; the mode is said in words and a border (law 7 as the primary
1899
+ channel): a flush strip on the tone's soft colour naming it at the top, a 2px inset frame around the viewport in its
1900
+ tone that stays while scrolling and above dialogs and sheets, and a plate in their headers. Nothing moves. ── */
1901
+ .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); }
1902
+ .alt-mode-strip[data-tone="accent"], .alt-mode-frame[data-tone="accent"] { --m: var(--accent); --m-soft: var(--accent-soft); }
1903
+ .alt-mode-strip[data-tone="critical"], .alt-mode-frame[data-tone="critical"] { --m: var(--critical); --m-soft: var(--critical-soft); }
1904
+ .alt-mode-strip[data-tone="neutral"], .alt-mode-frame[data-tone="neutral"] { --m: var(--ink-muted); --m-soft: var(--surface-well); }
1905
+ .alt-mode-label { font-weight: 600; }
1906
+ .alt-mode-desc { color: var(--ink); }
1907
+ .alt-mode-action { margin-left: auto; }
1908
+ .alt-mode-frame { --m: var(--caution); position: fixed; inset: 0; z-index: 2147483000; border: 2px solid var(--m); pointer-events: none; }
1909
+ .alt-mode-plate { margin-bottom: var(--space-1); align-self: flex-start; }
1910
+ @media (prefers-contrast: more) { .alt-mode-frame { border-width: 3px; } }
1911
+ [data-contrast="more"] .alt-mode-frame { border-width: 3px; }
1912
+ @media (forced-colors: active) { .alt-mode-frame { border: 4px double CanvasText; } .alt-mode-strip { border-bottom: 1px solid CanvasText; } }
1913
+
1914
+ /* ── DropZone's queue: each file is a slat on the ground under the zone. Uploading is a task, so a Progress fill rises
1915
+ out of its trench; processing is a write the server hasn't confirmed, so the slat hovers as pending; done lands and
1916
+ settles with the afterglow; failed drops to a trench with its reason and Retry. ── */
1917
+ .alt-drop-wrap { display: flex; flex-direction: column; gap: var(--space-3); }
1918
+ .alt-upload-list { display: flex; flex-direction: column; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
1919
+ .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); }
1920
+ .alt-upload-text { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; }
1921
+ .alt-upload-name { font: 500 14px/20px var(--font-sans); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1922
+ .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; }
1923
+ .alt-upload[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench); }
1924
+ .alt-upload[data-state="failed"] .alt-upload-state { color: var(--critical); }
1925
+ .alt-upload .alt-progress { margin-top: 2px; }
1926
+ .alt-upload-actions { display: flex; flex: none; align-items: center; gap: var(--space-1); }
1927
+
1928
+ /* ── FilterBar groups: nested logic drawn as brackets on the ground. A group is a flush 1px line-control rule around its
1929
+ own conditions and its own all/any switch: part of the record of the query, not a live surface. ── */
1930
+ .alt-filter-tree { display: flex; flex-direction: column; gap: var(--space-2); }
1931
+ .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); }
1932
+ .alt-filter-group-remove { position: absolute; top: 6px; right: 6px; }
1933
+ .alt-filter-add-group { align-self: flex-start; margin-left: var(--space-4); }
1934
+ @media (forced-colors: active) { .alt-filter-group { border-color: CanvasText; } }
1935
+
1936
+ /* ── Timeline: the ground is the record, literally. Events stand on a ground line turned upright (1px ink-muted, the
1937
+ rule Stepper and the chart baseline stand on) as flush entries stamped onto it, each with a flush plate on the line.
1938
+ A new event arrives lifted and settles (the afterglow, staggered); one you caused hovers until it lands, or drops to a
1939
+ trench with Retry; an event that links lifts when pointed at; its detail opens as a raised card, as an Accordion lid
1940
+ does. ── */
1941
+ .alt-timeline-list { position: relative; display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0 0 0 36px; list-style: none; }
1942
+ .alt-timeline-list::before { content: ""; position: absolute; left: 13px; top: 6px; bottom: 6px; width: 1px; background: var(--ink-muted); }
1943
+ .alt-timeline-day { padding: var(--space-3) 0 var(--space-1); }
1944
+ .alt-timeline-day:first-child { padding-top: 0; }
1945
+ .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; }
1946
+ .alt-timeline-item { position: relative; border-radius: var(--radius-md); }
1947
+ .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); }
1948
+ .alt-timeline-marker svg { width: 14px; height: 14px; }
1949
+ .alt-timeline-dot { width: 7px; height: 7px; border-radius: var(--radius-full); background: var(--ink-muted); }
1950
+ .alt-timeline-body { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; }
1951
+ .alt-timeline-entry, .alt-timeline-link { display: flex; flex-direction: column; gap: 2px; font: 400 14px/20px var(--font-sans); color: var(--ink); }
1952
+ .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); }
1953
+ @media (hover: hover) { .alt-timeline-link:hover { background: var(--surface-raised); box-shadow: var(--elev-1); } }
1954
+ .alt-timeline-link:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1955
+ .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); }
1956
+ .alt-timeline-state { display: inline-flex; align-items: center; gap: 4px; }
1957
+ .alt-timeline-state[data-failed] { color: var(--critical); }
1958
+ /* A failed event isn't new to the record: it drops to its trench without the afterglow's tint. */
1959
+ .alt-timeline-item[data-status="failed"]::before { animation: none; }
1960
+ .alt-timeline-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
1961
+ .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; }
1962
+ @media (prefers-contrast: more) { .alt-timeline-list::before { width: 2px; } }
1963
+ [data-contrast="more"] .alt-timeline-list::before { width: 2px; }
1964
+ @media (forced-colors: active) { .alt-timeline-list::before { background: CanvasText; } .alt-timeline-marker { box-shadow: none; border: 1px solid CanvasText; } }
1965
+
1966
+ /* ── NotificationCenter: the bell is live (a ghost button), only its count floats, the panel floats at level 3 (a
1967
+ Sheet at 4 on narrow screens), and the list in it lies on the ground: a record of what happened. Unread is a mark,
1968
+ not a height: an accent dot and weight 600, with "Unread" in the name. A new notification arrives lifted and
1969
+ settles; an item that links lifts when pointed at. Mark all as read removes the dots with a cross-fade. ── */
1970
+ .alt-notes-bell { position: relative; }
1971
+ .alt-notes-badge { position: absolute; top: -4px; right: -6px; pointer-events: none; }
1972
+ .alt-notes-pop { width: min(380px, calc(100vw - 32px)); padding: var(--space-3); }
1973
+ .alt-notes-title-bar { margin: 0 0 var(--space-1); font: 600 15px/20px var(--font-sans); color: var(--ink); }
1974
+ .alt-notes { display: flex; flex-direction: column; gap: var(--space-2); }
1975
+ .alt-notes-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-height: 30px; }
1976
+ .alt-notes-count { font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); }
1977
+ .alt-notes-list { display: flex; flex-direction: column; gap: 2px; max-height: 420px; overflow: auto; margin: 0 -4px; padding: 0 4px; list-style: none; }
1978
+ .alt-notes-day { padding: var(--space-2) var(--space-2) 2px; font: 600 12px/16px var(--font-sans); color: var(--ink-muted); }
1979
+ .alt-notes-item { border-radius: var(--radius-md); }
1980
+ .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); }
1981
+ @media (hover: hover) { a.alt-notes-link:hover { background: var(--surface-raised); box-shadow: var(--elev-1); } }
1982
+ a.alt-notes-link:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1983
+ .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); }
1984
+ .alt-notes-item[data-unread] .alt-notes-dot { opacity: 1; }
1985
+ .alt-notes-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1986
+ .alt-notes-title { font: 400 14px/20px var(--font-sans); }
1987
+ .alt-notes-item[data-unread] .alt-notes-title { font-weight: 600; }
1988
+ .alt-notes-body { font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
1989
+ .alt-notes-time { font: 400 12px/16px var(--font-sans); color: var(--ink-muted); }
1990
+ .alt-notes-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0 8px 8px 26px; }
1991
+ .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; }
1992
+ .alt-notes-foot { padding-top: var(--space-2); border-top: 1px solid var(--line); font: 400 13px/18px var(--font-sans); }
1993
+ @media (forced-colors: active) { .alt-notes-dot { forced-color-adjust: none; background: CanvasText; } }
1994
+
1995
+ /* ── SyncIndicator: whether work has reached the server, said on a flush Badge plate whose lamp relights when the
1996
+ state changes (saving pulses). A label never lifts, so a change of state cross-fades; only when the plate opens the
1997
+ queue of waiting changes does it lift when pointed at, because then it acts. ── */
1998
+ .alt-sync { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
1999
+ .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); }
2000
+ @media (hover: hover) { .alt-sync-button:hover { box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
2001
+ .alt-sync-button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
2002
+ .alt-sync-time { font-weight: 400; color: var(--ink-muted); }
2003
+ .alt-sync-queue { display: flex; flex-direction: column; gap: var(--space-2); width: 280px; max-width: 100%; }
2004
+ .alt-sync-queue-title { margin: 0; font: 600 13px/18px var(--font-sans); color: var(--ink); }
2005
+ [data-contrast="more"] .alt-sync-plate { box-shadow: inset 0 0 0 1px var(--line-control); }
2006
+
2007
+ /* ── DiffView: both versions are the record, so it lies flush with a line rim (as CodeBlock). Additions are positive-
2008
+ soft with a + sign, removals critical-soft and struck through with a − sign, and words that changed inside a line take
2009
+ a stronger tint; a changed property shows the old value struck through beside the new. Signs and words carry it, never
2010
+ colour alone. Long unchanged runs fold behind lids. ── */
2011
+ .alt-diff { margin: 0; min-width: 0; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface-ground); overflow: hidden; }
2012
+ .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); }
2013
+ .alt-diff-caption { font: 500 13px/18px var(--font-sans); color: var(--ink); }
2014
+ .alt-diff-summary { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2015
+ /* The buttons wrap rather than run past the frame, which clips them (a review's four don't fit a phone on one line). */
2016
+ .alt-diff-nav { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; gap: 2px; margin-left: auto; min-width: 0; }
2017
+ .alt-diff-body { overflow: auto; max-height: 420px; padding: var(--space-2) 0; }
2018
+ .alt-diff-lines { min-width: max-content; font: 400 13px/20px var(--font-mono); }
2019
+ .alt-diff-line { display: flex; padding-right: var(--space-4); outline: none; }
2020
+ .alt-diff-line:focus-visible { box-shadow: inset 2px 0 0 var(--accent-ink); }
2021
+ .alt-diff-sign { flex: none; width: 28px; text-align: center; color: var(--ink-muted); user-select: none; }
2022
+ .alt-diff-text { white-space: pre; text-decoration: none; color: var(--ink); }
2023
+ .alt-diff-line[data-kind="add"] { background: var(--positive-soft); }
2024
+ .alt-diff-line[data-kind="del"] { background: var(--critical-soft); }
2025
+ .alt-diff-line[data-kind="del"] .alt-diff-text { text-decoration: line-through; text-decoration-color: color-mix(in oklab, var(--critical) 60%, transparent); }
2026
+ .alt-diff-line[data-kind="add"] .alt-diff-sign { color: var(--positive); }
2027
+ .alt-diff-line[data-kind="del"] .alt-diff-sign { color: var(--critical); }
2028
+ .alt-diff-word { color: inherit; border-radius: 2px; }
2029
+ .alt-diff-line[data-kind="add"] .alt-diff-word { background: color-mix(in oklab, var(--positive) 22%, transparent); }
2030
+ .alt-diff-line[data-kind="del"] .alt-diff-word { background: color-mix(in oklab, var(--critical) 18%, transparent); }
2031
+ .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; }
2032
+ /* Reviewing (#282): a change awaiting a decision is a proposal, raised at level 1 around its lines (as PropertyList's
2033
+ proposed value); deciding lowers it to the record: accepted flush with one afterglow, rejected muted and collapsed. */
2034
+ .alt-diff-hunk { position: relative; margin: 4px 6px; border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--elev-1); overflow: hidden;
2035
+ 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); }
2036
+ .alt-diff-hunk[data-decision] { margin-inline: 0; border-radius: 0; background: none; box-shadow: none; }
2037
+ .alt-diff-hunk[data-decision="reject"] { color: var(--ink-muted); }
2038
+ .alt-diff-hunk[data-decision="reject"] .alt-diff-line { opacity: 0.6; }
2039
+ .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); }
2040
+ .alt-diff-hunk-state { display: inline-flex; align-items: center; gap: 5px; margin-right: auto; color: var(--ink-muted); }
2041
+ .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; }
2042
+ .alt-diff-hunk-folded:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
2043
+ .alt-diff-body { container-type: inline-size; }
2044
+ @media (prefers-reduced-motion: reduce) { .alt-diff-hunk { transition: none; } }
2045
+ @media (forced-colors: active) { .alt-diff-hunk:not([data-decision]) { border: 1px solid CanvasText; } }
2046
+ @media (hover: hover) { .alt-diff-fold button:hover { background: var(--surface-raised); color: var(--ink); } }
2047
+ .alt-diff-fold button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
2048
+ .alt-diff-props { display: flex; flex-direction: column; margin: 0; padding: 0 var(--space-4); }
2049
+ .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; }
2050
+ .alt-diff-prop:last-child { border-bottom: 0; }
2051
+ .alt-diff-prop:focus-visible { box-shadow: inset 2px 0 0 var(--accent-ink); }
2052
+ .alt-diff-prop dt { color: var(--ink-muted); }
2053
+ .alt-diff-prop dd { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; }
2054
+ .alt-diff-old { padding: 0 4px; border-radius: var(--radius-xs); background: var(--critical-soft); text-decoration: line-through; color: var(--ink); }
2055
+ .alt-diff-new { padding: 0 4px; border-radius: var(--radius-xs); background: var(--positive-soft); text-decoration: none; color: var(--ink); font-weight: 500; }
2056
+ .alt-diff-arrow { transform: rotate(-90deg); color: var(--ink-muted); }
2057
+ [data-contrast="more"] .alt-diff-line[data-kind="add"] { box-shadow: inset 2px 0 0 var(--positive); }
2058
+ [data-contrast="more"] .alt-diff-line[data-kind="del"] { box-shadow: inset 2px 0 0 var(--critical); }
2059
+ @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; } }
2060
+
2061
+ /* ── SaveBar: an unsaved draft at page scope is something in your hand, so the bar floats at level 3, docked to the
2062
+ bottom edge (below a Sheet or a Dialog, never competing with them). It rises from the edge once a change has settled
2063
+ into the draft; saving hovers as a pending write; saved, it lowers away while the fields settle; a failed save drops
2064
+ it to a trench, keeping the draft. ── */
2065
+ .alt-savebar {
2066
+ position: fixed; left: 50%; bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px)); z-index: var(--z-floating); translate: -50% 0;
2067
+ display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); width: max-content; max-width: calc(100vw - 32px);
2068
+ padding: 8px 8px 8px var(--space-4); border-radius: var(--radius-lg); background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--ink);
2069
+ 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);
2070
+ }
2071
+ .alt-savebar[data-state="closed"] { opacity: 0; transform: translateY(calc(16px * var(--alt-travel))); transition-timing-function: var(--ease-settle); }
2072
+ .alt-savebar[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench); outline: 1px solid var(--critical); animation-duration: var(--dur-travel-5); }
2073
+ .alt-savebar-text { display: inline-flex; align-items: center; gap: 6px; font: 500 14px/20px var(--font-sans); }
2074
+ .alt-savebar[data-status="failed"] .alt-savebar-text svg { color: var(--critical); }
2075
+ .alt-savebar-actions { display: inline-flex; gap: var(--space-2); margin-left: auto; }
2076
+ [data-contrast="more"] .alt-savebar { box-shadow: 0 3px 0 0 var(--ink), var(--elev-3); }
2077
+ @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; } }
2078
+
2079
+ /* ── Settings: a section is a raised Card (level 1) of rows; the danger zone is the same card with a critical rim,
2080
+ never higher. A row is flush, label and description on the start side, the control on the end side, stacked when
2081
+ the section is narrow. An apply-now control's write is the row's: pending hovers it, committed lands, failed drops
2082
+ it into a trench, with the state in words beside the control. ── */
2083
+ .alt-settings-section { container-type: inline-size; padding: var(--alt-inset-box); gap: var(--space-3); }
2084
+ .alt-settings-section[data-tone="critical"] { box-shadow: var(--elev-1), inset 0 0 0 1px var(--critical); }
2085
+ .alt-settings-head { display: flex; flex-direction: column; gap: var(--space-1); }
2086
+ .alt-settings-title { margin: 0; font: 600 17px/24px var(--font-sans); letter-spacing: -0.01em; }
2087
+ .alt-settings-desc, .alt-settings-readonly { margin: 0; font: 400 14px/20px var(--font-sans); color: var(--ink-muted); }
2088
+ .alt-settings-readonly { display: flex; gap: 6px; align-items: flex-start; }
2089
+ .alt-settings-readonly svg { flex: none; margin-top: 3px; }
2090
+ .alt-settings-rows { margin: 0; padding: 0; border: 0; min-width: 0; display: flex; flex-direction: column; }
2091
+ .alt-settings-row {
2092
+ display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-2) var(--space-5);
2093
+ margin-inline: calc(-1 * var(--space-3)); padding: var(--space-3); border-radius: var(--radius-md);
2094
+ }
2095
+ .alt-settings-row + .alt-settings-row { box-shadow: inset 0 1px 0 var(--line); }
2096
+ .alt-settings-row[data-status="pending"] { background: var(--surface-raised); }
2097
+ .alt-settings-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
2098
+ .alt-settings-label { font: 500 14px/20px var(--font-sans); color: var(--ink); }
2099
+ .alt-settings-rowdesc, .alt-settings-error { margin: 0; font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
2100
+ .alt-settings-error { display: flex; gap: 6px; align-items: flex-start; color: var(--critical); }
2101
+ .alt-settings-error svg { flex: none; margin-top: 2px; }
2102
+ .alt-settings-control { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; min-width: 0; }
2103
+ .alt-settings-control > .alt-input, .alt-settings-control > .alt-select { width: min(280px, 100%); }
2104
+ .alt-settings-status { font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2105
+ .alt-settings-status:empty { display: none; }
2106
+ .alt-settings-row[data-status="failed"] .alt-settings-status { color: var(--critical); }
2107
+ @container (max-width: 480px) {
2108
+ .alt-settings-row { grid-template-columns: minmax(0, 1fr); }
2109
+ .alt-settings-control { align-items: flex-start; }
2110
+ }
2111
+ @media (forced-colors: active) { .alt-settings-section[data-tone="critical"] { border: 2px solid CanvasText; } }
2112
+
2113
+ /* ── PermissionMatrix: roles against permissions. The grid is settled, on the ground with line dividers. An editable
2114
+ grant is a choice, a Checkbox's trench that the accent pours into; an inherited or fixed grant is already decided, so
2115
+ it's a flush stamped check (or a dash), muted, like a done Checklist item: pressed into the record, not disabled. The
2116
+ active cell is a 2px accent-ink ring, never a height. A saving cell hovers; a saved one settles. ── */
2117
+ .alt-matrix { overflow-x: auto; }
2118
+ .alt-matrix-grid { border-collapse: separate; border-spacing: 0; min-width: 100%; font: 400 14px/20px var(--font-sans); color: var(--ink); }
2119
+ .alt-matrix-grid th, .alt-matrix-grid td { padding: 8px 12px; border-bottom: 1px solid var(--line); text-align: center; }
2120
+ .alt-matrix-grid thead th { font: 600 13px/18px var(--font-sans); color: var(--ink); white-space: nowrap; }
2121
+ .alt-matrix-grid .alt-matrix-perm { text-align: left; font-weight: 400; white-space: nowrap; }
2122
+ .alt-matrix-group th { padding-top: var(--space-4); text-align: left; font: 600 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2123
+ .alt-matrix-cell { position: relative; }
2124
+ .alt-matrix-box { display: inline-grid; place-items: center; width: 30px; height: 30px; border-radius: var(--radius-sm); cursor: pointer; outline: none; }
2125
+ .alt-matrix-box[data-fixed] { cursor: help; }
2126
+ .alt-matrix-box:focus-visible { box-shadow: 0 0 0 2px var(--accent-ink); }
2127
+ .alt-matrix-box[data-on] .alt-check-fill { opacity: 1; clip-path: circle(75% at 50% 50%); }
2128
+ .alt-matrix-box[data-on] .alt-check-tick { stroke-dashoffset: 0; transition: stroke-dashoffset var(--dur-travel-2) var(--ease-rise) 60ms; }
2129
+ .alt-matrix-box[data-on] .alt-check-track { border-color: var(--accent); }
2130
+ .alt-matrix-stamp { display: grid; place-items: center; width: 22px; height: 22px; color: var(--ink-muted); }
2131
+ .alt-matrix-dash { display: block; width: 10px; height: 2px; border-radius: 1px; background: var(--ink-faint); }
2132
+ [data-contrast="more"] .alt-matrix-box:focus-visible { box-shadow: 0 0 0 3px var(--accent-ink); }
2133
+ @media (forced-colors: active) { .alt-matrix-box:focus-visible { box-shadow: none; outline: 2px solid Highlight; } .alt-matrix-dash { background: CanvasText; } }
2134
+
2135
+ /* ── PlanPicker: plans are positions in one choice, so the cards lie flush on the ground (a line rim) and one raised
2136
+ mark, the radio thumb's build, travels to the chosen card's corner on ease-glide; the chosen card also takes an
2137
+ accent rim. Pointing at another card shows a ghost of the mark there. "Current plan" and "Recommended" are flush plates,
2138
+ never extra height: the only level-2 slab is the Continue button beside it. A period change cross-fades the prices. ── */
2139
+ .alt-plans { display: flex; flex-direction: column; gap: var(--space-4); }
2140
+ .alt-plans-period { align-self: flex-start; }
2141
+ .alt-plans-grid { position: relative; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-3); }
2142
+ .alt-plan {
2143
+ position: relative; display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4); border-radius: var(--radius-lg);
2144
+ background: var(--surface-ground); box-shadow: inset 0 0 0 1px var(--line); cursor: pointer; outline: none;
2145
+ transition: box-shadow var(--dur-travel-2) var(--ease-rise);
2146
+ }
2147
+ .alt-plan[data-chosen] { box-shadow: inset 0 0 0 2px var(--accent); }
2148
+ .alt-plan[aria-disabled] { cursor: not-allowed; color: var(--ink-faint); }
2149
+ .alt-plan:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
2150
+ .alt-plan::after {
2151
+ content: ""; position: absolute; top: 15px; right: 15px; width: 14px; height: 14px; border-radius: var(--radius-full);
2152
+ 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);
2153
+ }
2154
+ @media (hover: hover) { .alt-plan:hover:not([data-chosen]):not([aria-disabled])::after { opacity: 0.5; scale: 0.85; } }
2155
+ .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; }
2156
+ .alt-plans-mark[data-ready] { transition: transform var(--dur-travel-3) var(--ease-glide), opacity var(--dur-travel-1); }
2157
+ .alt-plan-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding-right: 24px; }
2158
+ .alt-plan-name { font: 600 16px/22px var(--font-sans); }
2159
+ .alt-plan-plates { display: inline-flex; flex-wrap: wrap; gap: 4px; }
2160
+ .alt-plan-price { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px; }
2161
+ .alt-plan-amount { font: 600 26px/32px var(--font-sans); letter-spacing: -0.015em; }
2162
+ .alt-plan-per { font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
2163
+ .alt-plan-desc { font: 400 13.5px/19px var(--font-sans); color: var(--ink-muted); }
2164
+ .alt-plan-desc p { margin: 0 0 var(--space-2); }
2165
+ .alt-plan-desc ul { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; color: var(--ink); }
2166
+ .alt-plan-desc li { display: flex; gap: 6px; align-items: flex-start; }
2167
+ .alt-plan-desc li svg { flex: none; margin-top: 2px; color: var(--positive); }
2168
+ .alt-plan-why { margin: 0; color: var(--ink-muted); }
2169
+ @media (prefers-reduced-motion: reduce) { .alt-plans-mark[data-ready] { transition: opacity var(--dur-travel-1); } }
2170
+ [data-motion="reduced"] .alt-plans-mark[data-ready] { transition: opacity var(--dur-travel-1); }
2171
+ [data-contrast="more"] .alt-plans-mark { box-shadow: 0 2px 0 0 var(--ink), inset 0 0 0 3px var(--surface-raised); }
2172
+ @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; } }
2173
+
2174
+ /* ── FeatureGate: locked, not disabled. Disabled means nothing happens; a locked control explains the plan and offers
2175
+ the upgrade, so it keeps its own build and normal ink and lifts when pointed at (hover is a promise), with a lock and
2176
+ a flush plan plate. Pressing it opens a Popover at level 3 with the upgrade as its one primary. ── */
2177
+ .alt-gate { display: inline-flex; }
2178
+ .alt-gate-trigger .alt-btn-label, .alt-gate-trigger { gap: 6px; }
2179
+ .alt-gate-lock { flex: none; color: var(--ink-muted); }
2180
+ .alt-gate-plan { margin-left: 2px; }
2181
+ .alt-gate-pop { width: min(300px, calc(100vw - 32px)); }
2182
+ .alt-gate-body { display: flex; flex-direction: column; gap: var(--space-2); }
2183
+ .alt-gate-title { display: flex; align-items: center; gap: 6px; margin: 0; font: 600 14px/20px var(--font-sans); color: var(--ink); }
2184
+ .alt-gate-desc, .alt-gate-admin { margin: 0; font: 400 13.5px/19px var(--font-sans); color: var(--ink-muted); }
2185
+ .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); }
2186
+
2187
+ /* ── CommentThread: posted comments are the record, flush on the ground; the composer is an Input well that fills; a
2188
+ comment being posted hovers as pending, lands and settles, or drops to a trench keeping its text. Mentions are flush
2189
+ accent-soft plates in the text and a listbox at level 3 while writing. A resolved thread is stamped like a done
2190
+ Checklist item: muted, collapsed to one line, kept in place. Someone typing shows their presence colour, quietly. ── */
2191
+ .alt-comments { display: flex; flex-direction: column; gap: var(--space-3); }
2192
+ .alt-comment-list { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
2193
+ .alt-comment { border-radius: var(--radius-md); }
2194
+ .alt-comment-article { display: flex; gap: 10px; padding: 8px; }
2195
+ .alt-comment-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
2196
+ .alt-comment-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
2197
+ .alt-comment-author { font: 600 14px/20px var(--font-sans); color: var(--ink); }
2198
+ .alt-comment-time { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2199
+ .alt-comment-failed { display: inline-flex; align-items: center; gap: 4px; color: var(--critical); }
2200
+ .alt-comment-body { margin: 0; font: 400 14px/21px var(--font-sans); color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
2201
+ .alt-comment-mention { padding: 0 3px; border-radius: var(--radius-xs); background: var(--accent-soft); color: var(--accent-ink); font-weight: 500; }
2202
+ .alt-comment-actions { display: flex; gap: var(--space-2); margin-top: 4px; }
2203
+ .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; }
2204
+ .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); }
2205
+ .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); }
2206
+ .alt-comments[data-resolved] .alt-comment-list { opacity: 0.7; }
2207
+ .alt-comments-compose { display: flex; flex-direction: column; gap: var(--space-2); }
2208
+ .alt-comments-field { position: relative; }
2209
+ .alt-comments-field textarea { min-height: 56px; }
2210
+ .alt-comments-mentions { top: calc(100% + 4px); left: 0; min-width: 220px; padding: var(--space-1); }
2211
+ .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; }
2212
+ .alt-comments-option[data-active] { background: var(--accent-soft); }
2213
+ .alt-comments-actions { display: flex; justify-content: flex-end; gap: var(--space-2); }
2214
+ .alt-comments-typing { display: flex; align-items: center; gap: 6px; margin: 0; font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2215
+ .alt-comments-dots { display: inline-flex; gap: 3px; }
2216
+ .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; }
2217
+ .alt-comments-dots i:nth-child(2) { animation-delay: 0.15s; } .alt-comments-dots i:nth-child(3) { animation-delay: 0.3s; }
2218
+ @keyframes alt-typing { 50% { opacity: 0.35; } }
2219
+ @media (prefers-reduced-motion: reduce) { .alt-comments-dots i { animation: none; } }
2220
+ [data-motion="reduced"] .alt-comments-dots i { animation: none; }
2221
+ @media (forced-colors: active) { .alt-comment-mention { border: 1px solid CanvasText; } }
2222
+
2223
+ /* ── Tour: a note in the air above the page. One step at a time floats at level 3 beside its target, which takes a
2224
+ 2px accent-ink ring (a ring, not a lift: the target may not be something you press). It never dims the page,
2225
+ because receding is the summit's privilege, and it isn't modal. Moving on lifts the step off; the next rises at its
2226
+ own target. ── */
2227
+ .alt-tour {
2228
+ position: fixed; z-index: var(--z-floating); width: min(320px, calc(100vw - 16px)); padding: var(--space-4); border-radius: var(--radius-lg);
2229
+ background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--ink); outline: none;
2230
+ animation: alt-emerge var(--dur-travel-3) var(--ease-rise); --alt-from-y: 6px;
2231
+ }
2232
+ .alt-tour[data-side="above"] { --alt-from-y: -6px; }
2233
+ .alt-tour:focus-visible { box-shadow: var(--elev-3), 0 0 0 2px var(--accent-ink); }
2234
+ .alt-tour-count { margin: 0 0 2px; font: 500 12px/16px var(--font-sans); color: var(--ink-muted); }
2235
+ .alt-tour-title { margin: 0 24px 6px 0; font: 600 15px/21px var(--font-sans); }
2236
+ .alt-tour-body { font: 400 14px/20px var(--font-sans); color: var(--ink-muted); }
2237
+ .alt-tour-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-3); }
2238
+ .alt-tour-dots { display: inline-flex; gap: 4px; margin-right: auto; }
2239
+ .alt-tour-dots i { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--line-control); }
2240
+ .alt-tour-dots i[data-on] { background: var(--accent); }
2241
+ .alt-tour-close { position: absolute; top: 8px; right: 8px; }
2242
+ [data-tour-target] { outline: 2px solid var(--accent-ink) !important; outline-offset: 3px !important; border-radius: var(--radius-sm); }
2243
+ [data-contrast="more"] .alt-tour { box-shadow: 0 4px 0 0 var(--ink), var(--elev-3); }
2244
+ @media (forced-colors: active) { .alt-tour { border: 2px solid CanvasText; } [data-tour-target] { outline-color: Highlight !important; } }
2245
+
2246
+ /* ── PromptInput: where a person writes to an agent. The whole composer is the Input well (empty: its back wall shows and
2247
+ it deepens as the pointer nears; holding text: the floor rises flush), with attachments as Tag plates above the text
2248
+ and a toolbar below. Send is the level-2 primary; while generating it becomes Stop in the same slot and size, its icon
2249
+ cross-fading without travel. An empty composer's Send is flush and faint but stays focusable (aria-disabled).
2250
+ Dragging a file over drops the well to a trench, as DropZone. ── */
2251
+ .alt-prompt { position: relative; }
2252
+ .alt-prompt-well { position: relative; gap: var(--space-2); padding: var(--space-3); border-radius: var(--radius-lg); }
2253
+ .alt-prompt-well .alt-prompt-text { width: 100%; min-height: 24px; resize: none; transition: height var(--dur-travel-2) var(--ease-glide); }
2254
+ .alt-prompt-files { display: flex; flex-wrap: wrap; gap: var(--space-2); }
2255
+ /* @ mentions and / commands (#283): the list is a floating Combobox list (level 3) rising above the composer, grouped
2256
+ with headings; what's chosen is a flush plate in the composer's context row ("@label" stays in the text too). */
2257
+ .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; }
2258
+ .alt-prompt-suggest-head { margin: 4px 8px 2px; font: 500 12px/16px var(--font-sans); color: var(--ink-muted); }
2259
+ .alt-prompt-option { display: flex; flex-direction: column; gap: 1px; padding: 6px 8px; border-radius: var(--radius-sm); cursor: pointer; }
2260
+ .alt-prompt-option[data-active] { background: var(--surface-raised); box-shadow: var(--elev-1); }
2261
+ .alt-prompt-option-label { font: 500 14px/20px var(--font-sans); color: var(--ink); }
2262
+ .alt-prompt-option-label[data-kind="command"], .alt-prompt-command .alt-tag-face { font-family: var(--font-mono); }
2263
+ .alt-prompt-option-desc { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2264
+ .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); }
2265
+ .alt-prompt-searching .alt-skeleton { width: 80px; height: 10px; }
2266
+ /* Dictation (#284): listening is a latched toggle (a shallow well with accent-soft poured in, a steady lamp and the
2267
+ word), never a pulse or a waveform; interim words are a ghost in ink-faint, like a suggestion, never in the value. */
2268
+ .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) {
2269
+ --wall: 0px; background: var(--accent-soft); background-image: none; color: var(--accent-ink); box-shadow: var(--well); transform: none; }
2270
+ .alt-prompt-listening { display: inline-flex; align-items: center; gap: 5px; margin-left: 4px; font: 500 12.5px/17px var(--font-sans); }
2271
+ .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); }
2272
+ .alt-prompt-dictate[data-blocked] { color: var(--ink-faint); }
2273
+ .alt-prompt-interim { margin: 2px 0 0; font: 400 15px/23px var(--font-sans); color: var(--ink-faint); white-space: pre-wrap; }
2274
+ @media (forced-colors: active) { .alt-btn.alt-prompt-dictate[data-listening] { border: 2px solid Highlight; } }
2275
+ .alt-prompt-context { display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 8px 0; }
2276
+ .alt-prompt-kind { width: 6px; height: 6px; border-radius: 50%; background: var(--ink-muted); }
2277
+ .alt-prompt-kind[data-kind="file"] { background: var(--accent-ink); }
2278
+ .alt-prompt-kind[data-kind="person"] { background: var(--positive); }
2279
+ .alt-prompt-chip { display: inline-flex; border-radius: var(--radius-sm); }
2280
+ .alt-prompt-uploading { color: var(--ink-muted); }
2281
+ .alt-prompt-bar { display: flex; align-items: center; gap: var(--space-2); }
2282
+ .alt-prompt-send { margin-left: auto; }
2283
+ .alt-prompt-send svg { animation: alt-fade-in var(--dur-travel-1) var(--ease-rise); }
2284
+ @keyframes alt-fade-in { from { opacity: 0; } }
2285
+ .alt-prompt-send[aria-disabled] {
2286
+ 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;
2287
+ }
2288
+ @media (hover: hover) { .alt-prompt-send[aria-disabled]:is(:hover, :active, :focus-visible) {
2289
+ 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;
2290
+ } }
2291
+ .alt-prompt-well[data-drop="over"] { background: var(--accent-soft); box-shadow: var(--trench); border-color: var(--accent-ink); }
2292
+ .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; }
2293
+ @media (prefers-reduced-motion: reduce) { .alt-prompt-text { transition: none; } }
2294
+ [data-motion="reduced"] .alt-prompt-text { transition: none; }
2295
+ @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; } }
2296
+
2297
+ /* ── Conversation: settled messages are the record. A person's message is a flush accent-soft sheet; an assistant's is
2298
+ flush with no bubble. A streaming answer isn't the record yet and isn't a pending write either (nothing you sent is in
2299
+ flight), so it rests on the ground with charts' "not yet the record" treatment: a dashed line-control rule down its
2300
+ left edge, "Generating…" and a caret. Completing makes the rule solid and settles it once. "Jump to latest" floats at
2301
+ level 3 once you've scrolled up. ── */
2302
+ .alt-convo { position: relative; display: flex; flex-direction: column; min-height: 0; }
2303
+ .alt-convo-scroll { flex: 1; min-height: 0; overflow-y: auto; outline: none; border-radius: var(--radius-md); }
2304
+ .alt-convo-scroll:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
2305
+ /* The scrollbar sits beside the messages, never over them: a classic bar gets a gutter of its own, and the content keeps
2306
+ 12px clear of the right edge, where an overlay bar (macOS, phones) is drawn. */
2307
+ .alt-convo-scroll { scrollbar-gutter: stable; scrollbar-width: thin; }
2308
+ .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); }
2309
+ .alt-msg { display: flex; flex-direction: column; gap: 4px; max-width: 72ch; border-radius: var(--radius-md); }
2310
+ .alt-msg[data-from="user"] { align-self: flex-end; max-width: min(72ch, 85%); padding: 10px 14px; background: var(--accent-soft); color: var(--ink); }
2311
+ /* Attachments (#274): part of the record, on the ground. Images are 64px thumbnails in a 1px line rim; files are the
2312
+ composer's Tag plates, settled. One that opens lifts a level when pointed at, as any plate that acts. */
2313
+ .alt-msg-attachments { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; padding: 0; list-style: none; }
2314
+ .alt-msg[data-from="user"] .alt-msg-attachments { justify-content: flex-end; }
2315
+ .alt-msg-attachment { display: flex; min-width: 0; }
2316
+ .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); }
2317
+ .alt-msg-thumb img { display: block; height: 64px; width: auto; max-width: 160px; min-width: 48px; object-fit: cover; }
2318
+ .alt-msg-thumb::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px var(--line); pointer-events: none; }
2319
+ :is(a, button).alt-msg-thumb { cursor: pointer; }
2320
+ .alt-msg-file { height: 28px; }
2321
+ .alt-msg-file .alt-tag-face { max-width: 36ch; text-decoration: none; }
2322
+ :is(a, button).alt-tag-face { cursor: pointer; }
2323
+ .alt-msg-file-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2324
+ .alt-msg-file-meta { flex: none; color: var(--ink-muted); font-weight: 450; }
2325
+ .alt-msg-file[data-unavailable] { color: var(--ink-muted); }
2326
+ @media (hover: hover) {
2327
+ .alt-msg-file[data-acts]:hover { --t-bg: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
2328
+ :is(a, button).alt-msg-thumb:hover { box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
2329
+ }
2330
+ .alt-msg-thumb:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
2331
+ .alt-msg-file[data-acts]:active, :is(a, button).alt-msg-thumb:active { transform: none; box-shadow: none; transition-duration: var(--dur-press); }
2332
+ @media (forced-colors: active) { .alt-msg-thumb { border: 1px solid CanvasText; } }
2333
+ .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); }
2334
+ .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; }
2335
+ .alt-msg[data-stream="complete"], .alt-msg[data-stream="stopped"], .alt-msg[data-stream="error"] { box-shadow: inset 2px 0 0 0 var(--line); }
2336
+ .alt-msg[data-stream="error"] { box-shadow: inset 2px 0 0 0 var(--critical); }
2337
+ .alt-msg-name { margin: 0; font: 600 13px/18px var(--font-sans); color: var(--ink-muted); }
2338
+ .alt-msg-content { font: 400 15px/23px var(--font-sans); color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
2339
+
2340
+ /* ---- Terminal (#273) ----
2341
+ A command's output in CodeBlock's frame: flush on the ground, a 1px rim, mono. Running, it isn't the record yet, so
2342
+ a dashed line-control rule runs down the left; finished, the rule goes solid (critical when the exit is non-zero)
2343
+ with one afterglow. stderr lines carry a critical mark in the gutter. "Jump to latest output" floats at level 3. */
2344
+ .alt-term { position: relative; border-left-width: 2px; }
2345
+ .alt-term[data-status="running"] { border-left-style: dashed; border-left-color: var(--line-control); }
2346
+ .alt-term-cmd { min-width: 0; font: 500 13px/18px var(--font-mono); color: var(--ink); overflow-wrap: anywhere; }
2347
+ .alt-term-prompt { color: var(--ink-muted); }
2348
+ .alt-term-viewport { position: relative; }
2349
+ .alt-term-body { max-height: 320px; overflow: auto; scrollbar-width: thin; outline: none; }
2350
+ .alt-term-body:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
2351
+ .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; }
2352
+ .alt-term-line { position: relative; display: block; min-height: 20px; }
2353
+ .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); }
2354
+ .alt-term [data-tone="critical"] { color: var(--critical); }
2355
+ .alt-term [data-tone="caution"] { color: var(--caution); }
2356
+ .alt-term [data-tone="positive"] { color: var(--positive); }
2357
+ .alt-term [data-tone="accent"] { color: var(--accent-ink); }
2358
+ .alt-term [data-tone="muted"] { color: var(--ink-muted); }
2359
+ .alt-term [data-bold] { font-weight: 600; }
2360
+ .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; }
2361
+ .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; }
2362
+ .alt-term-exit[data-tone="critical"] { color: var(--critical); }
2363
+ [data-contrast="more"] .alt-term { border-color: var(--line-control); }
2364
+ @media (forced-colors: active) {
2365
+ .alt-term { border-color: CanvasText; }
2366
+ .alt-term[data-status="running"] { border-left-style: dotted; }
2367
+ .alt-term-line[data-stream="stderr"]::before { background: CanvasText; }
2368
+ }
2369
+
2370
+ /* ---- Suggestions (#272) ----
2371
+ Prompts to start from or follow up with are things you press: small secondary buttons, raised at level 1 with their
2372
+ wall, lifting to 2 when pointed at, never the view's level-2 primary (that's the composer's Send). Follow-ups merely
2373
+ became available, so they cross-fade in under the answer; they don't rise. */
2374
+ .alt-suggestions { display: flex; flex-wrap: wrap; gap: 6px; }
2375
+ .alt-suggestions[data-arrive] { animation: alt-fade-in var(--dur-onset) var(--ease-rise) both; }
2376
+ .alt-suggestion { max-width: 100%; }
2377
+ .alt-suggestion-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2378
+ .alt-msg .alt-suggestions { margin-top: 4px; }
2379
+ .alt-empty .alt-suggestions { justify-content: center; }
2380
+ @media (prefers-reduced-motion: reduce) { .alt-suggestions[data-arrive] { animation: none; } }
2381
+ [data-motion="reduced"] .alt-suggestions[data-arrive] { animation: none; }
2382
+
2383
+ /* ---- Markdown (#268) ----
2384
+ An answer's text set as the record: everything sits on the ground at level 0 and nothing here adds height. Code is
2385
+ CodeBlock, links are Link, [n] markers are InlineCitation (the only part that lifts, when pointed at), tables use
2386
+ Table's flush rows with line dividers, and an image waits in a sunk imprint (-1) and rises out of it. Headings are
2387
+ the heading style, never the title (which belongs to the page). Rims and rules are line; under contrast="more"
2388
+ they're line-control, and in forced colours CanvasText. */
2389
+ .alt-md { font: 400 15px/23px var(--font-sans); color: var(--ink); white-space: normal; overflow-wrap: anywhere; }
2390
+ .alt-md :is(p, .alt-md-list, .alt-md-quote, .alt-md-pre, .alt-md-table, .alt-md-html) { margin: 0 0 12px; }
2391
+ /* After the block margins: at equal specificity the later rule wins, so the last block doesn't carry 12px out of the answer. */
2392
+ .alt-md > :first-child { margin-top: 0; }
2393
+ .alt-md > :last-child { margin-bottom: 0; }
2394
+ .alt-md :is(li, blockquote) > :last-child { margin-bottom: 0; }
2395
+ .alt-md-h { margin: 20px 0 8px; color: var(--ink); font: 600 15px/23px var(--font-sans); }
2396
+ .alt-md-h[data-depth="1"] { font-size: 19px; line-height: 26px; letter-spacing: -0.01em; }
2397
+ .alt-md-h[data-depth="2"] { font-size: 17px; line-height: 24px; }
2398
+ .alt-md-list { padding-inline-start: 1.5em; }
2399
+ .alt-md-list li + li { margin-top: 4px; }
2400
+ .alt-md-list .alt-md-list { margin: 4px 0 0; }
2401
+ .alt-md-list li::marker { color: var(--ink-muted); }
2402
+ .alt-md-task-item { list-style: none; display: flex; gap: 8px; align-items: flex-start; margin-inline-start: -1.5em; }
2403
+ .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); }
2404
+ .alt-md-task[data-done] { background: var(--accent); box-shadow: none; }
2405
+ .alt-md-task-body { min-width: 0; }
2406
+ .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; }
2407
+ .alt-md-quote { margin-inline: 0; padding-inline-start: 12px; border-inline-start: 2px solid var(--line); color: var(--ink-muted); }
2408
+ .alt-md-hr { margin: 16px 0; }
2409
+ .alt-md-table { overflow-x: auto; outline: none; scrollbar-width: thin; }
2410
+ .alt-md-table:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--radius-md); }
2411
+ .alt-md-table .alt-table { width: auto; min-width: 100%; }
2412
+ .alt-md-table .alt-th, .alt-md-table .alt-td { padding: 8px 12px; }
2413
+ .alt-md del { color: var(--ink-muted); }
2414
+ .alt-md-img { position: relative; display: inline-block; max-width: 100%; vertical-align: top; }
2415
+ .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); }
2416
+ .alt-md-img[data-state="wait"] { width: min(100%, 320px); aspect-ratio: 16 / 9; }
2417
+ .alt-md-img[data-state="wait"] img { position: absolute; inset: 0; opacity: 0; translate: 0 2px; }
2418
+ .alt-md-img-imprint { position: absolute; inset: 0; border-radius: var(--radius-md); }
2419
+ .alt-md-img-failed { color: var(--ink-muted); font-style: italic; }
2420
+ @media (prefers-reduced-motion: reduce) { .alt-md-img img { transition: none; translate: none; } }
2421
+ [data-contrast="more"] .alt-md-code { box-shadow: inset 0 0 0 1px var(--line-control); }
2422
+ [data-contrast="more"] .alt-md-quote { border-inline-start-color: var(--line-control); }
2423
+ @media (forced-colors: active) {
2424
+ .alt-md-code, .alt-md-task { box-shadow: none; border: 1px solid CanvasText; }
2425
+ .alt-md-quote { border-inline-start-color: CanvasText; }
2426
+ .alt-md-task[data-done] { background: Highlight; color: HighlightText; }
2427
+ }
2428
+ .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; }
2429
+ @keyframes alt-caret { 50% { opacity: 0; } }
2430
+ .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); }
2431
+ .alt-msg-caption[data-tone="critical"] { color: var(--critical); }
2432
+ .alt-msg-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; margin-left: -6px; }
2433
+ .alt-msg-actions .alt-tgroup { gap: 2px; }
2434
+ .alt-msg-versions { display: inline-flex; align-items: center; gap: 2px; margin-right: 4px; }
2435
+ .alt-msg-version { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
2436
+ /* Editing a sent message (#275): the text stays where it is while a Textarea well opens around it, inside the sheet.
2437
+ Editing is local input, not a pending write, so nothing hovers; "Send edit" is secondary, leaving the composer's
2438
+ Send the view's one primary. */
2439
+ .alt-msg[data-from="user"][data-editing] { width: min(72ch, 85%); }
2440
+ .alt-msg-editor { display: flex; flex-direction: column; gap: 8px; }
2441
+ .alt-msg-editor .alt-textarea, .alt-msg-editor textarea { width: 100%; font: 400 15px/23px var(--font-sans); }
2442
+ .alt-msg-editor-actions { display: flex; justify-content: flex-end; gap: 6px; }
2443
+ .alt-msg[data-from="user"] .alt-msg-actions { justify-content: flex-end; margin-left: 0; margin-right: -6px; }
2444
+ .alt-msg[data-from="user"] .alt-msg-edited { justify-content: flex-end; }
2445
+ /* The queue (#276): written but not dispatched, so it doesn't hover; the person's sheet on the ground with a dashed
2446
+ 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. */
2447
+ .alt-convo-queue { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; margin: 0; padding: 0; list-style: none; }
2448
+ .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); }
2449
+ .alt-queued[data-editing] { width: min(72ch, 85%); padding-bottom: 8px; }
2450
+ .alt-queued-text { margin: 0; font: 400 15px/23px var(--font-sans); white-space: pre-wrap; overflow-wrap: anywhere; }
2451
+ .alt-queued-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 4px 12px; }
2452
+ .alt-queued-state { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
2453
+ .alt-queued-actions { display: inline-flex; gap: 2px; margin-right: -6px; }
2454
+ @media (forced-colors: active) { .alt-queued { border-color: CanvasText; } }
2455
+ /* Checkpoints (#277): a divider with a word between its lines, on the ground. While restoring, its label hovers as
2456
+ pending (after dur-grace); restored, it settles once; failed, it sinks into a trench. Restore is a ghost button. */
2457
+ .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); }
2458
+ .alt-checkpoint-line { flex: 1; min-width: 16px; height: 1px; background: var(--line); }
2459
+ .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); }
2460
+ .alt-checkpoint-label > * + *::before { content: "·"; margin-right: 6px; color: var(--ink-muted); }
2461
+ .alt-checkpoint-word { color: var(--ink); }
2462
+ .alt-checkpoint-name { overflow-wrap: anywhere; }
2463
+ .alt-checkpoint time { font-variant-numeric: tabular-nums; }
2464
+ .alt-checkpoint-actions { display: inline-flex; gap: 2px; }
2465
+ .alt-checkpoint-actions:empty { display: none; }
2466
+ .alt-checkpoint[data-phase="failed"] .alt-checkpoint-word { color: var(--critical); }
2467
+ [data-contrast="more"] .alt-checkpoint-line { background: var(--line-control); }
2468
+ @media (forced-colors: active) {
2469
+ .alt-checkpoint-line { background: CanvasText; }
2470
+ .alt-checkpoint-label[data-status="pending"] { outline: 1px dotted CanvasText; }
2471
+ .alt-checkpoint-label[data-status="failed"] { outline: 3px dashed CanvasText; }
2472
+ }
2473
+ .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; }
2474
+ @media (prefers-reduced-motion: reduce) { .alt-msg-caret { animation: none; } }
2475
+ [data-motion="reduced"] .alt-msg-caret { animation: none; }
2476
+ [data-contrast="more"] .alt-msg[data-stream] { box-shadow: inset 2px 0 0 0 var(--line-control); }
2477
+ [data-contrast="more"] .alt-msg[data-stream="streaming"] { box-shadow: none; background-size: 2px 8px; }
2478
+ @media (forced-colors: active) {
2479
+ .alt-msg[data-stream="streaming"] { background-image: none; border-left: 2px dotted CanvasText; }
2480
+ .alt-msg[data-stream="complete"], .alt-msg[data-stream="stopped"] { border-left: 2px solid CanvasText; }
2481
+ .alt-msg[data-from="user"] { border: 1px solid CanvasText; }
2482
+ .alt-msg-caret { forced-color-adjust: none; background: CanvasText; }
2483
+ }
2484
+
2485
+ /* ── Confirmation: a proposal from an agent, not yet dispatched. It's status that asks for action, so it's raised
2486
+ (level 1, Alert's raised rule), never pending: hovering would say the action is already in flight. Approving turns it
2487
+ into a pending write (hovers after dur-grace), which lands with the afterglow or drops to a trench. Rejected and
2488
+ expired are flush and muted, kept in the record. ── */
2489
+ .alt-confirm {
2490
+ display: flex; flex-direction: column; gap: 6px; padding: var(--space-4); border-radius: var(--radius-lg);
2491
+ background: var(--surface-ground); box-shadow: inset 0 0 0 1px var(--line); max-width: 560px;
2492
+ transition: background var(--dur-travel-2) var(--ease-rise), box-shadow var(--dur-travel-2) var(--ease-rise);
2493
+ }
2494
+ .alt-confirm[data-state="requested"] { background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1); }
2495
+ .alt-confirm[data-state="rejected"], .alt-confirm[data-state="expired"] { color: var(--ink-muted); }
2496
+ .alt-confirm[data-state="rejected"] .alt-confirm-title, .alt-confirm[data-state="expired"] .alt-confirm-title { color: var(--ink-muted); }
2497
+ .alt-confirm-line { display: flex; align-items: center; gap: 6px; margin: 0; font: 500 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2498
+ .alt-confirm-line[data-tone="accent"] { color: var(--accent-ink); }
2499
+ .alt-confirm-line[data-tone="critical"] { color: var(--critical); }
2500
+ .alt-confirm-title { margin: 0; font: 600 15px/21px var(--font-sans); color: var(--ink); }
2501
+ .alt-confirm-consequence { margin: 0; font: 400 14px/20px var(--font-sans); color: inherit; }
2502
+ .alt-confirm-details { margin-top: var(--space-2); }
2503
+ .alt-confirm-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }
2504
+ .alt-confirm-who { margin-right: auto; font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2505
+ [data-contrast="more"] .alt-confirm[data-state="requested"] { box-shadow: 0 3px 0 0 var(--ink), var(--elev-1); }
2506
+ /* Editing a proposal (#281): its fields open in place inside the raised card (wells), as EditRegion does; Reject and
2507
+ Approve stay; an edited proposal carries an "Edited" plate by its title. */
2508
+ .alt-confirm-edit { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-2); }
2509
+ .alt-confirm-edit-actions { display: flex; justify-content: flex-end; gap: var(--space-2); }
2510
+ .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;
2511
+ 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); }
2512
+ .alt-confirm-scope { display: flex; flex-direction: column; gap: 1px; padding-block: 6px; white-space: normal; }
2513
+ .alt-menu:has(.alt-confirm-scope) { min-width: min(300px, calc(100vw - 16px)); }
2514
+ .alt-menu-item:has(.alt-confirm-scope) { height: auto; min-height: var(--size-row-md, 32px); }
2515
+ .alt-confirm-scope-desc { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2516
+ @media (forced-colors: active) { .alt-confirm { border: 1px solid CanvasText; } }
2517
+ /* Question (#270): Confirmation's raised card asking for an answer rather than approval. Options are trenches (radio or
2518
+ checkbox), "Your own answer" a well; Answer is the one primary. Answered, it presses to the ground as one muted line. */
2519
+ .alt-question-options { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 0; }
2520
+ .alt-question-other { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
2521
+ .alt-question-other-label { font: 500 13px/18px var(--font-sans); color: var(--ink); }
2522
+ .alt-question[data-state="answered"], .alt-question[data-state="expired"] { padding-block: 8px; box-shadow: none; background: none; }
2523
+ .alt-question-record { display: flex; align-items: flex-start; gap: 6px; margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); }
2524
+ .alt-question-record svg { flex: none; margin-top: 2.5px; }
2525
+ .alt-question-asked { color: var(--ink); }
2526
+ .alt-question-asked::after { content: " ·"; color: var(--ink-muted); }
2527
+ .alt-question-set { display: flex; flex-direction: column; gap: var(--space-3); max-width: 560px; }
2528
+ .alt-question-set > .alt-confirm { max-width: none; }
2529
+
2530
+ /* ── Trace: an agent's steps as a vertical Stepper. Done steps are flush with a stamped check, the running step stands
2531
+ raised on its wall (no spinner: a small steady lamp in its mark), planned steps are imprints. A failed step drops to a
2532
+ trench with a critical rim and Retry. A step waiting for approval holds a Confirmation. Tool calls and reasoning sit
2533
+ behind Accordion lids under their step, indented to the text. ── */
2534
+ .alt-trace .alt-step { width: 100%; }
2535
+ .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); }
2536
+ .alt-trace .alt-step[data-state="failed"] .alt-step-mark { background: var(--critical); color: var(--on-critical); }
2537
+ .alt-trace-live { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--accent); }
2538
+ .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); }
2539
+ /* Indented to the step's text where there's room; in a narrow trace (under 480px) one small step in, so tool calls and
2540
+ approvals keep the width (a percentage margin reads the step's width: 40px at 480px, 12px from 452px down). */
2541
+ .alt-trace-detail { display: flex; flex-direction: column; gap: var(--space-2); margin: 6px 0 0 clamp(var(--space-3), 100% - 440px, 40px); }
2542
+ /* Subagents (#278): a nested run is a nested list behind an Accordion lid that says how it's going; steps that run at
2543
+ once sit in a labelled group with a 1px line-control bracket, several raised at once because each is in play. */
2544
+ .alt-trace-nested-title { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
2545
+ .alt-trace-nested-sum { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2546
+ .alt-trace-nested .alt-trace { padding: 4px 0 0; }
2547
+ .alt-trace-parallel { margin: 6px 0 0; padding: 2px 0 2px 12px; border-left: 1px solid var(--line-control); }
2548
+ .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); }
2549
+ .alt-trace-parallel-head > :first-child { color: var(--ink); }
2550
+ @media (forced-colors: active) { .alt-trace-parallel { border-left-color: CanvasText; } }
2551
+ .alt-trace-acc .alt-acc-trigger { padding-block: 6px; font-size: 13px; }
2552
+ .alt-trace-tool code { font: 500 12.5px/18px var(--font-mono); }
2553
+ .alt-trace-io { display: flex; flex-direction: column; gap: var(--space-2); }
2554
+ .alt-trace-wait, .alt-trace-reasoning { margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); white-space: pre-wrap; }
2555
+ /* Reasoning while it streams (#279): not yet the record, like a streaming answer: "Thinking…" on the lid, and a dashed
2556
+ line-control rule down the open compartment. No shimmer, no pulsing dots, no ticking counter; the lid's words
2557
+ cross-fade when it completes. */
2558
+ .alt-trace-thought-title { animation: alt-fade-in var(--dur-travel-1) var(--ease-rise) both; }
2559
+ .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); }
2560
+ .alt-trace-reasoning-hidden { margin: 0; padding: 4px 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); }
2561
+ @media (prefers-reduced-motion: reduce) { .alt-trace-thought-title { animation: none; } }
2562
+ [data-motion="reduced"] .alt-trace-thought-title { animation: none; }
2563
+ @media (forced-colors: active) { .alt-trace-thought[data-status="streaming"] .alt-trace-reasoning { border-left: 1px dotted CanvasText; } }
2564
+ .alt-trace-item:last-child .alt-step-line { display: none; }
2565
+ @media (forced-colors: active) { .alt-step[data-state="failed"] { border: 3px dashed CanvasText; } }
2566
+
2567
+ /* ── AILabel: marks what AI made. A label is part of the record, so it's a flush Badge plate with a small static icon
2568
+ and a word: no gradient, no glow, no shimmer (the light never moves, and a gradient rim reads as focus). It acts, so
2569
+ it lifts one level when pointed at, and opens a Popover at level 3 saying how the content was produced. ── */
2570
+ .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); }
2571
+ .alt-ailabel svg { flex: none; color: var(--ink-muted); }
2572
+ .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))); }
2573
+ @media (hover: hover) { .alt-ailabel:hover { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
2574
+ .alt-ailabel:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
2575
+ .alt-ailabel-pop { width: min(320px, calc(100vw - 32px)); }
2576
+ .alt-ailabel-body { display: flex; flex-direction: column; gap: var(--space-2); }
2577
+ .alt-ailabel-title { margin: 0; font: 600 14px/20px var(--font-sans); }
2578
+ .alt-ailabel-sub { margin: var(--space-1) 0 0; font: 600 12.5px/17px var(--font-sans); color: var(--ink-muted); }
2579
+ .alt-ailabel-facts { display: grid; gap: 4px; margin: 0; font: 400 13px/18px var(--font-sans); }
2580
+ .alt-ailabel-facts div { display: grid; grid-template-columns: 96px 1fr; gap: var(--space-2); }
2581
+ .alt-ailabel-facts dt { color: var(--ink-muted); }
2582
+ .alt-ailabel-facts dd { margin: 0; color: var(--ink); }
2583
+ .alt-ailabel-sources { margin: 0; padding-left: 1.3em; font: 400 13px/19px var(--font-sans); }
2584
+ .alt-ailabel-more { font: 400 13px/18px var(--font-sans); color: var(--ink-muted); }
2585
+ [data-contrast="more"] .alt-ailabel { box-shadow: inset 0 0 0 1px var(--line-control); }
2586
+ @media (forced-colors: active) { .alt-ailabel { border: 1px solid ButtonText; } }
2587
+
2588
+ /* ── InlineCitation: a numbered source marker in generated text. A citation is part of the record, so it's a small flush
2589
+ plate (ChartAnnotation's build) with a mono number, raised a little off the baseline like a superscript; it acts, so it
2590
+ lifts one level when pointed at, and a HoverCard previews the source at level 3. Sources is a flush ordered list. ── */
2591
+ .alt-cite {
2592
+ display: inline-flex; align-items: center; gap: 2px; min-width: 16px; height: 16px; padding: 0 4px; margin: 0 1px; border-radius: var(--radius-xs);
2593
+ vertical-align: 0.35em; background: color-mix(in oklab, var(--surface-ground), var(--surface-well) 70%); color: var(--ink-muted);
2594
+ 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;
2595
+ 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);
2596
+ }
2597
+ .alt-cite:focus-visible { background: var(--surface-raised); color: var(--accent-ink); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
2598
+ @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))); } }
2599
+ .alt-cite:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
2600
+ .alt-cite[data-unverified] svg { color: var(--caution); }
2601
+ .alt-cite-card { width: min(300px, calc(100vw - 32px)); }
2602
+ .alt-cite-preview { display: flex; flex-direction: column; gap: 4px; }
2603
+ .alt-cite-meta { margin: 0; font: 500 12px/16px var(--font-sans); color: var(--ink-muted); }
2604
+ .alt-cite-title { margin: 0; font: 600 14px/20px var(--font-sans); }
2605
+ .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); }
2606
+ .alt-cite-warn { display: inline-flex; align-items: center; gap: 4px; margin-left: 6px; color: var(--ink); font: 500 12px/16px var(--font-sans); }
2607
+ .alt-cite-meta .alt-cite-warn { margin-left: 0; }
2608
+ .alt-cite-warn svg { color: var(--caution); }
2609
+ .alt-sources { margin-top: var(--space-3); }
2610
+ .alt-sources-heading { margin: 0 0 6px; font: 600 13px/18px var(--font-sans); color: var(--ink-muted); }
2611
+ .alt-sources-list { margin: 0; padding-left: 1.4em; font: 400 13.5px/20px var(--font-sans); color: var(--ink); }
2612
+ .alt-sources-list li { scroll-margin: 40px; border-radius: var(--radius-xs); outline: none; }
2613
+ .alt-sources-list li:target, .alt-sources-list li:focus { background: var(--accent-soft); }
2614
+ .alt-sources-domain { color: var(--ink-muted); }
2615
+ [data-contrast="more"] .alt-cite { box-shadow: inset 0 0 0 1px var(--line-control); }
2616
+ @media (forced-colors: active) { .alt-cite { border: 1px solid LinkText; } }
2617
+
1099
2618
  /* ── Pile: thickness says roughly how many (ordinal), the badge says exactly. Click a pile that has items and they
1100
2619
  slide out from under the top card, floating over the page below (level 3) without moving anything. Click the top,
1101
2620
  press Escape or click away and they slide back under. ── */
@@ -1111,8 +2630,10 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1111
2630
  }
1112
2631
  .alt-pile-live .alt-pile-top { cursor: pointer; }
1113
2632
  /* Hover: the top lifts and the sheets beneath fan a little, a preview of what's underneath. */
1114
- .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))); }
1115
- .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); }
2633
+ .alt-pile-live .alt-pile-top:focus-visible { box-shadow: var(--elev-2); transform: translateY(calc(-3px * var(--alt-travel))); }
2634
+ @media (hover: hover) { .alt-pile-live .alt-pile-top:hover { box-shadow: var(--elev-2); transform: translateY(calc(-3px * var(--alt-travel))); } }
2635
+ .alt-pile-live:has(.alt-pile-top:focus-visible) .alt-pile-sheet { --fan: var(--alt-travel); }
2636
+ @media (hover: hover) { .alt-pile-live:has(.alt-pile-top:hover) .alt-pile-sheet { --fan: var(--alt-travel); } }
1116
2637
  .alt-pile-live .alt-pile-top:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1117
2638
  .alt-pile-count {
1118
2639
  position: absolute; z-index: 5; top: -8px; right: -8px; min-width: 22px; height: 22px; padding: 0 6px; display: grid; place-items: center;
@@ -1142,9 +2663,12 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1142
2663
  opacity: 1; transform: none;
1143
2664
  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);
1144
2665
  }
1145
- .alt-pile-list[data-state="open"] .alt-pile-item:hover, .alt-pile-list[data-state="open"] .alt-pile-item:focus-visible {
2666
+ .alt-pile-list[data-state="open"] .alt-pile-item:focus-visible {
1146
2667
  box-shadow: var(--elev-3); transform: translateY(calc(-2px * var(--alt-travel))); transition-delay: 0ms;
1147
2668
  }
2669
+ @media (hover: hover) { .alt-pile-list[data-state="open"] .alt-pile-item:hover {
2670
+ box-shadow: var(--elev-3); transform: translateY(calc(-2px * var(--alt-travel))); transition-delay: 0ms;
2671
+ } }
1148
2672
  .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; }
1149
2673
  .alt-pile-item:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1150
2674
 
@@ -1155,7 +2679,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1155
2679
  transition: box-shadow var(--dur-travel-2) var(--ease-rise);
1156
2680
  }
1157
2681
  .alt-scroll[data-top] .alt-scroll-header { box-shadow: var(--elev-2); }
1158
- .alt-scroll-body { overflow: auto; overscroll-behavior: contain; outline: none; scrollbar-width: thin; }
2682
+ .alt-scroll-body { overflow: auto; outline: none; scrollbar-width: thin; }
1159
2683
  .alt-scroll-body:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
1160
2684
  .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; }
1161
2685
  .alt-scroll-lip[data-edge="top"] { top: 0; box-shadow: var(--lip-top); }
@@ -1165,12 +2689,12 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1165
2689
 
1166
2690
  /* ── DropZone: flush until a file is in flight anywhere, then it opens; over it, it deepens. ── */
1167
2691
  .alt-drop {
1168
- display: grid; place-items: center; gap: var(--space-2); min-height: 132px; padding: var(--space-5); text-align: center;
2692
+ display: grid; place-items: center; gap: var(--space-2); min-height: 132px; padding: var(--alt-inset-box); text-align: center;
1169
2693
  border: 1.5px dashed var(--line-control); border-radius: var(--radius-lg); background: var(--surface-ground); box-shadow: var(--elev-0);
1170
2694
  color: var(--ink-muted); font: 500 14px/20px var(--font-sans); cursor: pointer;
1171
2695
  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);
1172
2696
  }
1173
- .alt-drop:hover { border-color: var(--ink-muted); }
2697
+ @media (hover: hover) { .alt-drop:hover { border-color: var(--ink-muted); } }
1174
2698
  .alt-drop:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1175
2699
  .alt-drop[data-drop="armed"] { background: var(--surface-well); box-shadow: var(--well); border-color: var(--ink-muted); }
1176
2700
  .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))); }
@@ -1182,7 +2706,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1182
2706
  It sinks in when it appears (flush to well, dur-travel-1, ease-sink), the inverse of content arriving lifted,
1183
2707
  and it never pulses. ── */
1184
2708
  .alt-empty {
1185
- display: flex; flex-direction: column; align-items: center; gap: var(--space-2); padding: var(--space-8) var(--space-6); text-align: center;
2709
+ display: flex; flex-direction: column; align-items: center; gap: var(--space-2); padding: var(--space-8) var(--alt-inset-page); text-align: center;
1186
2710
  border: 1.5px dashed var(--line-control); border-radius: var(--radius-lg); background: var(--surface-well); box-shadow: var(--well);
1187
2711
  color: var(--ink-muted); font: 400 15px/23px var(--font-sans);
1188
2712
  animation: alt-empty-sink var(--dur-travel-1) var(--ease-sink);
@@ -1194,7 +2718,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1194
2718
  .alt-empty-title { margin: 0; font: 600 19px/26px var(--font-sans); letter-spacing: -0.01em; color: var(--ink); text-wrap: balance; }
1195
2719
  .alt-empty-desc { margin: 0; max-width: 46ch; color: var(--ink-muted); text-wrap: pretty; }
1196
2720
  .alt-empty-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); margin-top: var(--space-3); }
1197
- .alt-empty:has(.alt-empty-actions > :first-child:is(:hover, :focus-visible):not(:disabled)) { box-shadow: var(--trench); border-color: var(--ink-muted); }
2721
+ @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); } }
1198
2722
  .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); }
1199
2723
  .alt-empty[data-size="sm"] .alt-empty-icon > svg { width: 18px; height: 18px; }
1200
2724
  .alt-empty[data-size="sm"] .alt-empty-title { font: 500 14px/20px var(--font-sans); letter-spacing: 0; }
@@ -1205,21 +2729,29 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1205
2729
  .alt-sort-item {
1206
2730
  position: relative; z-index: 1; display: flex; align-items: center; gap: var(--space-3); padding: 10px var(--space-4) 10px var(--space-3);
1207
2731
  border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--elev-1); color: var(--ink);
1208
- font: 500 14px/20px var(--font-sans); user-select: none; touch-action: none; cursor: grab; outline: none;
2732
+ 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;
1209
2733
  transition: transform var(--dur-travel-2) var(--ease-settle), box-shadow var(--dur-travel-2) var(--ease-rise), background-color var(--dur-travel-1);
1210
2734
  }
1211
- .alt-sort-item:hover { box-shadow: var(--elev-2); }
2735
+ @media (hover: hover) { .alt-sort-item:hover { box-shadow: var(--elev-2); } }
1212
2736
  .alt-sort-item:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1213
2737
  .alt-sort-item[data-held] { z-index: 3; cursor: grabbing; background: var(--surface-floating); box-shadow: var(--elev-3); }
1214
2738
  .alt-sort-item[data-held]:not([data-following]) { transform: translateY(calc(-2px * var(--alt-travel))) rotate(calc(-0.5deg * var(--alt-travel))); }
1215
2739
  .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)); }
1216
2740
  .alt-sort-grip { display: inline-flex; color: var(--ink-muted); flex: none; }
2741
+ /* 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). */
2742
+ .alt-sort-grip { position: relative; touch-action: none; }
2743
+ @media (pointer: coarse) { .alt-sort-grip::after { content: ""; position: absolute; inset: -14px -14px; } }
1217
2744
  .alt-sort-body { flex: 1; min-width: 0; }
1218
2745
  .alt-sort-slot {
1219
2746
  position: absolute; left: 0; right: 0; z-index: 0; border-radius: var(--radius-md); background: var(--surface-well); box-shadow: var(--well);
1220
2747
  border: 1.5px dashed var(--line-control); transition: top var(--dur-travel-2) var(--ease-settle);
1221
2748
  }
1222
2749
 
2750
+ /* ── Breadcrumb and Drilldown share one staircase (CrumbStep): every step stands on the ground line, each deeper one a
2751
+ taller slab. Breadcrumb's steps are links (or your router's), the current page a step that doesn't press, and a deep
2752
+ path's middle waits behind a "…" step that opens a menu. A route change re-renders it without motion. ── */
2753
+ .alt-breadcrumb ol { padding-top: 14px; padding-bottom: 4px; }
2754
+
1223
2755
  /* ── Drilldown: hierarchy as a stack. Each place you go into is laid on top of the last: the crumbs climb a
1224
2756
  staircase (each one step higher, its wall one pixel thicker), and the view sits on a pile as thick as you are deep. ── */
1225
2757
  .alt-drill { display: flex; flex-direction: column; gap: var(--space-4); }
@@ -1228,10 +2760,10 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1228
2760
  higher, so all the contact shadows line up. Face and wall share one hairline so the top edge always reads.
1229
2761
  Shadow list: [face ring, wall, wall ring, contact, ambient]. */
1230
2762
  .alt-crumbs li { position: relative; display: flex; --rise: calc(var(--d) * 3px); }
1231
- .alt-crumbs button {
2763
+ .alt-crumbs .alt-crumb-link {
1232
2764
  --wd: calc(2px + var(--rise)); --wall: var(--wd); --wall-c: color-mix(in oklab, var(--surface-trench), var(--ink) 6%);
1233
- position: relative; height: 30px; padding: 0 12px; border: 0; border-radius: var(--radius-sm);
1234
- font: 500 13px/18px var(--font-sans); color: var(--ink-muted); white-space: nowrap; cursor: pointer;
2765
+ position: relative; display: inline-flex; align-items: center; height: var(--size-control-sm); padding: 0 12px; border: 0; border-radius: var(--radius-sm);
2766
+ font: 500 13px/18px var(--font-sans); color: var(--ink-muted); white-space: nowrap; text-decoration: none; cursor: pointer;
1235
2767
  background: var(--surface-raised); background-image: var(--alt-sheen);
1236
2768
  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),
1237
2769
  0 calc(var(--wd) + 1px) 2px 0 var(--alt-cast), 0 calc(var(--wd) + 3px) 8px -3px var(--alt-cast);
@@ -1239,12 +2771,15 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1239
2771
  transition: transform var(--dur-travel-2) var(--ease-settle), box-shadow var(--dur-travel-2) var(--ease-settle),
1240
2772
  color var(--dur-travel-1), background-color var(--dur-travel-1);
1241
2773
  }
1242
- .alt-crumbs li:not([data-current]) button:hover, .alt-crumbs li:not([data-current]) button:focus-visible {
2774
+ .alt-crumbs li:not([data-current]) .alt-crumb-link:focus-visible {
1243
2775
  --wd: calc(3px + var(--rise)); color: var(--ink); background-color: var(--surface-lifted);
1244
2776
  }
1245
- .alt-crumbs li:not([data-current]) button:active { --wd: 0px; background-image: none; transition-duration: var(--dur-press); transition-timing-function: ease-out; }
1246
- .alt-crumbs li[data-current] button { color: var(--ink); font-weight: 600; cursor: default; }
1247
- .alt-crumbs li[data-new] button { animation: alt-crumb-rise var(--dur-travel-3) var(--ease-settle); }
2777
+ @media (hover: hover) { .alt-crumbs li:not([data-current]) .alt-crumb-link:hover {
2778
+ --wd: calc(3px + var(--rise)); color: var(--ink); background-color: var(--surface-lifted);
2779
+ } }
2780
+ .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; }
2781
+ .alt-crumbs li[data-current] .alt-crumb-link { color: var(--ink); font-weight: 600; cursor: default; }
2782
+ .alt-crumbs li[data-new] .alt-crumb-link { animation: alt-crumb-rise var(--dur-travel-3) var(--ease-settle); }
1248
2783
  /* A new step is built: it rises out of the ground to its height. */
1249
2784
  @keyframes alt-crumb-rise {
1250
2785
  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); }
@@ -1268,7 +2803,8 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1268
2803
  background: transparent; color: var(--ink); text-align: left; cursor: pointer; box-shadow: var(--elev-0);
1269
2804
  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);
1270
2805
  }
1271
- .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))); }
2806
+ .alt-drill-row:focus-visible { background: var(--surface-lifted); box-shadow: var(--elev-2); transform: translateY(calc(-1px * var(--alt-travel))); }
2807
+ @media (hover: hover) { .alt-drill-row:hover { background: var(--surface-lifted); box-shadow: var(--elev-2); transform: translateY(calc(-1px * var(--alt-travel))); } }
1272
2808
  .alt-drill-row:active { box-shadow: var(--elev-0); transform: translateY(calc(1px * var(--alt-travel))); transition-duration: var(--dur-press); }
1273
2809
  .alt-drill-row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1274
2810
  .alt-drill-row-text { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
@@ -1286,7 +2822,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1286
2822
  change, rows that survive travel to their new places (FLIP, ease-glide) and new ones rise 4px in, at most four at a
1287
2823
  time (riseDelay) and only near the top; the rest cross-fade. On opening, predicted items are there at once and the
1288
2824
  rest fade in over dur-onset. ── */
1289
- .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); }
2825
+ .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); }
1290
2826
  .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); }
1291
2827
  .alt-cmd {
1292
2828
  position: relative; display: flex; flex-direction: column; width: min(100%, 600px); max-height: 100%; overflow: hidden; outline: none;
@@ -1301,7 +2837,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1301
2837
  .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)); }
1302
2838
  .alt-cmd .alt-cmd-field input { font-size: 16px; }
1303
2839
  .alt-cmd .alt-cmd-field:focus-within, .alt-cmd .alt-cmd-field:has(input:not(:placeholder-shown)) { background: var(--surface-modal); }
1304
- .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; }
2840
+ .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; }
1305
2841
  .alt-cmd-viewport[data-ready] { transition: height var(--dur-travel-2) var(--ease-glide); }
1306
2842
  .alt-cmd-list { position: relative; padding: 0 var(--space-2) var(--space-2); outline: none; }
1307
2843
  .alt-cmd-group + .alt-cmd-group { margin-top: var(--space-1); }
@@ -1312,7 +2848,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1312
2848
  }
1313
2849
  .alt-cmd-hl[data-tone="critical"] { background: var(--critical-soft); }
1314
2850
  .alt-cmd-item {
1315
- position: relative; display: flex; align-items: center; gap: 10px; min-height: 40px; padding: 0 10px; border-radius: var(--radius-sm);
2851
+ position: relative; display: flex; align-items: center; gap: 10px; min-height: var(--size-field-md); padding: 0 10px; border-radius: var(--radius-sm);
1316
2852
  font: 500 14px/20px var(--font-sans); color: var(--ink); cursor: pointer; user-select: none;
1317
2853
  }
1318
2854
  .alt-cmd-item[data-tone="critical"] { color: var(--critical); }
@@ -1340,7 +2876,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1340
2876
  8px clear of its edge so its own edges, and the shadow it casts below them, read as height. While content scrolls
1341
2877
  under its header the header rises; where content continues below, the footer's top edge curves away. A bottom
1342
2878
  sheet has a finger pull: a small recess (an opening, like every place you put something into) to drag it down by. ── */
1343
- .alt-sheet-root { position: fixed; inset: 0; z-index: var(--z-overlay); pointer-events: none; }
2879
+ .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; }
1344
2880
  .alt-sheet-root > .alt-scrim { pointer-events: auto; }
1345
2881
  .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); }
1346
2882
  .alt-sheet {
@@ -1351,10 +2887,10 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1351
2887
  }
1352
2888
  .alt-sheet[data-size="sm"] { --w: 360px; }
1353
2889
  .alt-sheet[data-size="lg"] { --w: 560px; }
1354
- .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)); }
1355
- .alt-sheet[data-side="right"] { right: var(--edge); transform-origin: 100% 50%; }
1356
- .alt-sheet[data-side="left"] { left: var(--edge); transform-origin: 0 50%; }
1357
- .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%; }
2890
+ .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)); }
2891
+ .alt-sheet[data-side="right"] { right: calc(var(--edge) + env(safe-area-inset-right, 0px)); transform-origin: 100% 50%; }
2892
+ .alt-sheet[data-side="left"] { left: calc(var(--edge) + env(safe-area-inset-left, 0px)); transform-origin: 0 50%; }
2893
+ .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%; }
1358
2894
  .alt-sheet[data-side="bottom"][data-size="sm"] { --w: 480px; }
1359
2895
  .alt-sheet[data-side="bottom"][data-size="lg"] { --w: 960px; }
1360
2896
  .alt-sheet-root[data-state="closed"] .alt-sheet { opacity: 0; box-shadow: var(--elev-1); }
@@ -1366,19 +2902,19 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1366
2902
  transition-duration: calc(var(--dur-travel-4) * 0.7); transition-timing-function: var(--ease-sink);
1367
2903
  }
1368
2904
  .alt-sheet-scroll { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
1369
- .alt-sheet-inner { display: flex; flex-direction: column; gap: var(--space-4); min-height: 100%; padding: 0 var(--space-6); }
2905
+ .alt-sheet-inner { display: flex; flex-direction: column; gap: var(--space-4); min-height: 100%; padding: 0 var(--alt-inset-page); }
1370
2906
  .alt-sheet-header {
1371
- position: sticky; top: 0; z-index: 2; display: flex; flex-direction: column; gap: 6px; margin: 0 calc(-1 * var(--space-6));
1372
- padding: var(--space-6) calc(var(--space-6) + 36px) var(--space-2) var(--space-6); background: var(--surface-overlay); box-shadow: var(--elev-0);
2907
+ position: sticky; top: 0; z-index: 2; display: flex; flex-direction: column; gap: 6px; margin: 0 calc(-1 * var(--alt-inset-page));
2908
+ 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);
1373
2909
  transition: box-shadow var(--dur-travel-2) var(--ease-rise);
1374
2910
  }
1375
2911
  .alt-sheet[data-top] .alt-sheet-header { box-shadow: var(--elev-2); }
1376
2912
  .alt-sheet-title { margin: 0; font: 600 19px/26px var(--font-sans); letter-spacing: -0.01em; }
1377
2913
  .alt-sheet-desc { margin: 0; font: 400 14px/21px var(--font-sans); color: var(--ink-muted); }
1378
- .alt-sheet-inner > :last-child:not(.alt-sheet-footer) { margin-bottom: var(--space-6); }
2914
+ .alt-sheet-inner > :last-child:not(.alt-sheet-footer) { margin-bottom: var(--alt-inset-page); }
1379
2915
  .alt-sheet-footer {
1380
- 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;
1381
- padding: var(--space-4) var(--space-6) var(--space-6); background: var(--surface-overlay);
2916
+ 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;
2917
+ padding: var(--space-4) var(--alt-inset-page) var(--alt-inset-page); background: var(--surface-overlay);
1382
2918
  }
1383
2919
  .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); }
1384
2920
  .alt-sheet[data-bottom] .alt-sheet-footer::before { opacity: 1; }
@@ -1493,9 +3029,12 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1493
3029
  .alt-step-desc { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
1494
3030
  .alt-step[role="button"] { cursor: pointer; }
1495
3031
  .alt-step[role="button"][data-state="done"] { --wall: 0px; }
1496
- .alt-step[role="button"][data-state="done"]:hover, .alt-step[role="button"][data-state="done"]:focus-visible {
3032
+ .alt-step[role="button"][data-state="done"]:focus-visible {
1497
3033
  --wall: var(--wall-1); --st-ring: var(--alt-ring); --st-cast: var(--alt-cast); background: color-mix(in oklab, var(--surface-raised) 60%, transparent);
1498
3034
  }
3035
+ @media (hover: hover) { .alt-step[role="button"][data-state="done"]:hover {
3036
+ --wall: var(--wall-1); --st-ring: var(--alt-ring); --st-cast: var(--alt-cast); background: color-mix(in oklab, var(--surface-raised) 60%, transparent);
3037
+ } }
1499
3038
  .alt-step[role="button"][data-state="done"]:active { --wall: 0px; transition-duration: var(--dur-press); transition-timing-function: ease-out; }
1500
3039
  .alt-step[role="button"]:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1501
3040
  .alt-stepper[data-orientation="vertical"] { flex-direction: column; align-items: stretch; flex-wrap: nowrap; padding: 4px 0 0; }
@@ -1513,17 +3052,31 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1513
3052
  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
1514
3053
  single slab by its first cell, as wide as the table (--alt-row-w, measured). ── */
1515
3054
  .alt-data-table { min-width: 0; }
1516
- .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); }
1517
- .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); }
3055
+ .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); }
3056
+ .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); }
1518
3057
  .alt-table-selection .alt-badge[data-variant="count"] { position: static; }
1519
- .alt-table-tools-end { display: inline-flex; align-items: center; gap: var(--space-2); margin-left: auto; }
1520
- .alt-table-wrap { position: relative; isolation: isolate; overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; outline: none; }
3058
+ .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; }
3059
+ /* 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. */
3060
+ .alt-table-tools-end > .alt-input { flex: 0 1 auto; min-width: 0; }
3061
+ .alt-table-tools-end > .alt-input input { min-width: 0; }
3062
+ .alt-table-wrap { position: relative; isolation: isolate; overflow: auto; scrollbar-width: thin; outline: none; }
1521
3063
  .alt-table-wrap:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--radius-md); }
1522
3064
  .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); }
1523
3065
  .alt-table-wrap[data-bottom]::after { opacity: 1; }
1524
3066
  .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; }
1525
3067
  .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); }
1526
3068
  .alt-th[data-align="end"], .alt-td[data-align="end"] { text-align: right; }
3069
+ /* Narrow (#260): columns keep a readable width and the table scrolls inside its frame. A header never wraps, and a
3070
+ cell's min-width (from minWidth, or 6rem when the table is narrow) stops a column being squeezed to "Des…". */
3071
+ .alt-table-wrap { container-type: inline-size; }
3072
+ @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; } }
3073
+ /* A sticky column stays put over a flush 1px edge, on the ground surface (no shadow: it isn't live). */
3074
+ .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); }
3075
+ .alt-table .alt-th[data-sticky] { z-index: 5; }
3076
+ /* The edge with more columns fades; the start edge only without a sticky column, which already marks it. */
3077
+ .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); }
3078
+ .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); }
3079
+ .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); }
1527
3080
  .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; }
1528
3081
  .alt-td:first-child { position: relative; }
1529
3082
  .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); }
@@ -1535,17 +3088,18 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1535
3088
  .alt-table-wrap[data-top] .alt-table-head .alt-tr { --row-bg: var(--surface-raised); --row-sh: var(--elev-2); }
1536
3089
  .alt-table-wrap[data-top] .alt-th { border-bottom-color: transparent; }
1537
3090
  .alt-tr[data-interactive] { cursor: pointer; }
1538
- .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))); }
3091
+ .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))); }
3092
+ @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))); } }
1539
3093
  .alt-tr[data-interactive]:not([data-row-held]):active { --row-sh: var(--elev-0); transform: none; transition-duration: var(--dur-press); }
1540
3094
  .alt-tr[data-row-relevance="match"] { --row-bg: var(--surface-raised); --row-sh: var(--elev-1); }
1541
3095
  .alt-tr[data-row-relevance="miss"] { --row-bg: var(--surface-well); --row-sh: var(--well); color: var(--ink-muted); }
1542
3096
  .alt-tr[data-selected] { --row-bg: var(--accent-soft); --row-sh: var(--elev-1); }
1543
- .alt-tr[data-selected][data-interactive]:not([data-row-held]):hover { --row-bg: var(--accent-soft); --row-sh: var(--elev-2); }
3097
+ @media (hover: hover) { .alt-tr[data-selected][data-interactive]:not([data-row-held]):hover { --row-bg: var(--accent-soft); --row-sh: var(--elev-2); } }
1544
3098
  .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; }
1545
3099
  .alt-tr[data-row-held][data-selected] { --row-bg: color-mix(in oklab, var(--accent-soft) 70%, var(--surface-floating)); }
1546
3100
  .alt-tr[data-row-held]:not([data-following]) { transform: translateY(calc(-3px * var(--alt-travel))); }
1547
- .alt-tr:is([data-interactive]:hover, [data-selected], [data-row-relevance], [data-row-held]) > .alt-td,
1548
3101
  .alt-tr:has(+ .alt-tr:is([data-selected], [data-row-relevance], [data-row-held])) > .alt-td { border-bottom-color: transparent; }
3102
+ @media (hover: hover) { .alt-tr:is([data-interactive]:hover, [data-selected], [data-row-relevance], [data-row-held]) > .alt-td { border-bottom-color: transparent; } }
1549
3103
  .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; }
1550
3104
  .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; }
1551
3105
  .alt-table[data-dragging] .alt-tr:not([data-following]) { transition: transform var(--dur-travel-2) var(--ease-settle); }
@@ -1554,11 +3108,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1554
3108
  background: none; color: inherit; font: inherit; cursor: pointer; box-shadow: var(--elev-0);
1555
3109
  transition: background-color var(--dur-travel-1), color var(--dur-travel-1), box-shadow var(--dur-travel-1) var(--ease-rise);
1556
3110
  }
1557
- .alt-table-sort:hover { color: var(--ink); background: var(--surface-raised); box-shadow: var(--elev-1); }
3111
+ @media (hover: hover) { .alt-table-sort:hover { color: var(--ink); background: var(--surface-raised); box-shadow: var(--elev-1); } }
1558
3112
  .alt-table-sort:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1559
3113
  .alt-table-sort:not([data-sort="none"]) { color: var(--ink); }
1560
3114
  .alt-table-sort-icon { opacity: 0; transition: transform var(--dur-travel-2) var(--ease-settle), opacity var(--dur-travel-1); }
1561
- .alt-table-sort:hover .alt-table-sort-icon { opacity: 0.45; }
3115
+ @media (hover: hover) { .alt-table-sort:hover .alt-table-sort-icon { opacity: 0.45; } }
1562
3116
  .alt-table-sort:not([data-sort="none"]) .alt-table-sort-icon { opacity: 1; }
1563
3117
  .alt-table-sort[data-sort="descending"] .alt-table-sort-icon { transform: rotate(180deg); }
1564
3118
  .alt-th[data-align="end"] .alt-table-sort { flex-direction: row-reverse; }
@@ -1566,7 +3120,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1566
3120
  .alt-th-check .alt-check, .alt-td-check .alt-check { display: inline-flex; vertical-align: middle; }
1567
3121
  .alt-th-grip, .alt-td-grip { width: 24px; padding-right: 0; }
1568
3122
  .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; }
1569
- .alt-table-grip:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 7%, transparent); }
3123
+ @media (hover: hover) { .alt-table-grip:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 7%, transparent); } }
1570
3124
  .alt-table-grip:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
1571
3125
  .alt-tr[data-row-held] .alt-table-grip { cursor: grabbing; color: var(--ink); }
1572
3126
  .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); }
@@ -1602,11 +3156,11 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1602
3156
  .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); }
1603
3157
  .alt-board-card {
1604
3158
  position: relative; padding: 10px 12px; border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--elev-1);
1605
- color: var(--ink); font: 500 14px/20px var(--font-sans); cursor: grab; user-select: none; outline: none;
3159
+ 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;
1606
3160
  transition: box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1), transform var(--dur-travel-2) var(--ease-settle),
1607
3161
  rotate var(--dur-travel-2) var(--ease-settle), scale var(--dur-travel-2) var(--ease-settle);
1608
3162
  }
1609
- .alt-board-card:hover:not([data-carried]):not([data-held]) { box-shadow: var(--elev-2); }
3163
+ @media (hover: hover) { .alt-board-card:hover:not([data-carried]):not([data-held]) { box-shadow: var(--elev-2); } }
1610
3164
  .alt-board-card:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
1611
3165
  .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)); }
1612
3166
  .alt-board-card[data-carried]:not([data-following]) { transform: translateY(calc(-3px * var(--alt-travel))); }
@@ -1634,6 +3188,565 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1634
3188
  .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; }
1635
3189
  .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; }
1636
3190
 
3191
+ /* ════════════════════ Charts ════════════════════ */
3192
+
3193
+ /* ── ChartContainer: the frame every chart shares. A chart is the record drawn as a shape, so everything in the frame
3194
+ lies flush on whatever it sits on, the page or a Card's surface-raised: no level, no shadow, no wall, no sheen. Amounts
3195
+ are length and position on a flat plot standing on the ground line (1px ink-muted, as under Stepper and Drilldown),
3196
+ never height. Gridlines are decorative line hairlines, horizontal only; tick labels are caption text in ink-muted
3197
+ with tabular figures. Marks wear data-* fills and strokes and nothing else. On resize it lays out again without
3198
+ animating. ── */
3199
+ .alt-chart { margin: 0; min-width: 0; display: flex; flex-direction: column; gap: var(--space-3); color: var(--ink); }
3200
+ .alt-chart-head { display: flex; flex-direction: column; gap: 2px; }
3201
+ .alt-chart-title { font: 600 15px/22px var(--font-sans); color: var(--ink); }
3202
+ .alt-chart-desc { margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); }
3203
+ /* A takeaway (#187) is the chart's heading: text-heading in ink. The title over it becomes a label, caption-sized
3204
+ ink-muted, so the chart stays named. A changed takeaway cross-fades in place, like a Stat's value. */
3205
+ .alt-chart-takeaway { display: grid; margin: 0; font: 600 19px/26px var(--font-sans); letter-spacing: -0.01em; color: var(--ink); text-wrap: balance; }
3206
+ .alt-chart-head[data-takeaway] .alt-chart-title { font: 500 12.5px/17px var(--font-sans); letter-spacing: 0.005em; color: var(--ink-muted); }
3207
+ .alt-chart-head[data-takeaway] .alt-chart-desc { margin-top: 2px; }
3208
+ .alt-chart-takeaway-text { grid-area: 1 / 1; }
3209
+ .alt-chart-takeaway-text[data-in] { animation: alt-stat-in var(--dur-travel-2) var(--ease-glide) both; }
3210
+ .alt-chart-takeaway-text[data-out] { animation: alt-stat-out var(--dur-travel-2) var(--ease-glide) both; }
3211
+ .alt-chart-plot { position: relative; width: 100%; min-width: 0; }
3212
+ .alt-chart-svg { position: absolute; inset: 0; display: block; overflow: visible; }
3213
+ .alt-chart-grid line { stroke: var(--line); stroke-width: 1; shape-rendering: crispEdges; }
3214
+ .alt-chart-ground { stroke: var(--ink-muted); stroke-width: 1; shape-rendering: crispEdges; }
3215
+ .alt-chart-floor { stroke: var(--line); stroke-width: 1; shape-rendering: crispEdges; }
3216
+ /* Axis text is a caption in weight 400 (#193): the frame's words rank below the marks, the ground line and references.
3217
+ Inline y labels sit just above their gridlines on a halo of the plot's colour, as reference labels do. */
3218
+ .alt-chart-axis text { fill: var(--ink-muted); font: 400 13px/19px var(--font-sans); font-variant-numeric: tabular-nums; }
3219
+ .alt-chart-axis[data-inline] text { stroke: var(--alt-chart-bg); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; }
3220
+ .alt-chart-summary { margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); text-wrap: pretty; }
3221
+ /* ── BarChart: amounts as lengths standing on the ground line. Bars are the record, so they're flat data-* fills,
3222
+ square where they meet the ground and rounded (radius-xs) only at the data end; neighbours and stacked segments
3223
+ are parted by a real gap, never a shadow. A negative bar hangs below the ground line: that's position, not depth,
3224
+ so it has no inner wall and doesn't read as an opening. A bar never lifts, on hover or anywhere else. Value labels
3225
+ are selective and wear ink, never a series colour. ── */
3226
+ .alt-bar-label { fill: var(--ink); font: 500 12.5px/17px var(--font-sans); font-variant-numeric: tabular-nums; }
3227
+ @media (forced-colors: active) { .alt-bar-label { fill: CanvasText; } }
3228
+
3229
+ /* ── Not yet the record (#67): a period still being counted or a forecast. A pending write hovers, but a pending mark
3230
+ can't, since lifting a bar misstates it; so it's drawn with pattern and a word instead. Bars are hatched at 45° in
3231
+ their colour on the plot's own, with no solid fill (a projection also keeps its outline); lines are dashed from the
3232
+ last settled point and their points hollow; areas are hatched. The record ends at a 1px line-control rule named in
3233
+ words ("Today"). A partial period that completes cross-fades from hatch to solid over dur-travel-3: it's been pressed
3234
+ into the record. An uncertainty band is a flat, low-opacity fill: no gradient, no error bars. ── */
3235
+ .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); }
3236
+ /* Focus (#188): what a chart isn't about is drawn in data-context, one flat neutral, and its words recede to ink-muted.
3237
+ A change of focus cross-fades fills and strokes in place over dur-travel-1; under reduced motion it changes at once.
3238
+ Under forced colours context is GrayText at full strength, apart from a filter's misses, which also recede to 35%. */
3239
+ .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); }
3240
+ .alt-line-label[data-context], .alt-area-label[data-context] { fill: var(--ink-muted); }
3241
+ /* The record before (#189): both settled, both flat on the ground, the earlier one quieter. On bars, a 2px ink tick
3242
+ across the bar at the earlier value, 4px past it each side (the bullet graph's comparative measure); on lines, a
3243
+ 1.5px solid ink-muted trace under the series, named at its end. Never dotted: under forced colours dots mean pending. */
3244
+ .alt-bar-compare { stroke: var(--ink); stroke-width: 2; shape-rendering: crispEdges; }
3245
+ /* 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
3246
+ ease-settle, then takes the afterglow once. A pin icon tells it from an annotation; its ghost remove button lifts
3247
+ when pointed at, as a ghost button does. Two pins say their difference under the x axis on a line-control bracket. */
3248
+ .alt-chart-note[data-pin] { display: inline-flex; align-items: center; gap: 4px; padding-right: 2px;
3249
+ 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; }
3250
+ .alt-chart-pin-icon { flex: none; color: var(--ink-muted); }
3251
+ .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); }
3252
+ .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))); }
3253
+ @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))); } }
3254
+ @keyframes alt-pin-land { from { box-shadow: var(--elev-3); transform: translateY(calc(-6px * var(--alt-travel))); } }
3255
+ @keyframes alt-pin-glow { 0%, 12% { outline: 2px solid var(--accent-soft); outline-offset: 1px; } 100% { outline: 2px solid transparent; outline-offset: 1px; } }
3256
+ .alt-chart-pins-strip path { stroke: var(--line-control); stroke-width: 1; shape-rendering: crispEdges; }
3257
+ .alt-chart-pins-strip text { fill: var(--ink); font: 500 12.5px/17px var(--font-sans); font-variant-numeric: tabular-nums; }
3258
+ @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; } }
3259
+ .alt-line-compare { stroke: var(--ink-muted); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
3260
+ @media (forced-colors: active) { .alt-bar-compare { stroke: CanvasText; } .alt-line-compare { stroke: CanvasText; stroke-width: 1; } }
3261
+ /* ── DotPlot (#192): values where zero isn't part of the story, as position, never height. The values are the record,
3262
+ 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
3263
+ 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
3264
+ spread is a flat band of the series colour at 16% behind the dot, never error bars. A dumbbell joins a hollow "from"
3265
+ dot to a filled "to" dot with a 2px ink-muted link. A mark that acts gets a 2px ink outline, never a lift. ── */
3266
+ .alt-dot-guide { stroke: var(--line); stroke-width: 1; shape-rendering: crispEdges; }
3267
+ .alt-dot-range { fill-opacity: 0.16; }
3268
+ .alt-dot-link { stroke: var(--ink-muted); stroke-width: 2; stroke-linecap: round; }
3269
+ .alt-dot-hover { fill: none; stroke: var(--ink); stroke-width: 2; }
3270
+ .alt-chart-plot[data-interactive] .alt-dot-row { cursor: pointer; }
3271
+ @media (prefers-contrast: more) { .alt-dot-guide { stroke: var(--line-control); } }
3272
+ [data-contrast="more"] .alt-dot-guide { stroke: var(--line-control); }
3273
+ @media (forced-colors: active) {
3274
+ .alt-dot-guide { stroke: GrayText; }
3275
+ .alt-dot-range { fill: GrayText; fill-opacity: 0.4; }
3276
+ .alt-dot-link, .alt-dot-hover { stroke: CanvasText; }
3277
+ .alt-dot-mark[data-from] { stroke: CanvasText; fill: Canvas; }
3278
+ }
3279
+ /* Changed since a snapshot (#191): a changed bar's old value is the comparison's tick; a changed line point's old place
3280
+ is a small hollow ink-muted ring. The caption says how many changed, in a switch that recedes the rest. */
3281
+ .alt-line-since { fill: var(--alt-chart-bg); stroke: var(--ink-muted); stroke-width: 1.5; }
3282
+ .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); }
3283
+ @media (forced-colors: active) { .alt-line-since { stroke: CanvasText; fill: Canvas; } }
3284
+ .alt-legend-key[data-context] { color: var(--ink-muted); }
3285
+ @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; } }
3286
+ [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; }
3287
+ .alt-chart-boundary line { stroke: var(--line-control); stroke-width: 1; shape-rendering: crispEdges; }
3288
+ .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); }
3289
+ .alt-line-hollow { fill: var(--alt-chart-bg); stroke-width: 2; }
3290
+ .alt-bar-label-status { fill: var(--ink-muted); }
3291
+ .alt-chart-readout-note { margin-left: 6px; color: var(--ink); font-weight: 600; }
3292
+ .alt-chart-readout-note::before { content: "· "; }
3293
+ /* ── Filtered in place (#70): misses recede to 35% and keep their place and their colour slot, so positions survive
3294
+ the filter. Relevance elsewhere raises matches, but marks can't rise, so in a chart it's spent as contrast only.
3295
+ Misses cross-fade over dur-travel-1. The frame says how many match, in words. ── */
3296
+ .alt-chart-rel { transition: opacity var(--dur-travel-1) var(--ease-glide); }
3297
+ .alt-bar-label-miss { fill: var(--ink-muted); }
3298
+ .alt-chart-count { margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
3299
+ @media (forced-colors: active) {
3300
+ .alt-chart-rel[data-miss] { opacity: 1 !important; }
3301
+ .alt-chart-rel[data-miss] .alt-bar-mark:not([fill^="url"]), .alt-chart-rel[data-miss] .alt-area-fill:not([fill^="url"]) { fill: GrayText; }
3302
+ .alt-chart-rel[data-miss] [fill^="url"] { opacity: 0.45; }
3303
+ .alt-chart-rel[data-miss] .alt-line-stroke, .alt-chart-rel[data-miss] .alt-area-line { stroke: GrayText; }
3304
+ .alt-chart-rel[data-miss] circle, .alt-chart-rel[data-miss] rect, .alt-chart-rel[data-miss] path:not([class]) { fill: GrayText; stroke: GrayText; }
3305
+ .alt-bar-label-miss { fill: GrayText; }
3306
+ .alt-chart-count { color: CanvasText; }
3307
+ }
3308
+ /* ── ChartBrush (#74): an overview you slide a window along. The overview is an opening, the series flat in a trench;
3309
+ the window is a raised lens over it (level 1, accent rim, clear face), level 2 on hover and 3 while held, whose
3310
+ 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;
3311
+ outside the window it recedes to 35%. ── */
3312
+ .alt-brush { display: flex; flex-direction: column; gap: 6px; }
3313
+ .alt-brush-slide { position: relative; touch-action: none; user-select: none; cursor: pointer; padding: 2px 0; }
3314
+ .alt-brush-track { position: relative; border-radius: var(--radius-sm); background: var(--surface-trench); box-shadow: var(--trench); overflow: hidden; }
3315
+ .alt-brush-svg { display: block; }
3316
+ .alt-brush-line { stroke-width: 1.5px; stroke-linejoin: round; stroke-linecap: round; }
3317
+ .alt-brush-lens {
3318
+ position: absolute; top: 0; bottom: 0; box-sizing: border-box; border: 2px solid var(--accent); border-radius: var(--radius-sm);
3319
+ box-shadow: var(--elev-1); cursor: grab;
3320
+ 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);
3321
+ }
3322
+ @media (hover: hover) { .alt-brush-lens:hover { box-shadow: var(--elev-2); } }
3323
+ .alt-brush-lens[data-grabbed] { box-shadow: var(--elev-3); cursor: grabbing; }
3324
+ .alt-brush-lens:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3325
+ .alt-brush-grip {
3326
+ position: absolute; top: 50%; width: 10px; height: 24px; translate: -50% -50%; box-sizing: border-box;
3327
+ border: 2px solid var(--accent); border-radius: var(--radius-full); background: var(--surface-raised); background-image: var(--alt-sheen); box-shadow: var(--elev-1);
3328
+ 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);
3329
+ }
3330
+ @media (hover: hover) { .alt-brush-grip:hover { box-shadow: var(--elev-2); } }
3331
+ .alt-brush-grip[data-grabbed] { box-shadow: var(--elev-3); transform: scale(calc(1 + 0.12 * var(--alt-travel))); }
3332
+ .alt-brush-grip:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3333
+ .alt-brush[data-dragging] :is(.alt-brush-lens, .alt-brush-grip) { transition-property: box-shadow, transform; }
3334
+ .alt-brush-bubble {
3335
+ position: absolute; left: 50%; top: calc(100% + 8px); translate: -50% 0; padding: 3px 8px; text-align: center;
3336
+ border-radius: var(--radius-sm); background: var(--surface-floating); box-shadow: var(--elev-1); color: var(--ink);
3337
+ font: 500 12px/16px var(--font-mono); white-space: nowrap; pointer-events: none; z-index: 1;
3338
+ opacity: 0; transform: translateY(calc(-4px * var(--alt-travel))) scale(calc(1 - 0.1 * var(--alt-travel))); transform-origin: 50% 0;
3339
+ transition: opacity var(--dur-travel-1) var(--ease-sink), transform var(--dur-travel-1) var(--ease-sink);
3340
+ }
3341
+ .alt-brush-grip[data-edge="start"] .alt-brush-bubble { left: 50%; translate: 0 0; transform-origin: 0 0; }
3342
+ .alt-brush-grip[data-edge="end"] .alt-brush-bubble { left: auto; right: 50%; translate: 0 0; transform-origin: 100% 0; }
3343
+ .alt-brush-grip[data-grabbed] .alt-brush-bubble, .alt-brush-grip:focus-visible .alt-brush-bubble {
3344
+ opacity: 1; transform: none; box-shadow: var(--elev-3);
3345
+ 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);
3346
+ }
3347
+ .alt-brush-range { margin: 0; font: 400 13px/19px var(--font-sans); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
3348
+ [data-contrast="more"] .alt-brush-lens { border-color: var(--ink); }
3349
+ @media (forced-colors: active) {
3350
+ .alt-brush-track { border: 2px dashed CanvasText; }
3351
+ .alt-brush-lens { border: 1px solid Highlight; box-shadow: none; }
3352
+ .alt-brush-lens[data-grabbed] { border-width: 2px; }
3353
+ .alt-brush-grip { border: 1px solid CanvasText; }
3354
+ .alt-brush-line { forced-color-adjust: none; stroke: CanvasText; }
3355
+ .alt-brush-range { color: CanvasText; }
3356
+ }
3357
+ @media (prefers-reduced-motion: reduce) {
3358
+ .alt-brush-lens, .alt-brush-grip { transition-property: box-shadow; }
3359
+ }
3360
+
3361
+
3362
+ /* ── Marks you can select or drill into (#69). Hover is a promise: only where a click does something does the plot take
3363
+ a pointer cursor and outline the record under it in 2px ink (appearing over dur-travel-1), and the readout names the
3364
+ action. A mark can't lift without misstating its value, so in a chart the hover cue is the outline, the cursor and
3365
+ the readout; height stays with the readout. The selection keeps its colour and the rest recede, as a filter's do. ── */
3366
+ .alt-chart-plot[data-interactive] { cursor: pointer; }
3367
+ .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); }
3368
+ @keyframes alt-chart-hover-in { from { stroke-opacity: 0; } }
3369
+ .alt-chart-readout-action { margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--line); color: var(--ink-muted); font-weight: 500; }
3370
+ .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); }
3371
+ .alt-chart-selection:empty { display: none; }
3372
+ @media (forced-colors: active) {
3373
+ .alt-chart-hover path, .alt-chart-hover circle { stroke: Highlight; }
3374
+ .alt-chart-readout-action { border-top-color: CanvasText; }
3375
+ }
3376
+
3377
+ /* ── Live charts (#66): show the new state at once and leave a fading trace (phosphor). A new or changed mark carries
3378
+ a 2px ink outline (an outline, not height or a glow) that holds for the first second and decays over dur-settle on
3379
+ ease-decay, with its value beside it for that first second. A whole refresh is one change: the "Updated" caption
3380
+ takes a ground-level tint instead. Afterglows keep under reduced motion: they're tint, not travel. ── */
3381
+ .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; }
3382
+ .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; }
3383
+ @keyframes alt-chart-glow { 0%, 12% { stroke-opacity: 1; animation-timing-function: var(--ease-decay); } 100% { stroke-opacity: 0; } }
3384
+ @keyframes alt-chart-glow-label { 0%, 12% { opacity: 1; } 14%, 100% { opacity: 0; } }
3385
+ .alt-chart-live { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-3); }
3386
+ .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; }
3387
+ .alt-chart-updated[data-settle]::before { animation-name: alt-chart-tint-a; }
3388
+ .alt-chart-updated[data-settle="b"]::before { animation-name: alt-chart-tint-b; }
3389
+ @keyframes alt-chart-tint-a { 0%, 12% { background-color: var(--accent-soft); animation-timing-function: var(--ease-decay); } 100% { background-color: transparent; } }
3390
+ @keyframes alt-chart-tint-b { 0%, 12% { background-color: var(--accent-soft); animation-timing-function: var(--ease-decay); } 100% { background-color: transparent; } }
3391
+ @media (forced-colors: active) {
3392
+ .alt-chart-glow path, .alt-chart-glow circle { stroke: Highlight; }
3393
+ .alt-chart-glow text { fill: CanvasText; stroke: Canvas; }
3394
+ .alt-chart-updated[data-settle]::before { border: 1px solid Highlight; }
3395
+ }
3396
+
3397
+ /* ── References and annotations (#68): what a chart is read against. A reference line is 1px ink-muted and solid,
3398
+ because dashes mean projected; a status threshold wears caution or critical, with its icon in the label. A band is a
3399
+ flat line-coloured fill (a decorative token, so it isn't read as data or as an opening), under the marks. Labels are
3400
+ 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
3401
+ the plot (a label is part of the record) with a 1px line-control leader to its x; one that acts lifts one level. ── */
3402
+ .alt-chart-ref { stroke: var(--ink-muted); stroke-width: 1; shape-rendering: crispEdges; }
3403
+ .alt-chart-ref[data-tone="caution"] { stroke: var(--caution); }
3404
+ .alt-chart-ref[data-tone="critical"] { stroke: var(--critical); }
3405
+ .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; }
3406
+ .alt-chart-ref-label[data-tone] text { fill: var(--ink); }
3407
+ .alt-chart-ref-icon { color: var(--critical); }
3408
+ .alt-chart-ref-label[data-tone="caution"] .alt-chart-ref-icon { color: var(--caution); }
3409
+ .alt-chart-note-leader { stroke: var(--line-control); stroke-width: 1; shape-rendering: crispEdges; }
3410
+ .alt-chart-note { position: absolute; z-index: 1; justify-content: center; box-sizing: border-box; }
3411
+ 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); }
3412
+ @media (hover: hover) { button.alt-chart-note:hover { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
3413
+ button.alt-chart-note:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3414
+ @media (forced-colors: active) {
3415
+ .alt-bar-pending { stroke: CanvasText; stroke-dasharray: 2 2; }
3416
+ .alt-chart-ref, .alt-chart-ref[data-tone] { stroke: CanvasText; }
3417
+ .alt-chart-ref-label text, .alt-chart-ref-label[data-tone] text { fill: CanvasText; stroke: Canvas; }
3418
+ .alt-chart-ref-icon, .alt-chart-ref-label[data-tone="caution"] .alt-chart-ref-icon { color: CanvasText; }
3419
+ .alt-chart-note-leader { stroke: CanvasText; }
3420
+ .alt-chart-note, button.alt-chart-note { border: 1px solid CanvasText; }
3421
+ button.alt-chart-note:focus-visible { outline-color: Highlight; }
3422
+ .alt-chart-boundary line { stroke: CanvasText; }
3423
+ .alt-chart-boundary-label { fill: CanvasText; stroke: Canvas; }
3424
+ .alt-line-hollow { fill: Canvas; }
3425
+ .alt-bar-label-status { fill: CanvasText; }
3426
+ }
3427
+
3428
+ /* ── The table view: the same record as a sortable DataTable, behind a segmented Chart | Table control (one raised
3429
+ pill gliding between two views of one record). Both views sit in one box, so switching doesn't move the page: the
3430
+ table scrolls inside the chart's size, and the two cross-fade over dur-travel-2 rather than slide. The unchosen
3431
+ view stays mounted but inert. ── */
3432
+ .alt-chart-tabs { display: contents; }
3433
+ .alt-chart-bar { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }
3434
+ .alt-chart-bar .alt-tabs-list { flex: none; }
3435
+ .alt-chart-views { display: grid; min-width: 0; }
3436
+ .alt-chart-view { grid-area: 1 / 1; min-width: 0; transition: opacity var(--dur-travel-2) var(--ease-glide); }
3437
+ .alt-chart-view.alt-tabs-content { animation: none; }
3438
+ .alt-chart-view[data-state="inactive"] { opacity: 0; pointer-events: none; }
3439
+ .alt-chart-view[data-view="table"] { position: relative; }
3440
+ .alt-chart-tablebox { position: absolute; inset: 0; overflow: auto; }
3441
+
3442
+ /* ── Loading, refetch and nothing: an imprint in the chart's shape while data is on its way (surface-well bars or a
3443
+ band pressed into the page, no labels, after dur-grace like every imprint); data lands by fading in place, never
3444
+ rising, because a rising bar would leave the ground line. A refetch keeps the chart and, after dur-grace, drops its
3445
+ marks to half strength. Nothing to show is an opening: an EmptyState in the plot's own box, with no axes. ── */
3446
+ .alt-chart-imprint { display: flex; align-items: flex-end; gap: 8%; padding: 8px 4% 26px; box-sizing: border-box; }
3447
+ .alt-chart-imprint > .alt-skeleton { flex: 1; border-radius: var(--radius-xs) var(--radius-xs) 0 0; }
3448
+ .alt-chart-imprint[data-shape="line"] { padding: 0; }
3449
+ .alt-chart-imprint-line { fill: none; stroke: var(--surface-well); stroke-width: 10px; stroke-linecap: round; vector-effect: non-scaling-stroke; }
3450
+ .alt-chart-await[data-phase="reveal"] > .alt-await-face { --alt-from-y: 0px; }
3451
+ .alt-chart-plot[data-refreshing] .alt-chart-marks { opacity: 0.5; transition: opacity var(--dur-travel-1) var(--ease-glide) var(--dur-grace); }
3452
+ .alt-chart-marks { transition: opacity var(--dur-travel-1) var(--ease-glide); }
3453
+ .alt-chart-empty { position: absolute; inset: 0; display: grid; }
3454
+ .alt-chart-empty > .alt-empty { margin: 0; justify-content: center; }
3455
+ @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; } }
3456
+
3457
+ /* ── The keyboard path: a layer of focusable nodes over the aria-hidden SVG, one level at a time (summary, series,
3458
+ points) with a roving tabindex, so the plot is one Tab stop. A node is invisible until focused; then it's the focus
3459
+ ring itself, 2px accent-ink at a 2px offset, round on a point. It moves instantly, because it's focus, and the
3460
+ readout opens on the focused point. Nodes take no pointer events, so the plot's hover still works through them. ── */
3461
+ .alt-chart-nav { position: absolute; inset: 0; pointer-events: none; }
3462
+ .alt-chart-node { position: absolute; box-sizing: border-box; border-radius: var(--radius-sm); outline: none; }
3463
+ .alt-chart-node[data-level="point"] { border-radius: var(--radius-full); }
3464
+ .alt-chart-node:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3465
+ @media (forced-colors: active) { .alt-chart-node:focus-visible { outline-color: Highlight; } }
3466
+
3467
+ /* ── LineChart: change over time as 2px strokes lying on the plot, round joins, no fill, no glow. A missing value is a
3468
+ gap in the line, never bridged. The last point wears a marker and each line is named at its end in ink, so the
3469
+ lines are their own legend. ── */
3470
+ .alt-line-label { fill: var(--ink); font: 500 12.5px/17px var(--font-sans); }
3471
+ @media (forced-colors: active) { .alt-line-label { fill: CanvasText; } }
3472
+ .alt-line-active { stroke: var(--alt-chart-bg); stroke-width: 2; paint-order: stroke; }
3473
+
3474
+ /* ── Sparkline: a word-sized trend, the record in the row it sits in. It lies flush on the ground like every mark: a
3475
+ 1.5px stroke (2px under contrast="more"), round joins, no fill under it, no axes, no gridlines, no readout, and a 4px
3476
+ marker on the last point. Bars are flat fills parted by 1px, standing on zero. A normal range is a flat band of
3477
+ line behind the marks. Its height is one line of the text around it (1lh) and it sits on that text's baseline. The
3478
+ colour is context (ink-muted) unless it's the series (data-1) or a status. While its data loads it's an imprint one
3479
+ line high and its own width. ── */
3480
+ .alt-sparkline { --spark: var(--ink-muted); display: inline-block; vertical-align: middle; height: 1.4em; height: 1lh; min-width: 0; }
3481
+ .alt-sparkline > svg { display: block; overflow: visible; }
3482
+ .alt-sparkline[data-tone="data"] { --spark: var(--data-1); }
3483
+ .alt-sparkline[data-tone="positive"] { --spark: var(--positive); }
3484
+ .alt-sparkline[data-tone="caution"] { --spark: var(--caution); }
3485
+ .alt-sparkline[data-tone="critical"] { --spark: var(--critical); }
3486
+ .alt-sparkline-line { fill: none; stroke: var(--spark); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
3487
+ .alt-sparkline-end, .alt-sparkline-bar { fill: var(--spark); }
3488
+ .alt-sparkline-band { fill: var(--line); }
3489
+ .alt-sparkline-imprint { display: inline-block; vertical-align: middle; height: 0.6em; border-radius: var(--radius-xs); }
3490
+ @media (prefers-contrast: more) { .alt-sparkline-line { stroke-width: 2; } }
3491
+ [data-contrast="more"] .alt-sparkline-line { stroke-width: 2; }
3492
+ @media (forced-colors: active) {
3493
+ .alt-sparkline, .alt-sparkline[data-tone] { --spark: CanvasText; }
3494
+ .alt-sparkline-band { fill: none; stroke: GrayText; stroke-dasharray: 2 2; }
3495
+ }
3496
+
3497
+ /* ── Stat: a headline number. A number is the record, so a stat lies flush on what it sits on (a Card's surface, or
3498
+ the page): no level, no shadow. The label is text-label in ink-muted; the value is text-title (text-display when
3499
+ size="hero", once per view) in ink, weight 600, tabular figures. A change with no stated good direction is
3500
+ ink-muted text with an arrow; with one, it's a Badge plate whose lamp is positive or critical, so the word, the
3501
+ arrow and the colour each say it. A changed value cross-fades (dur-travel-2) and the stat takes the afterglow; it
3502
+ never counts up. While the value loads it's an imprint of a number's width under a label already in place. ── */
3503
+ .alt-stat { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; border-radius: var(--radius-md); }
3504
+ .alt-stat-label { font: 500 14px/20px var(--font-sans); color: var(--ink-muted); }
3505
+ .alt-stat-main { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
3506
+ .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; }
3507
+ .alt-stat[data-size="hero"] .alt-stat-value { font: 600 44px/48px var(--font-sans); letter-spacing: -0.025em; }
3508
+ .alt-stat-text { grid-area: 1 / 1; }
3509
+ .alt-stat-text[data-in] { animation: alt-stat-in var(--dur-travel-2) var(--ease-glide) both; }
3510
+ .alt-stat-text[data-out] { animation: alt-stat-out var(--dur-travel-2) var(--ease-glide) both; }
3511
+ @keyframes alt-stat-in { from { opacity: 0; } }
3512
+ @keyframes alt-stat-out { to { opacity: 0; } }
3513
+ .alt-stat-trend { display: inline-flex; align-items: center; min-width: 0; font: 400 15px/23px var(--font-sans); }
3514
+ .alt-stat-imprint { display: inline-block; vertical-align: middle; width: 4.5em; height: 0.6em; border-radius: var(--radius-xs); }
3515
+ .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); }
3516
+ .alt-stat-change { gap: 4px; font-variant-numeric: tabular-nums; }
3517
+ .alt-stat-change[data-neutral] { display: inline-flex; align-items: center; color: var(--ink-muted); }
3518
+ .alt-stat-arrow { display: block; flex: none; }
3519
+ .alt-stat-help { flex-basis: 100%; font-weight: 400; }
3520
+ .alt-stat-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: var(--space-5) var(--space-6); }
3521
+ @media (forced-colors: active) { .alt-stat-change.alt-badge { border: 1px solid CanvasText; } }
3522
+
3523
+ /* ── Meter: where a reading sits in a known range. A reading is the record, so unlike Progress (a task under way, live,
3524
+ a raised fill rising out of a trench) a meter lies on the ground: a flush track of line with the reading drawn
3525
+ across it as flat ink, data-1 by default, no shadow, wall or sheen. Thresholds are 1px ink-muted notches standing
3526
+ at the track's rim. Past `high` the fill turns caution and past the limit critical, cross-fading, and the state is
3527
+ said in a Badge whose lamp relights. Segments are flat data-* fills parted by 2px, with a legend of their values.
3528
+ The fill glides on ease-glide (dur-travel-3); under reduced motion it changes in place. ── */
3529
+ .alt-meter { --meter-fill: var(--data-1); --meter-bg: var(--surface-ground); display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
3530
+ .alt-card .alt-meter { --meter-bg: var(--surface-raised); }
3531
+ .alt-meter[data-state="high"] { --meter-fill: var(--caution); }
3532
+ .alt-meter[data-state="critical"] { --meter-fill: var(--critical); }
3533
+ .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); }
3534
+ .alt-meter-value { margin-left: auto; font-variant-numeric: tabular-nums; color: var(--ink-muted); }
3535
+ .alt-meter-state { order: 3; }
3536
+ .alt-meter-track { position: relative; height: 8px; border-radius: var(--radius-full); background: var(--line); }
3537
+ .alt-meter-fill {
3538
+ position: absolute; top: 0; bottom: 0; border-radius: inherit; background: var(--meter-fill);
3539
+ 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);
3540
+ }
3541
+ /* 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. */
3542
+ .alt-meter-fill[data-slot] { border-radius: 0; }
3543
+ .alt-meter-fill[data-slot] + .alt-meter-fill[data-slot] { border-left: 2px solid var(--meter-bg); background-clip: padding-box; }
3544
+ .alt-meter-fill[data-slot]:first-child { border-radius: var(--radius-full) 0 0 var(--radius-full); }
3545
+ .alt-meter-notch { position: absolute; top: -3px; bottom: -3px; width: 1px; margin-left: -0.5px; background: var(--ink-muted); }
3546
+ .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); }
3547
+ .alt-meter-legend-value { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
3548
+ .alt-meter-imprint { height: 8px; border-radius: var(--radius-full); }
3549
+ @media (prefers-contrast: more) { .alt-meter-track { box-shadow: inset 0 0 0 1px var(--ink); } .alt-meter-notch { background: var(--ink); } }
3550
+ [data-contrast="more"] .alt-meter-track { box-shadow: inset 0 0 0 1px var(--ink); }
3551
+ [data-contrast="more"] .alt-meter-notch { background: var(--ink); }
3552
+ @media (forced-colors: active) {
3553
+ .alt-meter-track { background: Canvas; border: 1px solid CanvasText; }
3554
+ .alt-meter-fill { background: Highlight; forced-color-adjust: none; }
3555
+ .alt-meter-fill[data-slot] { background: Highlight !important; }
3556
+ .alt-meter-fill[data-slot] + .alt-meter-fill[data-slot] { border-left-color: Canvas; background-clip: border-box; }
3557
+ .alt-meter-notch { background: CanvasText; }
3558
+ .alt-meter-legend rect { fill: CanvasText; }
3559
+ }
3560
+
3561
+ /* ── BarList: a ranked list whose rows are their own bars. Rows are the record, flush on the ground with line
3562
+ 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,
3563
+ its length the row's value against the largest; the label wraps rather than being cut, and the value sits at the
3564
+ row's end in tabular figures. A row that does something (a link or a button) lifts one level when pointed at, as
3565
+ Table's rows do, and its bar stays flat on it; a row that doesn't never moves. "Other" wears data-other. Filtering
3566
+ in place raises matches and sinks misses. Re-ranked rows are carried to their places (FLIP, dur-travel-3,
3567
+ ease-glide), and bars glide to their new lengths; under reduced motion both change in place. ── */
3568
+ .alt-barlist-wrap { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); min-width: 0; }
3569
+ .alt-barlist { align-self: stretch; margin: 0; padding: 0; list-style: none; font: 500 14px/20px var(--font-sans); color: var(--ink); }
3570
+ .alt-barlist-row { --bar: color-mix(in oklab, var(--data-1) 22%, transparent); position: relative; border-bottom: 1px solid var(--line); }
3571
+ .alt-barlist-row:last-child { border-bottom-color: transparent; }
3572
+ .alt-barlist-row[data-other] { --bar: color-mix(in oklab, var(--data-other) 22%, transparent); }
3573
+ .alt-barlist-item {
3574
+ position: relative; display: flex; align-items: center; gap: var(--space-2); width: 100%; min-height: 36px;
3575
+ margin: 0; padding: 3px var(--space-2) 3px 0; box-sizing: border-box; border: 0; border-radius: var(--radius-sm);
3576
+ background: transparent; font: inherit; color: inherit; text-align: start; text-decoration: none;
3577
+ }
3578
+ button.alt-barlist-item, a.alt-barlist-item {
3579
+ cursor: pointer; -webkit-tap-highlight-color: transparent; box-shadow: var(--elev-0);
3580
+ 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);
3581
+ }
3582
+ @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))); } }
3583
+ button.alt-barlist-item:active, a.alt-barlist-item:active { box-shadow: var(--elev-0); transform: none; transition-duration: var(--dur-press); }
3584
+ button.alt-barlist-item:focus-visible, a.alt-barlist-item:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3585
+ .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); }
3586
+ .alt-barlist-bar {
3587
+ position: absolute; z-index: -1; left: 0; top: 0; bottom: 0; border-radius: var(--radius-xs); background: var(--bar);
3588
+ transition: width calc(var(--dur-travel-3) * var(--alt-travel)) var(--ease-glide);
3589
+ }
3590
+ .alt-barlist-name { flex: 1; min-width: 0; overflow-wrap: break-word; }
3591
+ .alt-barlist-value { flex: none; font-variant-numeric: tabular-nums; }
3592
+ .alt-barlist-share { flex: none; min-width: 3.2em; text-align: end; font-variant-numeric: tabular-nums; color: var(--ink-muted); }
3593
+ .alt-barlist-row[data-other] .alt-barlist-name { color: var(--ink-muted); }
3594
+ .alt-barlist-imprint { display: block; height: 0.6em; margin: 14px 0; }
3595
+ .alt-barlist-row[data-relevance="miss"] .alt-barlist-name, .alt-barlist-row[data-relevance="miss"] .alt-barlist-value { color: var(--ink-muted); }
3596
+ @media (prefers-contrast: more) { .alt-barlist-bar { box-shadow: inset 0 0 0 1px var(--ink); } }
3597
+ [data-contrast="more"] .alt-barlist-bar { box-shadow: inset 0 0 0 1px var(--ink); }
3598
+ @media (forced-colors: active) {
3599
+ .alt-barlist-bar { forced-color-adjust: none; background: color-mix(in srgb, Highlight 25%, Canvas); border: 1px solid Highlight; }
3600
+ button.alt-barlist-item:hover, a.alt-barlist-item:hover { outline: 1px solid CanvasText; }
3601
+ .alt-barlist-row { border-bottom-color: GrayText; }
3602
+ .alt-barlist-row[data-relevance="match"] { outline: 1px solid CanvasText; outline-offset: -1px; }
3603
+ .alt-barlist-row[data-relevance="miss"] { border-bottom-style: dashed; }
3604
+ }
3605
+
3606
+ /* ── AreaChart: a total over time and what it's made of. Bands are the record: flat data-* fills stacked on the ground
3607
+ 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
3608
+ on the topmost. A single area is a flat data-1 fill at reduced opacity under its 2px line. No gradient fill, ever:
3609
+ the only face gradient in Altitude is the sheen, and it means "press it". Bands are named inside themselves in ink,
3610
+ on a halo of the plot's colour, where they're tall enough. ── */
3611
+ .alt-area-sep { stroke: var(--alt-chart-bg); stroke-width: 2; }
3612
+ .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; }
3613
+ .alt-chart-imprint[data-shape="area"] { padding: 0; }
3614
+ .alt-chart-imprint-area { fill: var(--surface-well); }
3615
+ @media (prefers-contrast: more) { .alt-area-sep { stroke: var(--ink); stroke-width: 3; } }
3616
+ [data-contrast="more"] .alt-area-sep { stroke: var(--ink); stroke-width: 3; }
3617
+ @media (forced-colors: active) {
3618
+ .alt-area-sep { stroke: Canvas; }
3619
+ .alt-area-label { fill: CanvasText; stroke: Canvas; }
3620
+ .alt-chart-imprint-area { fill: none; stroke: GrayText; stroke-dasharray: 6 4; stroke-width: 2px; vector-effect: non-scaling-stroke; }
3621
+ }
3622
+
3623
+ /* ── Heatmap (#61): a value per cell across two dimensions. Cells are the record, so they lie flat on the plot: a
3624
+ data-seq step (data-div around a midpoint for polarity), parted by a 2px gap of the plot's own colour and rounded
3625
+ at radius-xs (less on small cells), never raised, shaded or glowing. "No data" isn't an opening, since it isn't
3626
+ waiting for anything: no fill and a 1px line outline. Rows and columns are named in ink-muted like an axis; the
3627
+ legend is the steps as swatches with their ranges in ink. With no crosshair in a grid, the cell under the readout
3628
+ wears a 2px ink outline, which never lifts it. A value change cross-fades the cell's fill; under reduced motion it's
3629
+ instant. Under forced colours and print each step adds a pattern density (none, sparse dots, dense dots, hatching,
3630
+ crosshatch), and contrast="more" gives every cell a 1px ink edge. ── */
3631
+ .alt-heat-labels text { fill: var(--ink-muted); font: 500 12.5px/17px var(--font-sans); font-variant-numeric: tabular-nums; }
3632
+ .alt-heat-legend text { fill: var(--ink); font: 500 12.5px/17px var(--font-sans); font-variant-numeric: tabular-nums; }
3633
+ .alt-heat-fill { transition: fill var(--dur-travel-1) var(--ease-glide); }
3634
+ .alt-heat-none { fill: none; stroke: var(--line); stroke-width: 1; }
3635
+ .alt-heat-pattern { pointer-events: none; }
3636
+ .alt-heat-active { fill: none; stroke: var(--ink); stroke-width: 2; pointer-events: none; }
3637
+ .alt-chart-node[data-shape="cell"] { border-radius: var(--radius-xs); }
3638
+ .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; }
3639
+ .alt-chart-imprint[data-shape="grid"] > .alt-skeleton { border-radius: var(--radius-xs); }
3640
+ @media (prefers-reduced-motion: reduce) { .alt-heat-fill { transition: none; } }
3641
+ [data-motion="reduced"] .alt-heat-fill { transition: none; }
3642
+ @media (prefers-contrast: more) { .alt-heat-none { stroke: var(--line-control); } }
3643
+ [data-contrast="more"] .alt-heat-none { stroke: var(--line-control); }
3644
+ @media (forced-colors: active) {
3645
+ .alt-heat-labels text, .alt-heat-legend text { fill: CanvasText; }
3646
+ .alt-heat-none { stroke: GrayText; }
3647
+ .alt-heat-active { stroke: Highlight; }
3648
+ }
3649
+
3650
+ /* ── ScatterChart (#62): two measures per record. Dots are the record, so they lie on the plot: at least 8px, a data-*
3651
+ fill and a marker shape per group (circle, square, triangle), with a 2px ring of the plot's own colour so dots that
3652
+ overlap stay apart, and never a shadow, sheen or glow. At most three groups, the palette's first three slots, since
3653
+ any two dots can be neighbours. The dot under the readout is drawn again on top, so an overlapped dot is seen whole;
3654
+ it doesn't lift. Labelled outliers are ink on a halo with a 1px ink-muted leader; the trend is a 1px ink-muted
3655
+ line, named in words. Under forced colours the shapes carry the groups; contrast="more" rings each dot in 1px ink. ── */
3656
+ .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); }
3657
+ .alt-scatter-label line { stroke: var(--ink-muted); stroke-width: 1; }
3658
+ .alt-scatter-trend line { stroke: var(--ink-muted); stroke-width: 1; }
3659
+ .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); }
3660
+ .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); }
3661
+ .alt-chart-imprint[data-shape="dots"] { display: block; position: relative; padding: 0; }
3662
+ .alt-chart-imprint-dot { position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: var(--radius-full); }
3663
+ @media (forced-colors: active) {
3664
+ .alt-scatter-label text, .alt-scatter-trend text, .alt-scatter-axis-name text { fill: CanvasText; stroke: Canvas; }
3665
+ .alt-scatter-label line, .alt-scatter-trend line { stroke: CanvasText; }
3666
+ }
3667
+
3668
+ /* ── ChartTooltip: reading exact values. The crosshair is a mark on the ground (1px line-control, snapped to the nearest
3669
+ x); the readout is live, a level-3 floating surface like Tooltip. It rises 4px once as its shadow grows from elev-1
3670
+ to elev-3, then steps between x positions with no easing (it follows the data, not each pixel of the pointer), flips
3671
+ sides so it never covers the point, and leaves in 70% of the time. It shares Tooltip's 400ms warm-up. It's
3672
+ aria-hidden: the same values are in each datum's name, the keyboard path and the table view. ── */
3673
+ .alt-chart { --alt-chart-bg: var(--surface-ground); }
3674
+ .alt-card .alt-chart { --alt-chart-bg: var(--surface-raised); }
3675
+ .alt-chart-plot { touch-action: pan-y; }
3676
+ .alt-chart-crosshair { stroke: var(--line-control); stroke-width: 1; shape-rendering: crispEdges; }
3677
+ .alt-chart-readout {
3678
+ position: absolute; z-index: var(--z-floating); pointer-events: none; display: grid; gap: 3px; min-width: 112px;
3679
+ padding: 8px 10px; border-radius: var(--radius-lg); background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--ink);
3680
+ font: 400 12.5px/17px var(--font-sans); white-space: nowrap;
3681
+ 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);
3682
+ }
3683
+ .alt-chart-readout[data-state="closed"] {
3684
+ opacity: 0; box-shadow: var(--elev-1); transform: translateY(calc(4px * var(--alt-travel)));
3685
+ transition-timing-function: var(--ease-sink); transition-duration: calc(var(--dur-travel-3) * 0.7);
3686
+ }
3687
+ .alt-chart-readout[data-warm] { transition-duration: var(--dur-travel-1); }
3688
+ .alt-chart-readout[data-side="right"] { translate: 14px 0; }
3689
+ .alt-chart-readout[data-side="left"] { translate: calc(-100% - 14px) 0; }
3690
+ .alt-chart-readout[data-side="below"] { translate: 0 14px; }
3691
+ .alt-chart-readout[data-side="above"] { translate: 0 calc(-100% - 14px); }
3692
+ .alt-chart-readout-label { font-weight: 500; color: var(--ink-muted); margin-bottom: 1px; }
3693
+ .alt-chart-readout-row { display: grid; grid-template-columns: auto auto 1fr; align-items: center; gap: 6px; }
3694
+ .alt-chart-readout-key { display: block; }
3695
+ .alt-chart-readout-value { font-weight: 500; font-variant-numeric: tabular-nums; color: var(--ink); }
3696
+ .alt-chart-readout-name { color: var(--ink-muted); }
3697
+ /* ── ChartLegend: names the series. A legend that only labels is settled: keys flush on the ground, a swatch in the
3698
+ series colour and its name in ink. A legend that hides and shows series is a row of choices, so each key is built
3699
+ like a Checkbox: a ghost button that lifts one level when pointed at (clicking it does something), holding a trench
3700
+ that the series colour pours into while the series is shown and drains out of when it's hidden. The checked fill
3701
+ is the series colour, not accent, because the swatch is the key. Hidden series keep their colour slots. ── */
3702
+ .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); }
3703
+ .alt-chart-legend[data-toggle] { gap: var(--space-1); margin-left: calc(-1 * var(--space-2)); }
3704
+ .alt-legend-key { display: inline-flex; align-items: center; gap: var(--space-2); }
3705
+ .alt-legend-mark { display: block; flex: none; }
3706
+ button.alt-legend-key {
3707
+ appearance: none; border: 0; margin: 0; padding: 5px var(--space-2); border-radius: var(--radius-sm); font: inherit; color: var(--ink);
3708
+ background: transparent; box-shadow: var(--elev-0); cursor: pointer; -webkit-tap-highlight-color: transparent;
3709
+ 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);
3710
+ }
3711
+ button.alt-legend-key:focus-visible { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
3712
+ @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))); } }
3713
+ button.alt-legend-key:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3714
+ button.alt-legend-key[aria-pressed="false"] { color: var(--ink-muted); }
3715
+ button.alt-legend-key[aria-disabled] { cursor: default; }
3716
+ .alt-legend-swatch {
3717
+ display: inline-grid; place-items: center; width: 22px; height: 16px; box-sizing: border-box; border-radius: 5px;
3718
+ border: 1px solid var(--line-control); background: var(--surface-trench); box-shadow: var(--trench);
3719
+ }
3720
+ .alt-legend-swatch .alt-legend-mark {
3721
+ opacity: 0; clip-path: circle(0% at 50% 50%);
3722
+ transition: clip-path var(--dur-travel-1) var(--ease-sink), opacity var(--dur-travel-1) var(--ease-sink);
3723
+ }
3724
+ .alt-legend-swatch[data-shown] { border-color: transparent; }
3725
+ .alt-legend-swatch .alt-legend-mark { width: 22px; height: 16px; }
3726
+ .alt-legend-swatch[data-shown] .alt-legend-mark { opacity: 1; clip-path: circle(75% at 50% 50%); transition-timing-function: var(--ease-rise); }
3727
+ @media (prefers-contrast: more) { .alt-legend-swatch { box-shadow: inset 0 1px 0 0 var(--ink); } }
3728
+ [data-contrast="more"] .alt-legend-swatch { box-shadow: inset 0 1px 0 0 var(--ink); }
3729
+ @media (forced-colors: active) {
3730
+ .alt-chart-readout { border: 2px solid CanvasText; }
3731
+ button.alt-legend-key:hover:not([aria-disabled]) { border: 1px solid CanvasText; padding: 4px 7px; }
3732
+ .alt-legend-swatch { border-color: CanvasText; }
3733
+ .alt-legend-swatch:not([data-shown]) { border-style: dashed; }
3734
+ .alt-chart-crosshair { stroke: CanvasText; }
3735
+ .alt-line-active { stroke: Canvas; }
3736
+ }
3737
+ @media (prefers-contrast: more) {
3738
+ .alt-chart-grid line, .alt-chart-floor { stroke: var(--line-control); }
3739
+ .alt-chart-ground { stroke: var(--ink); }
3740
+ }
3741
+ [data-contrast="more"] .alt-chart-grid line, [data-contrast="more"] .alt-chart-floor { stroke: var(--line-control); }
3742
+ [data-contrast="more"] .alt-chart-ground { stroke: var(--ink); }
3743
+ @media (forced-colors: active) {
3744
+ .alt-chart-axis text { fill: CanvasText; }
3745
+ .alt-chart-axis[data-inline] text { stroke: Canvas; }
3746
+ .alt-chart-grid line, .alt-chart-floor { stroke: GrayText; }
3747
+ .alt-chart-ground { stroke: CanvasText; }
3748
+ }
3749
+
1637
3750
  /* ════════════════════ Accessible elevation ════════════════════ */
1638
3751
 
1639
3752
  /* High contrast: elevation becomes a hard slab whose thickness is the height (1 level = 1px more),
@@ -1651,7 +3764,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1651
3764
  --line: var(--line-control); --alt-sheen: none;
1652
3765
  --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);
1653
3766
  }
1654
- .alt-btn, .alt-kbd, .alt-crumbs button { --wall-c: var(--ink); }
3767
+ .alt-btn, .alt-kbd, .alt-crumbs .alt-crumb-link { --wall-c: var(--ink); }
1655
3768
  .alt-empty { border-style: solid; }
1656
3769
  }
1657
3770
  [data-contrast="more"], [data-contrast="more"] [data-theme] {
@@ -1666,7 +3779,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1666
3779
  --line: var(--line-control); --alt-sheen: none;
1667
3780
  --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);
1668
3781
  }
1669
- [data-contrast="more"] .alt-btn, [data-contrast="more"] .alt-kbd, [data-contrast="more"] .alt-crumbs button { --wall-c: var(--ink); }
3782
+ [data-contrast="more"] .alt-btn, [data-contrast="more"] .alt-kbd, [data-contrast="more"] .alt-crumbs .alt-crumb-link { --wall-c: var(--ink); }
1670
3783
  [data-contrast="more"] .alt-empty { border-style: solid; }
1671
3784
 
1672
3785
  /* Forced colours (Windows High Contrast) drop every box-shadow, which erases elevation entirely
@@ -1690,7 +3803,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1690
3803
  .alt-tabs[data-variant="folder"] .alt-tabs-trigger[aria-selected="true"] { border: 2px solid CanvasText; border-bottom: 0; }
1691
3804
  .alt-tabs[data-variant="folder"] > .alt-tabs-content { border: 1px solid CanvasText; }
1692
3805
  .alt-acc-item[data-state="open"] { border: 1px solid CanvasText; } .alt-acc-content { border: 1px dashed CanvasText; }
1693
- .alt-crumbs button { border: 1px solid CanvasText; } .alt-crumbs li[data-current] button { border-width: 2px; }
3806
+ .alt-crumbs .alt-crumb-link { border: 1px solid CanvasText; } .alt-crumbs li[data-current] .alt-crumb-link { border-width: 2px; }
1694
3807
  .alt-cmd { border: 4px double CanvasText; } .alt-cmd-hl { display: none; } .alt-cmd-item[aria-selected="true"] { outline: 2px solid Highlight; outline-offset: -2px; }
1695
3808
  .alt-sheet { border: 2px solid CanvasText; } .alt-sheet-pull { border: 1px dashed CanvasText; }
1696
3809
  .alt-input[data-filled]:not([data-invalid]) { border-style: solid !important; }
@@ -1699,5 +3812,769 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
1699
3812
  .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; }
1700
3813
  .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; }
1701
3814
  .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; }
1702
- .alt-avatar[data-status="offline"] .alt-avatar-face { border: 1px dashed CanvasText; } .alt-avatar[data-status="active"] .alt-avatar-face { border: 2px solid CanvasText; }
3815
+ .alt-avatar:is([data-status="offline"], [data-more]) .alt-avatar-face { border-color: GrayText; } .alt-avatar[data-live] .alt-avatar-face { border-width: 2px; }
3816
+ .alt-avatar[data-status="active"] .alt-avatar-lamp { forced-color-adjust: none; background: CanvasText; }
3817
+ }
3818
+
3819
+ /* Stationary dialogs keep their context still; the dimming scrim and modal tier remain. */
3820
+ .alt-dialog-root[data-background="stationary"] > .alt-scrim,
3821
+ [data-modal-stationary] > .alt-scrim {
3822
+ backdrop-filter: none;
3823
+ -webkit-backdrop-filter: none;
3824
+ }
3825
+
3826
+ /* ── InlineEdit: one value edited where it sits. The value's text lives in a slot with the control's own inset (12px
3827
+ and a transparent 1px border, as .alt-input and .alt-select-trigger have) and font, at the control's height, and the
3828
+ line reaches 13px past its column on each side so the text itself lines up with the page. Opening puts the control in
3829
+ the same slot: the text stays exactly where it was and only the well (floor, walls, border) fades in around it. Save
3830
+ and Cancel take the Edit button's place on the same row; notes under the line (an error, the key hint, "Choose to
3831
+ save") arrive as the line's glide reaches them. The status is announced, never shown. ── */
3832
+ .alt-inline-edit { min-width: 0; }
3833
+ .alt-inline-edit-surface { min-width: 0; border-radius: var(--radius-md); }
3834
+ .alt-inline-edit-line { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); min-width: 0; margin-inline: -13px; }
3835
+ .alt-inline-edit-slot, .alt-inline-edit-slot-box { flex: 0 1 auto; min-width: 0; }
3836
+ .alt-inline-edit[data-state]:not([data-state="read"]) .alt-inline-edit-slot-box { flex: 1 1 14rem; }
3837
+ .alt-inline-edit-slot {
3838
+ position: relative; box-sizing: border-box; display: flex; align-items: center; max-width: 100%; min-height: max(40px, calc(1lh + 18px));
3839
+ padding: 0 12px; border: 1px solid transparent; border-radius: var(--radius-md);
3840
+ }
3841
+ .alt-inline-edit-value { overflow-wrap: anywhere; min-width: 0; }
3842
+ .alt-inline-edit-slot-box > .alt-input:not([data-multiline]) { font: inherit; height: max(40px, calc(1lh + 18px)); }
3843
+ .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; }
3844
+ .alt-inline-edit-slot-box .alt-select > .alt-input { font: inherit; height: max(40px, calc(1lh + 18px)); }
3845
+ .alt-inline-edit .alt-input, .alt-inline-edit .alt-select { min-width: 0; }
3846
+ .alt-inline-edit .alt-input input { width: 100%; min-width: 0; }
3847
+ /* The well opens under text that stays put: only its floor, walls and border fade in. */
3848
+ .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); }
3849
+ @keyframes alt-well-open { from { background-color: transparent; border-color: transparent; box-shadow: none; } }
3850
+ .alt-inline-edit-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-2); margin-inline-start: auto; }
3851
+ .alt-inline-edit-actions .alt-btn { max-width: 100%; white-space: normal; }
3852
+ .alt-inline-edit-note { margin: var(--space-2) 0 0; }
3853
+ .alt-inline-edit-current { color: var(--ink-muted); font: 400 12.5px/17px var(--font-sans); overflow-wrap: anywhere; }
3854
+ /* Multiline: the slot takes the textarea's inset and the whole width; Edit, then Save and Cancel, sit under it on the right. */
3855
+ .alt-inline-edit[data-multiline] .alt-inline-edit-slot, .alt-inline-edit[data-multiline] .alt-inline-edit-slot-box { flex: 1 1 100%; }
3856
+ .alt-inline-edit[data-multiline] .alt-inline-edit-slot { align-items: flex-start; min-height: 0; padding: var(--space-2) 12px; }
3857
+ .alt-inline-edit[data-multiline] .alt-inline-edit-value { white-space: pre-wrap; }
3858
+ .alt-inline-edit[data-multiline] textarea { flex: none; min-height: 0; resize: none; }
3859
+ .alt-inline-edit[data-multiline] .alt-inline-edit-actions { margin-inline-start: 0; justify-content: flex-start; }
3860
+ .alt-inline-edit-keys { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 2px; }
3861
+
3862
+ /* The value as its own button ('pencil', 'field'): the same slot, so its text sits where it always does. */
3863
+ .alt-inline-edit-slot-button { width: auto; margin: 0; font: inherit; color: inherit; text-align: start; background: transparent; cursor: pointer;
3864
+ 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); }
3865
+ .alt-inline-edit-slot-button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
3866
+ .alt-inline-edit-slot-button:disabled { cursor: default; }
3867
+ .alt-inline-edit-slot-button[data-empty] .alt-inline-edit-value { color: var(--ink-muted); }
3868
+ /* Pencil: a faint well tint and the pencil, whose space is always reserved, so showing it moves nothing. Touch screens
3869
+ can't hover, so they always show it. */
3870
+ .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); }
3871
+ @media (hover: hover) { .alt-inline-edit[data-trigger="pencil"] .alt-inline-edit-slot-button:hover:not(:disabled) { background: var(--surface-well); } }
3872
+ .alt-inline-edit-slot-button:focus-visible .alt-inline-edit-pencil { opacity: 1; }
3873
+ @media (hover: hover) { .alt-inline-edit-slot-button:hover:not(:disabled) .alt-inline-edit-pencil { opacity: 1; } }
3874
+ @media (hover: none) { .alt-inline-edit-pencil { opacity: 1; } }
3875
+ /* Field: pointing at the value draws the filled input's border and floor around it, so the click only focuses it. */
3876
+ .alt-inline-edit[data-trigger="field"] .alt-inline-edit-slot-button { cursor: text; }
3877
+ @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); } }
3878
+ .alt-inline-edit[data-trigger="field"] .alt-inline-edit-slot-button:focus-visible { outline-color: var(--alt-rim-held); }
3879
+ /* It was drawn on hover already, so the real field doesn't fade in again. */
3880
+ .alt-inline-edit[data-trigger="field"] .alt-inline-edit-slot-box > .alt-input { animation: none; }
3881
+ /* hint="seam": at rest, a hairline groove is scored under the value where its well will open (a line-control cut with
3882
+ the well's light lip beneath it), so a value whose trigger only shows on hover still reads as something to point at.
3883
+ The field trigger's border replaces it on hover. Flat in contrast="more" and forced colours. */
3884
+ .alt-inline-edit [data-hint="seam"]::after {
3885
+ content: ""; position: absolute; left: 12px; right: 12px; bottom: 6px; height: 1px; border-radius: 1px; pointer-events: none;
3886
+ background: color-mix(in oklab, var(--line-control) 40%, transparent); box-shadow: 0 1px 0 0 var(--alt-lip);
3887
+ transition: opacity var(--dur-travel-1) var(--ease-rise);
3888
+ }
3889
+ .alt-inline-edit[data-trigger="pencil"] [data-hint="seam"]::after { right: calc(12px + 15px + var(--space-2)); }
3890
+ .alt-inline-edit-plate[data-hint="seam"] { position: relative; }
3891
+ .alt-inline-edit-plate[data-hint="seam"]::after { left: 12px; right: 12px; bottom: 3px; }
3892
+ @media (hover: hover) { .alt-inline-edit[data-trigger="field"] [data-hint="seam"]:is(:hover, :focus-visible)::after { opacity: 0; } }
3893
+ .alt-inline-edit [data-hint="seam"][data-empty]::after { display: none; }
3894
+ [data-contrast="more"] .alt-inline-edit [data-hint="seam"]::after { background: var(--line-control); box-shadow: none; }
3895
+ @media (forced-colors: active) {
3896
+ .alt-inline-edit [data-hint="seam"]::after { background: none; border-bottom: 1px dashed CanvasText; box-shadow: none; }
3897
+ .alt-inline-edit[data-trigger="field"] .alt-inline-edit-slot-button:is(:hover, :focus-visible) { border-color: CanvasText; }
3898
+ }
3899
+
3900
+ /* ── EditRegion: several fields of one record, saved together. Read mode is the record on the ground (level 0) with a
3901
+ visible Edit button. Editing lifts the region into a static raised card (level 1, no hover lift: it's being worked
3902
+ in, not offered), as an Accordion lid lifts over its compartment; the fields inside are openings (Input, Select…),
3903
+ and Surface's own signals carry the write: pending hovers after dur-grace, a known failure drops to a trench, an
3904
+ accepted save settles back to the ground with one afterglow for the whole region. The height glides open; the
3905
+ fields fade in together, never staggered. Forced colours and contrast="more" come from Surface and the controls. ── */
3906
+ .alt-edit-region { min-width: 0; --alt-er-pad: var(--space-3); }
3907
+ .alt-edit-region[data-layout="in-place"] { --alt-er-pad: calc(var(--space-2) + 13px); }
3908
+ /* The editor's padding and border are there in read mode too, under a matching negative margin, so lifting it changes
3909
+ colour and shadow but never moves the record; the afterglow fills that padded box, so it never sits against the text. */
3910
+ .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);
3911
+ 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); }
3912
+ .alt-edit-region:not([data-state="read"]) .alt-edit-region-surface { border-color: var(--line-control); }
3913
+ .alt-edit-region-view { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-2) var(--space-3); flex-wrap: wrap; }
3914
+ .alt-edit-region-values { flex: 1 1 14rem; min-width: 0; overflow-wrap: anywhere; }
3915
+ .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); }
3916
+ .alt-edit-region-fields { display: grid; gap: var(--space-3); min-width: 0; }
3917
+ .alt-edit-region-fields .alt-field, .alt-edit-region-fields .alt-input, .alt-edit-region-fields .alt-select { min-width: 0; }
3918
+ .alt-edit-region-fields .alt-input input { width: 100%; min-width: 0; }
3919
+ .alt-edit-region-chrome { display: grid; gap: var(--space-3); }
3920
+ .alt-edit-region-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
3921
+ .alt-edit-region-actions .alt-btn { max-width: 100%; white-space: normal; }
3922
+ /* In place: the form doesn't fade in as a whole; fields whose text lands where their value was simply appear, the
3923
+ rest fade in, and nothing slides. Controls take the value's font and reach 13px past their column on each side. */
3924
+ .alt-edit-region-form[data-layout="in-place"] { gap: var(--space-3); animation: none; }
3925
+ /* The fields sit where the values were, so the form adds no spacing of its own between them. */
3926
+ .alt-edit-region-form[data-layout="in-place"] .alt-edit-region-fields { display: block; }
3927
+ .alt-edit-region-form[data-layout="in-place"] :is(.alt-input:not(.alt-select > .alt-input), .alt-select) { margin-inline: -13px; }
3928
+ .alt-edit-region-form[data-layout="in-place"] :is(.alt-input input, .alt-input textarea, .alt-select-trigger, .alt-switch-row) { font: inherit; }
3929
+ .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)); }
3930
+ .alt-edit-region-form[data-layout="in-place"] .alt-edit-region-actions { justify-content: flex-end; }
3931
+ /* The record around the fields names them (a row's label, the value's place), so a Field's label is announced, not shown. */
3932
+ .alt-edit-region-form[data-layout="in-place"] .alt-field { gap: var(--space-1); }
3933
+ .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; }
3934
+ /* A reserved value already has its control's height and inset, so in labelled rows Edit moves nothing. */
3935
+ .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; }
3936
+ /* ── TagInput: a set of short values in one field. The field is an opening, built like Input; once it holds a tag its
3937
+ floor rises flush (filled). Each tag is a label, part of the record, so it's a Badge plate stamped flush into the
3938
+ floor, and stamped again (down 3px, settling) when it arrives. A first Backspace arms the last tag: it lifts one
3939
+ level on a critical tint; a second lifts it off (up 8px, fading) and the gap closes. A tag waiting on onAdd or
3940
+ onRemove hovers at level 2 after dur-grace; a rejected one drops to a trench with a critical rim. Suggestions are the
3941
+ Combobox's list at level 3. ── */
3942
+ .alt-tag-input { position: relative; display: block; min-width: 0; }
3943
+ .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; }
3944
+ .alt-tag-input > .alt-input[data-size="sm"] { min-height: var(--size-field-sm); padding-block: 3px; }
3945
+ .alt-tag-input > .alt-input > input { flex: 1 1 6ch; min-width: 6ch; height: 28px; }
3946
+ .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); }
3947
+ .alt-tag-list { display: contents; list-style: none; margin: 0; padding: 0; }
3948
+ .alt-tag {
3949
+ --t-bg: color-mix(in oklab, var(--surface-ground), var(--surface-well) 70%);
3950
+ position: relative; display: inline-flex; align-items: center; max-width: 100%; height: 24px; border-radius: var(--radius-sm);
3951
+ background: var(--t-bg); color: var(--ink); font: 550 12.5px/1 var(--font-sans); vertical-align: middle;
3952
+ 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);
3953
+ 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);
3954
+ }
3955
+ .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; }
3956
+ .alt-tag-face:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
3957
+ .alt-tag:has(.alt-tag-remove) .alt-tag-face { padding-right: 2px; }
3958
+ .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); }
3959
+ @media (hover: hover) { .alt-tag-remove:hover { background: var(--surface-raised); color: var(--ink); box-shadow: var(--elev-1); } }
3960
+ .alt-tag[data-stamp] { animation: alt-stamp-a var(--dur-travel-2) var(--ease-settle); }
3961
+ .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); }
3962
+ .alt-tag[data-status="pending"] { animation: alt-tag-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
3963
+ @keyframes alt-tag-pending { to { background: var(--surface-lifted); box-shadow: var(--elev-2); transform: translateY(calc(-2px * var(--alt-travel))); } }
3964
+ .alt-tag[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
3965
+ .alt-tag[data-leaving] { animation: alt-lifted-off 140ms var(--ease-rise) forwards; }
3966
+ .alt-tag-edit { display: inline-flex; }
3967
+ .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; }
3968
+ .alt-tag-edit-input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
3969
+ .alt-tag-edit-input[aria-invalid] { background: var(--critical-soft); border-color: var(--critical); }
3970
+ .alt-tag-hint { display: block; margin-top: var(--space-1); color: var(--ink-muted); font: 400 12.5px/17px var(--font-sans); }
3971
+ .alt-tag-error { margin: var(--space-1) 0 0; }
3972
+ .alt-tag-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
3973
+ .alt-tag-standalone { margin: 2px; }
3974
+ [data-contrast="more"] .alt-tag[data-armed] { box-shadow: 0 2px 0 0 var(--ink), inset 0 0 0 1px var(--critical); }
3975
+ @media (forced-colors: active) {
3976
+ .alt-tag { border: 1px solid CanvasText; } .alt-tag[data-armed] { border: 1px solid Highlight; outline: 1px solid Highlight; }
3977
+ .alt-tag[data-status="pending"] { border-style: dotted; } .alt-tag[data-status="failed"] { border: 2px dashed CanvasText; }
3978
+ .alt-tag-input > .alt-input:not([data-filled]) { border-style: dashed; }
3979
+ }
3980
+
3981
+ /* ── DatePicker, DateRangePicker, Calendar: the field is an opening built like Select. Day, month and year are typed
3982
+ segments; a partial date isn't a value, so the floor rises flush only once a whole, allowed date is held. The
3983
+ calendar floats at level 3 like a Popover. Days are flush (at this density a trench per day would be noise); the
3984
+ chosen day carries the one raised accent mark, the RadioGroup thumb's rule, and the mark is carried to a new day in
3985
+ the same month. Hovering a day shows the mark's ghost (feedforward). A range is the Slider's build: a sunk
3986
+ accent-soft track between two raised ends. A month change is lateral, so it cross-fades. Calendar on its own lies
3987
+ on the page at level 0. ── */
3988
+ .alt-date-picker { position: relative; display: block; min-width: 0; }
3989
+ .alt-date-picker > .alt-input { gap: 0; padding-right: 4px; cursor: text; }
3990
+ .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); }
3991
+ .alt-date-seg { padding: 1px 2px; border-radius: 3px; outline: none; caret-color: transparent; }
3992
+ .alt-date-seg[data-placeholder] { color: var(--ink-muted); }
3993
+ .alt-date-seg:focus { background: var(--accent); color: var(--on-accent); }
3994
+ .alt-date-sep { padding: 0 1px; color: var(--ink-muted); }
3995
+ .alt-date-to { padding: 0 var(--space-2); color: var(--ink-muted); transform: translateY(calc(var(--wd) * 0.5)); }
3996
+ .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); }
3997
+ @media (hover: hover) { .alt-date-button:hover:not(:disabled) { background: var(--surface-raised); color: var(--ink); box-shadow: var(--elev-1); } }
3998
+ .alt-date-button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
3999
+ .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); }
4000
+ .alt-date-picker > .alt-input:not([data-filled]):not([data-invalid]):not([data-disabled]):focus-within { --n: var(--alt-travel); }
4001
+ .alt-date-error { margin: var(--space-1) 0 0; }
4002
+ .alt-date-panel { padding: var(--space-3); min-width: 0; }
4003
+ .alt-date-step { margin: 0 0 var(--space-2); color: var(--ink-muted); font: 500 12.5px/17px var(--font-sans); }
4004
+ .alt-cal { width: max-content; color: var(--ink); font: 400 13.5px/1 var(--font-sans); font-variant-numeric: tabular-nums; }
4005
+ .alt-cal-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); margin-bottom: var(--space-2); }
4006
+ .alt-cal-title { margin: 0; font: 600 14px/20px var(--font-sans); }
4007
+ .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); }
4008
+ @media (hover: hover) { .alt-cal-nav:hover:not(:disabled) { background: var(--surface-raised); color: var(--ink); box-shadow: var(--elev-1); } }
4009
+ .alt-cal-nav:disabled { opacity: 0.4; cursor: default; }
4010
+ .alt-cal-nav:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4011
+ [dir="rtl"] .alt-cal-chevron { scale: -1 1; }
4012
+ .alt-cal-body { position: relative; }
4013
+ .alt-cal-grid { border-collapse: separate; border-spacing: 0 2px; animation: alt-cal-fade var(--dur-travel-1) ease-out; }
4014
+ @keyframes alt-cal-fade { from { opacity: 0; } }
4015
+ .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; }
4016
+ .alt-cal-grid td { padding: 0; text-align: center; }
4017
+ .alt-cal-day {
4018
+ position: relative; z-index: 1; display: inline-grid; place-items: center; width: 36px; height: 34px; padding: 0; border: 0; border-radius: var(--radius-sm);
4019
+ background: none; color: inherit; font: inherit; cursor: pointer; transition: color var(--dur-travel-2) var(--ease-settle);
4020
+ }
4021
+ .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); }
4022
+ @media (hover: hover) { .alt-cal-day:hover:not([aria-disabled]):not([data-chosen])::before { opacity: 0.16; scale: 1; } }
4023
+ .alt-cal-day:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
4024
+ .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); }
4025
+ .alt-cal-day[aria-disabled] { color: var(--ink-faint); cursor: default; }
4026
+ .alt-cal-day[data-chosen] { color: var(--on-accent); font-weight: 600; }
4027
+ .alt-cal-day[data-chosen][data-today]::after { background: var(--on-accent); }
4028
+ .alt-cal-mark {
4029
+ position: absolute; left: 0; top: 0; z-index: 0; border-radius: var(--radius-sm); pointer-events: none; opacity: 0;
4030
+ 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);
4031
+ 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);
4032
+ }
4033
+ /* Presets: a column of rows beside the calendar (above it when narrow), built like Select's options at level 3 inside
4034
+ the popover: one gliding highlight, the chosen row wearing the tick. A chosen preset fills the field with its name in
4035
+ ink and its dates in ink-muted, and settles with the value's afterglow. */
4036
+ .alt-date-panes { display: flex; align-items: flex-start; gap: var(--space-3); }
4037
+ .alt-date-presets { position: relative; flex: none; min-width: 168px; padding: 0 var(--space-3) 0 0; border-right: 1px solid var(--line); }
4038
+ .alt-date-presets .alt-menu-hl { left: 0; right: var(--space-3); }
4039
+ .alt-date-presets:focus { outline: none; }
4040
+ .alt-date-custom { min-width: 0; }
4041
+ @media (max-width: 480px) {
4042
+ .alt-date-panes { flex-direction: column; }
4043
+ .alt-date-presets { align-self: stretch; padding: 0 0 var(--space-2); border-right: 0; border-bottom: 1px solid var(--line); }
4044
+ .alt-date-presets .alt-menu-hl { right: 0; }
4045
+ }
4046
+ .alt-date-preset {
4047
+ 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;
4048
+ font: 400 14px/20px var(--font-sans); color: var(--ink); text-align: start; white-space: nowrap; cursor: pointer;
4049
+ transform: translateY(calc(var(--wd) * 0.5)); transition: transform var(--dur-travel-2) var(--ease-settle);
4050
+ }
4051
+ .alt-date-preset:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4052
+ .alt-date-preset-name { flex: none; font-weight: 500; }
4053
+ .alt-date-preset-dates { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
4054
+ .alt-date-preset[data-glow="a"] { animation: alt-value-a calc(var(--dur-settle) / 5) var(--ease-decay); }
4055
+ .alt-date-preset[data-glow="b"] { animation: alt-value-b calc(var(--dur-settle) / 5) var(--ease-decay); }
4056
+ @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; } }
4057
+ /* A range: the sunk track, then the two raised ends. */
4058
+ .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); }
4059
+ .alt-cal[data-range] .alt-cal-day[data-span-start]:not([data-chosen])::before { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
4060
+ .alt-cal[data-range] .alt-cal-day[data-span-end]:not([data-chosen])::before { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
4061
+ .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); }
4062
+ .alt-calendar { display: inline-block; }
4063
+ [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); }
4064
+ @media (forced-colors: active) {
4065
+ .alt-date-panel { border: 2px solid CanvasText; }
4066
+ .alt-date-picker > .alt-input:not([data-filled]) { border-style: dashed; }
4067
+ .alt-cal-mark, .alt-cal[data-range] .alt-cal-day[data-chosen]::before { forced-color-adjust: none; background: Highlight; }
4068
+ .alt-cal-day[data-chosen] { forced-color-adjust: none; color: HighlightText; }
4069
+ .alt-cal[data-range] .alt-cal-day[data-span]:not([data-chosen])::before { border: 1px dashed CanvasText; background: Canvas; }
4070
+ .alt-cal-day[data-today] { text-decoration: underline; }
4071
+ .alt-date-seg:focus { forced-color-adjust: none; background: Highlight; color: HighlightText; }
4072
+ }
4073
+ @media (prefers-reduced-motion: reduce) { .alt-cal-mark { transition: none; } }
4074
+
4075
+ /* ── Tree: a hierarchy seen all at once. Nodes are the record: flush rows on the ground, indented space-4 per level,
4076
+ like Table rows. Only a node that acts lifts on hover; the selected node is tinted accent-soft and raised one level.
4077
+ Expanding is disclosure of existing records, so it's a chevron turn, not a rise. Renaming sinks the label into a
4078
+ well (InlineEdit's field); a write hovers while pending and drops to a trench when it fails. A carried node floats
4079
+ 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
4080
+ depth, and a node it would go into opens to a trench with an accent rim (a dashed critical one if it can't). ── */
4081
+ .alt-tree-wrap { position: relative; min-width: 0; }
4082
+ .alt-tree { margin: 0; padding: 0; list-style: none; color: var(--ink); font: 400 14px/20px var(--font-sans); }
4083
+ .alt-tree-row {
4084
+ --indent: calc((var(--level, 1) - 1) * var(--space-4));
4085
+ 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);
4086
+ outline: none; cursor: default; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; touch-action: pan-y;
4087
+ 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);
4088
+ }
4089
+ .alt-tree-indent { flex: none; width: var(--indent); }
4090
+ .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; }
4091
+ .alt-tree-chevron svg { rotate: -90deg; transition: rotate var(--dur-travel-2) var(--ease-settle); }
4092
+ .alt-tree-chevron[data-open] svg { rotate: 0deg; }
4093
+ [dir="rtl"] .alt-tree-chevron:not([data-open]) svg { rotate: 90deg; }
4094
+ .alt-tree-leaf { flex: none; width: 20px; }
4095
+ .alt-tree-label { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4096
+ @media (hover: hover) { .alt-tree-row:hover:not([data-carried]) { background: var(--surface-raised); box-shadow: var(--elev-1); } }
4097
+ .alt-tree-row[aria-selected="true"] { background: var(--accent-soft); box-shadow: var(--elev-1); }
4098
+ .alt-tree-row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
4099
+ .alt-tree-row[data-status="pending"] { animation: alt-tree-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
4100
+ @keyframes alt-tree-pending { to { background: var(--surface-lifted); box-shadow: var(--elev-2); transform: translateY(calc(-1px * var(--alt-travel))); } }
4101
+ .alt-tree-row[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4102
+ .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)); }
4103
+ .alt-tree-row[data-carried="pointer"] { opacity: 0.4; }
4104
+ .alt-tree-row[data-into] { background: var(--surface-trench); box-shadow: var(--trench), inset 0 0 0 2px var(--accent); }
4105
+ .alt-tree-row[data-into="blocked"] { box-shadow: var(--trench), inset 0 0 0 2px var(--critical); }
4106
+ .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; }
4107
+ .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; }
4108
+ .alt-tree-ghost {
4109
+ 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);
4110
+ background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--ink); font: 400 14px/20px var(--font-sans); pointer-events: none;
4111
+ rotate: calc(-1.2deg * var(--alt-travel)); scale: calc(1 + 0.015 * var(--alt-travel));
4112
+ }
4113
+ .alt-tree-wrap[data-carrying="pointer"] { cursor: grabbing; }
4114
+ .alt-tree-name { position: relative; display: inline-flex; flex-direction: column; flex: 1; min-width: 0; }
4115
+ .alt-tree-name-input {
4116
+ width: 100%; min-width: 8ch; height: 26px; padding: 0 6px; border: 1px solid var(--line-control); border-radius: var(--radius-sm); outline: none;
4117
+ background: var(--surface-well); box-shadow: var(--well); color: var(--ink); font: inherit; animation: alt-tree-sink var(--dur-travel-1) var(--ease-sink);
4118
+ }
4119
+ @keyframes alt-tree-sink { from { background: var(--surface-ground); box-shadow: none; } }
4120
+ .alt-tree-name-input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4121
+ .alt-tree-name[data-invalid] .alt-tree-name-input { background: var(--critical-soft); border-color: var(--critical); box-shadow: var(--trench); }
4122
+ .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; }
4123
+ .alt-tree-new { cursor: default; }
4124
+ .alt-tree-imprint { width: 40%; height: 12px; border-radius: var(--radius-sm); }
4125
+ .alt-tree-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4126
+ [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); }
4127
+ @media (forced-colors: active) {
4128
+ .alt-tree-row[aria-selected="true"] { border: 1px solid Highlight; } .alt-tree-row[data-carried="keys"], .alt-tree-ghost { border: 2px solid CanvasText; }
4129
+ .alt-tree-slot { background: none; border-top: 2px dashed CanvasText; height: 0; } .alt-tree-row[data-into] { border: 3px dashed Highlight; }
4130
+ .alt-tree-row[data-status="pending"] { border: 1px dotted CanvasText; } .alt-tree-row[data-status="failed"] { border: 2px dashed CanvasText; }
4131
+ }
4132
+
4133
+ /* ── Checklist: a list you write into. Each item is the record, flush on the ground, and its line is the editor: no
4134
+ raised editor and no Save, the line commits when you leave it. The empty line at the end is an opening (a well
4135
+ while it's focused or holds text). The checkbox is the Checkbox's choice; a done item is pressed into the record,
4136
+ muted with a strike drawn left to right, and stays in place. A write hovers after dur-grace and a failure drops to
4137
+ a trench with Retry on the line. Nesting indents by space-6 and glides sideways, a change of hierarchy. ── */
4138
+ .alt-checklist { position: relative; min-width: 0; color: var(--ink); font: 400 14px/20px var(--font-sans); }
4139
+ .alt-checklist-list { margin: 0; padding: 0; list-style: none; }
4140
+ .alt-checklist-list .alt-checklist-list { padding-left: var(--space-6); }
4141
+ .alt-checklist-item { min-width: 0; border-radius: var(--radius-sm); outline: none; }
4142
+ .alt-checklist-item:focus-visible > .alt-checklist-line { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
4143
+ .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); }
4144
+ .alt-checklist-line > .alt-check { flex: none; }
4145
+ .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; }
4146
+ .alt-checklist-text:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-ink); }
4147
+ .alt-checklist-item[data-done] > .alt-checklist-line .alt-checklist-text {
4148
+ color: var(--ink-muted); text-decoration: line-through; text-decoration-color: var(--ink-muted);
4149
+ animation: alt-checklist-strike var(--dur-travel-2) var(--ease-rise);
4150
+ }
4151
+ @keyframes alt-checklist-strike { from { text-decoration-color: transparent; color: var(--ink); } }
4152
+ .alt-checklist-item[data-arrived] > .alt-checklist-line { animation: alt-checklist-open var(--dur-travel-1) var(--ease-sink); }
4153
+ @keyframes alt-checklist-open { from { background: var(--surface-well); box-shadow: var(--well); transform: translateY(calc(-4px * var(--alt-travel))); } }
4154
+ .alt-checklist-item[data-status="pending"] > .alt-checklist-line { animation: alt-checklist-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
4155
+ @keyframes alt-checklist-pending { to { background: var(--surface-lifted); box-shadow: var(--elev-2); } }
4156
+ .alt-checklist-item[data-status="failed"] > .alt-checklist-line { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4157
+ .alt-checklist-failed { display: inline-flex; flex: none; align-items: center; gap: 4px; color: var(--critical); font: 400 12.5px/17px var(--font-sans); }
4158
+ .alt-checklist-new .alt-checklist-line { color: var(--ink-muted); }
4159
+ .alt-checklist-plus { display: inline-grid; place-items: center; flex: none; width: 20px; font: 500 16px/1 var(--font-sans); }
4160
+ .alt-checklist-new .alt-checklist-text::placeholder { color: var(--ink-muted); opacity: 1; }
4161
+ .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); }
4162
+ .alt-checklist-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4163
+ [data-contrast="more"] .alt-checklist-new .alt-checklist-line:focus-within { box-shadow: inset 0 2px 0 0 var(--ink); }
4164
+ @media (forced-colors: active) {
4165
+ .alt-checklist-new .alt-checklist-line { border: 1px dashed CanvasText; }
4166
+ .alt-checklist-item[data-status="pending"] > .alt-checklist-line { border: 1px dotted CanvasText; }
4167
+ .alt-checklist-item[data-status="failed"] > .alt-checklist-line { border: 2px dashed CanvasText; }
4168
+ }
4169
+
4170
+ /* ── FilterBar: a query as a row of conditions. Applied filters are part of what you're looking at, so each condition
4171
+ is a plate stamped flush into the bar like a Badge, split into segments (field · condition · value · remove). Each
4172
+ segment opens a picker, so it lifts one level when pointed at (hover is a promise). A condition still waiting for
4173
+ a value keeps that segment as an opening (a dashed well), and so does Add filter, deepening under the pointer.
4174
+ Pickers float at level 3 as Select's list does. A changed condition is re-stamped (down 3px, settling). ── */
4175
+ .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); }
4176
+ .alt-filter-token {
4177
+ display: inline-flex; align-items: stretch; max-width: 100%; min-height: 28px; border-radius: var(--radius-sm);
4178
+ background: color-mix(in oklab, var(--surface-ground), var(--surface-well) 70%);
4179
+ 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);
4180
+ }
4181
+ .alt-filter-token[data-stamp] { animation: alt-stamp-a var(--dur-travel-2) var(--ease-settle); }
4182
+ .alt-filter-anchor { position: relative; display: inline-flex; min-width: 0; }
4183
+ .alt-filter-seg {
4184
+ 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);
4185
+ background: none; color: inherit; font: inherit; white-space: nowrap; cursor: pointer;
4186
+ 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);
4187
+ }
4188
+ .alt-filter-seg[aria-expanded="true"] { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); }
4189
+ @media (hover: hover) { .alt-filter-seg:hover { background: var(--surface-raised); box-shadow: var(--elev-1); transform: translateY(calc(-1px * var(--alt-travel))); } }
4190
+ .alt-filter-seg:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -1px; }
4191
+ .alt-filter-field { font-weight: 600; }
4192
+ .alt-filter-operator { color: var(--ink-muted); }
4193
+ .alt-filter-value { font-weight: 550; overflow: hidden; text-overflow: ellipsis; max-width: 24ch; }
4194
+ .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; }
4195
+ .alt-filter-remove { width: 28px; justify-content: center; padding: 0; color: var(--ink-muted); }
4196
+ .alt-filter-add { border: 1px dashed var(--line-control); background: var(--surface-well); box-shadow: var(--well); color: var(--ink-muted); }
4197
+ @media (hover: hover) { .alt-filter-add:hover { background: var(--surface-trench); box-shadow: var(--trench); transform: none; color: var(--ink); } }
4198
+ .alt-filter-plus { font: 500 15px/1 var(--font-sans); }
4199
+ .alt-filter-match { color: var(--ink-muted); }
4200
+ .alt-filter-match strong { color: var(--ink); font-weight: 600; }
4201
+ .alt-filter-count { color: var(--ink-muted); font: 400 12.5px/17px var(--font-sans); }
4202
+ .alt-filter-picker { min-width: 200px; max-width: min(320px, calc(100vw - 32px)); padding: var(--space-1); }
4203
+ .alt-filter-list { max-height: 260px; overflow-y: auto; outline: none; }
4204
+ .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); }
4205
+ .alt-filter-option[data-active] { background: var(--accent-soft); }
4206
+ .alt-filter-empty { padding: var(--space-2); color: var(--ink-muted); }
4207
+ .alt-filter-search, .alt-filter-input {
4208
+ 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);
4209
+ 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;
4210
+ }
4211
+ .alt-filter-search:focus-visible, .alt-filter-input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4212
+ .alt-filter-form { display: grid; gap: var(--space-2); padding: var(--space-2); }
4213
+ .alt-filter-form label { display: grid; gap: 2px; color: var(--ink-muted); font: 500 12px/16px var(--font-sans); }
4214
+ .alt-filter-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4215
+ @media (hover: hover) { [data-contrast="more"] .alt-filter-seg:hover { box-shadow: 0 2px 0 0 var(--ink); } }
4216
+ @media (forced-colors: active) {
4217
+ .alt-filter-token { border: 1px solid CanvasText; } .alt-filter-value[data-empty], .alt-filter-add { border: 1px dashed CanvasText; }
4218
+ .alt-filter-picker { border: 2px solid CanvasText; } .alt-filter-option[data-active] { forced-color-adjust: none; background: Highlight; color: HighlightText; }
4219
+ }
4220
+
4221
+ /* ── Schedule: time you handle directly. The grid is ground, with line rules on the hour. Events are live work items
4222
+ raised one level like BoardCards (a tinted left edge), lifting on hover. Time opens only while something could land
4223
+ in it: while an event is carried or a span drawn, the days sink into wells; the slot under a carried event deepens
4224
+ to a trench with an accent rim, as long as the event (critical rim where it can't go). A carried event floats at
4225
+ level 3, tilted. Drawing makes a well the length of the drag, which rises into an event to be named. Pending writes
4226
+ hover after dur-grace; failures drop to a trench. The now line is accent-ink: it isn't status. ── */
4227
+ .alt-schedule { --cols: var(--days); position: relative; min-width: 0; color: var(--ink); font: 400 12.5px/16px var(--font-sans); }
4228
+ .alt-schedule-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2); margin-bottom: var(--space-2); }
4229
+ .alt-schedule-period { margin: 0; font: 600 15px/22px var(--font-sans); }
4230
+ .alt-schedule-nav { display: inline-flex; gap: 2px; }
4231
+ .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; }
4232
+ @keyframes alt-schedule-fade { from { opacity: 0; } }
4233
+ .alt-schedule-row, .alt-schedule-body { display: contents; }
4234
+ .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; }
4235
+ .alt-schedule-day[data-today] { color: var(--accent-ink); font-weight: 650; }
4236
+ .alt-schedule-row[aria-rowindex="2"] .alt-schedule-time { transform: none; }
4237
+ .alt-schedule-time { height: var(--slot); padding-right: 6px; color: var(--ink-muted); font-variant-numeric: tabular-nums; text-align: right; transform: translateY(-7px); }
4238
+ .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); }
4239
+ .alt-schedule-row[data-hour] > .alt-schedule-cell { border-top: 1px solid var(--line); }
4240
+ .alt-schedule-cell:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-ink); z-index: 2; }
4241
+ .alt-schedule[data-carrying] .alt-schedule-cell { background: var(--surface-well); }
4242
+ .alt-schedule-event-box { position: absolute; z-index: 1; display: flex; }
4243
+ .alt-schedule-event {
4244
+ 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;
4245
+ 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;
4246
+ transition: box-shadow var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-1) var(--ease-rise);
4247
+ }
4248
+ .alt-schedule-event > * { flex: none; }
4249
+ .alt-schedule-event[data-short] { flex-direction: row; align-items: center; gap: 6px; padding-block: 0; }
4250
+ .alt-schedule-event[data-short] small { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
4251
+ .alt-schedule-event strong { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
4252
+ .alt-schedule-event small { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
4253
+ @media (hover: hover) { .alt-schedule-event:hover { box-shadow: var(--elev-2), inset 3px 0 0 0 var(--accent); } }
4254
+ .alt-schedule-event:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; z-index: 2; }
4255
+ .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; }
4256
+ .alt-schedule-event[data-status="pending"] { animation: alt-schedule-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
4257
+ @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))); } }
4258
+ .alt-schedule-event[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4259
+ .alt-schedule-event[data-held] { cursor: not-allowed; box-shadow: var(--elev-3), 0 0 0 2px var(--presence-1); }
4260
+ .alt-schedule-event[data-held="2"] { box-shadow: var(--elev-3), 0 0 0 2px var(--presence-2); }
4261
+ .alt-schedule-event[data-held="3"] { box-shadow: var(--elev-3), 0 0 0 2px var(--presence-3); }
4262
+ .alt-schedule-event[data-held="2"] .alt-schedule-holder { background: var(--presence-2); }
4263
+ .alt-schedule-event[data-held="3"] .alt-schedule-holder { background: var(--presence-3); }
4264
+ .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); }
4265
+ .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); }
4266
+ .alt-schedule-event-box:focus-within .alt-schedule-handle { opacity: 1; }
4267
+ @media (hover: hover) { .alt-schedule-event-box:hover .alt-schedule-handle { opacity: 1; } }
4268
+ .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); }
4269
+ .alt-schedule-slot[data-blocked] { box-shadow: var(--trench), inset 0 0 0 2px var(--critical); }
4270
+ .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); }
4271
+ .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); }
4272
+ .alt-schedule-draft small { padding-left: 4px; color: var(--ink-muted); }
4273
+ .alt-schedule-field { display: flex; flex-direction: column; min-width: 0; }
4274
+ .alt-schedule-event-box > .alt-schedule-field { position: absolute; inset: 2px; z-index: 3; }
4275
+ .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; }
4276
+ .alt-schedule-title-input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4277
+ .alt-schedule-title-input[aria-invalid] { background: var(--critical-soft); border-color: var(--critical); }
4278
+ .alt-schedule-error { display: inline-flex; align-items: center; gap: 3px; color: var(--critical); font: 400 11px/14px var(--font-sans); }
4279
+ .alt-schedule-now { position: absolute; left: -4px; right: 0; z-index: 4; height: 2px; background: var(--accent-ink); pointer-events: none; }
4280
+ .alt-schedule-now::before { content: ""; position: absolute; left: 0; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-ink); }
4281
+ .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); }
4282
+ .alt-schedule-failure > svg { color: var(--critical); }
4283
+ .alt-schedule-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4284
+ [data-contrast="more"] .alt-schedule-event { box-shadow: 0 2px 0 0 var(--ink), inset 3px 0 0 0 var(--accent); }
4285
+ [data-contrast="more"] .alt-schedule-event[data-carried] { box-shadow: 0 3px 0 0 var(--ink); }
4286
+ @media (forced-colors: active) {
4287
+ .alt-schedule-event { border: 1px solid CanvasText; } .alt-schedule-event[data-carried] { border-width: 2px; }
4288
+ .alt-schedule-slot, .alt-schedule-drawn { border: 2px dashed CanvasText; } .alt-schedule-now { background: Highlight; }
4289
+ .alt-schedule-event[data-status="pending"] { border-style: dotted; } .alt-schedule-event[data-status="failed"] { border: 2px dashed CanvasText; }
4290
+ }
4291
+
4292
+ /* ── DataGrid: a table edited cell by cell. Cells are the record, flush in Table's rows; the active cell is a focus
4293
+ ring, not a height, and a range is an accent-soft tint (choosing a target isn't live content). Editable columns
4294
+ carry a pencil in the header instead of a hover lift: a click selects rather than acts. The cell being edited opens
4295
+ into a well clipped to the cell, with the held rim; an invalid value deepens to a trench with its message beside it.
4296
+ A saving cell holds a small raised plate after dur-grace; a failed cell drops to a trench with a critical mark; a
4297
+ landed value settles, and a pasted range settles as one change. ── */
4298
+ .alt-data-grid { min-width: 0; }
4299
+ .alt-data-grid .alt-table { table-layout: fixed; }
4300
+ .alt-grid-head { display: inline-flex; align-items: center; gap: 4px; }
4301
+ .alt-grid-pencil { display: inline-flex; color: var(--ink-faint); }
4302
+ .alt-grid-cell { position: relative; outline: none; cursor: cell; overflow: visible; white-space: nowrap; }
4303
+ .alt-grid-value { display: block; overflow: hidden; text-overflow: ellipsis; }
4304
+ .alt-grid-cell[data-range] { background: var(--accent-soft); }
4305
+ .alt-grid-cell[data-active] { box-shadow: inset 0 0 0 2px var(--accent-ink); }
4306
+ .alt-grid-cell:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-ink); }
4307
+ .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); }
4308
+ @keyframes alt-grid-sink { from { background: transparent; box-shadow: none; } }
4309
+ .alt-grid-cell[data-editing]:has([aria-invalid="true"]) { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 2px var(--critical); }
4310
+ .alt-grid-cell[data-invalid]:not([data-editing]) { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4311
+ .alt-grid-cell[data-invalid][data-active]:not([data-editing]) { box-shadow: var(--trench), inset 0 0 0 2px var(--accent-ink); }
4312
+ @media (forced-colors: active) { .alt-grid-cell[data-invalid]:not([data-editing]) { outline: 2px dashed CanvasText; outline-offset: -3px; } }
4313
+ .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; }
4314
+ .alt-grid-cell[data-align="end"] .alt-grid-input { text-align: right; }
4315
+ .alt-grid-editor { display: block; padding: 3px; }
4316
+ .alt-grid-tip {
4317
+ position: absolute; left: 0; top: calc(100% + 4px); z-index: 6; max-width: 260px; padding: 4px 8px; border-radius: var(--radius-sm);
4318
+ background: var(--surface-floating); box-shadow: var(--elev-3); color: var(--critical); font: 400 12px/16px var(--font-sans); white-space: normal;
4319
+ }
4320
+ .alt-grid-unsaved { color: var(--ink-muted); }
4321
+ .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; }
4322
+ .alt-grid-cell[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4323
+ .alt-grid-fail { position: absolute; right: 6px; top: 50%; translate: 0 -50%; display: inline-flex; color: var(--critical); }
4324
+ .alt-grid-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4325
+ [data-contrast="more"] .alt-grid-cell[data-editing] { box-shadow: inset 0 2px 0 0 var(--ink), inset 0 0 0 2px var(--accent); }
4326
+ @media (forced-colors: active) {
4327
+ .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; }
4328
+ .alt-grid-cell[data-editing] { outline: 2px dashed CanvasText; } .alt-grid-cell[data-editing]:has([aria-invalid="true"]) { outline: 3px dashed CanvasText; }
4329
+ .alt-grid-cell[data-status="pending"] { outline: 1px dotted CanvasText; } .alt-grid-cell[data-status="failed"] { outline: 2px dashed CanvasText; }
4330
+ }
4331
+
4332
+ /* ── ItemGroup: a list changed in the list itself. Slats are the record, flush on the ground with line dividers, as
4333
+ Table rows. Editing lifts one slat at a time into its EditRegion (a static raised card) while its neighbours stay
4334
+ flush and readable. The add slat is an opening: a dashed well that deepens to a trench under the pointer. A slat
4335
+ picked up to reorder floats at level 3, tilted, over a slot that opens as a well. A recoverable removal leaves its
4336
+ RemovalReceipt in the slat's place, then glides the gap shut; a confirmed one asks in a stationary Dialog, then
4337
+ glides the slat shut. ── */
4338
+ .alt-item-group { position: relative; display: grid; gap: var(--space-2); min-width: 0; color: var(--ink); container-type: inline-size; }
4339
+ .alt-item-list { margin: 0; padding: 0; list-style: none; outline: none; }
4340
+ .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); }
4341
+ .alt-item-slat[data-editing] { border-bottom-color: transparent; }
4342
+ .alt-item-body { flex: 1; min-width: 0; }
4343
+ .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; }
4344
+ .alt-item-slat[data-status="pending"] { animation: alt-item-pending var(--dur-travel-1) var(--ease-rise) var(--dur-grace) both; }
4345
+ @keyframes alt-item-pending { to { background: var(--surface-lifted); box-shadow: var(--elev-2); } }
4346
+ .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); }
4347
+ .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; }
4348
+ @media (hover: hover) { .alt-item-grip:hover { background: var(--surface-raised); box-shadow: var(--elev-1); } }
4349
+ .alt-item-grip[aria-pressed="true"] { cursor: grabbing; color: var(--ink); }
4350
+ .alt-item-grip:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4351
+ .alt-item { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
4352
+ .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); }
4353
+ .alt-item-media[data-variant="avatar"] { background: none; }
4354
+ .alt-item-content { flex: 1; min-width: 0; }
4355
+ .alt-item-title { font: 500 14.5px/21px var(--font-sans); overflow-wrap: anywhere; }
4356
+ .alt-item-description { margin: 2px 0 0; color: var(--ink-muted); font: 400 13px/18px var(--font-sans); overflow-wrap: anywhere; }
4357
+ .alt-item-actions { display: flex; flex-wrap: wrap; gap: var(--space-1); flex: none; }
4358
+ .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; }
4359
+ /* Narrow (#267): under 480px of the group's width, actions take their own row, lined up with the content (after the
4360
+ media), so a row's title keeps the width; narrow="inline" keeps them beside it. */
4361
+ @container (max-width: 480px) {
4362
+ .alt-item:has(> .alt-item-actions[data-narrow="below"]) { flex-wrap: wrap; }
4363
+ .alt-item-actions[data-narrow="below"] { flex-basis: 100%; }
4364
+ .alt-item:has(> .alt-item-media) > .alt-item-actions[data-narrow="below"] { padding-left: calc(36px + var(--space-3)); }
4365
+ }
4366
+ .alt-item-separator { margin: 0; border: 0; border-top: 1px solid var(--line); }
4367
+ .alt-item-add {
4368
+ 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);
4369
+ background: var(--surface-well); box-shadow: var(--well); color: var(--ink-muted); font: 500 14px/20px var(--font-sans); cursor: pointer;
4370
+ transition: background-color var(--dur-travel-1) var(--ease-sink), box-shadow var(--dur-travel-1) var(--ease-sink), color var(--dur-travel-1);
4371
+ }
4372
+ .alt-item-add:focus-visible { background: var(--surface-trench); box-shadow: var(--trench); color: var(--ink); }
4373
+ @media (hover: hover) { .alt-item-add:hover { background: var(--surface-trench); box-shadow: var(--trench); color: var(--ink); } }
4374
+ .alt-item-add:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
4375
+ .alt-item-plus { font: 500 17px/1 var(--font-sans); }
4376
+ .alt-item-new { animation: alt-emerge var(--dur-travel-2) var(--ease-rise); }
4377
+ .alt-item-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
4378
+ [data-contrast="more"] .alt-item-slat[data-carried] { box-shadow: var(--elev-3), 0 3px 0 0 var(--ink); }
4379
+ @media (forced-colors: active) {
4380
+ .alt-item-add, .alt-item-slot { border: 1px dashed CanvasText; } .alt-item-slat[data-carried] { border: 2px solid CanvasText; }
4381
+ .alt-item-slat[data-status="pending"] { outline: 1px dotted CanvasText; }
4382
+ }
4383
+
4384
+ /* ── Board, edited in place. A column's add slot is an opening: a dashed well that deepens to a trench under the
4385
+ pointer. Activating it rises into a new card (static, level 1) holding a title field; the next slot is ready after
4386
+ each add. Renaming turns a card's title into a well; a card being edited can't be picked up. A pending card hovers
4387
+ after dur-grace; a failed one drops to a trench with its title kept and Retry. A fuller editor is an EditRegion in
4388
+ the card's place. ── */
4389
+ .alt-board-add-wrap { margin-top: var(--space-2); }
4390
+ .alt-board-add {
4391
+ 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);
4392
+ background: var(--surface-well); box-shadow: var(--well); color: var(--ink-muted); font: 500 13.5px/18px var(--font-sans); cursor: pointer;
4393
+ transition: background-color var(--dur-travel-1) var(--ease-sink), box-shadow var(--dur-travel-1) var(--ease-sink), color var(--dur-travel-1);
4394
+ }
4395
+ .alt-board-add:focus-visible { background: var(--surface-trench); box-shadow: var(--trench); color: var(--ink); }
4396
+ @media (hover: hover) { .alt-board-add:hover { background: var(--surface-trench); box-shadow: var(--trench); color: var(--ink); } }
4397
+ .alt-board-add:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
4398
+ .alt-board-add > span { font: 500 16px/1 var(--font-sans); }
4399
+ .alt-board-new { display: grid; gap: var(--space-1); cursor: default; animation: alt-emerge var(--dur-travel-2) var(--ease-rise); }
4400
+ .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; }
4401
+ @keyframes alt-board-pending { to { box-shadow: var(--elev-2); transform: translateY(calc(-2px * var(--alt-travel))); } }
4402
+ .alt-board-new[data-status="failed"] { background: var(--critical-soft); box-shadow: var(--trench), inset 0 0 0 1px var(--critical); }
4403
+ .alt-board-add-hint { color: var(--ink-muted); font: 400 12px/16px var(--font-sans); }
4404
+ .alt-board-add-error { display: inline-flex; align-items: center; gap: 4px; color: var(--critical); font: 400 12px/16px var(--font-sans); }
4405
+ .alt-board-rename { display: grid; gap: var(--space-1); }
4406
+ .alt-board-card[data-editing] { cursor: default; }
4407
+ .alt-board-card-tools { position: absolute; top: 4px; right: 4px; display: inline-flex; gap: 2px; opacity: 0; transition: opacity var(--dur-travel-1); }
4408
+ .alt-board-card:focus-within .alt-board-card-tools, .alt-board-card:focus-visible .alt-board-card-tools { opacity: 1; }
4409
+ @media (hover: hover) { .alt-board-card:hover .alt-board-card-tools { opacity: 1; } }
4410
+ /* Without hover the tools are always there, or a finger could never find them (#254). */
4411
+ @media (hover: none) { .alt-board-card-tools { opacity: 1; } }
4412
+ .alt-board-card-editor { min-width: 0; }
4413
+ @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; } }
4414
+
4415
+ /* ── DataTable, expanded and edited in its row. Read-only detail is a reveal of the record, so it stays on the ground:
4416
+ a full-width region under the row, inset by space-4, with a line rule, and the disclosure chevron turns. Editing is
4417
+ live, so it rises: the row and its region become one raised slab (elev-1, drawn by the first cell as for selected
4418
+ rows), holding a static EditRegion; the cells above keep the record identified. Sorting and reordering wait. ── */
4419
+ .alt-td-expand, .alt-th-expand { width: 40px; padding-inline: 6px; }
4420
+ /* Density: compact rows keep every target at least 24px tall (WCAG 2.5.8). Showing, hiding or reordering a column is a
4421
+ change of layout, not of the record, so the table doesn't animate it. */
4422
+ .alt-table[data-density="compact"] .alt-td { padding-block: 4px; }
4423
+ .alt-table[data-density="compact"] .alt-th { padding-block: 6px; }
4424
+ .alt-table-hidden { color: var(--ink-muted); font-weight: 400; }
4425
+ /* Paging: the footer holds Pagination (or "Load more") under a line. While the next page loads the current rows stay on
4426
+ the ground at half strength, as a refetching chart does, and the page thumb is held pressed. */
4427
+ .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; }
4428
+ .alt-table[data-refetching] tbody { opacity: 0.5; transition: opacity var(--dur-travel-2) var(--ease-sink); }
4429
+ /* Selecting past the page: once a page is selected, a flush accent-soft row offers every matching record; a selection of
4430
+ "all matching" is said in words. With actionsPlacement floating (or auto, once the toolbar scrolls away), the count and
4431
+ actions dock at the bottom edge as a bar at level 3: a selection is a set of records in your hand. */
4432
+ /* Saved views: folder Tabs whose active tab is one raised sheet with the filters and table beneath it. A view you've
4433
+ changed is a draft: a dot on its tab (and "modified" in its name), and Save view, Save as new view and Reset beside
4434
+ the tabs, as a SaveBar does for a page. */
4435
+ .alt-views { position: relative; }
4436
+ .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; }
4437
+ .alt-views-dot { display: inline-block; width: 6px; height: 6px; margin-left: 6px; border-radius: var(--radius-full); background: var(--accent); vertical-align: 2px; }
4438
+ .alt-views-sheet { display: flex; flex-direction: column; gap: var(--space-3); }
4439
+ .alt-views-new { display: flex; flex-direction: column; gap: var(--space-3); width: 240px; max-width: 100%; }
4440
+ .alt-views-new .alt-btn { align-self: flex-end; }
4441
+ @media (max-width: 640px) { .alt-views-actions { position: static; height: auto; margin-bottom: var(--space-2); justify-content: flex-start; } }
4442
+ @media (forced-colors: active) { .alt-views-dot { background: CanvasText; forced-color-adjust: none; } }
4443
+ .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); }
4444
+ .alt-table-allrow .alt-link { padding: 0; border: 0; background: none; font: inherit; font-weight: 600; }
4445
+ .alt-table-floatbar {
4446
+ position: sticky; bottom: var(--space-3); z-index: 6; display: flex; justify-content: center; margin-top: var(--space-3); pointer-events: none;
4447
+ }
4448
+ .alt-table-floatbar > .alt-table-selection {
4449
+ pointer-events: auto; display: inline-flex; align-items: center; gap: var(--space-2); padding: 6px 8px 6px 10px; border-radius: var(--radius-lg);
4450
+ background: var(--surface-floating); box-shadow: var(--elev-3); font: 500 14px/20px var(--font-sans);
4451
+ animation: alt-emerge var(--dur-travel-3) var(--ease-rise); --alt-from-y: 12px;
4452
+ }
4453
+ @media (forced-colors: active) { .alt-table-floatbar > .alt-table-selection { border: 2px solid CanvasText; } }
4454
+ .alt-table-columns { display: flex; flex-direction: column; gap: var(--space-2); width: 240px; max-width: 100%; }
4455
+ .alt-table-columns-title { margin: 0; font: 600 12.5px/17px var(--font-sans); color: var(--ink-muted); }
4456
+ .alt-table-columns-title + * { margin-bottom: var(--space-2); }
4457
+ .alt-table-columns .alt-sort-item { padding: 6px 8px; }
4458
+ .alt-td-edit, .alt-th-edit { width: 1%; white-space: nowrap; }
4459
+ .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); }
4460
+ @media (hover: hover) { .alt-table-disclose:hover { background: var(--surface-raised); box-shadow: var(--elev-1); color: var(--ink); } }
4461
+ .alt-table-disclose:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4462
+ .alt-table-disclose svg { rotate: -90deg; transition: rotate var(--dur-travel-2) var(--ease-settle); }
4463
+ .alt-table-disclose[aria-expanded="true"] svg { rotate: 0deg; }
4464
+ [dir="rtl"] .alt-table-disclose:not([aria-expanded="true"]) svg { rotate: 90deg; }
4465
+ .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; }
4466
+ .alt-tr[data-expanded]:not([data-editing]) > .alt-td { border-bottom-color: transparent; }
4467
+ .alt-tr[data-editing] { --row-bg: var(--surface-raised); --row-sh: var(--elev-1); }
4468
+ .alt-tr[data-editing]:not(.alt-table-editrow) > .alt-td { border-bottom-color: transparent; }
4469
+ .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); }
4470
+ .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); }
4471
+ .alt-table-editrow > .alt-td { padding: var(--space-2) var(--space-4) var(--space-4); white-space: normal; }
4472
+ .alt-table-editrow .alt-edit-region-surface { margin: 0; border: 0; padding: 0; background: none; box-shadow: none; }
4473
+ /* ItemGroup swaps a slat for its editor, so the editor keeps to the list's column rather than reaching past it. */
4474
+ .alt-item-group .alt-edit-region-surface { margin-inline: 0; }
4475
+ @media (forced-colors: active) { .alt-tr[data-editing] > .alt-td { border-block: 1px solid CanvasText; } }
4476
+
4477
+ /* ── PropertyList: one record's properties. They're the record, on the ground. An editable value is one plate (the
4478
+ InlineEdit trigger) that lifts one level when pointed at, because clicking it opens its editor; read-only values
4479
+ never lift, so editability shows without a pencil per row. An unset value is a small opening: a dashed well, the
4480
+ empty field's treatment at plate size, so a half-filled record reads at a glance. Edit all lifts the list into one
4481
+ raised EditRegion. ── */
4482
+ .alt-property-list { position: relative; min-width: 0; color: var(--ink); container-type: inline-size; }
4483
+ .alt-property-tools { display: flex; justify-content: flex-end; margin-bottom: var(--space-1); }
4484
+ .alt-property-dl { display: grid; gap: var(--space-3); margin: 0; }
4485
+ .alt-property { display: grid; gap: 2px; min-width: 0; }
4486
+ .alt-property-list[data-layout="inline"] .alt-property-dl { gap: 0; }
4487
+ .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); }
4488
+ .alt-property-label { color: var(--ink-muted); font: 500 12.5px/17px var(--font-sans); }
4489
+ .alt-property-value { min-width: 0; margin: 0; font: 400 14px/20px var(--font-sans); overflow-wrap: anywhere; }
4490
+ .alt-property-empty { color: var(--ink-muted); }
4491
+ .alt-property .alt-inline-edit-read { display: block; }
4492
+ /* Proposed changes: a field with a proposal is status that asks for action, so it's raised one level (as Confirmation),
4493
+ showing the current value struck beside the proposed one (DiffView's property change), with Reject and Accept. Nothing
4494
+ touches the record until it's accepted; accepting hovers as pending, then settles with the afterglow. The summary
4495
+ offers Accept all and Reject all. */
4496
+ .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); }
4497
+ .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); }
4498
+ .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); }
4499
+ .alt-property-list[data-layout="inline"] .alt-property[data-proposed] { align-items: start; padding: var(--space-2); border-bottom-color: transparent; }
4500
+ .alt-property-list[data-layout="inline"] .alt-property[data-proposed] .alt-property-label { padding-top: 6px; }
4501
+ .alt-property-proposal { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 2px var(--space-2); }
4502
+ .alt-property-change { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
4503
+ .alt-property-proposal-by { font: 400 12.5px/17px var(--font-sans); color: var(--ink-muted); }
4504
+ .alt-property-proposal-error { display: inline-flex; align-items: center; gap: 4px; color: var(--critical); }
4505
+ .alt-property-proposal-by { grid-column: 1; }
4506
+ .alt-property-proposal-actions { display: inline-flex; gap: 4px; grid-column: 2; grid-row: 1 / span 2; }
4507
+ /* Narrow (#258): under the narrow breakpoint, an inline list stacks each label over its value, and a proposal stacks its
4508
+ change, who proposed it and its actions, so the values are never squeezed into a column a few letters wide. */
4509
+ @container (max-width: 480px) {
4510
+ .alt-property-list[data-layout="inline"] .alt-property { grid-template-columns: minmax(0, 1fr); gap: 2px; }
4511
+ .alt-property-list[data-layout="inline"] .alt-property[data-proposed] .alt-property-label { padding-top: 0; }
4512
+ .alt-property-proposal { grid-template-columns: minmax(0, 1fr); }
4513
+ .alt-property-proposal-actions { grid-column: 1; grid-row: auto; margin-top: var(--space-1); }
4514
+ }
4515
+ @media (forced-colors: active) { .alt-property[data-proposed], .alt-property-proposals { border: 1px solid CanvasText; } }
4516
+ .alt-inline-edit-plate {
4517
+ display: inline-flex; align-items: center; min-height: 30px; max-width: 100%; padding: 2px 12px; border: 1px solid transparent; border-radius: var(--radius-sm);
4518
+ background: none; color: inherit; font: inherit; text-align: left; cursor: pointer;
4519
+ 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);
4520
+ }
4521
+ @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))); } }
4522
+ .alt-inline-edit-plate:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
4523
+ .alt-inline-edit-plate:disabled { cursor: default; }
4524
+ .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; }
4525
+ @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); } }
4526
+ @media (hover: hover) { [data-contrast="more"] .alt-inline-edit-plate:hover:not(:disabled) { box-shadow: 0 2px 0 0 var(--ink); } }
4527
+ @media (forced-colors: active) { .alt-inline-edit-plate[data-empty] { border: 1px dashed CanvasText; } .alt-inline-edit-plate:hover { outline: 1px solid CanvasText; } }
4528
+
4529
+ /* Workflow feedback uses the existing invalid trench and readable ground signals. */
4530
+ .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; }
4531
+ .alt-error-summary h2 { margin: 0 0 var(--space-2); font: 600 var(--text-heading)/1.3 var(--font-sans); }
4532
+ .alt-error-summary ul { display: grid; gap: var(--space-1); margin: 0; padding-inline-start: var(--space-5); }
4533
+ /* Each link is a 24px target (WCAG 2.2 SC 2.5.8) whatever line-height the host page sets. */
4534
+ .alt-error-summary a { display: inline-flex; align-items: center; min-block-size: 24px; color: var(--ink); text-decoration: underline; }
4535
+ .alt-error-summary:focus { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
4536
+ @media (forced-colors: active) { .alt-error-summary { border-color: CanvasText; } .alt-error-summary a { color: LinkText; } }
4537
+
4538
+ /* ── Field text on touch (#250): under a coarse pointer, text you type is at least 16px, the size below which iOS
4539
+ Safari zooms the page when a field is focused (and leaves it zoomed). max(16px, 1em) keeps a field that inherits
4540
+ larger text (an in-place title) at its own size. It's a platform minimum, so it wins over every field's own font
4541
+ (the only !important in the stylesheet's sizing). Labels stay text-label; only the text you type grows. Never
4542
+ maximum-scale=1 instead: that stops pinch zoom and fails WCAG 1.4.4. ── */
4543
+ @media (pointer: coarse) {
4544
+ :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"]) {
4545
+ font-size: max(16px, 1em) !important;
4546
+ }
4547
+ }
4548
+
4549
+ /* ── Hit areas (#261): a small face keeps its size and its tier, and a transparent ::after centred on it reaches
4550
+ size-target-min, 24px (WCAG 2.5.8) or 44px under a coarse pointer, so a finger finds it. It has no colour, border
4551
+ or shadow, so forced colours and every elevation are untouched. Date segments sit side by side, so theirs stops at
4552
+ 24px rather than covering a neighbour. ── */
4553
+ :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; }
4554
+ :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 {
4555
+ 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%);
4556
+ }
4557
+ .alt-numfield .alt-numfield-step::after { width: 100%; }
4558
+ /* Links and relative times sit in rows of text: they reach 24px tall, never wider, so neighbours stay apart. */
4559
+ :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%); }
4560
+ .alt-date-seg::after { content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, 24px); height: max(100%, 24px); transform: translate(-50%, -50%); }
4561
+
4562
+ /* ── A menu as a sheet (#249): on a narrow touch screen DropdownMenu and Select open at the bottom edge, where the thumb
4563
+ is, rather than wherever the trigger happened to be. It's still the menu, at level 3 (surface-floating, elev-3):
4564
+ changing edges doesn't climb a tier. It clears the safe areas, holds at most 70% of the visible height, has a head
4565
+ with its title and Close, rows at size-row-md, and dims the page at half the scrim's strength. It rises from the
4566
+ edge over dur-travel-3 on ease-rise, as a Sheet does, and fades under reduced motion. ── */
4567
+ .alt-pop[data-presentation="sheet"] {
4568
+ 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));
4569
+ bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px)); translate: none; width: auto; min-width: 0; max-width: none;
4570
+ max-height: min(70vh, 560px); max-height: min(calc(var(--alt-vvh, 100dvh) * 0.7), 560px); overflow-y: auto; overscroll-behavior: contain;
4571
+ padding: 0 var(--space-1) var(--space-1); transform-origin: 50% 100%;
4572
+ box-shadow: var(--elev-3), 0 0 0 100vmax color-mix(in oklab, var(--scrim) 50%, transparent);
1703
4573
  }
4574
+ .alt-pop[data-presentation="sheet"][data-state="closed"] { transform: translateY(calc(24px * var(--alt-travel))); }
4575
+ .alt-menu-sheet .alt-menu { position: relative; min-width: 0; padding: 0; }
4576
+ .alt-menu-sheet :is(.alt-menu-item, .alt-select-option) { min-height: var(--size-row-md); }
4577
+ .alt-menu-sheet-head { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
4578
+ margin: 0 calc(-1 * var(--space-1)); padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4); background: var(--surface-floating); }
4579
+ .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; }
4580
+ @media (forced-colors: active) { .alt-pop[data-presentation="sheet"] { border: 2px solid CanvasText; box-shadow: none; } }