@zeyos/zx 4.4.0 → 5.2.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 (252) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/README.md +44 -11
  3. package/dist/types/components/account-menu/account-menu.d.ts +8 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +31 -1
  5. package/dist/types/components/app-icon/app-icon.d.ts +14 -2
  6. package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
  7. package/dist/types/components/avatar/avatar.d.ts +46 -7
  8. package/dist/types/components/badge/badge.d.ts +21 -1
  9. package/dist/types/components/button/button.d.ts +15 -0
  10. package/dist/types/components/calendar/calendar.d.ts +67 -5
  11. package/dist/types/components/card-view/card-view.d.ts +7 -4
  12. package/dist/types/components/card-view/record-card.d.ts +11 -0
  13. package/dist/types/components/chart/chart.d.ts +10 -0
  14. package/dist/types/components/checklist/checklist.d.ts +9 -0
  15. package/dist/types/components/code-editor/code-editor.d.ts +6 -17
  16. package/dist/types/components/context-menu/context-menu.d.ts +6 -2
  17. package/dist/types/components/data-filter/data-filter.d.ts +4 -4
  18. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  19. package/dist/types/components/datebox/datebox.d.ts +7 -2
  20. package/dist/types/components/dialog/dialog.d.ts +46 -6
  21. package/dist/types/components/dropdown/dropdown.d.ts +24 -9
  22. package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
  23. package/dist/types/components/field/field.d.ts +54 -2
  24. package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
  25. package/dist/types/components/fieldset/fieldset.d.ts +26 -1
  26. package/dist/types/components/file-item/file-item.d.ts +34 -6
  27. package/dist/types/components/file-list/file-list.d.ts +35 -3
  28. package/dist/types/components/filter/filter.d.ts +127 -3
  29. package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
  30. package/dist/types/components/form/form.d.ts +12 -1
  31. package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
  32. package/dist/types/components/launcher/launcher.d.ts +268 -15
  33. package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
  34. package/dist/types/components/menu-button/menu-button.d.ts +39 -5
  35. package/dist/types/components/message/message.d.ts +132 -17
  36. package/dist/types/components/modal/modal.d.ts +43 -2
  37. package/dist/types/components/module-bar/module-bar.d.ts +395 -0
  38. package/dist/types/components/pagination/pagination.d.ts +17 -3
  39. package/dist/types/components/select/color.d.ts +12 -0
  40. package/dist/types/components/select/glyphs.d.ts +124 -0
  41. package/dist/types/components/select/select.d.ts +17 -3
  42. package/dist/types/components/sheet/sheet.d.ts +15 -0
  43. package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
  44. package/dist/types/components/tabbox/tabbox.d.ts +9 -0
  45. package/dist/types/components/table/table.d.ts +311 -18
  46. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  47. package/dist/types/components/tree/tree.d.ts +13 -0
  48. package/dist/types/components/view/record-view.d.ts +59 -2
  49. package/dist/types/components/view-settings/view-settings.d.ts +879 -0
  50. package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
  51. package/dist/types/core/breakpoint.d.ts +1 -1
  52. package/dist/types/core/component.d.ts +12 -2
  53. package/dist/types/core/i18n.d.ts +27 -3
  54. package/dist/types/core/icons.d.ts +23 -25
  55. package/dist/types/core/keyboard.d.ts +98 -1
  56. package/dist/types/core/messages.d.ts +382 -0
  57. package/dist/types/core/motion.d.ts +59 -0
  58. package/dist/types/core/overlay-host.d.ts +42 -1
  59. package/dist/types/core/overlay-stack.d.ts +260 -0
  60. package/dist/types/index.d.ts +10 -5
  61. package/dist/types/internal/app-rail.d.ts +38 -0
  62. package/dist/types/internal/menu-items.d.ts +71 -3
  63. package/dist/types/zeyos/connect.d.ts +6 -0
  64. package/dist/types/zeyos/form.d.ts +50 -9
  65. package/dist/types/zeyos/index.d.ts +2 -1
  66. package/dist/types/zeyos/launcher.d.ts +9 -0
  67. package/dist/types/zeyos/module-bar.d.ts +176 -0
  68. package/dist/types/zeyos/query.d.ts +110 -5
  69. package/dist/types/zeyos/schema.d.ts +24 -1
  70. package/dist/types/zeyos/table.d.ts +345 -22
  71. package/dist/types/zeyos/view.d.ts +26 -2
  72. package/dist/zx-zeyos.esm.js +6178 -1008
  73. package/dist/zx-zeyos.esm.min.js +53 -5
  74. package/dist/zx.css +2861 -827
  75. package/dist/zx.esm.js +8909 -1480
  76. package/dist/zx.esm.min.js +55 -7
  77. package/dist/zx.global.js +8911 -1482
  78. package/dist/zx.global.min.js +55 -7
  79. package/dist/zx.min.css +1 -1
  80. package/docs/api.json +1079 -117
  81. package/docs/llms.md +956 -154
  82. package/docs/llms.txt +9 -5
  83. package/package.json +1 -1
  84. package/src/components/account-menu/account-menu.css +35 -10
  85. package/src/components/account-menu/account-menu.js +12 -0
  86. package/src/components/activity-item/activity-item.css +1 -1
  87. package/src/components/activity-item/activity-item.js +3 -0
  88. package/src/components/activity-list/activity-list.css +38 -3
  89. package/src/components/activity-list/activity-list.js +61 -1
  90. package/src/components/app-icon/app-icon.css +14 -3
  91. package/src/components/app-icon/app-icon.js +11 -1
  92. package/src/components/app-rail/app-rail.css +59 -79
  93. package/src/components/app-sidebar/app-sidebar.css +304 -168
  94. package/src/components/app-sidebar/app-sidebar.js +667 -46
  95. package/src/components/aurora/aurora.css +5 -3
  96. package/src/components/avatar/avatar.css +32 -7
  97. package/src/components/avatar/avatar.js +72 -8
  98. package/src/components/badge/badge.css +54 -2
  99. package/src/components/badge/badge.js +29 -2
  100. package/src/components/breadcrumb/breadcrumb.css +1 -1
  101. package/src/components/button/button.css +67 -9
  102. package/src/components/button/button.js +38 -0
  103. package/src/components/calendar/calendar.css +47 -47
  104. package/src/components/calendar/calendar.js +26 -52
  105. package/src/components/card/card.css +4 -4
  106. package/src/components/card-view/card-view.css +14 -12
  107. package/src/components/card-view/card-view.js +13 -9
  108. package/src/components/card-view/record-card.js +14 -3
  109. package/src/components/chart/chart.css +4 -1
  110. package/src/components/chart/chart.js +12 -0
  111. package/src/components/check-button/check-button.css +10 -2
  112. package/src/components/checklist/checklist.css +9 -2
  113. package/src/components/checklist/checklist.js +11 -5
  114. package/src/components/checklist/field-adapter.js +3 -1
  115. package/src/components/code-editor/code-editor.css +7 -6
  116. package/src/components/code-editor/code-editor.js +19 -1
  117. package/src/components/code-editor/field-adapter.js +9 -2
  118. package/src/components/context-menu/context-menu.css +9 -4
  119. package/src/components/context-menu/context-menu.js +8 -2
  120. package/src/components/copy/copy-button.css +6 -0
  121. package/src/components/copy/copy-input.css +9 -4
  122. package/src/components/data-filter/data-filter.css +5 -4
  123. package/src/components/data-filter/data-filter.js +2 -2
  124. package/src/components/date-picker/date-picker.css +6 -6
  125. package/src/components/date-picker/date-range-picker.css +3 -3
  126. package/src/components/date-picker/time-picker.css +3 -3
  127. package/src/components/datebox/date-range-box.css +79 -2
  128. package/src/components/datebox/date-range-box.js +13 -2
  129. package/src/components/datebox/datebox.css +79 -2
  130. package/src/components/datebox/datebox.js +13 -2
  131. package/src/components/dialog/dialog.css +26 -0
  132. package/src/components/dialog/dialog.js +99 -17
  133. package/src/components/dock/dock.css +5 -4
  134. package/src/components/dropdown/dropdown.css +1 -1
  135. package/src/components/dropdown/dropdown.js +159 -72
  136. package/src/components/empty-state/empty-state.css +2 -2
  137. package/src/components/entity-ref/entity-ref.css +1 -1
  138. package/src/components/entity-ref/entity-ref.js +1 -1
  139. package/src/components/field/field.css +28 -9
  140. package/src/components/field/field.js +101 -29
  141. package/src/components/field-upload/field-upload.css +1 -1
  142. package/src/components/fieldset/external-adapter.js +363 -0
  143. package/src/components/fieldset/fieldset.css +5 -3
  144. package/src/components/fieldset/fieldset.js +38 -64
  145. package/src/components/file-item/file-item.js +144 -10
  146. package/src/components/file-list/file-list.js +144 -21
  147. package/src/components/filter/filter.css +86 -2
  148. package/src/components/filter/filter.js +307 -11
  149. package/src/components/filter-panel/filter-panel.css +1 -1
  150. package/src/components/filter-panel/filter-panel.js +149 -32
  151. package/src/components/finder/finder.css +16 -14
  152. package/src/components/form/form.css +5 -4
  153. package/src/components/form/form.js +11 -2
  154. package/src/components/grid/grid.css +1 -1
  155. package/src/components/groupbox/groupbox.css +1 -1
  156. package/src/components/kanban-view/kanban-card.js +11 -3
  157. package/src/components/kanban-view/kanban-view.css +42 -18
  158. package/src/components/kanban-view/kanban-view.js +9 -6
  159. package/src/components/launcher/launcher.css +108 -26
  160. package/src/components/launcher/launcher.js +530 -42
  161. package/src/components/list-toolbar/list-toolbar.css +13 -8
  162. package/src/components/list-toolbar/list-toolbar.js +40 -14
  163. package/src/components/master-panel/master-panel.css +2 -0
  164. package/src/components/master-panel/master-panel.js +0 -1
  165. package/src/components/menu-button/menu-button.css +72 -4
  166. package/src/components/menu-button/menu-button.js +160 -18
  167. package/src/components/message/message.css +33 -3
  168. package/src/components/message/message.js +264 -22
  169. package/src/components/modal/modal.css +1 -1
  170. package/src/components/modal/modal.js +83 -15
  171. package/src/components/module-bar/module-bar.css +486 -0
  172. package/src/components/module-bar/module-bar.js +1074 -0
  173. package/src/components/multi-value-editor/multi-value-editor.css +1 -1
  174. package/src/components/navigation-bar/navigation-bar.css +18 -9
  175. package/src/components/number-field/field-adapter.js +3 -1
  176. package/src/components/number-field/number-field.css +70 -0
  177. package/src/components/number-field/number-field.js +2 -0
  178. package/src/components/pagination/pagination.css +13 -11
  179. package/src/components/pagination/pagination.js +20 -4
  180. package/src/components/panel/panel.css +4 -3
  181. package/src/components/questionnaire/questionnaire.css +4 -4
  182. package/src/components/rating/field-adapter.js +2 -1
  183. package/src/components/rating/rating.css +8 -1
  184. package/src/components/search/search.css +4 -1
  185. package/src/components/select/color.js +22 -0
  186. package/src/components/select/field-adapter.js +2 -0
  187. package/src/components/select/glyphs.js +165 -0
  188. package/src/components/select/select.css +138 -45
  189. package/src/components/select/select.js +92 -47
  190. package/src/components/sheet/sheet.css +12 -9
  191. package/src/components/sheet/sheet.js +74 -32
  192. package/src/components/slider/field-adapter.js +6 -1
  193. package/src/components/slider/slider.css +18 -4
  194. package/src/components/slider/slider.js +1 -1
  195. package/src/components/sort-control/sort-control.css +4 -1
  196. package/src/components/split-view/split-view.css +1 -1
  197. package/src/components/stat-tile/stat-tile.css +42 -47
  198. package/src/components/stat-tile/stat-tile.js +17 -6
  199. package/src/components/stepper/stepper.css +1 -1
  200. package/src/components/tabbox/tabbox.css +41 -32
  201. package/src/components/tabbox/tabbox.js +5 -1
  202. package/src/components/table/table.css +187 -37
  203. package/src/components/table/table.js +718 -73
  204. package/src/components/tag-picker/field-adapter.js +2 -1
  205. package/src/components/tag-picker/tag-picker.css +83 -5
  206. package/src/components/tag-picker/tag-picker.js +25 -8
  207. package/src/components/timebox/timebox.css +22 -2
  208. package/src/components/toggle/toggle.css +12 -1
  209. package/src/components/toolbar/toolbar.js +2 -2
  210. package/src/components/tooltip/tooltip.css +4 -0
  211. package/src/components/tooltip/tooltip.js +44 -19
  212. package/src/components/tree/tree.css +26 -17
  213. package/src/components/tree/tree.js +31 -0
  214. package/src/components/value-list/value-list.css +3 -3
  215. package/src/components/view/record-view.css +1 -1
  216. package/src/components/view/record-view.js +178 -18
  217. package/src/components/view-settings/view-settings.css +285 -0
  218. package/src/components/view-settings/view-settings.js +1830 -0
  219. package/src/components/workspace-switcher/workspace-switcher.css +188 -0
  220. package/src/components/workspace-switcher/workspace-switcher.js +463 -0
  221. package/src/core/breakpoint.js +1 -1
  222. package/src/core/component.js +88 -17
  223. package/src/core/fontawesome.js +5 -1
  224. package/src/core/i18n.js +47 -4
  225. package/src/core/icons.js +204 -61
  226. package/src/core/keyboard.js +206 -11
  227. package/src/core/messages.js +427 -0
  228. package/src/core/motion.js +147 -0
  229. package/src/core/overlay-host.js +77 -3
  230. package/src/core/overlay-stack.js +487 -0
  231. package/src/core/position.js +22 -3
  232. package/src/index.js +10 -5
  233. package/src/internal/app-rail.js +135 -55
  234. package/src/internal/menu-items.js +61 -3
  235. package/src/zeyos/connect.js +6 -2
  236. package/src/zeyos/form.js +69 -27
  237. package/src/zeyos/index.js +4 -1
  238. package/src/zeyos/launcher.js +8 -3
  239. package/src/zeyos/module-bar.js +181 -0
  240. package/src/zeyos/query.js +197 -13
  241. package/src/zeyos/schema.js +144 -27
  242. package/src/zeyos/table.js +287 -84
  243. package/src/zeyos/view.js +49 -93
  244. package/styles/base.css +72 -1
  245. package/styles/icons.css +11 -1
  246. package/styles/print.css +1 -1
  247. package/styles/tokens/dark.css +117 -37
  248. package/styles/tokens/global.css +121 -27
  249. package/styles/tokens/semantic.css +140 -43
  250. package/styles/tokens/themes.css +7 -6
  251. package/styles/zx.css +3 -1
  252. package/styles/tokens/density.css +0 -9
