@godxjp/ui 28.13.0 → 30.0.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 (164) hide show
  1. package/agent/START-HERE.md +29 -10
  2. package/agent/components/Anchor.json +6 -1
  3. package/agent/components/AppShell.json +1 -1
  4. package/agent/components/AreaChart.json +19 -1
  5. package/agent/components/BarChart.json +11 -1
  6. package/agent/components/Cascader.json +1 -1
  7. package/agent/components/CompactBarTrend.json +1 -1
  8. package/agent/components/DataState.json +1 -1
  9. package/agent/components/DataTable.json +4 -4
  10. package/agent/components/FormField.json +1 -1
  11. package/agent/components/InfiniteQueryState.json +1 -1
  12. package/agent/components/Input.json +1 -1
  13. package/agent/components/LineChart.json +20 -2
  14. package/agent/components/ListRow.json +1 -1
  15. package/agent/components/Masonry.json +1 -1
  16. package/agent/components/MasterDetail.json +1 -1
  17. package/agent/components/PasswordStrength.json +1 -1
  18. package/agent/components/PermissionMatrix.json +1 -1
  19. package/agent/components/Select.json +1 -0
  20. package/agent/components/Sidebar.json +1 -1
  21. package/agent/components/Table.json +8 -3
  22. package/agent/components/ThemeScope.json +49 -0
  23. package/agent/components/Topbar.json +1 -0
  24. package/agent/components/TopbarItem.json +2 -1
  25. package/agent/components/Transfer.json +1 -1
  26. package/agent/components/TreeSelect.json +1 -1
  27. package/agent/components/UploadCropDialog.json +1 -1
  28. package/agent/components/formatDate.json +1 -1
  29. package/agent/components-index.json +5 -0
  30. package/agent/components.json +138 -30
  31. package/agent/index.json +19 -9
  32. package/agent/llms.txt +10 -10
  33. package/agent/patterns/tenant-brand-color.json +28 -0
  34. package/agent/patterns-index.json +27 -0
  35. package/agent/patterns.json +28 -0
  36. package/agent/rules.json +15 -0
  37. package/agent/tokens.json +5055 -1060
  38. package/dist/app/index.d.ts +3 -0
  39. package/dist/app/index.js +3 -0
  40. package/dist/app/tenant-theme.d.ts +80 -0
  41. package/dist/app/tenant-theme.js +154 -0
  42. package/dist/app/theme-axes.d.ts +14 -1
  43. package/dist/app/theme-axes.js +24 -31
  44. package/dist/components/charts/chart-cartesian.d.ts +5 -1
  45. package/dist/components/charts/chart-cartesian.js +15 -8
  46. package/dist/components/data-display/badge.d.ts +1 -1
  47. package/dist/components/data-display/badge.js +21 -3
  48. package/dist/components/data-display/carousel.js +4 -4
  49. package/dist/components/data-display/data-table.js +13 -2
  50. package/dist/components/data-display/permission-matrix.js +1 -1
  51. package/dist/components/data-display/table.d.ts +11 -2
  52. package/dist/components/data-display/table.js +19 -3
  53. package/dist/components/data-entry/control-appearance.d.ts +12 -6
  54. package/dist/components/data-entry/control-appearance.js +1 -1
  55. package/dist/components/data-entry/select.js +4 -3
  56. package/dist/components/feedback/dialog.js +6 -3
  57. package/dist/components/feedback/overlay-header-tone.d.ts +7 -0
  58. package/dist/components/feedback/overlay-header-tone.js +4 -4
  59. package/dist/components/feedback/sheet.d.ts +1 -1
  60. package/dist/components/feedback/sheet.js +6 -9
  61. package/dist/components/feedback/sonner.js +16 -3
  62. package/dist/components/general/button.js +22 -5
  63. package/dist/components/layout/affix.js +15 -1
  64. package/dist/components/layout/sidebar.js +7 -1
  65. package/dist/components/navigation/anchor.d.ts +1 -1
  66. package/dist/components/navigation/anchor.js +5 -4
  67. package/dist/components/navigation/app-setting-picker.js +1 -1
  68. package/dist/components/navigation/pagination.js +1 -1
  69. package/dist/components/navigation/tabs.js +15 -2
  70. package/dist/components/query/infinite-query-state.d.ts +22 -6
  71. package/dist/contracts/measurement.json +1 -1
  72. package/dist/lib/control-styles.d.ts +31 -11
  73. package/dist/lib/control-styles.js +6 -6
  74. package/dist/lib/overlay-portal.d.ts +20 -0
  75. package/dist/lib/overlay-portal.js +93 -0
  76. package/dist/props/components/app.prop.d.ts +12 -0
  77. package/dist/props/components/charts.prop.d.ts +30 -0
  78. package/dist/props/components/index.d.ts +1 -1
  79. package/dist/props/components/navigation.prop.d.ts +21 -2
  80. package/dist/props/components/query.prop.d.ts +36 -2
  81. package/dist/props/registry.d.ts +46 -1
  82. package/dist/props/registry.js +38 -3
  83. package/dist/styles/alert-layout.css +39 -19
  84. package/dist/styles/badge-layout.css +11 -7
  85. package/dist/styles/base.css +14 -5
  86. package/dist/styles/card-layout.css +29 -16
  87. package/dist/styles/chart-layout.css +27 -5
  88. package/dist/styles/control.css +225 -103
  89. package/dist/styles/data-display-layout.css +178 -76
  90. package/dist/styles/data-entry-layout.css +35 -99
  91. package/dist/styles/dialog-layout.css +58 -28
  92. package/dist/styles/float-button-layout.css +5 -5
  93. package/dist/styles/focus-ring.css +11 -7
  94. package/dist/styles/layout.css +53 -25
  95. package/dist/styles/logo-layout.css +3 -3
  96. package/dist/styles/motion.css +2 -2
  97. package/dist/styles/navigation-layout.css +117 -44
  98. package/dist/styles/shell-layout.css +90 -124
  99. package/dist/styles/table-layout.css +66 -24
  100. package/dist/styles/text-layout.css +13 -4
  101. package/dist/styles/toggle.css +9 -3
  102. package/dist/tokens/components/actions.css +1 -1
  103. package/dist/tokens/components/attachments.css +4 -4
  104. package/dist/tokens/components/badge.css +4 -4
  105. package/dist/tokens/components/banner.css +1 -1
  106. package/dist/tokens/components/callout.css +1 -1
  107. package/dist/tokens/components/card.css +13 -8
  108. package/dist/tokens/components/chart.css +11 -2
  109. package/dist/tokens/components/chat-bubble.css +2 -2
  110. package/dist/tokens/components/control.css +41 -22
  111. package/dist/tokens/components/conversations.css +2 -1
  112. package/dist/tokens/components/data-display.css +23 -18
  113. package/dist/tokens/components/data-entry.css +1 -1
  114. package/dist/tokens/components/descriptions.css +2 -2
  115. package/dist/tokens/components/draggable-panel.css +2 -2
  116. package/dist/tokens/components/feedback.css +29 -9
  117. package/dist/tokens/components/float-button.css +1 -1
  118. package/dist/tokens/components/legal-document.css +2 -2
  119. package/dist/tokens/components/logo.css +2 -2
  120. package/dist/tokens/components/mega-menu.css +6 -4
  121. package/dist/tokens/components/navigation.css +27 -13
  122. package/dist/tokens/components/segmented.css +11 -6
  123. package/dist/tokens/components/separator.css +1 -1
  124. package/dist/tokens/components/shell.css +24 -10
  125. package/dist/tokens/components/table.css +13 -5
  126. package/dist/tokens/components/thought-chain.css +2 -2
  127. package/dist/tokens/components/toggle.css +3 -1
  128. package/dist/tokens/components/tree.css +3 -1
  129. package/dist/tokens/components/upload.css +8 -8
  130. package/dist/tokens/components/welcome.css +1 -1
  131. package/dist/tokens/foundation.css +30 -3
  132. package/docs/COMPOSITION-VS-COMPONENT.md +31 -0
  133. package/docs/CUSTOMER-THEMING.md +637 -1
  134. package/docs/FRAME-COVERAGE-REPORT.md +3 -2
  135. package/docs/GLASSMORPHISM-STANDARD.md +196 -0
  136. package/docs/THEME-API-COVERAGE.md +538 -0
  137. package/docs/TOKEN-RESOLUTION.md +195 -0
  138. package/docs/TOKENS.md +63 -24
  139. package/docs/asset-modules.d.ts +7 -0
  140. package/docs/data-display/charts.tsx +80 -0
  141. package/docs/data-display/data-table/index.tsx +30 -0
  142. package/docs/data-display/popover.tsx +1 -1
  143. package/docs/data-display/table.tsx +52 -0
  144. package/docs/feedback/sheet.tsx +10 -10
  145. package/docs/foundation/density.tsx +4 -4
  146. package/docs/i18n/messages/en.json +506 -0
  147. package/docs/i18n/messages/ja.json +506 -0
  148. package/docs/i18n/messages/vi.json +506 -0
  149. package/docs/layout/account-chip.tsx +2 -2
  150. package/docs/layout/responsive-grid.tsx +1 -1
  151. package/docs/navigation/toolbar.tsx +20 -12
  152. package/docs/providers/theme-scope.tsx +186 -0
  153. package/docs/showcase/caimono-price-comparison.tsx +911 -0
  154. package/docs/showcase/case4-login.tsx +2 -2
  155. package/docs/showcase/permission-matrix.tsx +13 -5
  156. package/docs/showcase/table-pagination.tsx +2 -1
  157. package/docs/showcase/tenant-brand-color.tsx +338 -0
  158. package/docs/showcase/theme-lab.tsx +2125 -0
  159. package/docs/themes/flat.css +462 -0
  160. package/docs/themes/glassmorphism.css +958 -0
  161. package/docs/themes/index.ts +219 -0
  162. package/docs/themes/neubrutalism.css +475 -0
  163. package/package.json +4 -3
  164. package/scripts/explain-token.mjs +382 -0
