@flyos/design-system 3.11.0 → 3.13.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.
- package/fesm2022/flyos-design-system.mjs +175 -146
- package/fesm2022/flyos-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/scss/_app-surface-tokens.scss +592 -589
- package/scss/_nova-glass.scss +575 -575
- package/scss/_nova-tokens.scss +37 -0
- package/scss/_shell-embed-bridge.scss +32 -24
- package/types/flyos-design-system.d.ts +137 -6
- package/types/flyos-design-system.d.ts.map +1 -1
|
@@ -1,589 +1,592 @@
|
|
|
1
|
-
// ─── App-surface tokens — the BUSINESS-APP content layer ─────────────────────
|
|
2
|
-
// WHY THIS IS A SEPARATE FAMILY FROM `--label-*` / `--surface-*`
|
|
3
|
-
// The DS carries two token families because it dresses two genuinely different
|
|
4
|
-
// surfaces, not because anyone forgot to merge them:
|
|
5
|
-
|
|
6
|
-
// 1. `--label-*` / `--surface-*` / `--window-*` / `--glass-*` (_theme-light/dark)
|
|
7
|
-
// SHELL CHROME. Translucent glass composited over the user's wallpaper.
|
|
8
|
-
// `--surface-card` is a 20%-white LIFT on the window plate — it is only ever
|
|
9
|
-
// meaningful stacked on that plate.
|
|
10
|
-
|
|
11
|
-
// 2. `--ink-*` / `--bg-*` / `--line-*` (this file)
|
|
12
|
-
// BUSINESS-APP CONTENT. An app's own opaque reading surface: white cards on
|
|
13
|
-
// a near-white page in light, dark cards on a darker page in dark. Apps that
|
|
14
|
-
// also embed in the shell re-tint THESE to glass in their own
|
|
15
|
-
// `.fly-shell <app>` block (see Circles' `circles-theme.scss`) — that
|
|
16
|
-
// override layer is the seam, and it only works because the resting values
|
|
17
|
-
// are opaque.
|
|
18
|
-
|
|
19
|
-
// Pointing `--bg-2` at `--surface-card` was evaluated and rejected: standalone,
|
|
20
|
-
// a 20%-white card over a 98.5%-white page stops reading as a card at all. The
|
|
21
|
-
// two families stay distinct on purpose; this comment is the contract.
|
|
22
|
-
|
|
23
|
-
// ADDITIVE ONLY. `tools/ds-compat` classifies re-valuing an existing theme
|
|
24
|
-
// token as MAJOR (it forks the Native Federation shared singleton for every
|
|
25
|
-
// remote), so nothing here may redefine a token another DS partial owns.
|
|
26
|
-
|
|
27
|
-
// Three tokens are deliberately ABSENT — they are the consuming app's BRAND and
|
|
28
|
-
// every app already sets them in its own theme file:
|
|
29
|
-
// --accent --accent-soft --focus-ring
|
|
30
|
-
|
|
31
|
-
// The other nine platform tokens (--danger* --warning* --purple --scrim
|
|
32
|
-
// --glass-blur) USED to be absent too, on the assumption that the platform layer
|
|
33
|
-
// supplies them. That assumption only holds when an app runs FEDERATED, where
|
|
34
|
-
// the desktop shell emits `_theme-light/dark.scss`. A business app running
|
|
35
|
-
// STANDALONE loads only this file plus its own theme — `@use 'fly-theme'` is
|
|
36
|
-
// commented out in both Circles and Thoughts — so `var(--danger)` resolved to
|
|
37
|
-
// nothing and error text rendered colourless. Confirmed in a live browser, not
|
|
38
|
-
// inferred: 9 of the 12 computed to the empty string on Thoughts standalone.
|
|
39
|
-
|
|
40
|
-
// They are now mirrored below at the SAME values `_theme-light/dark.scss` uses.
|
|
41
|
-
// Mirroring (rather than picking new values) is what makes this safe under the
|
|
42
|
-
// cascade: this file's `:root, html.light-theme` selector can tie with the
|
|
43
|
-
// shell's `html.light-theme`, and when the values are identical it does not
|
|
44
|
-
// matter which one wins.
|
|
45
|
-
|
|
46
|
-
// Consumed by the `fly-*` component library in `lib/components/`. Emitted via
|
|
47
|
-
// `_fly-theme.scss`, so `@use 'fly-theme'` is all a consumer needs.
|
|
48
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
// ── The platform TYPE layer (UX v2, S1.7–S1.10 fallout) ──────────────────────
|
|
51
|
-
// `_tokens.scss` owns `--font-family` / `--font-family-mono` and the `--font-*`
|
|
52
|
-
// role ramp (`--font-footnote`, `--font-caption1`, …). Like the two Nova
|
|
53
|
-
// partials below it was reachable only through `_fly-theme.scss`, i.e. only in
|
|
54
|
-
// the desktop shell.
|
|
55
|
-
|
|
56
|
-
// The type layer fails harder than the colour layers do, and it fails silently.
|
|
57
|
-
// The Nova form/overlay/content tasks landed 26 declarations across `lib/**` of
|
|
58
|
-
// the form `font: 600 13px/1.3 var(--font-family)` or `font: var(--nova-font-
|
|
59
|
-
// menu-item)`. A `var()` naming an undefined custom property is invalid at
|
|
60
|
-
// computed-value time, and in a SHORTHAND that invalidates the WHOLE
|
|
61
|
-
// declaration — weight, size and line-height all fall back to inherited, not
|
|
62
|
-
// just the family. Standalone that is a 13px control label rendering at the
|
|
63
|
-
// app's 16px body size inside a box hard-sized to 36/30/26px: a `fly-select`
|
|
64
|
-
// trigger, a `fly-form-field` label, a `fly-checkbox` label, a `fly-drawer`
|
|
65
|
-
// title. Nothing reported it because the shell renders every one of them
|
|
66
|
-
// correctly. Before this line the DS had ZERO `var(--font-family)` references
|
|
67
|
-
// in `lib/**`; the guard that now walks those files lives in
|
|
68
|
-
// `app-surface-nova-bridge.spec.ts`.
|
|
69
|
-
|
|
70
|
-
// Idempotent in the shell for the same reason as the two below: `_fly-theme.scss`
|
|
71
|
-
// already `@use`s `tokens` FIRST, Sass loads a module once per compilation, and
|
|
72
|
-
// the compiled entry is byte-identical either way.
|
|
73
|
-
|
|
74
|
-
// Standalone it adds 120 greenfield names, of which the three External Apps
|
|
75
|
-
// reference five. Four of those (`--font-title2`, `--system-blue`,
|
|
76
|
-
// `--system-indigo`, `--system-red`) were dangling references that now resolve.
|
|
77
|
-
// The fifth is `--font-family` itself, and it is the one behavioural change in
|
|
78
|
-
// this commit: Circles already loads the DS's own Geist from its
|
|
79
|
-
// `public/ui-fonts/geist.css` and is unaffected, but Thoughts and PPM write
|
|
80
|
-
// `var(--font-family, 'Roboto', …)` on `html, body` while bundling Roboto and
|
|
81
|
-
// NOT Geist. Both must bind `--font-family` to their own stack (or drop the
|
|
82
|
-
// indirection) before taking this DS release, or their body text falls through
|
|
83
|
-
// to a face they do not ship. Recorded here rather than worked around: the
|
|
84
|
-
// alternative — leaving the token undefined so their fallback keeps firing —
|
|
85
|
-
// is what dropped the 26 declarations above.
|
|
86
|
-
@use 'tokens';
|
|
87
|
-
|
|
88
|
-
// ── The Nova bridge (UX v2, S1.11) ───────────────────────────────────────────
|
|
89
|
-
// `_nova-tokens.scss` carries the Nova vocabulary — the `--w*` ink ramp,
|
|
90
|
-
// `--mat-*`, `--sys-*`, `--tint-*`, the `--glass2-*` quartet. `_fly-theme.scss`
|
|
91
|
-
// loads it, and the desktop shell is the only consumer that loads that entry.
|
|
92
|
-
// The three External Apps do NOT: Circles, Thoughts and PPM each `@use` THIS
|
|
93
|
-
// partial by bare name, and Circles keeps `// @use 'fly-theme';` commented out
|
|
94
|
-
// on line 1 of its `styles.scss` — the same fact this file's header records.
|
|
95
|
-
|
|
96
|
-
// So without the line below, the Nova names are undefined in precisely the
|
|
97
|
-
// builds that render this family on its own opaque paper. That matters for what
|
|
98
|
-
// comes NEXT rather than for anything today: at the DS 2.0 flip the values in
|
|
99
|
-
// this file are re-pointed at ramp steps (`--ink: var(--w95)`,
|
|
100
|
-
// `--line: var(--w18)`; the whole table is in `.workflow/plans/ux-refresh/
|
|
101
|
-
// notes/S1.11-app-surface-bridge.md`). A `var()` whose token is undefined makes
|
|
102
|
-
// the entire declaration invalid at computed-value time, so that flip would
|
|
103
|
-
// silently unset ink and hairlines for every standalone External App while
|
|
104
|
-
// looking perfect in the shell — the light-mode-only failure that
|
|
105
|
-
// `_shell-embed-bridge.scss` and `_ink-baseline.scss` were each written to end,
|
|
106
|
-
// arriving a third time through a different door.
|
|
107
|
-
|
|
108
|
-
// It is additive in both directions. In the shell it changes nothing at all:
|
|
109
|
-
// `_fly-theme.scss` already `@use`s `nova-tokens` first, Sass loads a module
|
|
110
|
-
// once per compilation, and the compiled entry is byte-identical either way.
|
|
111
|
-
// Standalone it adds greenfield names only — no Nova name is referenced or
|
|
112
|
-
// declared anywhere in Circles, Thoughts or PPM. It is also invisible to
|
|
113
|
-
// `tools/ds-compat`, which derives the token surface by scanning `src/scss/**`
|
|
114
|
-
// per file, not by compiling this entry.
|
|
115
|
-
|
|
116
|
-
// `app-surface-nova-bridge.spec.ts` holds the wire in place and proves that
|
|
117
|
-
// every `var()` this file emits still resolves inside the standalone bundle.
|
|
118
|
-
@use 'nova-tokens';
|
|
119
|
-
|
|
120
|
-
// The same argument, for motion. The app-surface components now reach for the
|
|
121
|
-
// Nova keyframes (`itemIn` / `riseIn` / `appTileIn`) and the `--nova-ease-*` /
|
|
122
|
-
// `--nova-duration-*` tokens, and those live in `_nova-motion.scss` — which,
|
|
123
|
-
// like `_nova-tokens.scss`, was reachable only through `_fly-theme.scss` and so
|
|
124
|
-
// only in the shell. Standalone, an `animation: itemIn …` naming an undefined
|
|
125
|
-
// keyframe is not an error: it silently does nothing, so a list that staggers
|
|
126
|
-
// in for a Core App would appear instantly in an External App with no gate
|
|
127
|
-
// anywhere reporting a difference. Bridging it here is what makes the two
|
|
128
|
-
// builds agree. Same idempotence: the shell already loads this module, so the
|
|
129
|
-
// compiled shell entry is unchanged, and standalone it adds one `:root` block,
|
|
130
|
-
// the keyframe inventory and the single `prefers-reduced-motion` block.
|
|
131
|
-
@use 'nova-motion';
|
|
132
|
-
|
|
133
|
-
// The same argument again, for the Nova TYPE roles — and the reason `tokens`
|
|
134
|
-
// above is not optional. Half the `--nova-font-*` table SNAPs onto the platform
|
|
135
|
-
// ramp by reference (`--nova-font-menu-item: var(--font-footnote)`,
|
|
136
|
-
// `--nova-font-body`, `--nova-font-tooltip`), so loading this partial without
|
|
137
|
-
// `tokens` would not fix anything: it would move the dangling reference one
|
|
138
|
-
// level down, where a component's `font: var(--nova-font-menu-item)` still
|
|
139
|
-
// resolves to an invalid value and still drops whole. The two travel together
|
|
140
|
-
// or neither is worth loading.
|
|
141
|
-
|
|
142
|
-
// This is the one `@use` in this file that the partial's own header argues
|
|
143
|
-
// against ("`@use`d exactly once, from `_fly-theme.scss` … never a second
|
|
144
|
-
// time"). That warning is about COMPONENT-level `@use`, which re-emits the
|
|
145
|
-
// `:root` block into each component's compiled stylesheet; this is a global
|
|
146
|
-
// entry, the same role `_fly-theme.scss` plays, and Sass still emits the module
|
|
147
|
-
// exactly once per compilation. Federated it is `_fly-theme.scss`'s copy;
|
|
148
|
-
// standalone it is this one. Never both.
|
|
149
|
-
@use 'nova-type';
|
|
150
|
-
|
|
151
|
-
:root,
|
|
152
|
-
html.light-theme {
|
|
153
|
-
// ── Neutrals — text ──────────────────────────────────────────────────────
|
|
154
|
-
--ink: oklch(18% 0.005 250deg); // primary text, primary button fills
|
|
155
|
-
--ink-2: oklch(32% 0.005 250deg); // secondary text
|
|
156
|
-
--ink-3: oklch(50% 0.005 250deg); // tertiary text, icons
|
|
157
|
-
--ink-4: oklch(65% 0.005 250deg); // disabled, muted labels
|
|
158
|
-
--ink-inverse: oklch(99% 0 0deg); // text on dark / gradient fills
|
|
159
|
-
--ink-hover: oklch(28% 0.005 250deg); // hover shade of --ink-filled controls
|
|
160
|
-
|
|
161
|
-
// Label for a fill of `--ink` itself (the primary button). Distinct from
|
|
162
|
-
// `--ink-inverse`, which is a FIXED near-white for coloured/gradient fills and
|
|
163
|
-
// therefore wrong here: `--ink` flips per theme, so its label must flip too.
|
|
164
|
-
|
|
165
|
-
// CONTRACT — anything that redefines `--ink` MUST redefine `--on-ink`.
|
|
166
|
-
// It must also be OPAQUE. The shell-embed glass remaps in Circles and Thoughts
|
|
167
|
-
// set `--ink` to white and every surface token to a translucent veil; a
|
|
168
|
-
// translucent label composites into the fill and vanishes.
|
|
169
|
-
--on-ink: var(--ink-inverse);
|
|
170
|
-
|
|
171
|
-
// ── Neutrals — borders ───────────────────────────────────────────────────
|
|
172
|
-
--line: oklch(92% 0.004 250deg); // default borders
|
|
173
|
-
--line-2: oklch(88% 0.004 250deg); // hover borders
|
|
174
|
-
--line-3: oklch(96% 0.003 250deg); // inner row dividers
|
|
175
|
-
|
|
176
|
-
// ── Surfaces (opaque by design — see the header note) ────────────────────
|
|
177
|
-
--bg: oklch(98.5% 0.003 100deg); // app background (warm off-white)
|
|
178
|
-
--bg-2: oklch(100% 0 0deg); // cards, table
|
|
179
|
-
--bg-3: oklch(97% 0.004 100deg); // sidebar, table header
|
|
180
|
-
--bg-hover: oklch(96% 0.005 250deg);
|
|
181
|
-
|
|
182
|
-
// ── Brand primitives ─────────────────────────────────────────────────────
|
|
183
|
-
// `--purple` / `--accent` / `--accent-soft` are platform-owned (_theme-*.scss).
|
|
184
|
-
--teal: oklch(74% 0.13 195deg);
|
|
185
|
-
--grad: linear-gradient(
|
|
186
|
-
135deg,
|
|
187
|
-
oklch(82% 0.12 325deg),
|
|
188
|
-
oklch(60% 0.19 305deg),
|
|
189
|
-
oklch(45% 0.2 285deg)
|
|
190
|
-
);
|
|
191
|
-
--grad-soft: linear-gradient(135deg, oklch(74% 0.13 195deg / 12%), oklch(55% 0.18 295deg / 12%));
|
|
192
|
-
|
|
193
|
-
// ── Accent plate/ink PAIRS ───────────────────────────────────────────────
|
|
194
|
-
// NEVER paint --accent on an --accent-soft plate: that is purple-on-purple,
|
|
195
|
-
// and it is the bug that keeps coming back. Two plates, two inks:
|
|
196
|
-
|
|
197
|
-
// 1. --accent-soft is a TINT the consuming context may redefine (the shell
|
|
198
|
-
// resolves it to `color-mix(… 18%, transparent)`, i.e. a translucent wash
|
|
199
|
-
// over unknowable glass). The only ink correct against it in every mode
|
|
200
|
-
// is the ambient --ink, which each theme already contrasts with its own
|
|
201
|
-
// background.
|
|
202
|
-
--on-accent-soft: var(--ink);
|
|
203
|
-
|
|
204
|
-
// 2. --accent-fill is a SOLID plate we own outright, so its ink is knowable
|
|
205
|
-
// regardless of backdrop — knowable FROM THE PLATE, not "always white".
|
|
206
|
-
// Badges/chips use this pair, so their contrast cannot be broken by a
|
|
207
|
-
// host token override.
|
|
208
|
-
--accent-fill: var(--accent);
|
|
209
|
-
|
|
210
|
-
// FALLBACK RUNG ONLY. The accent-aware derivation at the foot of this file
|
|
211
|
-
// overrides this wherever relative colour syntax parses; this is what an
|
|
212
|
-
// older engine gets, and Nova's theme-invariant `--on-accent` (#fff) is the
|
|
213
|
-
// right answer for the platform violet in both themes.
|
|
214
|
-
|
|
215
|
-
// It is deliberately NOT an alias of `--ink-inverse` any more. Two tokens that
|
|
216
|
-
// happen to agree today are not the same requirement: "ink on a dark/gradient
|
|
217
|
-
// fill" is a constant, "ink on the accent plate" is a function of the accent.
|
|
218
|
-
// Aliasing them made the second one's contrast depend on a token that is not
|
|
219
|
-
// the plate. See the foot of the file for what it depends on now.
|
|
220
|
-
--on-accent-fill: var(--on-accent);
|
|
221
|
-
|
|
222
|
-
// ── Interaction state layers ─────────────────────────────────────────────
|
|
223
|
-
// Translucent veils layered OVER a surface for hover/press/selected instead
|
|
224
|
-
// of swapping its bg colour — one value reads correctly on any background
|
|
225
|
-
// (the visionOS / Material "state layer" model), so hover looks consistent
|
|
226
|
-
// across cards, rows, chips and glass plates alike.
|
|
227
|
-
--state-hover: oklch(50% 0.03 285deg / 5%);
|
|
228
|
-
--state-press: oklch(50% 0.03 285deg / 9%);
|
|
229
|
-
--state-selected: var(--accent-soft);
|
|
230
|
-
|
|
231
|
-
// Bright top rim that gives a translucent plate physical depth. Inert on
|
|
232
|
-
// opaque surfaces; the shell's glass mode leans on it.
|
|
233
|
-
--edge-highlight: oklch(100% 0 0deg / 50%);
|
|
234
|
-
|
|
235
|
-
// ── Icon size ramp ───────────────────────────────────────────────────────
|
|
236
|
-
// ONE scale for every icon (PrimeIcons + inline SVG). Icons had drifted to
|
|
237
|
-
// ~13 different sizes, which is why they never lined up beside each other.
|
|
238
|
-
--icon-xs: 12px; // dense meta rows, chips
|
|
239
|
-
--icon-sm: 14px; // text buttons, section-header actions
|
|
240
|
-
--icon-md: 16px; // default — icon buttons, rails, nav
|
|
241
|
-
--icon-lg: 20px; // emphasis / empty states
|
|
242
|
-
|
|
243
|
-
// ── Status chips (workflow-generic) ──────────────────────────────────────
|
|
244
|
-
--st-published: oklch(55% 0.13 165deg);
|
|
245
|
-
--st-published-bg: oklch(96% 0.04 165deg);
|
|
246
|
-
--st-approved: oklch(55% 0.15 245deg);
|
|
247
|
-
--st-approved-bg: oklch(96% 0.03 245deg);
|
|
248
|
-
--st-draft: oklch(55% 0.005 250deg);
|
|
249
|
-
--st-draft-bg: oklch(95% 0.003 250deg);
|
|
250
|
-
--st-archived: oklch(55% 0.06 60deg);
|
|
251
|
-
--st-archived-bg: oklch(96% 0.025 80deg);
|
|
252
|
-
|
|
253
|
-
// ── Semantic feedback ────────────────────────────────────────────────────
|
|
254
|
-
// Success has no platform equivalent (`--status-success` is a raw dot colour,
|
|
255
|
-
// not a fg/bg pair), so it ships here, aliased onto the published status chip.
|
|
256
|
-
--success: var(--st-published);
|
|
257
|
-
--success-bg: var(--st-published-bg);
|
|
258
|
-
|
|
259
|
-
// Standalone fallbacks for the platform tokens — see the header. Values are
|
|
260
|
-
// copied verbatim from `_theme-light.scss`; keep them in sync if that file
|
|
261
|
-
// changes, or a standalone app drifts from its federated self.
|
|
262
|
-
--danger: #ff6b6b;
|
|
263
|
-
--danger-bg: rgb(255 59 48 / 20%);
|
|
264
|
-
--danger-line: rgb(255 59 48 / 40%);
|
|
265
|
-
--warning: #febc2e;
|
|
266
|
-
--warning-bg: rgb(254 188 46 / 18%);
|
|
267
|
-
--warning-fg: rgb(255 255 255 / 96%);
|
|
268
|
-
--purple: #9333ea;
|
|
269
|
-
--scrim: rgb(0 0 0 / 50%);
|
|
270
|
-
--glass-blur: 24px;
|
|
271
|
-
|
|
272
|
-
// ── Module-card teal (app-home fidelity pass, 2026-08-20) ────────────────
|
|
273
|
-
// The launch-card family (`.ah__card--feature` tile, hover wash, hover ring)
|
|
274
|
-
// is deliberately THEME-FIXED
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
// color-
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
--
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
--sp-
|
|
288
|
-
--sp-
|
|
289
|
-
--sp-
|
|
290
|
-
--sp-
|
|
291
|
-
--sp-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
--
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
--text-
|
|
298
|
-
--text-
|
|
299
|
-
--text-
|
|
300
|
-
--text-
|
|
301
|
-
--text-
|
|
302
|
-
--text-
|
|
303
|
-
--text-
|
|
304
|
-
--
|
|
305
|
-
--
|
|
306
|
-
--
|
|
307
|
-
--fw-
|
|
308
|
-
--
|
|
309
|
-
--
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
//
|
|
322
|
-
--
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
--r-
|
|
326
|
-
|
|
327
|
-
//
|
|
328
|
-
--
|
|
329
|
-
|
|
330
|
-
// ──
|
|
331
|
-
--
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
--z-
|
|
335
|
-
--z-
|
|
336
|
-
--z-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
--
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
--shadow-
|
|
343
|
-
--shadow-
|
|
344
|
-
--shadow-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
--
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
--shadow-
|
|
353
|
-
--shadow-
|
|
354
|
-
--shadow-
|
|
355
|
-
|
|
356
|
-
--shadow-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
--t-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
//
|
|
379
|
-
--
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
--ink
|
|
383
|
-
--ink-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
--
|
|
389
|
-
--
|
|
390
|
-
|
|
391
|
-
--
|
|
392
|
-
--
|
|
393
|
-
--
|
|
394
|
-
--
|
|
395
|
-
--bg
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
--
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
--
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
--
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
--st-
|
|
417
|
-
--st-
|
|
418
|
-
--st-
|
|
419
|
-
--st-
|
|
420
|
-
--st-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
--
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
--shadow-
|
|
427
|
-
--shadow-
|
|
428
|
-
--shadow-
|
|
429
|
-
--shadow-
|
|
430
|
-
--shadow-
|
|
431
|
-
--shadow-
|
|
432
|
-
--shadow-
|
|
433
|
-
--shadow-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
--
|
|
466
|
-
--
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
--
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
--surface-
|
|
473
|
-
--surface-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
--surface-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
--surface-
|
|
480
|
-
--
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
//
|
|
508
|
-
//
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
// · `
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
//
|
|
533
|
-
// as
|
|
534
|
-
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
//
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
|
|
559
|
-
//
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
//
|
|
569
|
-
//
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
//
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
//
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
//
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
1
|
+
// ─── App-surface tokens — the BUSINESS-APP content layer ─────────────────────
|
|
2
|
+
// WHY THIS IS A SEPARATE FAMILY FROM `--label-*` / `--surface-*`
|
|
3
|
+
// The DS carries two token families because it dresses two genuinely different
|
|
4
|
+
// surfaces, not because anyone forgot to merge them:
|
|
5
|
+
|
|
6
|
+
// 1. `--label-*` / `--surface-*` / `--window-*` / `--glass-*` (_theme-light/dark)
|
|
7
|
+
// SHELL CHROME. Translucent glass composited over the user's wallpaper.
|
|
8
|
+
// `--surface-card` is a 20%-white LIFT on the window plate — it is only ever
|
|
9
|
+
// meaningful stacked on that plate.
|
|
10
|
+
|
|
11
|
+
// 2. `--ink-*` / `--bg-*` / `--line-*` (this file)
|
|
12
|
+
// BUSINESS-APP CONTENT. An app's own opaque reading surface: white cards on
|
|
13
|
+
// a near-white page in light, dark cards on a darker page in dark. Apps that
|
|
14
|
+
// also embed in the shell re-tint THESE to glass in their own
|
|
15
|
+
// `.fly-shell <app>` block (see Circles' `circles-theme.scss`) — that
|
|
16
|
+
// override layer is the seam, and it only works because the resting values
|
|
17
|
+
// are opaque.
|
|
18
|
+
|
|
19
|
+
// Pointing `--bg-2` at `--surface-card` was evaluated and rejected: standalone,
|
|
20
|
+
// a 20%-white card over a 98.5%-white page stops reading as a card at all. The
|
|
21
|
+
// two families stay distinct on purpose; this comment is the contract.
|
|
22
|
+
|
|
23
|
+
// ADDITIVE ONLY. `tools/ds-compat` classifies re-valuing an existing theme
|
|
24
|
+
// token as MAJOR (it forks the Native Federation shared singleton for every
|
|
25
|
+
// remote), so nothing here may redefine a token another DS partial owns.
|
|
26
|
+
|
|
27
|
+
// Three tokens are deliberately ABSENT — they are the consuming app's BRAND and
|
|
28
|
+
// every app already sets them in its own theme file:
|
|
29
|
+
// --accent --accent-soft --focus-ring
|
|
30
|
+
|
|
31
|
+
// The other nine platform tokens (--danger* --warning* --purple --scrim
|
|
32
|
+
// --glass-blur) USED to be absent too, on the assumption that the platform layer
|
|
33
|
+
// supplies them. That assumption only holds when an app runs FEDERATED, where
|
|
34
|
+
// the desktop shell emits `_theme-light/dark.scss`. A business app running
|
|
35
|
+
// STANDALONE loads only this file plus its own theme — `@use 'fly-theme'` is
|
|
36
|
+
// commented out in both Circles and Thoughts — so `var(--danger)` resolved to
|
|
37
|
+
// nothing and error text rendered colourless. Confirmed in a live browser, not
|
|
38
|
+
// inferred: 9 of the 12 computed to the empty string on Thoughts standalone.
|
|
39
|
+
|
|
40
|
+
// They are now mirrored below at the SAME values `_theme-light/dark.scss` uses.
|
|
41
|
+
// Mirroring (rather than picking new values) is what makes this safe under the
|
|
42
|
+
// cascade: this file's `:root, html.light-theme` selector can tie with the
|
|
43
|
+
// shell's `html.light-theme`, and when the values are identical it does not
|
|
44
|
+
// matter which one wins.
|
|
45
|
+
|
|
46
|
+
// Consumed by the `fly-*` component library in `lib/components/`. Emitted via
|
|
47
|
+
// `_fly-theme.scss`, so `@use 'fly-theme'` is all a consumer needs.
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
// ── The platform TYPE layer (UX v2, S1.7–S1.10 fallout) ──────────────────────
|
|
51
|
+
// `_tokens.scss` owns `--font-family` / `--font-family-mono` and the `--font-*`
|
|
52
|
+
// role ramp (`--font-footnote`, `--font-caption1`, …). Like the two Nova
|
|
53
|
+
// partials below it was reachable only through `_fly-theme.scss`, i.e. only in
|
|
54
|
+
// the desktop shell.
|
|
55
|
+
|
|
56
|
+
// The type layer fails harder than the colour layers do, and it fails silently.
|
|
57
|
+
// The Nova form/overlay/content tasks landed 26 declarations across `lib/**` of
|
|
58
|
+
// the form `font: 600 13px/1.3 var(--font-family)` or `font: var(--nova-font-
|
|
59
|
+
// menu-item)`. A `var()` naming an undefined custom property is invalid at
|
|
60
|
+
// computed-value time, and in a SHORTHAND that invalidates the WHOLE
|
|
61
|
+
// declaration — weight, size and line-height all fall back to inherited, not
|
|
62
|
+
// just the family. Standalone that is a 13px control label rendering at the
|
|
63
|
+
// app's 16px body size inside a box hard-sized to 36/30/26px: a `fly-select`
|
|
64
|
+
// trigger, a `fly-form-field` label, a `fly-checkbox` label, a `fly-drawer`
|
|
65
|
+
// title. Nothing reported it because the shell renders every one of them
|
|
66
|
+
// correctly. Before this line the DS had ZERO `var(--font-family)` references
|
|
67
|
+
// in `lib/**`; the guard that now walks those files lives in
|
|
68
|
+
// `app-surface-nova-bridge.spec.ts`.
|
|
69
|
+
|
|
70
|
+
// Idempotent in the shell for the same reason as the two below: `_fly-theme.scss`
|
|
71
|
+
// already `@use`s `tokens` FIRST, Sass loads a module once per compilation, and
|
|
72
|
+
// the compiled entry is byte-identical either way.
|
|
73
|
+
|
|
74
|
+
// Standalone it adds 120 greenfield names, of which the three External Apps
|
|
75
|
+
// reference five. Four of those (`--font-title2`, `--system-blue`,
|
|
76
|
+
// `--system-indigo`, `--system-red`) were dangling references that now resolve.
|
|
77
|
+
// The fifth is `--font-family` itself, and it is the one behavioural change in
|
|
78
|
+
// this commit: Circles already loads the DS's own Geist from its
|
|
79
|
+
// `public/ui-fonts/geist.css` and is unaffected, but Thoughts and PPM write
|
|
80
|
+
// `var(--font-family, 'Roboto', …)` on `html, body` while bundling Roboto and
|
|
81
|
+
// NOT Geist. Both must bind `--font-family` to their own stack (or drop the
|
|
82
|
+
// indirection) before taking this DS release, or their body text falls through
|
|
83
|
+
// to a face they do not ship. Recorded here rather than worked around: the
|
|
84
|
+
// alternative — leaving the token undefined so their fallback keeps firing —
|
|
85
|
+
// is what dropped the 26 declarations above.
|
|
86
|
+
@use 'tokens';
|
|
87
|
+
|
|
88
|
+
// ── The Nova bridge (UX v2, S1.11) ───────────────────────────────────────────
|
|
89
|
+
// `_nova-tokens.scss` carries the Nova vocabulary — the `--w*` ink ramp,
|
|
90
|
+
// `--mat-*`, `--sys-*`, `--tint-*`, the `--glass2-*` quartet. `_fly-theme.scss`
|
|
91
|
+
// loads it, and the desktop shell is the only consumer that loads that entry.
|
|
92
|
+
// The three External Apps do NOT: Circles, Thoughts and PPM each `@use` THIS
|
|
93
|
+
// partial by bare name, and Circles keeps `// @use 'fly-theme';` commented out
|
|
94
|
+
// on line 1 of its `styles.scss` — the same fact this file's header records.
|
|
95
|
+
|
|
96
|
+
// So without the line below, the Nova names are undefined in precisely the
|
|
97
|
+
// builds that render this family on its own opaque paper. That matters for what
|
|
98
|
+
// comes NEXT rather than for anything today: at the DS 2.0 flip the values in
|
|
99
|
+
// this file are re-pointed at ramp steps (`--ink: var(--w95)`,
|
|
100
|
+
// `--line: var(--w18)`; the whole table is in `.workflow/plans/ux-refresh/
|
|
101
|
+
// notes/S1.11-app-surface-bridge.md`). A `var()` whose token is undefined makes
|
|
102
|
+
// the entire declaration invalid at computed-value time, so that flip would
|
|
103
|
+
// silently unset ink and hairlines for every standalone External App while
|
|
104
|
+
// looking perfect in the shell — the light-mode-only failure that
|
|
105
|
+
// `_shell-embed-bridge.scss` and `_ink-baseline.scss` were each written to end,
|
|
106
|
+
// arriving a third time through a different door.
|
|
107
|
+
|
|
108
|
+
// It is additive in both directions. In the shell it changes nothing at all:
|
|
109
|
+
// `_fly-theme.scss` already `@use`s `nova-tokens` first, Sass loads a module
|
|
110
|
+
// once per compilation, and the compiled entry is byte-identical either way.
|
|
111
|
+
// Standalone it adds greenfield names only — no Nova name is referenced or
|
|
112
|
+
// declared anywhere in Circles, Thoughts or PPM. It is also invisible to
|
|
113
|
+
// `tools/ds-compat`, which derives the token surface by scanning `src/scss/**`
|
|
114
|
+
// per file, not by compiling this entry.
|
|
115
|
+
|
|
116
|
+
// `app-surface-nova-bridge.spec.ts` holds the wire in place and proves that
|
|
117
|
+
// every `var()` this file emits still resolves inside the standalone bundle.
|
|
118
|
+
@use 'nova-tokens';
|
|
119
|
+
|
|
120
|
+
// The same argument, for motion. The app-surface components now reach for the
|
|
121
|
+
// Nova keyframes (`itemIn` / `riseIn` / `appTileIn`) and the `--nova-ease-*` /
|
|
122
|
+
// `--nova-duration-*` tokens, and those live in `_nova-motion.scss` — which,
|
|
123
|
+
// like `_nova-tokens.scss`, was reachable only through `_fly-theme.scss` and so
|
|
124
|
+
// only in the shell. Standalone, an `animation: itemIn …` naming an undefined
|
|
125
|
+
// keyframe is not an error: it silently does nothing, so a list that staggers
|
|
126
|
+
// in for a Core App would appear instantly in an External App with no gate
|
|
127
|
+
// anywhere reporting a difference. Bridging it here is what makes the two
|
|
128
|
+
// builds agree. Same idempotence: the shell already loads this module, so the
|
|
129
|
+
// compiled shell entry is unchanged, and standalone it adds one `:root` block,
|
|
130
|
+
// the keyframe inventory and the single `prefers-reduced-motion` block.
|
|
131
|
+
@use 'nova-motion';
|
|
132
|
+
|
|
133
|
+
// The same argument again, for the Nova TYPE roles — and the reason `tokens`
|
|
134
|
+
// above is not optional. Half the `--nova-font-*` table SNAPs onto the platform
|
|
135
|
+
// ramp by reference (`--nova-font-menu-item: var(--font-footnote)`,
|
|
136
|
+
// `--nova-font-body`, `--nova-font-tooltip`), so loading this partial without
|
|
137
|
+
// `tokens` would not fix anything: it would move the dangling reference one
|
|
138
|
+
// level down, where a component's `font: var(--nova-font-menu-item)` still
|
|
139
|
+
// resolves to an invalid value and still drops whole. The two travel together
|
|
140
|
+
// or neither is worth loading.
|
|
141
|
+
|
|
142
|
+
// This is the one `@use` in this file that the partial's own header argues
|
|
143
|
+
// against ("`@use`d exactly once, from `_fly-theme.scss` … never a second
|
|
144
|
+
// time"). That warning is about COMPONENT-level `@use`, which re-emits the
|
|
145
|
+
// `:root` block into each component's compiled stylesheet; this is a global
|
|
146
|
+
// entry, the same role `_fly-theme.scss` plays, and Sass still emits the module
|
|
147
|
+
// exactly once per compilation. Federated it is `_fly-theme.scss`'s copy;
|
|
148
|
+
// standalone it is this one. Never both.
|
|
149
|
+
@use 'nova-type';
|
|
150
|
+
|
|
151
|
+
:root,
|
|
152
|
+
html.light-theme {
|
|
153
|
+
// ── Neutrals — text ──────────────────────────────────────────────────────
|
|
154
|
+
--ink: oklch(18% 0.005 250deg); // primary text, primary button fills
|
|
155
|
+
--ink-2: oklch(32% 0.005 250deg); // secondary text
|
|
156
|
+
--ink-3: oklch(50% 0.005 250deg); // tertiary text, icons
|
|
157
|
+
--ink-4: oklch(65% 0.005 250deg); // disabled, muted labels
|
|
158
|
+
--ink-inverse: oklch(99% 0 0deg); // text on dark / gradient fills
|
|
159
|
+
--ink-hover: oklch(28% 0.005 250deg); // hover shade of --ink-filled controls
|
|
160
|
+
|
|
161
|
+
// Label for a fill of `--ink` itself (the primary button). Distinct from
|
|
162
|
+
// `--ink-inverse`, which is a FIXED near-white for coloured/gradient fills and
|
|
163
|
+
// therefore wrong here: `--ink` flips per theme, so its label must flip too.
|
|
164
|
+
|
|
165
|
+
// CONTRACT — anything that redefines `--ink` MUST redefine `--on-ink`.
|
|
166
|
+
// It must also be OPAQUE. The shell-embed glass remaps in Circles and Thoughts
|
|
167
|
+
// set `--ink` to white and every surface token to a translucent veil; a
|
|
168
|
+
// translucent label composites into the fill and vanishes.
|
|
169
|
+
--on-ink: var(--ink-inverse);
|
|
170
|
+
|
|
171
|
+
// ── Neutrals — borders ───────────────────────────────────────────────────
|
|
172
|
+
--line: oklch(92% 0.004 250deg); // default borders
|
|
173
|
+
--line-2: oklch(88% 0.004 250deg); // hover borders
|
|
174
|
+
--line-3: oklch(96% 0.003 250deg); // inner row dividers
|
|
175
|
+
|
|
176
|
+
// ── Surfaces (opaque by design — see the header note) ────────────────────
|
|
177
|
+
--bg: oklch(98.5% 0.003 100deg); // app background (warm off-white)
|
|
178
|
+
--bg-2: oklch(100% 0 0deg); // cards, table
|
|
179
|
+
--bg-3: oklch(97% 0.004 100deg); // sidebar, table header
|
|
180
|
+
--bg-hover: oklch(96% 0.005 250deg);
|
|
181
|
+
|
|
182
|
+
// ── Brand primitives ─────────────────────────────────────────────────────
|
|
183
|
+
// `--purple` / `--accent` / `--accent-soft` are platform-owned (_theme-*.scss).
|
|
184
|
+
--teal: oklch(74% 0.13 195deg);
|
|
185
|
+
--grad: linear-gradient(
|
|
186
|
+
135deg,
|
|
187
|
+
oklch(82% 0.12 325deg),
|
|
188
|
+
oklch(60% 0.19 305deg),
|
|
189
|
+
oklch(45% 0.2 285deg)
|
|
190
|
+
);
|
|
191
|
+
--grad-soft: linear-gradient(135deg, oklch(74% 0.13 195deg / 12%), oklch(55% 0.18 295deg / 12%));
|
|
192
|
+
|
|
193
|
+
// ── Accent plate/ink PAIRS ───────────────────────────────────────────────
|
|
194
|
+
// NEVER paint --accent on an --accent-soft plate: that is purple-on-purple,
|
|
195
|
+
// and it is the bug that keeps coming back. Two plates, two inks:
|
|
196
|
+
|
|
197
|
+
// 1. --accent-soft is a TINT the consuming context may redefine (the shell
|
|
198
|
+
// resolves it to `color-mix(… 18%, transparent)`, i.e. a translucent wash
|
|
199
|
+
// over unknowable glass). The only ink correct against it in every mode
|
|
200
|
+
// is the ambient --ink, which each theme already contrasts with its own
|
|
201
|
+
// background.
|
|
202
|
+
--on-accent-soft: var(--ink);
|
|
203
|
+
|
|
204
|
+
// 2. --accent-fill is a SOLID plate we own outright, so its ink is knowable
|
|
205
|
+
// regardless of backdrop — knowable FROM THE PLATE, not "always white".
|
|
206
|
+
// Badges/chips use this pair, so their contrast cannot be broken by a
|
|
207
|
+
// host token override.
|
|
208
|
+
--accent-fill: var(--accent);
|
|
209
|
+
|
|
210
|
+
// FALLBACK RUNG ONLY. The accent-aware derivation at the foot of this file
|
|
211
|
+
// overrides this wherever relative colour syntax parses; this is what an
|
|
212
|
+
// older engine gets, and Nova's theme-invariant `--on-accent` (#fff) is the
|
|
213
|
+
// right answer for the platform violet in both themes.
|
|
214
|
+
|
|
215
|
+
// It is deliberately NOT an alias of `--ink-inverse` any more. Two tokens that
|
|
216
|
+
// happen to agree today are not the same requirement: "ink on a dark/gradient
|
|
217
|
+
// fill" is a constant, "ink on the accent plate" is a function of the accent.
|
|
218
|
+
// Aliasing them made the second one's contrast depend on a token that is not
|
|
219
|
+
// the plate. See the foot of the file for what it depends on now.
|
|
220
|
+
--on-accent-fill: var(--on-accent);
|
|
221
|
+
|
|
222
|
+
// ── Interaction state layers ─────────────────────────────────────────────
|
|
223
|
+
// Translucent veils layered OVER a surface for hover/press/selected instead
|
|
224
|
+
// of swapping its bg colour — one value reads correctly on any background
|
|
225
|
+
// (the visionOS / Material "state layer" model), so hover looks consistent
|
|
226
|
+
// across cards, rows, chips and glass plates alike.
|
|
227
|
+
--state-hover: oklch(50% 0.03 285deg / 5%);
|
|
228
|
+
--state-press: oklch(50% 0.03 285deg / 9%);
|
|
229
|
+
--state-selected: var(--accent-soft);
|
|
230
|
+
|
|
231
|
+
// Bright top rim that gives a translucent plate physical depth. Inert on
|
|
232
|
+
// opaque surfaces; the shell's glass mode leans on it.
|
|
233
|
+
--edge-highlight: oklch(100% 0 0deg / 50%);
|
|
234
|
+
|
|
235
|
+
// ── Icon size ramp ───────────────────────────────────────────────────────
|
|
236
|
+
// ONE scale for every icon (PrimeIcons + inline SVG). Icons had drifted to
|
|
237
|
+
// ~13 different sizes, which is why they never lined up beside each other.
|
|
238
|
+
--icon-xs: 12px; // dense meta rows, chips
|
|
239
|
+
--icon-sm: 14px; // text buttons, section-header actions
|
|
240
|
+
--icon-md: 16px; // default — icon buttons, rails, nav
|
|
241
|
+
--icon-lg: 20px; // emphasis / empty states
|
|
242
|
+
|
|
243
|
+
// ── Status chips (workflow-generic) ──────────────────────────────────────
|
|
244
|
+
--st-published: oklch(55% 0.13 165deg);
|
|
245
|
+
--st-published-bg: oklch(96% 0.04 165deg);
|
|
246
|
+
--st-approved: oklch(55% 0.15 245deg);
|
|
247
|
+
--st-approved-bg: oklch(96% 0.03 245deg);
|
|
248
|
+
--st-draft: oklch(55% 0.005 250deg);
|
|
249
|
+
--st-draft-bg: oklch(95% 0.003 250deg);
|
|
250
|
+
--st-archived: oklch(55% 0.06 60deg);
|
|
251
|
+
--st-archived-bg: oklch(96% 0.025 80deg);
|
|
252
|
+
|
|
253
|
+
// ── Semantic feedback ────────────────────────────────────────────────────
|
|
254
|
+
// Success has no platform equivalent (`--status-success` is a raw dot colour,
|
|
255
|
+
// not a fg/bg pair), so it ships here, aliased onto the published status chip.
|
|
256
|
+
--success: var(--st-published);
|
|
257
|
+
--success-bg: var(--st-published-bg);
|
|
258
|
+
|
|
259
|
+
// Standalone fallbacks for the platform tokens — see the header. Values are
|
|
260
|
+
// copied verbatim from `_theme-light.scss`; keep them in sync if that file
|
|
261
|
+
// changes, or a standalone app drifts from its federated self.
|
|
262
|
+
--danger: #ff6b6b;
|
|
263
|
+
--danger-bg: rgb(255 59 48 / 20%);
|
|
264
|
+
--danger-line: rgb(255 59 48 / 40%);
|
|
265
|
+
--warning: #febc2e;
|
|
266
|
+
--warning-bg: rgb(254 188 46 / 18%);
|
|
267
|
+
--warning-fg: rgb(255 255 255 / 96%);
|
|
268
|
+
--purple: #9333ea;
|
|
269
|
+
--scrim: rgb(0 0 0 / 50%);
|
|
270
|
+
--glass-blur: 24px;
|
|
271
|
+
|
|
272
|
+
// ── Module-card teal (app-home fidelity pass, 2026-08-20) ────────────────
|
|
273
|
+
// The launch-card family (`.ah__card--feature` tile, hover wash, hover ring)
|
|
274
|
+
// is deliberately THEME-FIXED — decoupled from the running `--accent` and
|
|
275
|
+
// from `--sys-teal`'s per-theme flip. Since the app-brand cascade (3.12.0)
|
|
276
|
+
// this pair is the FALLBACK: `fly-app-home` prefers a cascaded
|
|
277
|
+
// `--app-color`/`--app-color-deep` (published by the shell window from the
|
|
278
|
+
// app's authored brand colour), and renders this fixed teal only where no
|
|
279
|
+
// app colour cascades in. Consumers derive the design's alphas via
|
|
280
|
+
// color-mix; only these two literals exist (conventions-spec rule: raw
|
|
281
|
+
// colour values live in this file alone). Declared once in `:root`, no
|
|
282
|
+
// dark-scope redefinition, same as `--ink-inverse`.
|
|
283
|
+
--module-teal: #2896a0;
|
|
284
|
+
--module-teal-deep: #105a5a;
|
|
285
|
+
|
|
286
|
+
// ── Spacing scale (4px base) ─────────────────────────────────────────────
|
|
287
|
+
--sp-1: 4px;
|
|
288
|
+
--sp-2: 8px;
|
|
289
|
+
--sp-3: 12px;
|
|
290
|
+
--sp-4: 16px;
|
|
291
|
+
--sp-5: 22px;
|
|
292
|
+
--sp-6: 28px;
|
|
293
|
+
--sp-7: 40px;
|
|
294
|
+
--sp-8: 56px;
|
|
295
|
+
|
|
296
|
+
// ── Typography scale ─────────────────────────────────────────────────────
|
|
297
|
+
--text-2xs: 11.5px; // field labels, field errors, footer hints
|
|
298
|
+
--text-xs: 11px; // eyebrows, badges, metric labels
|
|
299
|
+
--text-sm: 12.5px; // meta, hints, crumbs
|
|
300
|
+
--text-md: 13.5px; // body in cards, subtitles
|
|
301
|
+
--text-base: 14px; // controls, table body
|
|
302
|
+
--text-lg: 16px; // page body
|
|
303
|
+
--text-xl: 18px; // card titles, dialog titles
|
|
304
|
+
--text-2xl: 28px; // detail page h1
|
|
305
|
+
--text-3xl: 32px; // listing page h1
|
|
306
|
+
--text-display: 44px; // hero title
|
|
307
|
+
--fw-book: 450; // between regular and medium (segmented buttons)
|
|
308
|
+
--fw-medium: 500;
|
|
309
|
+
--fw-semibold: 600;
|
|
310
|
+
--fw-bold: 700;
|
|
311
|
+
--tracking-tight: -0.02em;
|
|
312
|
+
--tracking-wide: 0.06em;
|
|
313
|
+
|
|
314
|
+
// Type families. `--font-family` / `--font-family-mono` are the platform's
|
|
315
|
+
// names; these are the short aliases the component library reads.
|
|
316
|
+
--font-sans: var(--font-family);
|
|
317
|
+
|
|
318
|
+
// Monospace is retired — the UI is one sans face throughout. Kept (aliased)
|
|
319
|
+
// rather than deleted so existing `var(--font-mono)` consumers keep
|
|
320
|
+
// resolving; the `.mono` utility still contributes tabular-nums, which is
|
|
321
|
+
// what those id/number treatments actually need.
|
|
322
|
+
--font-mono: var(--font-sans);
|
|
323
|
+
|
|
324
|
+
// ── Radii ────────────────────────────────────────────────────────────────
|
|
325
|
+
--r-sm: 6px; // icon buttons, kbd
|
|
326
|
+
--r-md: 8px; // buttons, inputs, nav items
|
|
327
|
+
--r-lg: 12px; // cards, table wrapper, filter panel
|
|
328
|
+
--r-xl: 16px; // reserved
|
|
329
|
+
|
|
330
|
+
// ── Layout ───────────────────────────────────────────────────────────────
|
|
331
|
+
--row-h: 56px; // table row
|
|
332
|
+
|
|
333
|
+
// ── Z-index scale ────────────────────────────────────────────────────────
|
|
334
|
+
--z-sticky: 10;
|
|
335
|
+
--z-bulkbar: 50;
|
|
336
|
+
--z-overlay: 100; // drawers, scrims
|
|
337
|
+
--z-dialog: 110; // centered dialogs / confirms
|
|
338
|
+
--z-toast: 200;
|
|
339
|
+
--z-tooltip: 1000;
|
|
340
|
+
|
|
341
|
+
// ── Shadows ──────────────────────────────────────────────────────────────
|
|
342
|
+
--shadow-card: 0 1px 2px oklch(0% 0 0deg / 4%);
|
|
343
|
+
--shadow-active: 0 1px 2px oklch(0% 0 0deg / 4%), 0 0 0 1px var(--line);
|
|
344
|
+
--shadow-bulk: 0 6px 20px oklch(0% 0 0deg / 10%);
|
|
345
|
+
--shadow-filter: 0 12px 36px oklch(0% 0 0deg / 6%);
|
|
346
|
+
--shadow-panel: var(--shadow-filter), 0 1px 0 oklch(100% 0 0deg) inset;
|
|
347
|
+
--shadow-raised: 0 1px 2px oklch(0% 0 0deg / 8%);
|
|
348
|
+
|
|
349
|
+
// DERIVED off --accent, never a copy of its hex: a hardcoded purple here
|
|
350
|
+
// would not follow a runtime accent override (FlyThemeService.applyAccent),
|
|
351
|
+
// so you would get the new accent with the old glow beside it.
|
|
352
|
+
--shadow-accent: 0 4px 12px color-mix(in srgb, var(--accent) 25%, transparent);
|
|
353
|
+
--shadow-tooltip: 0 4px 16px oklch(0% 0 0deg / 18%);
|
|
354
|
+
--shadow-focus-soft: 0 0 0 3px oklch(0% 0 0deg / 4%);
|
|
355
|
+
--shadow-drawer: -24px 0 64px oklch(0% 0 0deg / 8%); // end-side panel (LTR)
|
|
356
|
+
--shadow-drawer-rtl: 24px 0 64px oklch(0% 0 0deg / 8%); // mirrored under RTL
|
|
357
|
+
--shadow-dialog:
|
|
358
|
+
0 24px 64px oklch(18% 0.005 250deg / 18%), 0 8px 24px oklch(18% 0.005 250deg / 10%);
|
|
359
|
+
--shadow-toast:
|
|
360
|
+
0 12px 32px oklch(18% 0.005 250deg / 14%), 0 4px 12px oklch(18% 0.005 250deg / 8%);
|
|
361
|
+
|
|
362
|
+
// ── Transitions ──────────────────────────────────────────────────────────
|
|
363
|
+
--t-state: 0.12s ease;
|
|
364
|
+
--t-layout: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
365
|
+
--t-overlay: 0.18s cubic-bezier(0.2, 0.7, 0.2, 1);
|
|
366
|
+
--t-drawer: 0.32s cubic-bezier(0.32, 0.72, 0.2, 1);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ── Dark theme ───────────────────────────────────────────────────────────────
|
|
370
|
+
// Sizing, radii, type, spacing, z-index and transition tokens are theme-neutral
|
|
371
|
+
// and inherit the block above unchanged. Alias-based tokens (--success*,
|
|
372
|
+
// --on-accent-*, --state-selected, --font-*) re-resolve through their targets
|
|
373
|
+
// automatically and are NOT redeclared — a second declaration would just be a
|
|
374
|
+
// second place to get it wrong.
|
|
375
|
+
html.dark-theme {
|
|
376
|
+
// The nine platform fallbacks carry the SAME values in both themes upstream,
|
|
377
|
+
// except this one — `_theme-dark.scss` widens the blur. Redeclaring only the
|
|
378
|
+
// token that actually differs keeps the rest inheriting from the block above.
|
|
379
|
+
--glass-blur: 40px;
|
|
380
|
+
|
|
381
|
+
// ── Neutrals (inverted lightness) ────────────────────────────────────────
|
|
382
|
+
--ink: oklch(95% 0.005 250deg);
|
|
383
|
+
--ink-2: oklch(78% 0.005 250deg);
|
|
384
|
+
--ink-3: oklch(62% 0.005 250deg);
|
|
385
|
+
--ink-4: oklch(45% 0.005 250deg);
|
|
386
|
+
--ink-hover: oklch(85% 0.005 250deg);
|
|
387
|
+
|
|
388
|
+
// Flips with --ink above: the primary button is a near-white pill here, so its
|
|
389
|
+
// label goes near-black. Aliased to --bg (the darkest opaque surface in this
|
|
390
|
+
// theme) rather than repeating its literal — see the contract on the :root decl.
|
|
391
|
+
--on-ink: var(--bg);
|
|
392
|
+
--line: oklch(22% 0.004 250deg);
|
|
393
|
+
--line-2: oklch(28% 0.004 250deg);
|
|
394
|
+
--line-3: oklch(18% 0.003 250deg);
|
|
395
|
+
--bg: oklch(14% 0.003 250deg);
|
|
396
|
+
--bg-2: oklch(17% 0.003 250deg);
|
|
397
|
+
--bg-3: oklch(20% 0.004 250deg);
|
|
398
|
+
--bg-hover: oklch(24% 0.005 250deg);
|
|
399
|
+
|
|
400
|
+
// ── Brand — higher chroma/lightness to pop on dark ───────────────────────
|
|
401
|
+
--teal: oklch(78% 0.13 195deg);
|
|
402
|
+
--grad: linear-gradient(
|
|
403
|
+
135deg,
|
|
404
|
+
oklch(85% 0.12 325deg),
|
|
405
|
+
oklch(70% 0.19 305deg),
|
|
406
|
+
oklch(60% 0.2 285deg)
|
|
407
|
+
);
|
|
408
|
+
--grad-soft: linear-gradient(135deg, oklch(78% 0.13 195deg / 18%), oklch(68% 0.18 295deg / 18%));
|
|
409
|
+
|
|
410
|
+
// ── Interaction state layers — light veil on dark surfaces ───────────────
|
|
411
|
+
--state-hover: oklch(95% 0.02 285deg / 6%);
|
|
412
|
+
--state-press: oklch(95% 0.02 285deg / 11%);
|
|
413
|
+
--edge-highlight: oklch(100% 0 0deg / 6%);
|
|
414
|
+
|
|
415
|
+
// ── Status chips — dim bg, bright fg ─────────────────────────────────────
|
|
416
|
+
--st-published: oklch(85% 0.13 165deg);
|
|
417
|
+
--st-published-bg: oklch(28% 0.06 165deg);
|
|
418
|
+
--st-approved: oklch(82% 0.15 245deg);
|
|
419
|
+
--st-approved-bg: oklch(28% 0.06 245deg);
|
|
420
|
+
--st-draft: var(--ink-2); // same grey as secondary text on dark
|
|
421
|
+
--st-draft-bg: oklch(24% 0.003 250deg);
|
|
422
|
+
--st-archived: oklch(82% 0.06 60deg);
|
|
423
|
+
--st-archived-bg: oklch(28% 0.05 60deg);
|
|
424
|
+
|
|
425
|
+
// ── Shadows — deeper opacity on dark surfaces ────────────────────────────
|
|
426
|
+
--shadow-card: 0 1px 2px oklch(0% 0 0deg / 35%);
|
|
427
|
+
--shadow-active: 0 1px 2px oklch(0% 0 0deg / 40%), 0 0 0 1px var(--line);
|
|
428
|
+
--shadow-bulk: 0 6px 20px oklch(0% 0 0deg / 55%);
|
|
429
|
+
--shadow-filter: 0 12px 36px oklch(0% 0 0deg / 50%);
|
|
430
|
+
--shadow-panel: var(--shadow-filter), 0 1px 0 oklch(100% 0 0deg / 5%) inset;
|
|
431
|
+
--shadow-accent: 0 4px 12px color-mix(in srgb, var(--accent) 30%, transparent);
|
|
432
|
+
--shadow-focus-soft: 0 0 0 3px oklch(100% 0 0deg / 6%);
|
|
433
|
+
--shadow-drawer: -24px 0 64px oklch(0% 0 0deg / 50%);
|
|
434
|
+
--shadow-drawer-rtl: 24px 0 64px oklch(0% 0 0deg / 50%);
|
|
435
|
+
--shadow-dialog: 0 24px 64px oklch(0% 0 0deg / 60%), 0 8px 24px oklch(0% 0 0deg / 40%);
|
|
436
|
+
--shadow-toast: 0 12px 32px oklch(0% 0 0deg / 50%), 0 4px 12px oklch(0% 0 0deg / 30%);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ── Chrome-token bridge (STANDALONE ONLY) ────────────────────────────────────
|
|
440
|
+
// The older DS components (everything outside lib/app-surface) theme from the
|
|
441
|
+
// SHELL-CHROME family — --label-*, --surface-*, --text-color*, --separator* —
|
|
442
|
+
// and each reference carries a hardcoded literal fallback, e.g.
|
|
443
|
+
// `var(--label-primary, #1d1d1f)` or `var(--surface-card, #fff)`. 211 such
|
|
444
|
+
// fallbacks exist across the library.
|
|
445
|
+
|
|
446
|
+
// Those literals are theme-BLIND: a single constant picked by whoever wrote the
|
|
447
|
+
// component. Federated that never shows, because the desktop shell defines the
|
|
448
|
+
// whole chrome family. Standalone, no one does — so a business app got whatever
|
|
449
|
+
// constant each component happened to hardcode, in BOTH light and dark.
|
|
450
|
+
|
|
451
|
+
// Aliasing the chrome names onto the app-surface family fixes all 211 sites at
|
|
452
|
+
// once, without touching a single component: the var() resolves through the
|
|
453
|
+
// token and never reaches its literal. The targets are theme-aware, so these
|
|
454
|
+
// follow light/dark automatically.
|
|
455
|
+
|
|
456
|
+
// SELECTOR IS LOAD-BEARING: this block is `:root` ALONE — deliberately not
|
|
457
|
+
// `:root, html.light-theme` like the blocks above. The shell declares every one
|
|
458
|
+
// of these under `html.light-theme` / `html.dark-theme` (specificity 0,1,1),
|
|
459
|
+
// which beats a bare `:root` (0,1,0). So federated, the shell's chrome always
|
|
460
|
+
// wins and nothing about the desktop changes; standalone, nothing else declares
|
|
461
|
+
// them and these apply. Widening this selector would create a specificity TIE
|
|
462
|
+
// with the shell and hand the desktop's own chrome to source order.
|
|
463
|
+
:root {
|
|
464
|
+
// Text
|
|
465
|
+
--label-primary: var(--ink);
|
|
466
|
+
--label-secondary: var(--ink-3);
|
|
467
|
+
--label-tertiary: var(--ink-4);
|
|
468
|
+
--text-color: var(--ink);
|
|
469
|
+
--text-color-secondary: var(--ink-3);
|
|
470
|
+
|
|
471
|
+
// Surfaces
|
|
472
|
+
--surface-ground: var(--bg);
|
|
473
|
+
--surface-card: var(--bg-2);
|
|
474
|
+
--surface-section: var(--bg-3);
|
|
475
|
+
--surface-overlay: var(--bg-2);
|
|
476
|
+
--surface-overlay-strong: var(--bg-2);
|
|
477
|
+
|
|
478
|
+
// Lines + interaction states
|
|
479
|
+
--surface-border: var(--line);
|
|
480
|
+
--separator: var(--line);
|
|
481
|
+
--separator-primary: var(--line);
|
|
482
|
+
--surface-hover: var(--bg-hover);
|
|
483
|
+
--surface-active: var(--state-selected);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ── Plate ink, derived from the plate (UX v2, S7.1a) ─────────────────────────
|
|
487
|
+
// `--on-accent-fill` labels a SOLID `--accent-fill` plate: `fly-detail-shell`'s
|
|
488
|
+
// active tab, `badge()`'s default, `fly-pagination`'s current-page pill.
|
|
489
|
+
|
|
490
|
+
// THE FAILURE IS THE EXTERNAL-APP ACCENT, NOT THE EMBED. A fixed near-white is
|
|
491
|
+
// right for the platform violet (5.25:1) and for its `--accent-hi` partner
|
|
492
|
+
// (≈6.9:1 since the 2026-08-17 accent-family re-value; `plate-ink.spec.ts`
|
|
493
|
+
// recomputes it live), and catastrophic for an External App that remaps `--accent` to a
|
|
494
|
+
// light brand colour: white on a pale gold #F5C542 measures 1.58:1, on a cyan
|
|
495
|
+
// #22D3EE 1.76:1. Three tokens are deliberately absent from this file precisely
|
|
496
|
+
// because the accent is the consuming app's to choose (see the header), so this
|
|
497
|
+
// is not a hypothetical — it is what the seam is for. The label has to follow
|
|
498
|
+
// the plate's LUMINANCE, which means the plate is what it is computed from.
|
|
499
|
+
|
|
500
|
+
// WHAT S1.11 RECORDED HERE WAS WRONG, and the correction is the reason this is
|
|
501
|
+
// a derivation rather than the one-line repoint that note proposed. The claim
|
|
502
|
+
// was that `app-surface-on-glass` pinning `--ink-inverse` to a dark literal
|
|
503
|
+
// dragged `--on-accent-fill` (then an alias of it) down to ~2.5:1 on #9333EA.
|
|
504
|
+
// It never did. A custom property is substituted at the element that DECLARES
|
|
505
|
+
// it: `--on-accent-fill: var(--ink-inverse)` resolves at `:root`, and the
|
|
506
|
+
// resolved value is what inherits. Every shipped `app-surface-on-glass` call
|
|
507
|
+
// site is a component host far below `:root`, so it re-values `--ink-inverse`
|
|
508
|
+
// for its own subtree and cannot reach an `--on-accent-fill` that was computed
|
|
509
|
+
// above it. Measured in Chromium against the real compiled mixin: inside the
|
|
510
|
+
// mixin `--ink-inverse` is `oklch(18% 0.005 250deg)` while `--on-accent-fill`
|
|
511
|
+
// is still `oklch(99% 0 0deg)`, rendering 5.25:1 — identical to outside it.
|
|
512
|
+
// The same rule is why there is no generic `--on-plate` token; see below.
|
|
513
|
+
|
|
514
|
+
// WHY A LIVE `var()` CHAIN AND NOT A BUILD-TIME COMPUTATION. Sass cannot see
|
|
515
|
+
// the input. `--accent` is a runtime knob: `FlyThemeService.applyAccent` writes
|
|
516
|
+
// it as an INLINE style on `<html>` from tenant/user branding, and
|
|
517
|
+
// `isAccentColor` admits anything `CSS.supports('color', …)` does. Any value
|
|
518
|
+
// resolved at compile time is answering a question about a colour that does not
|
|
519
|
+
// exist yet. Same reason `--shadow-accent` is a `color-mix` and not a hex.
|
|
520
|
+
|
|
521
|
+
// MECHANISMS REJECTED.
|
|
522
|
+
// · `contrast-color(var(--accent-fill))` is CSS Color 5 and is literally this
|
|
523
|
+
// feature, but it is not yet carried widely enough to be the only rung — and
|
|
524
|
+
// it splits black/white on the same WCAG luminance the expression below
|
|
525
|
+
// computes, so it would buy a dependency rather than an answer.
|
|
526
|
+
// · `light-dark()` pairs on the THEME. An accent's lightness is independent of
|
|
527
|
+
// the theme (an app may set a pale accent in dark mode), so it answers a
|
|
528
|
+
// different question and fails on exactly the case that motivates this.
|
|
529
|
+
// · `color-mix(… var(--accent) N%, white)` scales lightness monotonically; it
|
|
530
|
+
// cannot invert polarity, so it cannot cross the light/dark accent boundary.
|
|
531
|
+
// · "External Apps override the token" moves the defect into every integrator,
|
|
532
|
+
// silently — nothing can gate an app that forgets. The token stays
|
|
533
|
+
// overridable as an escape hatch; it is not the contract.
|
|
534
|
+
|
|
535
|
+
// THE EXPRESSION. Relative colour syntax in `srgb-linear` exposes `r`/`g`/`b`
|
|
536
|
+
// as exactly the linearised channels WCAG's relative-luminance formula takes,
|
|
537
|
+
// so `0.2126r + 0.7152g + 0.0722b` IS that luminance — no approximation. An
|
|
538
|
+
// oklch-`l` split is the tempting one-channel version and it is not safe: `l`
|
|
539
|
+
// and WCAG luminance disagree by up to ~0.04 on saturated hues, which near the
|
|
540
|
+
// boundary picks the wrong pole and ships a ~3.7:1 label. `(0.1791 - Y) *
|
|
541
|
+
// infinity` clamped to 0..1 is a step function: 1 (white) below the threshold,
|
|
542
|
+
// 0 (black) above. 0.1791 is where white and black contrast a background
|
|
543
|
+
// EQUALLY, so the worst case over the entire sRGB cube is 4.58:1 — the AA floor
|
|
544
|
+
// here is arithmetic, not a review. The explicit `/ 100%` is load-bearing:
|
|
545
|
+
// relative colour syntax inherits the ORIGIN's alpha when the slot is omitted,
|
|
546
|
+
// and this file's header requires a counter-ink to be OPAQUE or it composites
|
|
547
|
+
// into its own fill. A tenant accent may legitimately carry one — `isAccentColor`
|
|
548
|
+
// admits any value `CSS.supports('color', …)` accepts.
|
|
549
|
+
|
|
550
|
+
// TWO RUNGS. Relative colour syntax is Baseline since Firefox 128 (2024-07);
|
|
551
|
+
// anything older keeps the `--on-accent` white declared above, i.e. today's
|
|
552
|
+
// platform-correct behaviour. `@supports` is what makes that a fallback at all:
|
|
553
|
+
// a custom property accepts almost any token sequence at PARSE time, so the
|
|
554
|
+
// usual declare-it-twice ladder does not work here — the later declaration
|
|
555
|
+
// always wins and the failure surfaces at the consumer as an unset `color`.
|
|
556
|
+
// The `infinity` keyword needs no rung of its own; every engine with relative
|
|
557
|
+
// colour syntax predates its own support for it.
|
|
558
|
+
|
|
559
|
+
// THERE IS NO GENERIC `--on-plate` TOKEN, and the substitution rule above is
|
|
560
|
+
// why. A plate that is not the accent — `-board`'s presence chips carry a
|
|
561
|
+
// per-user colour from `presenceColorFor`, bound inline per element — cannot be
|
|
562
|
+
// served by a `:root` token, because a `:root` declaration substitutes `:root`'s
|
|
563
|
+
// input, not the chip's. Tried and measured: with an `--on-plate` token declared
|
|
564
|
+
// here and its input set on the chip, every chip resolved to the `:root`
|
|
565
|
+
// fallback and nothing changed. A plate the DS cannot know has to be derived in the
|
|
566
|
+
// CONSUMING declaration, on the element that paints it, which is what
|
|
567
|
+
// `canvas-diagram` / `canvas-fishbone` now do. `$_plate-ink` is the one
|
|
568
|
+
// statement of the formula; `plate-ink.spec.ts` holds those copies to it, and
|
|
569
|
+
// evaluates this one at both accent poles rather than pattern-matching it.
|
|
570
|
+
|
|
571
|
+
// Both theme classes are listed rather than leaning on `:root` alone: a future
|
|
572
|
+
// redeclaration of `--on-accent-fill` inside `html.dark-theme` (0,1,1) would
|
|
573
|
+
// otherwise silently outrank a bare `:root` (0,1,0) override.
|
|
574
|
+
|
|
575
|
+
// A Sass-private string, interpolated once per channel: all three take the SAME
|
|
576
|
+
// switch, and written inline Sass would try to evaluate `0.2126 * r` as
|
|
577
|
+
// arithmetic and fail. So this is both the escape hatch and the single place
|
|
578
|
+
// the formula exists — it cannot drift between the channels or the tokens.
|
|
579
|
+
$_plate-ink: 'clamp(0, (0.1791 - (0.2126 * r + 0.7152 * g + 0.0722 * b)) * infinity, 1)';
|
|
580
|
+
|
|
581
|
+
// The probe colour is `rgb(0 0 0)`, not a hex, so the identical prelude can be
|
|
582
|
+
// copied verbatim into `design-system-board` — whose own gate bans bare colour
|
|
583
|
+
// literals anywhere in a component stylesheet, prelude and comments included.
|
|
584
|
+
@supports (color: color(from rgb(0 0 0) srgb-linear r g b)) {
|
|
585
|
+
:root,
|
|
586
|
+
html.light-theme,
|
|
587
|
+
html.dark-theme {
|
|
588
|
+
--on-accent-fill: color(
|
|
589
|
+
from var(--accent-fill) srgb-linear #{$_plate-ink} #{$_plate-ink} #{$_plate-ink} / 100%
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
}
|