@@ -2,39 +2,49 @@
2
2
  * AppSidebar — the surface an application is judged by before it is read.
3
3
  *
4
4
  * Everything here hangs off one spacing scale declared on the root. Item height, icon gutter,
5
- * row gap, group gap and indentation are derived from it rather than picked per rule, which is
6
- * what makes a density switch a real change of rhythm instead of a smaller font.
5
+ * row gap, group gap and indentation are derived from the tier-2 control and spacing tokens
6
+ * rather than picked per rule, so a theme that changes `--zx-space-unit` or
7
+ * `--zx-control-height` changes the rhythm of the whole nav instead of one row of it.
7
8
  *
8
- * Two deliberate choices worth stating:
9
+ * Three deliberate choices worth stating:
9
10
  *
10
11
  * - **Labels wrap, they never truncate.** German runs 20-35% longer than English and a nav is
11
12
  * where that shows first: "Belege & Rechnungen" is two lines in a 16.5rem sidebar. An ellipsis
12
13
  * there hides the word that distinguishes one destination from another, so the row grows
13
14
  * instead and the fixed icon column keeps the second line aligned under the first.
14
- * - **The active state never rests on colour alone.** It is a filled pill, a marker docked to the
15
- * sidebar's outer edge, and a heavier label — three signals, one of which survives greyscale,
16
- * forced colours and a reduced-transparency page.
15
+ * - **The active state never rests on colour alone.** It is a neutral fill and a heavier label —
16
+ * where you are, not something you picked, so it takes the `--zx-color-bg-current` role rather
17
+ * than the accent. The weight survives greyscale and a reduced-transparency page, and forced
18
+ * colours swap the fill for the system's own selected pair. Nothing is docked to the edge.
19
+ * - **A child never starts further in than its parent's label.** A text-only child lines up with
20
+ * the words above it; a child with its own icon steps in only far enough for that icon to clear
21
+ * the guide line. A parent list without icons indents its children by a short text step.
17
22
  */