@@ -0,0 +1,958 @@
1
+ /**
2
+ * glassmorphism theme — a CONSUMER-SURFACE experiment, not a framework change.
3
+ *
4
+ * Everything below is a `--custom-property` declaration inside `[data-theme-style="glass"]`.
5
+ * No selector here targets a `.ui-*` internal class, no `backdrop-filter`/`background`/
6
+ * `box-shadow` is written directly — every visual effect below is produced by EXISTING
7
+ * `src/styles/**` rules that already read the token being set. That is the whole point of the
8
+ * exercise: find out what a consumer can reach through the published theme API alone, the way an
9
+ * Ant Design consumer is limited to `theme.token` / `theme.components`.
10
+ *
11
+ * Where a component has no such hook, this file does not fake one.
12
+ *
13
+ * ── A STANDING RULE FOR THIS FILE, AND IT WAS EARNED TWICE ──────────────────────────────────
14
+ * **No comment here — and no sentence on the showcase page — may assert that a surface is
15
+ * unreachable.** A reachability claim is true on the day it is written and false the moment
16
+ * someone adds a knob, and it silently becomes the most damaging kind of wrong, because the whole
17
+ * claim of this exercise is to be a measurement. It has rotted twice already. The Sheet is the
18
+ * worked example: this file and the page it fed both said "sheet.tsx bakes `bg-background` into
19
+ * its className, there is no `--sheet-background` to set", and gh#880 had added
20
+ * `--sheet-surface-background` hours earlier — the panel renders `rgba(29, 33, 53, 0.92)` while a
21
+ * paragraph inside it described a dead end.
22
+ *
23
+ * So the only claims kept below are the ones this file DEMONSTRATES by setting a token and the
24
+ * ones a reader can re-run: `scripts/measure-glass.mjs` reads the computed value of every surface
25
+ * and every overlay and is the authority on what a theme reaches. If a surface interests you, run
26
+ * it; do not trust a sentence here about a token this file does not set.
27
+ *
28
+ * ── The one trick this file leans on: an alpha suffix inside an HSL-triplet token ────────────
29
+ * Most colour tokens in this system are declared as a bare "H S% L%" triplet and consumed as
30
+ * `hsl(var(--token))` at the call site (src/styles/**). CSS Color 4's `hsl()` also accepts a
31
+ * `/ A` alpha component in that same string, so a token can carry `"H S% L% / A%"` and the
32
+ * existing `hsl(var(--token))` call becomes `hsl(H S% L% / A%)` — a real translucent colour —
33
+ * with ZERO change to the consuming stylesheet. This works ONLY where the token is the sole
34
+ * content of an `hsl()` call; several tokens below are consumed as a COMPLETE value instead
35
+ * (`background-color: var(--token, hsl(...)))`), so those are written as a full `hsl(...)` string
36
+ * instead of a bare triplet. Each declaration below says which form the consumer expects.
37
+ *
38
+ * ── The shadow seed — and the freeze trap it hits, measured, not assumed ─────────────────────
39
+ * `--shadow-color` (src/tokens/foundation.css:290) is the documented brand-shadow seed —
40
+ * `--shadow-xs` … `--shadow-2xl` all derive `rgb(var(--shadow-color) / <fixed alpha>)`, and Select/
41
+ * DropdownMenu/Dialog/`.ui-button--outline` read one of those steps DIRECTLY, so retinting
42
+ * `--shadow-color` (plus restating the one or two steps actually used — see below) was all they
43
+ * needed. Card/Sheet/Popover/Tooltip/Segmented instead go through a component-tier MIRROR
44
+ * (`--card-shadow: var(--shadow-sm)`, declared once in src/tokens/components/*.css) — the exact
45
+ * two-hop shape `docs/showcase/theme-customization.tsx` already documents for `--ring: var(
46
+ * --primary)`: a `var()` resolves at the element that DECLARES the referencing property, and
47
+ * since `--card-shadow` is declared only at `:root`, it resolved once against `:root`'s original
48
+ * `--shadow-sm` and inherits that frozen result — restating `--shadow-sm` alone left every one of
49
+ * these five surfaces' shadow at `rgba(0, 0, 0, …)`, confirmed with `getComputedStyle`. Closing it
50
+ * costs one more line per mirror, each just re-pointing at the (now-scoped) step it already named.
51
+ */
52
+ [data-theme-style="glass"] {
53
+ /* ── THE POLARITY LAYER (gh#896) ────────────────────────────────────────────────────────────
54
+ * Five private knobs, holding every value in this file that FLIPS between light and dark. The
55
+ * declarations below read them instead of repeating a literal, so the dark branch at the bottom
56
+ * of this file is those five lines and not a second copy of the theme.
57
+ *
58
+ * THE FREEZE RULE DOES NOT BITE HERE, and it is worth saying why, because it bites everywhere
59
+ * else in this system (docs/TOKEN-RESOLUTION.md §3). A `var()` substitutes where the referencing
60
+ * property is DECLARED — so a knob declared at `:root` and overridden in a scope below is frozen
61
+ * and unreachable. These five are declared on the SAME ELEMENT as every declaration that reads
62
+ * them (the theme scope div), and `.dark [data-theme-style="glass"]` re-declares them on that
63
+ * same element with higher specificity. Same element, so the reads resolve against the winning
64
+ * value. This is the one shape where a private indirection layer is safe.
65
+ *
66
+ * Counts are measured, not estimated — how many literal sites each knob replaced. */
67
+ --glass-ink: 230 30% 14%; /* primary ink */
68
+ --glass-ink-muted: 230 22% 26%; /* secondary ink — same ratio, lower emphasis */
69
+ --glass-ink-soft: 230 20% 22%; /* muted / tertiary */
70
+ --glass-focus: 262 95% 32%; /* the focus mark, which must survive ANY backdrop */
71
+ --glass-panel: 0 0% 100%; /* the opaque-end overlay panels: select · dropdown · toast · dialog · sheet */
72
+ --glass-panel-sl: 0% 100%; /* the same panel for the relative-colour layer, where the hue is the seed's */
73
+ --glass-status-l: 21%; /* status ink lightness — the hue carries the meaning, this carries legibility */
74
+ --glass-canvas-sl: 55% 86%; /* S L of the page canvas */
75
+ --glass-muted-sl: 40% 80%;
76
+ --glass-accent-sl: 45% 74%;
77
+ --glass-secondary-sl: 38% 78%;
78
+ /* THE BACKDROP IS THE HALF THE FIRST FLIP MISSED, and it is the half you SEE. `--background`
79
+ * was lightened while `--gradient-glow` — which `.app-main` paints ON TOP of it — kept a
80
+ * `linear-gradient(160deg, hsl(230 55% 11%), hsl(210 45% 16%))` floor. L 11% and 16% is a DARK
81
+ * page, so dark ink landed on a dark ground and the content area was unreadable while the token
82
+ * that names the canvas said "light". Reported as "mode light sao chữ ko đọc được gì".
83
+ *
84
+ * A light glass theme is NOT a dark one with the ink inverted. Signal 2 of the standard says the
85
+ * palette comes from the backdrop, so the backdrop is what has to move: bright, airy, still
86
+ * saturated. The three lobes keep their distinct lightnesses and their spread — they are
87
+ * deliberately different and the dark branch has to stay byte-identical. */
88
+ /* THE BRAND INK ON GLASS. Six declarations in the seed layer hardcoded L 88% / 90% — pale
89
+ * violet, right on a dark pane and invisible on a light one. Three of the six
90
+ * (`--text-link`/`-brand`/`-primary`) are ALSO emitted by `tenantTheme` as inline literals and so
91
+ * are overridden anyway; the other three — the active sidebar row, its icon, MegaMenu's current
92
+ * trigger — are not, which is why the sidebar's selected label was the one string still washed
93
+ * out after the backdrop was fixed. Measured: `color(srgb 0.875 0.805 0.995)` on a
94
+ * `rgba(124,58,237,0.26)` pill. */
95
+ --glass-brand-ink-l: 30 24%;
96
+ --glass-brand-ink-strong-l: %;
97
+ --glass-lobe-a-l: 58%;
98
+ --glass-lobe-a-alpha: 0.5;
99
+ --glass-lobe-b-l: 52%;
100
+ --glass-lobe-b-alpha: 0.45;
101
+ --glass-lobe-c-l: 60%;
102
+ --glass-lobe-c-alpha: 0.4;
103
+ --glass-floor-l-1: 70%;
104
+ --glass-floor-l-2: 60%;
105
+ --glass-edge: 0 0% 100% / 55%; /* the glass RIM — decorative, brighter than the pane */
106
+ --glass-rule: 230 22% 38% / 60%; /* a boundary that CARRIES MEANING: >= 3:1, WCAG 2.2 SC 1.4.11 */
107
+
108
+ /* ── Seed: seven surfaces' elevation, retinted at once — BUT ONLY once the freeze trap below is
109
+ * also closed (see docblock above and the note here; this is the SAME trap
110
+ * docs/showcase/theme-customization.tsx documents for `--ring` and `--radius`).
111
+ * `--shadow-sm` / `--shadow-md` / `--shadow-lg` are declared at `:root`
112
+ * (src/tokens/foundation.css:291-296) as `rgb(var(--shadow-color) / <fixed alpha>)`. A `var()`
113
+ * substitutes where the CONSUMING property is declared, and `--shadow-sm` etc. are declared
114
+ * ONLY at `:root` — never re-declared inside this scope — so they resolve once against
115
+ * `:root`'s `--shadow-color` and inherit that already-resolved value down. Measured: setting
116
+ * `--shadow-color` here alone left Card/Popover/Tooltip/Segmented/Select/DropdownMenu's shadow
117
+ * at `rgba(0, 0, 0, …)`, unchanged. Closing it costs one restatement per step actually consumed
118
+ * by this component set (sm/md/lg) — the exact fix `[data-godx-theme] { --ring: var(--primary);
119
+ * }` already uses for the same class of bug. */
120
+ --shadow-color: 30 27 75; /* cool indigo, instead of the default neutral black */
121
+ --shadow-sm: 0 1px 2px 0 rgb(var(--shadow-color) / 0.05);
122
+ --shadow-md:
123
+ 0 4px 6px -1px rgb(var(--shadow-color) / 0.1), 0 2px 4px -2px rgb(var(--shadow-color) / 0.1);
124
+ --shadow-lg:
125
+ 0 10px 15px -3px rgb(var(--shadow-color) / 0.1), 0 4px 6px -4px rgb(var(--shadow-color) / 0.1);
126
+ /* The trap is TWO LAYERS deep for anything that goes through a component-tier "shadow" MIRROR
127
+ * (Card/Sheet/Popover/Tooltip/Segmented all do; Select/DropdownMenu/Dialog/`.ui-button--outline`
128
+ * read `--shadow-md`/`--shadow-lg`/`--shadow-sm` DIRECTLY and needed nothing further, confirmed
129
+ * by measurement). `--card-shadow: var(--shadow-sm)` (src/tokens/components/card.css) is ITSELF
130
+ * declared only at `:root`, so restating the foundation step above was not enough — measured:
131
+ * Card/Popover/Tooltip/Segmented's shadow stayed `rgba(0, 0, 0, …)` until these five mirrors were
132
+ * ALSO restated here, each one line, each just pointing at the (now-scoped) step it already
133
+ * named. */
134
+ --card-shadow: var(--shadow-sm);
135
+ --card-hover-shadow: var(--shadow-md);
136
+ --sheet-shadow: var(--shadow-lg);
137
+ --popover-shadow: var(--shadow-md);
138
+ --tooltip-shadow: var(--shadow-md);
139
+ --segmented-item-selected-shadow: var(--shadow-md);
140
+
141
+ /* ── Border — the SAME shared-role shape as `--card`/`--popover` below: no dedicated token
142
+ * exists for Select/DropdownMenu/Sidebar/Tabs-panel/AppShell's `.app-topbar`, or — the one
143
+ * that matters most — Dialog's own panel (dialog-layout.css:55 hard-codes
144
+ * `border: 1px solid hsl(var(--border))`). Verified no consumer composes a SECOND alpha after
145
+ * it (unlike `--background`, see below), so a bare triplet + alpha is safe here and reaches
146
+ * all of the above at once — the widest-reaching single line in this file. */
147
+ --border: var(--glass-edge);
148
+
149
+ /* ── `--background` IS SHARED, and the sharing is the constraint — not the syntax, which the
150
+ * seed layer at the bottom of this file dissolves. It is consumed BOTH bare
151
+ * (`hsl(var(--background))` — the page canvas, base.css:260) AND pre-composed with its own
152
+ * alpha (`hsl(var(--background) / var(--topbar-background-alpha))`, shell-layout.css:2648,
153
+ * the Topbar glass rule). A bare-triplet alpha suffix here makes Topbar's rule read
154
+ * `hsl(H S% L% / A% / …)` — invalid CSS, two `/` — which drops `background-color` to
155
+ * `transparent`; measured, the Topbar's fill vanished and the page canvas went see-through.
156
+ * Relative colour removes exactly that syntactic problem (see the seed layer). What it does
157
+ * not remove is that one alpha here would make the PAGE CANVAS translucent too, which is why
158
+ * this token stays opaque and the surfaces that want translucency use their own knobs. ──── */
159
+
160
+ /* ── Ambient backdrop — .app-main already exposes this as an opt-in hook
161
+ * (src/styles/shell-layout.css: "background-image: var(--gradient-glow)"; default `none`).
162
+ * A vivid gradient ground is what makes translucency legible at all — glass is invisible on
163
+ * a flat canvas. `--app-shell-main-background` (also consumed as a complete value, not
164
+ * wrapped in hsl()) is the solid floor underneath it. ────────────────────────────────── */
165
+ --gradient-glow:
166
+ radial-gradient(
167
+ circle at 12% 18%,
168
+ hsl(262 83% var(--glass-lobe-a-l) / var(--glass-lobe-a-alpha)),
169
+ transparent 42%
170
+ ),
171
+ radial-gradient(
172
+ circle at 88% 12%,
173
+ hsl(199 89% var(--glass-lobe-b-l) / var(--glass-lobe-b-alpha)),
174
+ transparent 48%
175
+ ),
176
+ radial-gradient(
177
+ circle at 55% 92%,
178
+ hsl(330 81% var(--glass-lobe-c-l) / var(--glass-lobe-c-alpha)),
179
+ transparent 52%
180
+ ),
181
+ linear-gradient(
182
+ 160deg,
183
+ hsl(230 55% var(--glass-floor-l-1)),
184
+ hsl(210 45% var(--glass-floor-l-2))
185
+ );
186
+ --app-shell-main-background: hsl(222 var(--glass-canvas-sl));
187
+
188
+ /* ── Gradient is not only a PAGE-backdrop thing — three more surfaces have a real, CLEAN
189
+ * single-slot `background-image: var(--token)` hook (verified with `getComputedStyle`, not
190
+ * assumed): AppShell's `.app-topbar` (shell-layout.css:389, `--topbar-gradient`), `.app-sidebar`
191
+ * (shell-layout.css:230, `--sidebar-gradient`), and `PageContainer`'s own header band
192
+ * (layout.css:892, `.ui-page-header { background-image: var(--gradient-hero) }` — rendered
193
+ * here because this page passes `title`/`subtitle`). All three take an arbitrary
194
+ * `linear-gradient(...)` string with NO artifact, because the call site assigns the token
195
+ * ONCE, unlike Card below. ──────────────────────────────────────────────────────────────── */
196
+ --topbar-gradient: linear-gradient(
197
+ 120deg,
198
+ hsl(262 83% var(--glass-lobe-a-l) / 0.35) 0%,
199
+ hsl(199 89% var(--glass-lobe-b-l) / 0.2) 100%
200
+ );
201
+ --sidebar-gradient: linear-gradient(
202
+ 160deg,
203
+ hsl(330 81% var(--glass-lobe-c-l) / 0.22) 0%,
204
+ hsl(262 83% var(--glass-lobe-a-l) / 0.16) 100%
205
+ );
206
+ /* NO `--gradient-hero`, AND THAT IS THE POINT (reported twice: "phần header tự dưng tại sao có
207
+ * bg riêng grandient riêng thế??? trông nó ko đồng bộ tí đéo nào cả", then "cái header này tự
208
+ * dưng cho bg làm gì ?? đã nói nó ko đồng bộ mà").
209
+ *
210
+ * It was `linear-gradient(100deg, pink/0.25, violet/0.15)` — a saturated wash at 100deg across
211
+ * one band of a page whose backdrop runs at 160deg with three radial lobes. Two independent
212
+ * gradients, so the header read as a stripe pasted over the page. My first fix softened it to a
213
+ * white scrim on the page's own axis, which was still A BAND: subtler, same defect, and the
214
+ * report came back.
215
+ *
216
+ * The reason I kept it was that exercising a hook is this file's job — and that was my agenda,
217
+ * not a requirement. A theme that demonstrates a knob at the cost of looking wrong has answered
218
+ * the wrong question. So the declaration is gone and `.ui-page-header` falls back to the
219
+ * library's own `--gradient-hero: none`: no background-image, so the PAGE backdrop is what shows
220
+ * through the header, continuous with everything around it. Signal 2 of the standard says the
221
+ * palette comes from ONE backdrop; a header is not an exception to that.
222
+ *
223
+ * The hook is still proven reachable by `--topbar-gradient` and `--sidebar-gradient` above,
224
+ * which are SHELL chrome at the page's edge — a wash there reads as chrome. `.ui-page-header`
225
+ * sits inside the content column, where a separate fill can only ever read as a stripe. */
226
+
227
+ /* ── Card — dedicated tokens, consumed AS hsl(var(--token)), so a bare triplet + alpha works.
228
+ * src/styles/card-layout.css:44 (border), :49 (background). ────────────────────────────── */
229
+ --card-alpha: 12%;
230
+ --card-border: var(--glass-edge);
231
+
232
+ /* ── Dialog — `--dialog-content-glow` is an additive box-shadow LAYER, not the panel fill: the
233
+ * glass edge highlight, on top of the retinted elevation. The panel's own fill is set in the
234
+ * seed layer through `--dialog-surface-background`. ─────────────────────────────────────── */
235
+ --dialog-content-glow: inset 0 1px 0 hsl(0 0% 100% / 0.5);
236
+
237
+ /* ── Sheet — the panel fill is set in the seed layer through `--sheet-surface-background`, at
238
+ * 92%: §4 of the standard, a drawer covers content that must stay unreadable-but-present, so
239
+ * it sits at the opaque end of the range. Its border rides the `--border` restatement above —
240
+ * measured, not assumed: an earlier draft claimed Sheet drew no border at all and
241
+ * `getComputedStyle` said `border-width: 0 0 0 1px`, translucent. ──────────────────────── */
242
+
243
+ /* ── Popover / Tooltip — dedicated background and border tokens, both consumed as a COMPLETE
244
+ * value (dialog-layout.css:342-343, :362-363), so each needs its own `hsl(...)` wrapper here
245
+ * rather than a bare triplet. The Popover's fill is re-set darker in the seed layer, for the
246
+ * reason measured there. ───────────────────────────────────────────────────────────────── */
247
+ --popover-surface-border-color-alpha: 30%;
248
+
249
+ /* Dark frosted chip — the conventional tooltip treatment even inside an otherwise light-glass
250
+ * page. Re-derived from the seed in the seed layer. */
251
+ --tooltip-background: hsl(230 45% 12% / 78%);
252
+ --tooltip-border-color-alpha: 14%;
253
+
254
+ /* ── `--popover` is the FOUNDATION-tier role several floating panels fall back to when their own
255
+ * knob is unset; it is always consumed as `hsl(var(--popover))`, so a bare triplet with an
256
+ * alpha reaches every one of them at once. Select's and DropdownMenu's own dedicated fills
257
+ * are set in the seed layer, which is what they actually render. ───────────────────────── */
258
+ --popover-alpha: 22%;
259
+
260
+ /* ── Table — three different levers for three different lines, verified individually:
261
+ * · The OUTER frame (`.ui-data-table-surface`, what a reader actually sees around a plain
262
+ * DataTable) hard-codes `border: 1px solid hsl(var(--border))` (table-layout.css:372) —
263
+ * bare role, already covered by the `--border` restatement above. `--table-border-color`
264
+ * does NOT reach it (measured: the rendered alpha was 32%, `--border`'s, not this
265
+ * token's 30% — confirms which one actually wins before trusting the doc comment).
266
+ * · `--table-border-color` is real, but scoped to internal cell/row dividers under
267
+ * `<Table bordered>` (table-layout.css:685,689) — not exercised by this page's plain
268
+ * DataTable, kept here for completeness.
269
+ * · The header BAND is its own dedicated token, consumed as a complete value
270
+ * (table-layout.css:159) — confirmed applying (measured 18% alpha, exact match).
271
+ * · The table's own SURFACE re-uses `--card-background` (table-layout.css:540), already
272
+ * set above: DataTable in a Card gets the Card's glass for free, by design ("Card +
273
+ * CardContent flush + DataTable" is the documented composition). ─────────────────────── */
274
+ --table-border-color: hsl(var(--glass-rule));
275
+
276
+ /* ── Input / Select trigger / TagInput — the SHARED `.ui-control-surface` base
277
+ * (src/lib/control-styles.ts:1791-1792), both consumed as a complete value. This is the one
278
+ * component-tier knob pair the form-control family has; there is no per-control override. */
279
+ /* 18%, not 35%. §4b asks a field to be MORE opaque than the card it sits on — it is (the Card is
280
+ * at 12%) — but 35% white over a dark canvas put the Select's own value at 4.20:1, just under
281
+ * the floor, on every seed. The border keeps its 45% because §4b's other requirement is a
282
+ * boundary at 3:1 or better, and that one is carried by the edge rather than the fill. */
283
+ --control-surface-background-alpha: 18%;
284
+ --control-surface-border-color: hsl(var(--glass-rule));
285
+
286
+ /* ── Button — its three variant fills are set further down, through their own knobs. Its BORDER
287
+ * and its SHADOW ride the `--border` and `--shadow-sm` restatements above for free. ────── */
288
+
289
+ /* ── Badge — the only knobs are the two MIX PERCENTAGES color-mix reads
290
+ * (badge-layout.css:64-70), not colours themselves; no shadow property exists on a badge at
291
+ * all (flat chip by design). ──────────────────────────────────────────────────────────── */
292
+ --badge-tint-fill: 22%;
293
+ --badge-tint-edge: 45%;
294
+
295
+ /* ── Tabs — the "card" tab-list and its panel, both consumed as hsl(var(--token))
296
+ * (navigation-layout.css:482,653). The default (underline) tab list paints nothing, so it is
297
+ * already "glass" by having no surface; its own list and active-trigger fills are set further
298
+ * down. ─────────────────────────────────────────────────────────────────────────────────── */
299
+ --tabs-panel-background-alpha: 18%;
300
+ --tabs-card-background-alpha: 30%;
301
+
302
+ /* ── Alert — only the FOUR TONED variants (destructive/warning/success/info) resolve their
303
+ * SURFACE through `--alert-bg-alpha` / `--alert-border-alpha` (alert-layout.css:25-44). The
304
+ * default/neutral tone's border is `hsl(var(--border))` bare — covered by the restatement
305
+ * above. Its background composes `--muted` with its own alpha, so this file does not try to
306
+ * add a second one there; run the instrument rather than trusting this sentence. */
307
+ --alert-bg-alpha: 16%;
308
+ --alert-border-alpha: 35%;
309
+
310
+ /* ── Toast (Sonner) — sonner.tsx sets `--normal-bg: hsl(var(--popover))` /
311
+ * `--normal-border: hsl(var(--border))` inline on the Toaster, so it rides the two shared
312
+ * roles above; its own `--toast-background` is set in the seed layer and is what renders. */
313
+
314
+ /* ── Avatar — its own dedicated token (data-display-layout.css:14), consumed as a complete
315
+ * value. No border or shadow property exists on `.ui-avatar` at all (flat fill by design). */
316
+ /* 14%, not 40%. At 40% the chip composited LIGHT over the dark canvas while the fallback
317
+ * initials inherit the theme's near-white ink: measured 1.70:1 on three avatars in a row and
318
+ * 1.81:1 on the Topbar's. A fallback is TEXT, so the chip is a text ground and has to be
319
+ * treated as one. */
320
+ --avatar-background-alpha: 14%;
321
+
322
+ /* ── Progress — track has a dedicated token, consumed as a complete value
323
+ * (data-display-layout.css:203); the FILL is deliberately left alone — it is the data
324
+ * (WCAG 2.2 SC 1.4.11 contrast mark), not a decorative surface. */
325
+ --progress-track-background-alpha: 25%;
326
+
327
+ /* ── Sidebar — Sidebar itself is `display: contents` (no box of its own); the surface a
328
+ * consumer actually sees is AppShell's `.app-sidebar`, which hard-codes
329
+ * `border-inline-end: 1px solid hsl(var(--border)); background: hsl(var(--card));`
330
+ * (shell-layout.css:216-229) as its FALLBACK. Border is covered by the `--border`
331
+ * restatement above; `--card` is only otherwise read as Card's own fallback (moot once
332
+ * `--card-background` is set directly above). The Sidebar's own fill and blur knobs are set
333
+ * further down and are what render.
334
+ *
335
+ * WHAT IS STILL TRUE, AND IT IS A PLACEMENT FACT RATHER THAN A TOKEN ONE: neither
336
+ * `.app-sidebar` nor `.app-topbar` sits inside `.app-main`, and `--gradient-glow` is painted
337
+ * only inside `.app-main` (shell-layout.css:403). So both chrome surfaces blur and tint over
338
+ * the page's own flat canvas, not over the vivid ground. Glass needs a busy ground BEHIND the
339
+ * element wearing it, and AppShell's chrome grid sits outside the one region that has one —
340
+ * which is also why the Topbar's Popover was the only overlay on this page that failed AA
341
+ * until its fill was moved to the dark end (see the seed layer). */
342
+
343
+ /* ── The bar — AND IT IS **ONE** SURFACE, WHICH TOOK A MEASUREMENT TO GET RIGHT (gh#895).
344
+ *
345
+ * There are two elements here and the token names do not say so. `--topbar-gradient` and
346
+ * `--app-shell-bar-background` paint `.app-topbar`, the full-width row. `--topbar-background-
347
+ * alpha` and `--topbar-backdrop-blur-size` paint `.ui-topbar`, the `<Topbar>` COMPONENT,
348
+ * which that row insets by `--app-shell-bar-inset` on each side. Measured at 1440px:
349
+ *
350
+ * .app-topbar x=256 w=1184 .ui-topbar x=280 w=1136
351
+ *
352
+ * so this file used to set a 55% dark fill on the inner element while the row underneath
353
+ * carried 12% white plus the 120deg gradient — and the 24px gutters showed the row through,
354
+ * as two strips of different colour (the gradient runs across them, so one read yellow and
355
+ * the other red). Four `--topbar-*` knobs, two elements, and nothing in the names to warn you.
356
+ *
357
+ * The row is the one that reaches the edges, so the row carries the glass. Its FILL needs no
358
+ * line here — `.app-topbar` defaults to `hsl(var(--card))`, which this file already set to
359
+ * 12% white, and that is the correct 0.12 from the standard's §1 recipe. What was missing was
360
+ * the BLUR: `--app-shell-bar-backdrop-blur-size` is the knob gh#895 added for the row. The
361
+ * sidebar had had one since gh#880; the bar row had none, which is what forced a theme onto
362
+ * the inner element in the first place.
363
+ *
364
+ * `.ui-topbar` goes back to being what its own rule calls it, "a layout-only wrapper with no
365
+ * fill and no border": both of its knobs are left at `initial`, so each declaration is
366
+ * invalid-at-computed-value-time and the property takes its own initial value. NOT `0` and
367
+ * NOT `transparent` — a literal `blur(0px)` would still promote it to a backdrop root and
368
+ * make it the containing block for its fixed descendants. */
369
+ --app-shell-bar-backdrop-blur-size: var(--blur-lg);
370
+
371
+ /* ── ONE INK RAMP, MEASURED ON THE COMPOSITED GLASS (docs/GLASSMORPHISM-STANDARD.md §3)
372
+ * The first pass set no text token at all, so every string kept a default tuned for an
373
+ * OPAQUE white card. Sampled on the real composited pixel, the lightest glass ground on the
374
+ * page is rgb(172,159,172) — and against it the stock `--muted-foreground` rgb(104,102,94)
375
+ * measures 2.28:1, against a 4.5:1 floor. Five separate strings on one card failed.
376
+ *
377
+ * THE RULE THE STANDARD STATES: on glass, "muted" cannot mean LOWER CONTRAST. It means
378
+ * smaller, or lighter in weight, at the SAME ratio. So the secondary ink is not a lighter
379
+ * grey here — it is the same family, and the hierarchy is carried by size and weight, which
380
+ * the components already vary on their own.
381
+ *
382
+ * Solved, not guessed: against that worst ground a neutral ink needs L ≤ 22.5% for 4.5:1 and
383
+ * L ≤ 9.5% for 7:1. Body sits at 7:1 (AAA) so it keeps a margin when the backdrop shifts
384
+ * under it; secondary sits at 20%, comfortably inside the AA bound with room for the
385
+ * gradient to move. */
386
+ /* ── LIGHT INK, BECAUSE THE GLASS IS DARK. I built this the other way round first and the
387
+ * owner's screenshots showed why it could not work: at 42% white the panes were light, so
388
+ * the ink went dark — and then the PAGE HEADER, which sits on the bare dark backdrop with no
389
+ * pane under it at all, measured 1.63:1 and its subtitle 1.30:1. One ramp cannot serve a
390
+ * light pane and a dark ground; §3 of the standard calls that a POLARITY bug and I wrote
391
+ * that sentence while still having it.
392
+ *
393
+ * At the spec's 0.12 the panes are dark — white at 12% over a dark backdrop lightens it
394
+ * slightly and no more — so light ink is right on the panes AND on the ground, and the
395
+ * polarity problem stops existing rather than being worked around. There is no
396
+ * `--page-title-color` token; this is why one is not needed.
397
+ *
398
+ * Solved over the three regions the backdrop actually produces, worst case first:
399
+ *
400
+ * region pane (12% white over it) off-white ink bare ground
401
+ * blob xanh rgb(66,110,154) 4.98:1 6.67:1
402
+ * blob tím sáng rgb(112,83,163) 5.66:1 7.69:1
403
+ * darkest base rgb(55,57,70) 10.65:1 15.36:1
404
+ *
405
+ * Every one clears AA 4.5:1, measured against the WORST region rather than a convenient
406
+ * sample (§3). Off-white rather than pure white: it keeps a hair of warmth against the cool
407
+ * backdrop and still clears 4.98:1 at the worst point. */
408
+ --foreground: var(--glass-ink);
409
+ --card-foreground: var(--glass-ink);
410
+ --popover-foreground: var(--glass-ink);
411
+ /* "Muted" is SMALLER AND LIGHTER IN WEIGHT, never lower contrast — §3. 82% still measures
412
+ * 3.49:1 on the worst pane, so it stays at 90% and the hierarchy comes from size. */
413
+ --muted-foreground: var(--glass-ink-soft);
414
+ --text-tertiary: 48 10% 93%;
415
+
416
+ /* Status ink lightened to match. The hue carries the meaning; the lightness carries the
417
+ * legibility, and on dark glass those pull in the opposite direction from the light build. */
418
+ --text-success: 152 60% var(--glass-status-l);
419
+ --text-warning: 42 90% var(--glass-status-l);
420
+ --text-info: 210 90% var(--glass-status-l);
421
+ --text-error: 6 85% var(--glass-status-l);
422
+
423
+ /* ── THE CARD WAS NOT CONSISTENT WITH ITS OWN CONTENTS, which is what the owner spotted by eye:
424
+ * a translucent Card whose Segmented track stayed opaque `--muted` (measured
425
+ * rgb(244,243,240)). This knob EXISTS and the first pass simply never set it — unlike
426
+ * `--tabs-list-background` and `--tabs-trigger-active-background`, which do not exist at all
427
+ * and are the library half of gh#880. So Segmented is fixed here and Tabs cannot be. */
428
+ --segmented-track-background-alpha: 10%;
429
+ --segmented-item-selected-background-alpha: 16%;
430
+ /* The ITEM ink too — an unselected item measured 2.86:1 because only the track had been
431
+ * retinted and the label kept its own default. Same ramp as everything else. */
432
+ /* The table's header strip: both knobs exist, and the first build set neither, so it kept an
433
+ * opaque `--muted` band with the default dark ink — the same "container is glass, its own parts
434
+ * are not" defect the owner spotted on the Card's Segmented. */
435
+ --table-header-background-alpha: 8%;
436
+ --table-header-foreground: hsl(var(--glass-ink-soft));
437
+
438
+ --segmented-item-color: var(--glass-ink-soft);
439
+ --segmented-item-selected-color: var(--glass-ink);
440
+
441
+ /* ── ONE INK RAMP ONLY WORKS IF EVERY GLASS SURFACE SHARES A POLARITY, and that is the part
442
+ * §3 of the standard did not say. Card is white glass over a dark canvas; Alert was a toned
443
+ * tint — `--surface-*` at 16% over the same dark backdrop — and the same ink measured 1.27:1
444
+ * and 1.35:1 on it. Not a contrast bug in the ink: a
445
+ * POLARITY bug, two surfaces of opposite value asking one ramp to serve both.
446
+ *
447
+ * Fixed by making the alert a light pane like every other surface, tinted by hue rather
448
+ * than darkened by it. The status still reads — the hue is in the border and the icon —
449
+ * and the ink stops fighting its own ground. gh#866 shipped these four roles an hour ago;
450
+ * this is the first theme to need them. */
451
+ --surface-success: hsl(152 60% 40% / 0.22);
452
+ --surface-warning: hsl(42 80% 45% / 0.22);
453
+ --surface-info: hsl(214 70% 50% / 0.22);
454
+ --surface-destructive: hsl(0 70% 50% / 0.22);
455
+
456
+ /* ══ THE RECIPE, NOW THAT THE KNOBS EXIST (gh#880) ════════════════════════════════════════
457
+ * namethatui.com, verbatim: background rgba(255,255,255,.12) · backdrop-filter blur(16px) ·
458
+ * border 1px rgba(255,255,255,.25) · radius 16px. Everything above this block was written when
459
+ * only ONE surface in the library could take a blur; these 20 lines are what the other eleven
460
+ * were waiting for.
461
+ *
462
+ * BLUR SIZE IS PER SURFACE, SATURATE IS SHARED. `--surface-backdrop-saturate` is one knob
463
+ * because saturation is a property of the MATERIAL — the per-surface decisions the standard
464
+ * actually names are the fill alpha and the blur radius. The 160% lives at each call site, so
465
+ * asking for a blur can never accidentally ask for fog. */
466
+ /* ── A TRAP THIS FILE FELL INTO, WRITTEN DOWN SO THE NEXT READER DOES NOT.
467
+ * This library has TWO token value forms and nothing announces which a given knob takes:
468
+ *
469
+ * HSL COMPONENTS `--card: 60 33% 99%` read as `hsl(var(--card))`
470
+ * COMPLETE COLOUR `--card-background: …` read as `var(--card-background)` directly
471
+ *
472
+ * Every one of the 29 knobs gh#880 added is the SECOND kind; every older role token is the
473
+ * first. I set fourteen of them in the components form, and the failure is silent: the
474
+ * substituted declaration is invalid at computed-value time, so the property takes its own
475
+ * initial value or the call-site fallback, and the surface simply keeps the colour it had.
476
+ * The table header measured 1.03:1 — light ink on the `--muted` it had fallen back to —
477
+ * and looked, from the token file, exactly like a knob that does not work. */
478
+ /* ── THE PAGE CANVAS MUST BE DARK, and not setting it was the single biggest visual defect.
479
+ * `--gradient-glow` paints on `.app-main` ONLY (shell-layout.css:412). Sidebar and Topbar are
480
+ * its SIBLINGS, not its children, so nothing of the gradient is behind them — they were
481
+ * translucent white over the package's light `--background`, which is why the sidebar read as
482
+ * pink and the bar ran pink on the left and blue on the right. Neither is a gap: the canvas
483
+ * under the whole shell is `--background`, and a dark-glass theme has to say so.
484
+ *
485
+ * `--foreground` is already the light ink; this is its missing pair. Setting one without the
486
+ * other is what produced a light chrome carrying near-white labels. */
487
+ --background: 230 var(--glass-canvas-sl);
488
+ --muted: 230 var(--glass-muted-sl);
489
+ --muted-foreground: var(--glass-ink-soft);
490
+ --accent: 262 var(--glass-accent-sl);
491
+ --accent-foreground: var(--glass-ink);
492
+ --secondary: 230 var(--glass-secondary-sl);
493
+ --secondary-foreground: var(--glass-ink);
494
+ --input-alpha: 18%;
495
+
496
+ /* ── THE BRAND INK RAMP — the second half of §3, and the half the first build skipped ────────
497
+ * `--text-link` / `--text-brand` / `--text-primary` and the per-component actives below all
498
+ * derive from `--primary` at the root, where a `var()` substitutes ONCE — so they compute
499
+ * against the ROOT's light-theme seed and inherit that frozen dark violet into this dark
500
+ * scope. Measured before these lines, on the violet seed alone: the Sidebar's active label
501
+ * 1.46:1, NavList's 1.39:1, MegaMenu's trigger 1.13:1, the Anchor link and `Button
502
+ * variant="link"` 2.44:1, `Text link` 1.85:1. None of it is a contrast bug in the ink: it is
503
+ * the `:root` freeze rule (docs/TOKEN-RESOLUTION.md §3) applied to the whole action family.
504
+ *
505
+ * Every one of them has a knob. Restating them here is what a dark theme owes, and the split
506
+ * between the two forms is the trap this file already documents: a token read INSIDE `hsl()`
507
+ * takes a bare triplet, a token read as a complete value takes a whole `hsl(...)`. Getting
508
+ * that backwards fails silently — the declaration is invalid at computed-value time and the
509
+ * surface simply keeps the colour it had. ──────────────────────────────────────────────── */
510
+ --text-link: 48 20% 92%;
511
+ --text-brand: 48 20% 92%;
512
+ --text-primary: 48 20% 92%;
513
+ --anchor-ink-color: 48 20% 92%;
514
+ --org-switcher-meta-foreground: var(--glass-ink-muted);
515
+ --separator-label-color: var(--glass-ink-muted);
516
+ --sidebar-item-active-foreground: hsl(var(--glass-ink));
517
+ --sidebar-nav-item-foreground: hsl(var(--glass-ink-muted));
518
+ --sidebar-nav-icon-foreground: hsl(var(--glass-ink-muted));
519
+ --sidebar-nav-icon-active-foreground: hsl(var(--glass-ink));
520
+ --mega-menu-trigger-current-foreground: hsl(var(--glass-ink));
521
+ --mega-menu-trigger-open-foreground: hsl(var(--glass-ink));
522
+ --mega-menu-link-current-foreground: hsl(var(--glass-ink));
523
+ --timeline-grid-event-color: hsl(var(--glass-ink));
524
+ /* Three more ROOT-BOUND ink bindings, found the same way — by measuring, not by reading. Each is
525
+ * `--x: hsl(var(--foreground))` or `var(--muted-foreground)` declared once at `:root`, so it
526
+ * resolves against the root's dark ink and inherits that into this light-ink scope. The numbers
527
+ * before restating them, on the violet seed: TimelineGrid's event time and description 1.14:1,
528
+ * MegaMenu's non-current trigger 1.13:1, AuthDivider's label 2.42:1. */
529
+ --timeline-grid-event-muted-color: hsl(var(--glass-ink-muted));
530
+ --mega-menu-trigger-foreground: hsl(var(--glass-ink));
531
+ --mega-menu-link-foreground: hsl(var(--glass-ink));
532
+ --mega-menu-panel-foreground: hsl(var(--glass-ink));
533
+ --mega-menu-group-label-foreground: hsl(var(--glass-ink-muted));
534
+ --mega-menu-group-description-foreground: hsl(var(--glass-ink-muted));
535
+ --mega-menu-link-description-foreground: hsl(var(--glass-ink-muted));
536
+ --auth-shell-divider-label-color: var(--glass-ink-muted);
537
+ --org-switcher-current-foreground: hsl(var(--glass-ink));
538
+
539
+ /* ══ INTERACTION STATES — 55 knobs exist, this theme used 3 of them, and every state defect the
540
+ * owner found by hovering was in the other 52 ═══════════════════════════════════════════
541
+ *
542
+ * Reported from screenshots, all three the same shape — a knob that EXISTS and that the theme
543
+ * never set, so the surface kept a value calibrated for a light canvas:
544
+ * · the Sidebar's active row — dark violet ink on a violet wash over a dark canvas;
545
+ * · a hovered Segmented item — dark fill, dark text;
546
+ * · the Topbar's bell on hover — a solid brand block.
547
+ *
548
+ * §3 DECIDES THE VALUES, and it is the rule that is easy to get backwards in this direction:
549
+ * a secondary or unselected state may not be expressed as LOWER CONTRAST. Smaller, lighter in
550
+ * weight, a quieter fill — but the ink keeps its ratio. So every hover and selected fill below
551
+ * is a LIGHTER wash on this dark glass, never a darker one, and every state ink stays on the
552
+ * one ramp.
553
+ *
554
+ * FOCUS IS NOT OPTIONAL (§4b). `--focus-ring-color` defaults to `--ring`, which
555
+ * `tenantTheme()` pins to the seed — and a ring tinted to the seed is invisible over the
556
+ * light lobe of this backdrop, which is most of the top-left, and invisible again on the navy
557
+ * seed everywhere. So the ring is pinned to a warm near-white that clears every band instead
558
+ * of following the brand. ────────────────────────────────────────────────────────────── */
559
+
560
+ /* Triplet form — read inside `hsl()`. */
561
+ --focus-ring-color: var(--glass-focus);
562
+ --focus-outline-color: var(--glass-focus);
563
+ /* 2px, because §4b asks for 2px and the package ships a hairline. The three width tokens are all
564
+ * `:root`-bound (`--focus-ring-width: calc(var(--focus-ring-weight) * var(--focus-outline))`,
565
+ * `--focus-ring-weight: var(--focus-outline-weight)`, `--focus-outline-weight:
566
+ * var(--stroke-hairline)`), so a scope has to restate the one that is actually read — measured,
567
+ * the ring computed `outline-width: 1px` until this line existed. `var(--focus-outline)` is kept
568
+ * in the calc so the package's own on/off toggle still works. */
569
+ --focus-ring-width: calc(2px * var(--focus-outline));
570
+ /* A SECOND ring token, found only by focusing every control: Checkbox (and the choice family with
571
+ * it) draws its ring from `--control-outline`, which derives from `--primary` and not from
572
+ * `--focus-ring-color` — measured `3px rgb(124, 58, 237)` on the violet seed, which on the navy
573
+ * seed over this canvas would be a ring nobody can see. §4b: the ring must survive ANY backdrop,
574
+ * so it is pinned to the same warm near-white rather than to the brand. */
575
+ --control-outline: var(--glass-focus);
576
+ --focus-ring-glow-color: var(--glass-focus);
577
+ /* AND ONE THAT NO TOKEN REACHED, recorded rather than worked around: the choice family's heavy
578
+ * ring stays `3px` of the SEED — measured `rgb(10, 31, 68)` on navy and `rgb(255, 212, 0)` on
579
+ * citron, i.e. the brand colour itself, on a canvas made of the brand colour. Setting
580
+ * `--focus-outline-color`, `--focus-ring-color`, `--control-outline`, `--focus-ring-glow-color`
581
+ * and `--primary-border` in turn moved none of it, so it is reported here and not faked. Every
582
+ * other control on the page takes the 2px warm-white ring. */
583
+ --topbar-item-hover-background-alpha: 16%;
584
+ --topbar-item-hover-color: var(--glass-ink);
585
+ --segmented-item-hover-background-alpha: 18%;
586
+ --segmented-item-hover-color: var(--glass-ink);
587
+ --segmented-item-active-background-alpha: 26%;
588
+ --app-launcher-tile-hover-background-alpha: 16%;
589
+ --app-launcher-tile-hover-color: var(--glass-ink);
590
+ --control-variant-filled-hover-background-alpha: 24%;
591
+ --choice-button-selected-color: var(--glass-ink);
592
+ --choice-button-selected-border-color-alpha: 60%;
593
+ --slider-dot-active-border-color: var(--glass-ink);
594
+ --secondary-hover-alpha: 20%;
595
+ --secondary-active-alpha: 26%;
596
+
597
+ /* Complete-value form — read as `var(--token, …)` directly. */
598
+ --region-focus-ring-color: hsl(var(--glass-focus));
599
+ --menu-item-hover-background-alpha: 14%;
600
+ --menu-item-hover-foreground: hsl(var(--glass-ink));
601
+ --table-row-hover-background-alpha: 10%;
602
+ --table-row-selected-background-alpha: 16%;
603
+ --tree-node-hover-background-alpha: 10%;
604
+ --tree-node-selected-background-alpha: 18%;
605
+ --tree-node-selected-foreground: hsl(var(--glass-ink));
606
+ --mega-menu-trigger-hover-background-alpha: 14%;
607
+ --mega-menu-trigger-hover-foreground: hsl(var(--glass-ink));
608
+ --mega-menu-link-hover-background-alpha: 14%;
609
+ --mega-menu-link-hover-foreground: hsl(var(--glass-ink));
610
+ --sidebar-item-active-background: hsl(var(--primary) / 26%);
611
+ --sidebar-nav-item-hover-foreground: hsl(var(--glass-ink));
612
+ --sidebar-nav-icon-hover-foreground: hsl(var(--glass-ink));
613
+ --conversations-item-active-background-alpha: 18%;
614
+ --conversations-item-active-color: hsl(var(--glass-ink));
615
+ --tabs-count-active-background-alpha: 24%;
616
+ --tabs-count-active-color: hsl(var(--glass-ink));
617
+ --toggle-pressed-count-background-alpha: 26%;
618
+ --toggle-pressed-count-color: hsl(var(--glass-ink));
619
+ --toggle-pressed-border-color-alpha: 45%;
620
+ --checkbox-checked-background: hsl(var(--primary) / 90%);
621
+ --switch-checked-background: hsl(var(--primary) / 90%);
622
+ --switch-unchecked-background-alpha: 26%;
623
+ --switch-content-unchecked-foreground: hsl(48 14% 92%);
624
+ --text-action-color: hsl(48 20% 94%);
625
+ --text-action-color-hover: hsl(48 20% 99%);
626
+
627
+ --surface-backdrop-saturate: 170%;
628
+
629
+ /* §4's OPACITY TIERS. A dropdown sits over arbitrary content and needs the most opaque fill of
630
+ * the three; a drawer next; a content card the least. Blur follows the same order — a small
631
+ * pane over busy content needs more frosting than a large one. */
632
+ --card-backdrop-blur-size: var(--blur-lg); /* 16px — the spec's number */
633
+ --table-surface-backdrop-blur-size: var(--blur-lg);
634
+ --sidebar-backdrop-blur-size: var(--blur-lg);
635
+ --button-backdrop-blur-size: var(--blur-md);
636
+
637
+ --popover-backdrop-blur-size: var(--blur-lg);
638
+ --select-content-backdrop-blur-size: var(--blur-lg);
639
+ --dropdown-content-backdrop-blur-size: var(--blur-lg);
640
+ --tooltip-backdrop-blur-size: var(--blur-md);
641
+ --toast-backdrop-blur-size: var(--blur-lg);
642
+ /* The modal blur belongs on the SCRIM, not the box — an element with a backdrop-filter becomes
643
+ * the containing block for its fixed descendants (shell-layout.css:1728). */
644
+ --dialog-overlay-alpha: 62%;
645
+ --dialog-overlay-backdrop-blur-size: var(--blur-lg);
646
+ --sheet-overlay-backdrop-blur-size: var(--blur-lg);
647
+
648
+ /* FILLS at the tiers. Card at the spec's .12; overlays heavier because a 7-line menu over a
649
+ * photograph is unreadable at .12. */
650
+ --select-content-background: hsl(var(--glass-panel) / 88%);
651
+ --dropdown-content-background: hsl(var(--glass-panel) / 88%);
652
+ --toast-background: hsl(var(--glass-panel) / 90%);
653
+ /* A MODAL IS NOT A CARD. §4: a drawer covers content that must stay unreadable-but-present, and
654
+ * a dialog the same — at 20% the page read straight through the panel. The scrim blurs what is
655
+ * behind; the panel then sits on that blur at an alpha high enough to carry text. These two are
656
+ * the most opaque surfaces in the theme, deliberately. */
657
+ --dialog-surface-background: hsl(var(--glass-panel) / 88%);
658
+ --sheet-surface-background: hsl(var(--glass-panel) / 92%);
659
+ --sidebar-surface-background-alpha: 10%;
660
+ --table-surface-background-alpha: 8%;
661
+
662
+ /* The nested surfaces that made the Card disagree with its own contents. */
663
+ --tabs-list-background-alpha: 6%;
664
+ /* 15%, not 24%. The selected tab's own label measured 4.10-4.48:1 on the citron and navy seeds
665
+ * at 24% — a light fill under light ink. §3 again: the selected state is carried by the fill's
666
+ * PRESENCE and the label's weight, not by making the label harder to read. */
667
+ --tabs-trigger-active-background-alpha: 15%;
668
+
669
+ /* Buttons are IN scope — glassmorphism skins any surface; it is Liquid Glass that reserves
670
+ * glass for the control layer. A solid button on a glass page is that split applied backwards. */
671
+ --button-outline-background-alpha: 14%;
672
+ --button-secondary-background-alpha: 14%;
673
+ /* THE SEED, not a literal violet: `--primary` arrives from `tenantTheme(hex).vars` as an
674
+ * inline declaration on the very element this rule matches, so the alpha suffix composes
675
+ * with whatever seed the switcher is on. No relative-colour support needed for this one.
676
+ *
677
+ * 85%, AND THE NUMBER IS A MEASUREMENT. At the 55% this started on, the coral seed's confirm
678
+ * button measured 2.96:1 inside the Dialog — because `tenantTheme` guarantees its
679
+ * `--primary`/`--primary-foreground` pair on the SOLID seed, and coral (#E2564A, relative
680
+ * luminance 0.22) sits just above the 0.179 pivot where black wins, at 4.66:1 black against
681
+ * 4.51:1 white. Put an alpha on the fill and it composites towards the dark panel behind it,
682
+ * the black label loses its ground, and the pair the function promised is void. No seed is
683
+ * wrong here and the function is not wrong either: ALPHA ON `--primary` IS OUTSIDE WHAT IT
684
+ * PROMISES. 85% keeps the fill close enough to the solid seed for the pair to hold on all five
685
+ * (measured: coral 2.96:1 -> 4.53:1) while still reading as glass. */
686
+ /* 92%, NOT 85%, AND THE 7 POINTS ARE A CONTRACT NOT A TASTE (gh#904).
687
+ *
688
+ * `tenantTheme` guarantees `--primary-foreground` clears 4.5:1 on `--primary` — computed against
689
+ * the OPAQUE fill. Making the fill translucent composites it with whatever pane is behind it, so
690
+ * the ground the label actually sits on is no longer the colour the guarantee was proved for, and
691
+ * the pair quietly stops holding. At 85% on the coral seed it landed at 4.48:1 — 0.02 short, on
692
+ * twelve strings at once, every primary Button on the page.
693
+ *
694
+ * This is the same shape as the ink-surface finding (gh#887, gh#896): a contrast guarantee
695
+ * computed on one ground, applied on another. The difference is that this one is the theme's own
696
+ * doing, so the theme is where it gets paid for.
697
+ *
698
+ * 92% is measured, not chosen: it is where every glass cell returns 562/562 in both polarities
699
+ * and at all five seeds. The glass look survives — 8% of the pane still reads through the fill —
700
+ * and a consumer who wants it thinner has to re-check the pair, which is what the number above
701
+ * this comment is for. */
702
+ --button-default-background: hsl(var(--primary) / 92%);
703
+
704
+ /* A REAL two-stop gradient, which the Card could not take until card-layout.css:49 stopped
705
+ * repeating one variable as both stops. */
706
+ /* THE TINT IS THE PANE'S LIFT, AND 0.1 WAS NOT ONE (gh#902). `--card` now supplies the COLOUR
707
+ * and flips with the polarity (gh#901), which is right — but it also means the pane is the
708
+ * backdrop's own colour at 12%, i.e. very nearly the backdrop. Measured: the Card sat 1.137:1
709
+ * from the page behind it in dark and 1.026:1 in light, which is no pane at all. §1 of the
710
+ * standard makes "frosted translucent panels" signal ONE, so a pane you cannot see fails the
711
+ * look however correct its alpha is. This white scrim is what separates pane from backdrop, in
712
+ * both polarities, and it is deliberately NOT a role: a lift is white on dark glass and white on
713
+ * light glass alike. Dark 1.137 -> 1.957, light 1.026 -> 1.507. */
714
+ --card-tint: hsl(0 0% 100% / 0.34);
715
+ --card-tint-end: hsl(0 0% 100% / 0.18);
716
+ --card-tint-angle: 135deg;
717
+ }
718
+
719
+ /* ══ THE SEED LAYER — everything above that was a literal violet, re-derived from --primary ═══
720
+ *
721
+ * gh#882's second question: "nothing proves the theme is not tuned to violet". It was — the
722
+ * backdrop blobs, the three surface gradients and the page canvas were hand-picked 262°/199°/330°
723
+ * literals, so every seed but violet would have rendered a violet page with a differently coloured
724
+ * Button. Everything below re-derives those from `--primary`, which arrives on THIS very element
725
+ * as an inline declaration from `tenantTheme(hex).vars`.
726
+ *
727
+ * ── THE PROBLEM THIS BLOCK EXISTS TO SOLVE, and it is a real limit of the token model ─────────
728
+ * Half this system's colour roles are declared as a BARE TRIPLET ("H S% L%") and read as
729
+ * `hsl(var(--token))` at the call site. CSS relative colour (`hsl(from C h s l)`) returns a
730
+ * COLOUR, never a triplet, so a triplet-shaped token cannot be computed from another
731
+ * triplet-shaped token by any function CSS has: `--background: hsl(from …)` is simply invalid
732
+ * where the consumer writes `hsl(var(--background))`.
733
+ *
734
+ * The way through is to put the `from` clause INSIDE the token and let the consumer's own `hsl()`
735
+ * close it — `--background: from hsl(var(--primary)) h 40% 9%` makes `hsl(var(--background))`
736
+ * expand to `hsl(from hsl(262 83% 58%) h 40% 9%)`, valid relative colour evaluated at the call
737
+ * site against the scope's own seed. It is still one custom-property declaration and nothing else.
738
+ *
739
+ * A SIDE EFFECT WORTH RECORDING: it also dissolves the "`--background` cannot take an alpha" dead
740
+ * end documented at the top of this file. Topbar's `hsl(var(--background) / var(--topbar-
741
+ * background-alpha))` becomes `hsl(from … h 40% 9% / 55%)`, which is legal — the second `/` that
742
+ * broke the bare-triplet form never appears. So that dead end was SYNTACTIC, and relative colour
743
+ * removes it. What it does NOT remove is the SHARING: `--background` is still the page canvas as
744
+ * well as Dialog's and Sheet's panel, so one alpha there would still make the canvas see-through.
745
+ * Dialog and Sheet are therefore fixed through their own gh#880 knobs, not through this.
746
+ *
747
+ * `@supports` guards the whole layer, so an engine without relative colour keeps the literal
748
+ * violet above rather than losing the theme — the same shape `src/tokens/derived.css` uses for its
749
+ * own `hsl(from …)` branch. ════════════════════════════════════════════════════════════════════ */
750
+ @supports (color: hsl(from red h s l)) {
751
+ [data-theme-style="glass"] {
752
+ /* TRIPLET-shaped roles — the `from` clause lives in the token, the consumer's hsl() closes it. */
753
+ --background: from hsl(var(--primary)) h var(--glass-canvas-sl);
754
+ --muted: from hsl(var(--primary)) h var(--glass-muted-sl);
755
+ --accent: from hsl(var(--primary)) h var(--glass-accent-sl);
756
+ --secondary: from hsl(var(--primary)) h var(--glass-secondary-sl);
757
+
758
+ /* COMPLETE-VALUE tokens — these call sites take a colour, so ordinary relative colour works. */
759
+ --app-shell-main-background: hsl(from hsl(var(--primary)) h var(--glass-canvas-sl));
760
+ --gradient-glow:
761
+ radial-gradient(
762
+ circle at 12% 18%,
763
+ hsl(from hsl(var(--primary)) h s var(--glass-lobe-a-l) / var(--glass-lobe-a-alpha)),
764
+ transparent 42%
765
+ ),
766
+ radial-gradient(
767
+ circle at 88% 12%,
768
+ hsl(
769
+ from hsl(var(--primary)) calc(h - 63) 89% var(--glass-lobe-b-l) /
770
+ var(--glass-lobe-b-alpha)
771
+ ),
772
+ transparent 48%
773
+ ),
774
+ radial-gradient(
775
+ circle at 55% 92%,
776
+ hsl(
777
+ from hsl(var(--primary)) calc(h + 68) 81% var(--glass-lobe-c-l) /
778
+ var(--glass-lobe-c-alpha)
779
+ ),
780
+ transparent 52%
781
+ ),
782
+ linear-gradient(
783
+ 160deg,
784
+ hsl(from hsl(var(--primary)) h 55% var(--glass-floor-l-1)),
785
+ hsl(from hsl(var(--primary)) calc(h - 52) 45% var(--glass-floor-l-2))
786
+ );
787
+ --topbar-gradient: linear-gradient(
788
+ 120deg,
789
+ hsl(from hsl(var(--primary)) h s var(--glass-lobe-a-l) / 0.35) 0%,
790
+ hsl(from hsl(var(--primary)) calc(h - 63) 89% var(--glass-lobe-b-l) / 0.2) 100%
791
+ );
792
+ --sidebar-gradient: linear-gradient(
793
+ 160deg,
794
+ hsl(from hsl(var(--primary)) calc(h + 68) 81% var(--glass-lobe-c-l) / 0.22) 0%,
795
+ hsl(from hsl(var(--primary)) h s var(--glass-lobe-a-l) / 0.16) 100%
796
+ );
797
+ --tooltip-background: hsl(from hsl(var(--primary)) h 45% 12% / 78%);
798
+ --select-content-background: hsl(from hsl(var(--primary)) h var(--glass-panel-sl) / 88%);
799
+ --dropdown-content-background: hsl(from hsl(var(--primary)) h var(--glass-panel-sl) / 88%);
800
+ --toast-background: hsl(from hsl(var(--primary)) h var(--glass-panel-sl) / 90%);
801
+ --dialog-surface-background: hsl(from hsl(var(--primary)) h var(--glass-panel-sl) / 88%);
802
+ --sheet-surface-background: hsl(from hsl(var(--primary)) h var(--glass-panel-sl) / 92%);
803
+
804
+ /* ── THE POPOVER WAS THE ONE OVERLAY LEFT WHITE, AND IT WAS THE ONLY OVERLAY THAT FAILED AA.
805
+ * Measured on every seed: 2.42–3.12:1 for its title and description, while Dialog, Sheet,
806
+ * Select, DropdownMenu, Tooltip and Toast all cleared it. The cause is not the fill's alpha
807
+ * but WHERE this Popover opens — its trigger is a Topbar cell, and AppShell paints
808
+ * `--gradient-glow` only inside `.app-main`, so the panel floats over the light page canvas
809
+ * rather than over the dark ground every other overlay gets. A 22% WHITE fill there
810
+ * composites light, and the theme's one ink ramp is near-white by design (§3: one ramp, or
811
+ * a card has one string that passes and two that fail).
812
+ *
813
+ * So the fill joins the other overlays at the dark end instead of the ink being split. That
814
+ * is §3's remedy — a scrim, not darker ink — applied to the panel itself. ─────────────── */
815
+ --popover-surface-background: hsl(from hsl(var(--primary)) h var(--glass-panel-sl) / 88%);
816
+ --popover-surface-border-color-alpha: 22%;
817
+
818
+ /* ── THE SCRIM KNOB IS A SHARE, NOT AN OPACITY — measured, and it is a trap worth writing down.
819
+ * `--dialog-overlay-alpha: 62%` above reads as "62% black". It is not: dialog-layout.css:26
820
+ * resolves it as `color-mix(in srgb, var(--overlay-background) 62%, transparent)`, and
821
+ * `--overlay-background` is ITSELF `rgb(0 0 0 / 0.5)` (semantic/layout.css:109). The two
822
+ * alphas multiply, so the computed scrim was `color(srgb 0 0 0 / 0.31)` — half what the
823
+ * theme asked for, and confirmed by reading the computed value rather than the declaration.
824
+ * The Sheet's 40% share landed at 0.20 the same way.
825
+ *
826
+ * `--dialog-overlay-background` / `--sheet-overlay-background` are the direct knobs the same
827
+ * rule offers, so the number written here is the number that paints. ──────────────────── */
828
+ /* The brand ink, tinted by the seed rather than fixed warm-white — same split of forms as the
829
+ * literals above, and the `from` clause lives inside the triplet-shaped ones. */
830
+ --text-link: from hsl(var(--primary)) h 90% var(--glass-brand-ink-l);
831
+ --text-brand: from hsl(var(--primary)) h 90% var(--glass-brand-ink-l);
832
+ --text-primary: from hsl(var(--primary)) h 90% var(--glass-brand-ink-l);
833
+ --anchor-ink-color: from hsl(var(--primary)) h 90% var(--glass-brand-ink-l);
834
+ --sidebar-item-active-foreground: hsl(
835
+ from hsl(var(--primary)) h 95% var(--glass-brand-ink-strong-l)
836
+ );
837
+ --sidebar-nav-icon-active-foreground: hsl(
838
+ from hsl(var(--primary)) h 95% var(--glass-brand-ink-strong-l)
839
+ );
840
+ --mega-menu-trigger-current-foreground: hsl(
841
+ from hsl(var(--primary)) h 95% var(--glass-brand-ink-strong-l)
842
+ );
843
+ --mega-menu-trigger-open-foreground: hsl(
844
+ from hsl(var(--primary)) h 95% var(--glass-brand-ink-strong-l)
845
+ );
846
+ --mega-menu-link-current-foreground: hsl(
847
+ from hsl(var(--primary)) h 95% var(--glass-brand-ink-strong-l)
848
+ );
849
+
850
+ --dialog-overlay-background: hsl(0 0% 0% / 62%);
851
+ --sheet-overlay-background: hsl(0 0% 0% / 55%);
852
+ }
853
+ }
854
+
855
+ /* ── THE TWO FALLBACKS THIS THEME OWES BECAUSE IT MADE THESE SURFACES TRANSLUCENT ────────────
856
+ * docs/TOKEN-RESOLUTION.md §5: a theme that makes a surface translucent owns its own
857
+ * `prefers-reduced-transparency` and `@supports not (backdrop-filter)` fallbacks, because only the
858
+ * theme knows what opaque colour it wants — the library cannot guess it (see the removed
859
+ * `src/styles/translucency-fallbacks.css`, gh#880). Both branches below are CUSTOM-PROPERTY
860
+ * DECLARATIONS ONLY, same as every rule above: no `.ui-*` selector, no `background-color` or
861
+ * `backdrop-filter` written directly. Each token here is re-read by the SAME `src/styles/**` rule
862
+ * the top of this file already names for it, so restating it opaque under either condition
863
+ * repaints that surface through the existing rule, nothing new.
864
+ *
865
+ * Borders are deliberately NOT restated here — a translucent hairline does not carry text, so it
866
+ * is not the contrast hazard a translucent fill is; the library's own fallback never touched
867
+ * border-color either.
868
+ *
869
+ * `--app-shell-bar-backdrop-blur-size` is the one blur this theme actually turns on (every other
870
+ * surface here stays at the package default of `initial` = no blur), so it is the only knob reset
871
+ * under `reduced-transparency`; `@supports not (...)` needs no blur reset because an engine that cannot
872
+ * blur was never applying one. */
873
+ @supports not (backdrop-filter: blur(1px)) {
874
+ [data-theme-style="glass"] {
875
+ --card-background: 0 0% 100%;
876
+ --card: 0 0% 100%;
877
+ --popover: 0 0% 100%;
878
+ --popover-surface-background: hsl(0 0% 100%);
879
+ --tooltip-background: hsl(230 45% 12%);
880
+ /* The BAR ROW, not `.ui-topbar` (gh#895). It used to read `--topbar-background-alpha: 100%`,
881
+ * which now solidifies only the inset wrapper and would paint a dark block inside a white row.
882
+ * Pinned rather than left to `--card`, because the line above turns `--card` white for the
883
+ * page's cards and the bar is dark chrome, not a surface. */
884
+ --app-shell-bar-background: hsl(var(--background));
885
+ }
886
+ }
887
+
888
+ @media (prefers-reduced-transparency: reduce) {
889
+ [data-theme-style="glass"] {
890
+ --card-background: 0 0% 100%;
891
+ --card: 0 0% 100%;
892
+ --popover: 0 0% 100%;
893
+ --popover-surface-background: hsl(0 0% 100%);
894
+ --tooltip-background: hsl(230 45% 12%);
895
+ /* Same pair as the `@supports` block above, plus the blur off — and both now name the ROW's
896
+ * knobs, since that is where this theme's bar glass lives (gh#895). */
897
+ --app-shell-bar-background: hsl(var(--background));
898
+ --app-shell-bar-backdrop-blur-size: initial;
899
+ }
900
+ }
901
+
902
+ /* ══ THE DARK POLARITY (gh#896) ═══════════════════════════════════════════════════════════════
903
+ *
904
+ * The library ships two colour polarities and this theme had one. Measured before this block, by
905
+ * toggling the library's own switch and counting how many of five core tokens move:
906
+ *
907
+ * base 5/5 the package default — works
908
+ * glass 0/5 nothing moves. Permanently dark.
909
+ *
910
+ * The cause is the `:root` freeze rule running BACKWARDS (docs/TOKEN-RESOLUTION.md §3 documents
911
+ * only the forward direction). Forward: a binding at `:root` freezes and a scope below cannot
912
+ * reach it. Backwards, which is this: a scope below SHADOWS a branch at the root, and the root
913
+ * cannot reach in. `[data-theme-style="glass"]` sits on a div under `<html>`, and the polarity
914
+ * switch is `.dark, :root[data-theme="dark"]` ON the root — so every value this theme declares
915
+ * beat the dark branch by being nearer the element, and toggling dark mode changed nothing.
916
+ *
917
+ * WHY THIS IS TWELVE LINES AND NOT A SECOND COPY OF THE THEME. Everything that flips between the
918
+ * two polarities was first lifted into the private knobs at the top of the file — a change proved
919
+ * to be a no-op by measurement (40 computed values across two seeds, 0 changed). The 53 literal
920
+ * sites that used to hold an ink or a panel colour now read a knob, so a polarity is the knob
921
+ * values and nothing else. Both blocks declare them on the SAME element, which is the one shape
922
+ * where an indirection layer is safe from the freeze rule — see the note at the top.
923
+ *
924
+ * WHAT DOES **NOT** FLIP, and each is a decision, not an oversight:
925
+ * · the blur sizes and `--surface-backdrop-saturate` — a blur is a blur
926
+ * · every white scrim alpha (the 0.12 pane, the hover washes) — §1 of the standard says 0.12 is
927
+ * THE number, and it holds in both: white at 12% lifts a dark ground slightly and lightens a
928
+ * vivid light one slightly. The pane reads as frosted either way.
929
+ * · `--tooltip-background` stays the dark chip in both, which is the conventional treatment and
930
+ * what this file already documented — so its ink is pinned light in both, not flipped.
931
+ * · the shadow seed and its five mirrors — elevation is not a polarity.
932
+ */
933
+ .dark [data-theme-style="glass"],
934
+ [data-theme="dark"] [data-theme-style="glass"] {
935
+ --glass-ink: 48 20% 96%;
936
+ --glass-ink-muted: 48 14% 90%;
937
+ --glass-ink-soft: 48 10% 94%;
938
+ --glass-focus: 48 100% 82%;
939
+ --glass-panel: 230 30% 16%;
940
+ --glass-panel-sl: 30% 16%;
941
+ --glass-status-l: 90%;
942
+ --glass-canvas-sl: 40% 9%;
943
+ --glass-muted-sl: 24% 18%;
944
+ --glass-accent-sl: 45% 32%;
945
+ --glass-secondary-sl: 38% 22%;
946
+ --glass-brand-ink-l: 24 19%;
947
+ --glass-brand-ink-strong-l: %;
948
+ --glass-lobe-a-l: 58%;
949
+ --glass-lobe-a-alpha: 0.55;
950
+ --glass-lobe-b-l: 48%;
951
+ --glass-lobe-b-alpha: 0.5;
952
+ --glass-lobe-c-l: 60%;
953
+ --glass-lobe-c-alpha: 0.42;
954
+ --glass-floor-l-1: 11%;
955
+ --glass-floor-l-2: 16%;
956
+ --glass-edge: 0 0% 100% / 25%;
957
+ --glass-rule: 0 0% 100% / 45%;
958
+ }