18
23
 
19
24
  .zx-app-sidebar {
20
- /* The scale. `--zx-control-height` and `--zx-space-1..4` are density-aware, so compact
21
- tightens the item, the gaps and the padding together while `--zx-space-5/6` hold the icon
22
- column and the indent steady — a narrower rhythm, not a smaller one. */
25
+ /* The scale. A row is exactly one control tall, the icon column is as wide as the 24px module
26
+ chips at the default unit, and every gap and inset is a step of the spacing scale. */
23
27
  --zx-app-sidebar-gutter: var(--zx-space-2);
24
28
  --zx-app-sidebar-icon: var(--zx-space-6);
25
- --zx-app-sidebar-item-gap: var(--zx-space-3);
26
- --zx-app-sidebar-item-height: calc(var(--zx-control-height) + var(--zx-space-2));
27
- --zx-app-sidebar-pad-x: var(--zx-space-3);
28
- --zx-app-sidebar-row-gap: var(--zx-space-1);
29
- --zx-app-sidebar-group-gap: var(--zx-space-4);
30
- --zx-app-sidebar-indent: var(--zx-space-5);
31
- --zx-app-sidebar-marker: 3px;
32
- --zx-app-sidebar-radius: var(--zx-radius-xl);
33
- /* One overlay expression for hover and press. Mixing the *text* colour into the surface moves
34
- the fill away from the page in whichever direction the theme runs, so a single declaration
35
- is correct in light and in dark instead of needing a per-theme override. */
36
- --zx-app-sidebar-hover: color-mix(in srgb, var(--zx-color-text) 7%, transparent);
37
- --zx-app-sidebar-press: color-mix(in srgb, var(--zx-color-text) 14%, transparent);
29
+ --zx-app-sidebar-item-gap: var(--zx-space-2);
30
+ --zx-app-sidebar-item-height: var(--zx-control-height);
31
+ --zx-app-sidebar-pad-x: var(--zx-space-2);
32
+ --zx-app-sidebar-row-gap: 2px;
33
+ --zx-app-sidebar-group-gap: var(--zx-space-3);
34
+ /* The indent under a parent list that has no icon column to hand down. */
35
+ --zx-app-sidebar-indent: var(--zx-space-3);
36
+ /* The indent of a child list that brings icons of its own: enough for them to clear the guide,
37
+ not a whole icon column, so a deep branch never walks across the sidebar. */
38
+ --zx-app-sidebar-icon-indent: var(--zx-space-4);
39
+ --zx-app-sidebar-radius: var(--zx-radius-md);
40
+ /* Three steps of one overlay: hover, the current row, and press. The hover and current roles
41
+ mix the *text* colour into whatever sits behind, which moves the fill away from the page in
42
+ whichever direction the theme runs, so a sidebar on the grey chrome tone and one on a white
43
+ surface both read correctly. Hover stays below the current fill, so pointing at a row never
44
+ looks like being on it; press goes one step past it. */
45
+ --zx-app-sidebar-hover: var(--zx-color-bg-hover);
46
+ --zx-app-sidebar-current: var(--zx-color-bg-current);
47
+ --zx-app-sidebar-press: color-mix(in srgb, var(--zx-color-text) 12%, transparent);
38
48
  display: flex;
39
49
  min-block-size: 0;
40
50
  border: 1px solid var(--zx-color-glass-border);
@@ -43,11 +53,6 @@
43
53
  color: var(--zx-color-text);
44
54
  }
45
55
 
46
- [data-zx-density="compact"] .zx-app-sidebar,
47
- .zx-app-sidebar[data-zx-density="compact"] {
48
- --zx-app-sidebar-group-gap: var(--zx-space-3);
49
- }
50
-
51
56
  .zx-app-sidebar[data-orientation="horizontal"] {
52
57
  inline-size: 100%;
53
58
  }
@@ -84,6 +89,9 @@
84
89
 
85
90
  .zx-app-sidebar__header-content { min-inline-size: 0; }
86
91
 
92
+ /* `collapsible: false` hides the minimise button; the icon-button display rule must not show it. */
93
+ .zx-app-sidebar .zx-app-sidebar__collapse[hidden] { display: none; }
94
+
87
95
  /* The block padding is not decoration: the focus ring is a 3px outer glow, and a nav that padded
88
96
  its scroll box by less would clip the ring off the first and last row. */
89
97
  .zx-app-sidebar__nav {
@@ -101,13 +109,44 @@
101
109
  list-style: none;
102
110
  }
103
111
 
104
- /* A sidebar whose items carry no icons should not pay for an empty gutter; one that has even a
105
- single icon keeps the column on every row so the labels stay in one line down the list. */
106
- .zx-app-sidebar__list:not(:has(.zx-app-sidebar__item-icon)) {
112
+ /* A list whose items carry no icons should not pay for an empty gutter; one that has even a
113
+ single icon keeps the column on every row so the labels stay in one line down the list. Each
114
+ list answers for its own rows only — a branch of plain labels under iconned parents drops the
115
+ column without taking it from the parents. */
116
+ .zx-app-sidebar__list:not(:has(> .zx-app-sidebar__row > .zx-app-sidebar__item .zx-app-sidebar__item-icon,
117
+ > .zx-app-sidebar__section > .zx-app-sidebar__section-list > .zx-app-sidebar__row > .zx-app-sidebar__item .zx-app-sidebar__item-icon)),
118
+ .zx-app-sidebar__section-list:not(:has(> .zx-app-sidebar__row > .zx-app-sidebar__item .zx-app-sidebar__item-icon)),
119
+ .zx-app-sidebar__sublist:not(:has(> .zx-app-sidebar__row > .zx-app-sidebar__item .zx-app-sidebar__item-icon)) {
107
120
  --zx-app-sidebar-icon: 0px;
108
121
  --zx-app-sidebar-item-gap: 0px;
109
122
  }
110
123
 
124
+ /* Where a branch's children start, measured from the row padding, and where the guide that scopes
125
+ them runs. Both are resolved on the parent row — where the parent's own icon column is still
126
+ the one in force — and inherited from there, so a child list that drops its column cannot pull
127
+ the indent or the guide back with it. A row's step is its icon column, or the text indent when
128
+ it has none; level 3 adds its own step to the one level 2 already sits at. */
129
+ .zx-app-sidebar__row[data-level="1"] {
130
+ --zx-app-sidebar-step-1: max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon) + var(--zx-app-sidebar-item-gap));
131
+ --zx-app-sidebar-guide: calc(var(--zx-app-sidebar-pad-x) + max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon)) / 2);
132
+ }
133
+
134
+ .zx-app-sidebar__row[data-level="2"] {
135
+ --zx-app-sidebar-step-2: calc(var(--zx-app-sidebar-step-1) + max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon) + var(--zx-app-sidebar-item-gap)));
136
+ --zx-app-sidebar-guide: calc(var(--zx-app-sidebar-pad-x) + var(--zx-app-sidebar-step-1) + max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon)) / 2);
137
+ }
138
+
139
+ /* Children with icons of their own step in by the shorter icon indent instead: their icons sit
140
+ just past the guide rather than under the parent's label. Text-only children keep lining up
141
+ with the words above them. */
142
+ .zx-app-sidebar__row[data-level="1"]:has(> .zx-app-sidebar__sublist > .zx-app-sidebar__row > .zx-app-sidebar__item .zx-app-sidebar__item-icon) {
143
+ --zx-app-sidebar-step-1: max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon-indent));
144
+ }
145
+
146
+ .zx-app-sidebar__row[data-level="2"]:has(> .zx-app-sidebar__sublist > .zx-app-sidebar__row > .zx-app-sidebar__item .zx-app-sidebar__item-icon) {
147
+ --zx-app-sidebar-step-2: calc(var(--zx-app-sidebar-step-1) + max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon-indent)));
148
+ }
149
+
111
150
  .zx-app-sidebar__sublist { position: relative; margin-block-start: var(--zx-app-sidebar-row-gap); }
112
151
  .zx-app-sidebar__sublist[hidden] { display: none; }
113
152
 
@@ -116,8 +155,8 @@
116
155
  .zx-app-sidebar__sublist::before {
117
156
  content: "";
118
157
  position: absolute;
119
- inset-block: var(--zx-space-1);
120
- inset-inline-start: calc(var(--zx-app-sidebar-pad-x) + var(--zx-app-sidebar-icon) / 2);
158
+ inset-block: 2px;
159
+ inset-inline-start: var(--zx-app-sidebar-guide);
121
160
  inline-size: 1px;
122
161
  border-radius: var(--zx-radius-full);
123
162
  background: var(--zx-color-border);
@@ -132,6 +171,43 @@
132
171
 
133
172
  .zx-app-sidebar__list > .zx-app-sidebar__row:first-child { margin-block-start: 0; }
134
173
 
174
+ /*
175
+ * Sections. A heading over rows that stay at its own level: not a branch, never collapsed, never
176
+ * focused. Sections are separated by the group gap, like the groups a branch opens, and the
177
+ * heading is the same quiet sentence-case caption a flyout uses for its title.
178
+ */
179
+ .zx-app-sidebar__section { list-style: none; }
180
+
181
+ .zx-app-sidebar__section + .zx-app-sidebar__section,
182
+ .zx-app-sidebar__section + .zx-app-sidebar__row,
183
+ .zx-app-sidebar__row + .zx-app-sidebar__section {
184
+ margin-block-start: var(--zx-app-sidebar-group-gap);
185
+ }
186
+
187
+ .zx-app-sidebar__section-header {
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: space-between;
191
+ gap: var(--zx-space-2);
192
+ min-block-size: calc(var(--zx-control-height) - var(--zx-space-1));
193
+ padding-inline: var(--zx-app-sidebar-pad-x) var(--zx-space-1);
194
+ color: var(--zx-color-text-muted);
195
+ font-size: var(--zx-text-xs);
196
+ font-weight: var(--zx-weight-semibold);
197
+ line-height: 1.3;
198
+ }
199
+
200
+ .zx-app-sidebar__section-label { min-inline-size: 0; overflow-wrap: break-word; }
201
+ .zx-app-sidebar__section-actions { display: inline-flex; flex: none; align-items: center; gap: var(--zx-space-1); }
202
+
203
+ .zx-app-sidebar__section-list {
204
+ display: grid;
205
+ gap: var(--zx-app-sidebar-row-gap);
206
+ margin: 0;
207
+ padding: 0;
208
+ list-style: none;
209
+ }
210
+
135
211
  .zx-app-sidebar__item {
136
212
  position: relative;
137
213
  display: grid;
@@ -146,7 +222,9 @@
146
222
  border: 0;
147
223
  border-radius: var(--zx-app-sidebar-radius);
148
224
  background: transparent;
149
- padding-block: var(--zx-space-1);
225
+ /* The icon column fills the row exactly; a row without one keeps a space-1 margin round its
226
+ label. Either way a single-line row is one control tall, and a wrapped one grows. */
227
+ padding-block: min(var(--zx-space-1), (var(--zx-app-sidebar-item-height) - var(--zx-app-sidebar-icon)) / 2);
150
228
  padding-inline: var(--zx-app-sidebar-pad-x);
151
229
  color: var(--zx-color-text);
152
230
  font-size: var(--zx-text-md);
@@ -156,49 +234,14 @@
156
234
  text-decoration: none;
157
235
  }
158
236
 
159
- /* A nav is a touch target before it is a dense list. Compact tightens the drawn row, and a
160
- coarse pointer puts the hit area back to 40px whatever the density says. */
237
+ /* A nav is a touch target before it is a dense list. The drawn row is one control tall, and a
238
+ coarse pointer puts the hit area back to 40px whatever the theme's control height says. */
161
239
  @media (pointer: coarse) {
162
240
  .zx-app-sidebar__item { min-block-size: 40px; }
163
241
  }
164
242
 
165
- .zx-app-sidebar__row[data-level="2"] > .zx-app-sidebar__item { padding-inline-start: calc(var(--zx-app-sidebar-pad-x) + var(--zx-app-sidebar-indent)); }
166
- .zx-app-sidebar__row[data-level="3"] > .zx-app-sidebar__item { padding-inline-start: calc(var(--zx-app-sidebar-pad-x) + 2 * var(--zx-app-sidebar-indent)); }
167
-
168
- /* The active marker. It is anchored to the control and not to the row, because a row is the
169
- branch *and everything open under it* — a marker measured against that would grow into a
170
- bracket down the whole group. It sits in the nav's own gutter, docked to the sidebar's outer
171
- edge, so every marker lines up whatever the nesting depth, and it is the signal that survives
172
- when colour does not. A collapsed branch that merely *contains* the active route gets a short
173
- stub of the same marker: the same vocabulary, a quieter sentence. */
174
- .zx-app-sidebar__item::before {
175
- content: "";
176
- position: absolute;
177
- inset-block: 50%;
178
- inset-inline-start: calc(-1 * var(--zx-app-sidebar-gutter));
179
- inline-size: var(--zx-app-sidebar-marker);
180
- border-start-end-radius: var(--zx-radius-full);
181
- border-end-end-radius: var(--zx-radius-full);
182
- background: var(--zx-color-accent);
183
- opacity: 0;
184
- }
185
-
186
- .zx-app-sidebar[data-side="right"] .zx-app-sidebar__item::before {
187
- inset-inline: auto calc(-1 * var(--zx-app-sidebar-gutter));
188
- border-radius: 0;
189
- border-start-start-radius: var(--zx-radius-full);
190
- border-end-start-radius: var(--zx-radius-full);
191
- }
192
-
193
- .zx-app-sidebar__item[aria-current="page"]::before {
194
- inset-block: 25%;
195
- opacity: 1;
196
- }
197
-
198
- .zx-app-sidebar__item[data-active-descendant="true"][aria-expanded="false"]::before {
199
- inset-block: 38%;
200
- opacity: .55;
201
- }
243
+ .zx-app-sidebar__row[data-level="2"] > .zx-app-sidebar__item { padding-inline-start: calc(var(--zx-app-sidebar-pad-x) + var(--zx-app-sidebar-step-1)); }
244
+ .zx-app-sidebar__row[data-level="3"] > .zx-app-sidebar__item { padding-inline-start: calc(var(--zx-app-sidebar-pad-x) + var(--zx-app-sidebar-step-2)); }
202
245
 
203
246
  .zx-app-sidebar__item-icon {
204
247
  grid-column: 1;
@@ -241,42 +284,34 @@
241
284
  .zx-app-sidebar__item:hover .zx-app-sidebar__item-disclosure { color: var(--zx-color-text); }
242
285
  .zx-app-sidebar__item:active { background: var(--zx-app-sidebar-press); }
243
286
 
244
- /* A branch that is closed over the active route keeps the accent in the label and the marker
245
- stub outside it; a branch that is open says so with its chevron and needs neither. */
287
+ /* A branch that is closed over the active route carries the heavier label, so the way back to
288
+ the current page is visible without opening it; a branch that is open says so with its chevron
289
+ and shows the active row itself. */
246
290
  .zx-app-sidebar__item[data-active-descendant="true"][aria-expanded="false"] {
247
- color: var(--zx-color-accent);
248
- font-weight: 600;
291
+ font-weight: var(--zx-weight-semibold);
249
292
  }
250
293
 
251
- .zx-app-sidebar__item[aria-current="page"] {
252
- background: var(--zx-color-accent);
253
- color: var(--zx-color-on-accent);
254
- font-weight: 600;
294
+ .zx-app-sidebar__item[data-active-descendant="true"][aria-expanded="false"] .zx-app-sidebar__item-icon {
295
+ color: var(--zx-color-text);
255
296
  }
256
297
 
257
- /* Hover and press on the pill move the fill toward the text colour, which raises the contrast
258
- with `--zx-color-on-accent` in both polarities instead of dropping it in one of them. */
259
- .zx-app-sidebar__item[aria-current="page"]:hover {
260
- background: color-mix(in srgb, var(--zx-color-text) 8%, var(--zx-color-accent));
298
+ /* The current page: a neutral fill, the full text colour, a heavier label. Focus is a ring on top
299
+ of it, never a replacement for it, so a keyboard user arrowing past the active route can still
300
+ tell which row is current — the shared `--zx-focus-ring` reads on a neutral fill as it does on
301
+ none. */
302
+ .zx-app-sidebar__item[aria-current="page"] {
303
+ background: var(--zx-app-sidebar-current);
304
+ color: var(--zx-color-text);
305
+ font-weight: var(--zx-weight-semibold);
261
306
  }
262
307
 
263
308
  .zx-app-sidebar__item[aria-current="page"]:active {
264
- background: color-mix(in srgb, var(--zx-color-text) 16%, var(--zx-color-accent));
265
- }
266
-
267
- /* Focus is a ring, never a fill: a keyboard user arrowing past the active route must still be
268
- able to tell which row is current. Ordinary rows take the shared `--zx-focus-ring` from
269
- `base.css` untouched; only the filled pill needs help, because an accent glow around an accent
270
- fill is a ring nobody can see. The inner line is drawn in the pill's own text colour, which the
271
- preset contract already guarantees against that fill. */
272
- .zx-app-sidebar__item[aria-current="page"]:focus-visible {
273
- box-shadow: inset 0 0 0 2px var(--zx-color-on-accent), var(--zx-focus-ring);
309
+ background: var(--zx-app-sidebar-press);
274
310
  }
275
311
 
276
312
  .zx-app-sidebar__item[aria-current="page"] .zx-app-sidebar__item-icon,
277
- .zx-app-sidebar__item[aria-current="page"] .zx-app-sidebar__item-badge,
278
313
  .zx-app-sidebar__item[aria-current="page"] .zx-app-sidebar__item-disclosure {
279
- color: inherit;
314
+ color: var(--zx-color-text);
280
315
  }
281
316
 
282
317
  /* Unavailable, and still readable. A blanket `opacity` on the row took the label under 3:1, which
@@ -298,86 +333,200 @@
298
333
  }
299
334
  .zx-app-sidebar__footer[hidden] { display: none; }
300
335
 
301
- .zx-app-sidebar__rail-header { display: grid; gap: var(--zx-space-1); }
336
+ .zx-app-sidebar__rail-header { display: grid; gap: var(--zx-space-1); justify-items: center; }
302
337
 
303
338
  /*
304
- * The minimized rail. Only the presentation changes; the same vocabulary applies — filled pill,
305
- * docked marker, badge that survives the loss of the label.
339
+ * The modules layout: the module strip beside a panel of one module's menu. Both share the
340
+ * sidebar's chrome surface; the subtle rule between them is the one line tone cannot draw, since
341
+ * they are the same tone.
306
342
  */
307
- .zx-app-sidebar__rail .zx-app-rail__item[aria-current="page"] {
308
- background: var(--zx-color-accent);
309
- color: var(--zx-color-on-accent);
343
+ .zx-app-sidebar__modules {
344
+ display: flex;
345
+ min-inline-size: 0;
346
+ min-block-size: 0;
310
347
  }
311
348
 
312
- .zx-app-sidebar__rail .zx-app-rail__item[aria-current="page"]:hover {
313
- background: color-mix(in srgb, var(--zx-color-text) 8%, var(--zx-color-accent));
349
+ .zx-app-sidebar[data-side="right"] .zx-app-sidebar__modules { flex-direction: row-reverse; }
350
+
351
+ .zx-app-sidebar__strip.zx-module-bar { background: transparent; }
352
+ .zx-app-sidebar__modules > .zx-app-sidebar__strip { border-inline-end: 1px solid var(--zx-color-border-subtle); }
353
+ .zx-app-sidebar[data-side="right"] .zx-app-sidebar__modules > .zx-app-sidebar__strip {
354
+ border-inline-end: 0;
355
+ border-inline-start: 1px solid var(--zx-color-border-subtle);
314
356
  }
315
357
 
316
- .zx-app-sidebar__rail .zx-app-rail__item:not([aria-current="page"]):hover,
317
- .zx-app-sidebar__rail .zx-app-rail__item:not([aria-current="page"])[aria-expanded="true"] {
318
- background: var(--zx-app-sidebar-hover);
358
+ .zx-app-sidebar[data-layout="modules"][data-orientation="horizontal"] > .zx-app-sidebar__strip { inline-size: 100%; }
359
+
360
+ /* The foot of the strip: the one expand/collapse control, then what the application put there.
361
+ With names beside the tiles it lines up with their tiles rather than sitting centred. */
362
+ .zx-app-sidebar__strip-foot {
363
+ display: grid;
364
+ justify-items: center;
365
+ gap: var(--zx-space-2);
366
+ }
367
+
368
+ .zx-app-sidebar__strip[data-labels="beside"] .zx-app-sidebar__strip-foot {
369
+ justify-items: stretch;
370
+ /* The toggle's centre on the tiles' centre: list inset + row inset, less the footer's own pad. */
371
+ padding-inline-start: calc(var(--zx-space-1) + var(--zx-space-2) - var(--zx-module-bar-pad)
372
+ + (var(--zx-module-bar-tile) - var(--zx-control-height)) / 2);
373
+ }
374
+
375
+ /* With names printed the strip's head spans the column, like the rows under it. */
376
+ .zx-app-sidebar__strip[data-labels="beside"] .zx-app-sidebar__rail-header { justify-items: stretch; }
377
+
378
+ .zx-app-sidebar__presentation {
379
+ display: inline-grid;
380
+ justify-self: center;
381
+ gap: 2px;
382
+ border-radius: var(--zx-radius-md);
383
+ background: var(--zx-color-bg-hover);
384
+ padding: 2px;
385
+ }
386
+
387
+ /* Named strip: the three options in a row at the start of the foot; icon strip: a column. */
388
+ .zx-app-sidebar__strip[data-labels="beside"] .zx-app-sidebar__presentation {
389
+ grid-auto-flow: column;
390
+ justify-self: start;
391
+ }
392
+
393
+ .zx-app-sidebar__presentation-option {
394
+ display: grid;
395
+ place-items: center;
396
+ inline-size: var(--zx-space-6);
397
+ block-size: var(--zx-space-6);
398
+ border: 0;
399
+ border-radius: var(--zx-radius-sm);
400
+ background: transparent;
401
+ padding: 0;
402
+ color: var(--zx-color-text-muted);
403
+ cursor: pointer;
404
+ }
405
+
406
+ .zx-app-sidebar__presentation-option:hover { color: var(--zx-color-text); }
407
+
408
+ /* The chosen presentation is where you are: the surface lifted out of the track, full text colour. */
409
+ .zx-app-sidebar__presentation-option[aria-checked="true"] {
410
+ background: var(--zx-color-bg-surface);
411
+ box-shadow: 0 0 0 1px var(--zx-color-border-subtle);
319
412
  color: var(--zx-color-text);
320
413
  }
321
414
 
322
- .zx-app-sidebar__rail .zx-app-rail__item:not([aria-current="page"]):active {
323
- background: var(--zx-app-sidebar-press);
415
+ .zx-app-sidebar__presentation-option:focus-visible {
416
+ outline-color: transparent;
417
+ box-shadow: var(--zx-focus-ring);
324
418
  }
325
419
 
326
- .zx-app-sidebar__rail .zx-app-rail__item[aria-current="page"]:focus-visible {
327
- box-shadow: inset 0 0 0 2px var(--zx-color-on-accent), var(--zx-focus-ring);
420
+ /*
421
+ * The pictogram: a window with its navigation drawn in it — a module strip, wide when it is named,
422
+ * and a side menu when it shows. Drawn from boxes in the text colour, so it needs no icon set and
423
+ * follows the theme, forced colours included.
424
+ */
425
+ .zx-app-sidebar__presentation-glyph {
426
+ display: flex;
427
+ gap: 1px;
428
+ box-sizing: border-box;
429
+ inline-size: 16px;
430
+ block-size: 12px;
431
+ border: 1.5px solid currentColor;
432
+ border-radius: 2px;
433
+ padding: 1px;
328
434
  }
329
435
 
330
- .zx-app-sidebar__rail .zx-app-rail__item::after {
331
- content: "";
332
- position: absolute;
333
- inset-block: 22%;
334
- inset-inline-start: calc(-1 * var(--zx-space-1));
335
- inline-size: var(--zx-app-sidebar-marker);
336
- border-start-end-radius: var(--zx-radius-full);
337
- border-end-end-radius: var(--zx-radius-full);
338
- background: var(--zx-color-accent);
339
- opacity: 0;
436
+ .zx-app-sidebar__presentation-strip,
437
+ .zx-app-sidebar__presentation-panel {
438
+ border-radius: 1px;
439
+ background: currentColor;
340
440
  }
341
441
 
342
- .zx-app-sidebar[data-side="right"] .zx-app-rail__item::after {
343
- inset-inline: auto calc(-1 * var(--zx-space-1));
344
- border-radius: 0;
345
- border-start-start-radius: var(--zx-radius-full);
346
- border-end-start-radius: var(--zx-radius-full);
442
+ .zx-app-sidebar__presentation-strip { flex: 0 0 2px; }
443
+ .zx-app-sidebar__presentation-panel { flex: 0 0 4px; opacity: .5; }
444
+ .zx-app-sidebar__presentation-glyph[data-presentation="expanded"] > .zx-app-sidebar__presentation-strip { flex-basis: 4px; }
445
+ .zx-app-sidebar__presentation-glyph[data-presentation="collapsed"] > .zx-app-sidebar__presentation-panel { display: none; }
446
+
447
+ @media (forced-colors: active) {
448
+ .zx-app-sidebar__presentation-option[aria-checked="true"] {
449
+ background: Highlight;
450
+ color: HighlightText;
451
+ forced-color-adjust: none;
452
+ }
347
453
  }
348
454
 
349
- .zx-app-sidebar[data-orientation="horizontal"] .zx-app-rail__item::after {
350
- inset-block: auto calc(-1 * var(--zx-space-1));
351
- inset-inline: 22%;
352
- inline-size: auto;
353
- block-size: var(--zx-app-sidebar-marker);
354
- border-radius: 0;
355
- border-start-start-radius: var(--zx-radius-full);
356
- border-start-end-radius: var(--zx-radius-full);
455
+ .zx-app-sidebar__panel {
456
+ display: grid;
457
+ grid-template-rows: auto minmax(0, 1fr) auto;
458
+ inline-size: min(14rem, 100vw - var(--zx-control-height) * 3);
459
+ min-block-size: 0;
357
460
  }
358
461
 
359
- .zx-app-sidebar[data-side="bottom"] .zx-app-rail__item::after {
360
- inset-block: calc(-1 * var(--zx-space-1)) auto;
361
- border-radius: 0;
362
- border-end-start-radius: var(--zx-radius-full);
363
- border-end-end-radius: var(--zx-radius-full);
462
+ .zx-app-sidebar__panel-title {
463
+ color: var(--zx-color-text);
464
+ font-size: var(--zx-text-md);
465
+ font-weight: var(--zx-weight-semibold);
466
+ line-height: 1.3;
467
+ overflow-wrap: break-word;
364
468
  }
365
469
 
366
- .zx-app-sidebar__rail .zx-app-rail__item[aria-current="page"]::after { opacity: 1; }
367
- .zx-app-sidebar__rail .zx-app-rail__item[data-active-descendant="true"]:not([aria-current="page"])::after {
368
- inset-block: 34%;
369
- opacity: .55;
470
+ /* One scroller for the module's lead content, its menu, and its own content, so a long menu and
471
+ a long list under it read as one column. The nav loses its own scroll box inside it. */
472
+ .zx-app-sidebar__panel-body {
473
+ display: grid;
474
+ /* Rows take their content's height and the body scrolls; a row the grid could shrink would
475
+ draw the menu under the content beneath it. */
476
+ grid-auto-rows: max-content;
477
+ align-content: start;
478
+ min-block-size: 0;
479
+ overflow: auto;
480
+ }
481
+
482
+ .zx-app-sidebar__panel-body > .zx-app-sidebar__nav { overflow: visible; min-block-size: auto; }
483
+ .zx-app-sidebar__panel-body > [hidden] { display: none; }
484
+
485
+ .zx-app-sidebar__panel-lead {
486
+ display: grid;
487
+ gap: var(--zx-space-2);
488
+ padding: var(--zx-space-1) var(--zx-space-3) 0;
489
+ }
490
+
491
+ .zx-app-sidebar__panel-content {
492
+ min-inline-size: 0;
493
+ padding: 0 var(--zx-app-sidebar-gutter) var(--zx-space-3);
494
+ }
495
+
496
+ /*
497
+ * The minimized rail. Only the presentation changes; the same vocabulary applies — the neutral
498
+ * current fill, the full text colour for a branch over the active route, a badge that survives
499
+ * the loss of the label.
500
+ */
501
+ .zx-app-sidebar__rail .zx-app-rail__item {
502
+ color: var(--zx-color-text-muted);
503
+ }
504
+
505
+ .zx-app-sidebar__rail .zx-app-rail__item:not([aria-current="page"]):hover,
506
+ .zx-app-sidebar__rail .zx-app-rail__item:not([aria-current="page"])[aria-expanded="true"] {
507
+ background: var(--zx-app-sidebar-hover);
508
+ color: var(--zx-color-text);
370
509
  }
371
510
 
372
- .zx-app-sidebar[data-orientation="horizontal"] .zx-app-rail__item[data-active-descendant="true"]:not([aria-current="page"])::after {
373
- inset-block: auto calc(-1 * var(--zx-space-1));
374
- inset-inline: 34%;
511
+ .zx-app-sidebar__rail .zx-app-rail__item:active {
512
+ background: var(--zx-app-sidebar-press);
513
+ }
514
+
515
+ .zx-app-sidebar__rail .zx-app-rail__item[aria-current="page"] {
516
+ background: var(--zx-app-sidebar-current);
517
+ color: var(--zx-color-text);
518
+ }
519
+
520
+ /* A rail item whose flyout holds the active route says so the way a closed branch does in the
521
+ tree: in the full text colour, without a fill of its own. */
522
+ .zx-app-sidebar__rail .zx-app-rail__item[data-active-descendant="true"]:not([aria-current="page"]) {
523
+ color: var(--zx-color-text);
375
524
  }
376
525
 
377
526
  /*
378
527
  * The count a minimized rail would otherwise lose. `data-zx-badge` is set by the component from
379
528
  * the item's own badge: a number that fits stays a number, and one that does not becomes a dot,
380
- * because a three-digit pill on a 32px target stops being a badge and starts being a label. The
529
+ * because a three-digit pill on a 30px target stops being a badge and starts being a label. The
381
530
  * figure itself moves into the control's accessible name either way, so nothing is dropped —
382
531
  * only redrawn.
383
532
  */
@@ -393,7 +542,7 @@
393
542
  color: var(--zx-color-on-accent);
394
543
  font-size: var(--zx-text-xs);
395
544
  font-variant-numeric: tabular-nums;
396
- font-weight: 600;
545
+ font-weight: var(--zx-weight-semibold);
397
546
  line-height: var(--zx-space-4);
398
547
  text-align: center;
399
548
  }
@@ -407,14 +556,6 @@
407
556
  line-height: 0;
408
557
  }
409
558
 
410
- /* On the filled pill the badge would be accent-on-accent. Invert it so the count keeps a fill
411
- of its own without introducing a third colour. */
412
- .zx-app-sidebar__rail .zx-app-rail__item[aria-current="page"] .zx-app-rail__item-badge {
413
- background: var(--zx-color-on-accent);
414
- box-shadow: none;
415
- color: var(--zx-color-accent);
416
- }
417
-
418
559
  /*
419
560
  * Motion. Everything here is decoration on a state change that is already expressed statically,
420
561
  * so all of it is opt-in.
@@ -431,19 +572,17 @@
431
572
  box-shadow var(--zx-dur-fast) var(--zx-ease);
432
573
  }
433
574
 
434
- .zx-app-sidebar__item::before,
435
- .zx-app-sidebar__rail .zx-app-rail__item::after {
436
- transition: opacity var(--zx-dur-base) var(--zx-ease),
437
- inset-block var(--zx-dur-base) var(--zx-ease);
438
- }
439
-
440
575
  .zx-app-sidebar__item:active,
441
576
  .zx-app-sidebar__rail .zx-app-rail__item:active { transform: scale(.985); }
442
577
 
443
578
  .zx-app-sidebar__expanded,
444
- .zx-app-sidebar__rail { animation: zx-app-sidebar-mode var(--zx-dur-base) var(--zx-ease); }
579
+ .zx-app-sidebar__rail,
580
+ .zx-app-sidebar__panel { animation: zx-app-sidebar-mode var(--zx-dur-base) var(--zx-ease); }
445
581
 
446
582
  .zx-app-sidebar__sublist[data-branch-motion="expand"] { animation: zx-app-sidebar-branch var(--zx-dur-base) var(--zx-ease); }
583
+
584
+ /* The panel's content cross-fades when it switches module; the width never animates. */
585
+ .zx-app-sidebar__panel-body[data-module-motion="enter"] { animation: zx-app-sidebar-mode var(--zx-dur-base) var(--zx-ease); }
447
586
  }
448
587
 
449
588
  @keyframes zx-app-sidebar-mode {
@@ -456,8 +595,8 @@
456
595
  to { opacity: 1; transform: translateY(0); }
457
596
  }
458
597
 
459
- /* Forced colours drop backgrounds and box-shadows, so the pill and the marker both disappear.
460
- The system's own selected-item pair carries the state there instead. */
598
+ /* Forced colours drop author backgrounds, so the current fill would disappear. The system's own
599
+ selected-item pair carries the state there instead. */
461
600
  @media (forced-colors: active) {
462
601
  .zx-app-sidebar__item[aria-current="page"],
463
602
  .zx-app-sidebar__rail .zx-app-rail__item[aria-current="page"],
@@ -465,9 +604,6 @@
465
604
  background: Highlight;
466
605
  color: HighlightText;
467
606
  }
468
-
469
- .zx-app-sidebar__item::before,
470
- .zx-app-sidebar__rail .zx-app-rail__item::after { background: Highlight; }
471
607
  }
472
608
 
473
609
  @media (prefers-reduced-transparency: reduce) {