@flyos/design-system 1.6.0 → 1.8.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 +2787 -413
- package/fesm2022/flyos-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/scss/_app-surface-tokens.scss +101 -0
- package/scss/_app-surface-utilities.scss +7 -2
- package/scss/_business-app-buttons.scss +35 -8
- package/scss/_fly-theme.scss +9 -0
- package/scss/{_vos-button-mixins.scss → _fos-button-mixins.scss} +28 -28
- package/scss/_ink-baseline.scss +29 -29
- package/scss/_nova-glass.scss +427 -0
- package/scss/_nova-motion-mixins.scss +42 -0
- package/scss/_nova-motion.scss +324 -0
- package/scss/_nova-tokens.scss +295 -0
- package/scss/_nova-type.scss +188 -0
- package/scss/_shell-embed-bridge.scss +113 -81
- package/scss/_theme-dark.scss +1 -1
- package/scss/_theme-light.scss +2 -2
- package/scss/_tokens.scss +5 -5
- package/types/flyos-design-system.d.ts +1643 -49
- package/types/flyos-design-system.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -47,6 +47,107 @@
|
|
|
47
47
|
// `_fly-theme.scss`, so `@use 'fly-theme'` is all a consumer needs.
|
|
48
48
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
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
|
+
|
|
50
151
|
:root,
|
|
51
152
|
html.light-theme {
|
|
52
153
|
// ── Neutrals — text ──────────────────────────────────────────────────────
|
|
@@ -27,13 +27,18 @@
|
|
|
27
27
|
// plate ~47px wide) cut every tool's tip off inside its own column, and no z-index could
|
|
28
28
|
// help — clipping is not a stacking question, and `position: fixed` on the pseudo would
|
|
29
29
|
// still be trapped by those plates' `backdrop-filter` containing block.
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
// `FlyTooltipDirective` now owns the surface for BOTH `[flyTooltip]` and `[data-tooltip]`:
|
|
32
32
|
// a `position: fixed` node parented to `<body>`, so it escapes every clip, flips placement
|
|
33
33
|
// and clamps to the viewport. Components that write `data-tooltip` imperatively (the DS
|
|
34
34
|
// icon button) list the directive in `hostDirectives`; templates with a static or bound
|
|
35
35
|
// `data-tooltip` must IMPORT `FlyTooltipDirective` for the label to appear.
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
// The surface itself (Nova's `--mat-tip-a`/`-b` material, opaque, no blur) lives entirely
|
|
38
|
+
// in the directive's injected `<style>` — see `fly-tooltip.directive.ts` for the recipe and
|
|
39
|
+
// why it is deliberately NOT a `glass.popover()` thin-blur surface (S1.8). Nothing here
|
|
40
|
+
// draws a tooltip; this selector only keeps the positioning context callers relied on.
|
|
41
|
+
|
|
37
42
|
// Only the positioning context the attribute implied is kept, since callers may still hang
|
|
38
43
|
// their own absolutely-positioned adornments (badges, dots) off a tooltip'd control.
|
|
39
44
|
[data-tooltip] {
|
|
@@ -1,9 +1,32 @@
|
|
|
1
|
-
@use '
|
|
1
|
+
@use 'fos-button-mixins' as *;
|
|
2
2
|
|
|
3
|
-
// ───
|
|
3
|
+
// ─── FlyOS Button Utility Classes (shared with the desktop shell) ─────────────
|
|
4
4
|
// Requires @use 'fly-theme' (or equivalent tokens) before this partial.
|
|
5
5
|
|
|
6
|
+
// ## The `.vos-` aliases are a deprecation window, not a style
|
|
7
|
+
|
|
8
|
+
// These three classes are the only PUBLISHED part of the old `vos-` prefix: External
|
|
9
|
+
// Apps write them in their own templates (measured: Circles authors `vos-btn` x21 and
|
|
10
|
+
// `vos-link` x4 across 6 files; Thoughts and PPM use none). Removing the old spelling
|
|
11
|
+
// is therefore a breaking change to this package's CSS surface — and a silent one,
|
|
12
|
+
// because a class that stops matching is not a build error in either repo, it simply
|
|
13
|
+
// renders unstyled.
|
|
14
|
+
|
|
15
|
+
// **And no gate would have caught it.** ds-compat extracts four things — the TS export
|
|
16
|
+
// surface, CSS CUSTOM PROPERTIES, CSS layers, and peer floors. It has no class-name
|
|
17
|
+
// extractor, so a renamed class is invisible to it: dropping `.vos-btn` outright still
|
|
18
|
+
// reports "federation-safe". The gate protects the token surface, not the class surface,
|
|
19
|
+
// even though the class surface is the one External-App TEMPLATES are written against.
|
|
20
|
+
|
|
21
|
+
// So `fos-` is canonical and `vos-` rides along as an alias, and the alias is doing the
|
|
22
|
+
// work the gate cannot.
|
|
23
|
+
|
|
24
|
+
// **Delete the `.vos-` halves at DS 2.0 (plan item S6.1b) — and only AFTER S6.2 lands
|
|
25
|
+
// the Circles migration.** Removing them first breaks Circles with no diagnostic.
|
|
26
|
+
// Verify against a grep of the Circles worktree, not of this repo.
|
|
27
|
+
|
|
6
28
|
@layer designsystem {
|
|
29
|
+
.fos-btn,
|
|
7
30
|
.vos-btn {
|
|
8
31
|
position: relative;
|
|
9
32
|
display: inline-flex;
|
|
@@ -60,11 +83,11 @@
|
|
|
60
83
|
}
|
|
61
84
|
|
|
62
85
|
&.platter::before {
|
|
63
|
-
@include
|
|
86
|
+
@include fos-platter-layers(inherit);
|
|
64
87
|
}
|
|
65
88
|
|
|
66
89
|
&:hover:not(:disabled, .selected, .active)::before {
|
|
67
|
-
@include
|
|
90
|
+
@include fos-platter-layers(inherit);
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
&.selected,
|
|
@@ -81,7 +104,7 @@
|
|
|
81
104
|
pointer-events: none;
|
|
82
105
|
|
|
83
106
|
&::before {
|
|
84
|
-
@include
|
|
107
|
+
@include fos-disabled-layers(inherit);
|
|
85
108
|
}
|
|
86
109
|
}
|
|
87
110
|
|
|
@@ -104,6 +127,8 @@
|
|
|
104
127
|
}
|
|
105
128
|
}
|
|
106
129
|
|
|
130
|
+
// `.vos-btn-symbol` alias: see the deprecation note at the top of this file.
|
|
131
|
+
.fos-btn-symbol,
|
|
107
132
|
.vos-btn-symbol {
|
|
108
133
|
position: relative;
|
|
109
134
|
display: inline-flex;
|
|
@@ -152,11 +177,11 @@
|
|
|
152
177
|
}
|
|
153
178
|
|
|
154
179
|
&.platter::before {
|
|
155
|
-
@include
|
|
180
|
+
@include fos-platter-layers(500px);
|
|
156
181
|
}
|
|
157
182
|
|
|
158
183
|
&:hover:not(:disabled, .selected, .active)::before {
|
|
159
|
-
@include
|
|
184
|
+
@include fos-platter-layers(500px);
|
|
160
185
|
}
|
|
161
186
|
|
|
162
187
|
&.selected,
|
|
@@ -173,7 +198,7 @@
|
|
|
173
198
|
pointer-events: none;
|
|
174
199
|
|
|
175
200
|
&::before {
|
|
176
|
-
@include
|
|
201
|
+
@include fos-disabled-layers(500px);
|
|
177
202
|
}
|
|
178
203
|
}
|
|
179
204
|
|
|
@@ -185,6 +210,8 @@
|
|
|
185
210
|
}
|
|
186
211
|
}
|
|
187
212
|
|
|
213
|
+
// `.vos-link` alias: see the deprecation note at the top of this file.
|
|
214
|
+
.fos-link,
|
|
188
215
|
.vos-link {
|
|
189
216
|
display: inline-flex;
|
|
190
217
|
align-items: center;
|
package/scss/_fly-theme.scss
CHANGED
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
@use 'theme-light';
|
|
7
7
|
@use 'theme-dark';
|
|
8
8
|
|
|
9
|
+
// ── Nova (UX v2) design language ─────────────────────────────────────────────
|
|
10
|
+
// Additive layer: new vocabulary only, nothing here re-values a token the three
|
|
11
|
+
// partials above already define (the colliding glass quartet is renamed
|
|
12
|
+
// `--glass2-*` per the D1 ruling). Canon: `skills/desktop-design-language.md`.
|
|
13
|
+
// `nova-glass` is mixins-only and is @used by components, not from here.
|
|
14
|
+
@use 'nova-tokens';
|
|
15
|
+
@use 'nova-motion';
|
|
16
|
+
@use 'nova-type';
|
|
17
|
+
|
|
9
18
|
// The inherited `color` for themed documents. Emits a rule, not tokens, so it
|
|
10
19
|
// lives apart from the theme partials — see its header for why the absence of a
|
|
11
20
|
// baseline was the root cause of the recurring black-ink-on-glass bug.
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Kept separate from desktop-app glass mixins so Business Apps only pull button-related SCSS.
|
|
3
|
-
|
|
4
|
-
@mixin
|
|
5
|
-
content: '';
|
|
6
|
-
position: absolute;
|
|
7
|
-
inset: 0;
|
|
8
|
-
border-radius: $radius;
|
|
9
|
-
pointer-events: none;
|
|
10
|
-
background:
|
|
11
|
-
linear-gradient(var(--btn-platter-lighten), var(--btn-platter-lighten)),
|
|
12
|
-
linear-gradient(var(--btn-platter-dodge), var(--btn-platter-dodge));
|
|
13
|
-
background-blend-mode: lighten, color-dodge;
|
|
14
|
-
mix-blend-mode: screen;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@mixin
|
|
18
|
-
content: '';
|
|
19
|
-
position: absolute;
|
|
20
|
-
inset: 0;
|
|
21
|
-
border-radius: $radius;
|
|
22
|
-
pointer-events: none;
|
|
23
|
-
background:
|
|
24
|
-
linear-gradient(var(--btn-disabled-lighten), var(--btn-disabled-lighten)),
|
|
25
|
-
linear-gradient(var(--btn-disabled-dodge), var(--btn-disabled-dodge));
|
|
26
|
-
background-blend-mode: lighten, color-dodge;
|
|
27
|
-
mix-blend-mode: screen;
|
|
28
|
-
}
|
|
1
|
+
// FlyOS button pseudo-element layers (used by _business-app-buttons.scss).
|
|
2
|
+
// Kept separate from desktop-app glass mixins so Business Apps only pull button-related SCSS.
|
|
3
|
+
|
|
4
|
+
@mixin fos-platter-layers($radius) {
|
|
5
|
+
content: '';
|
|
6
|
+
position: absolute;
|
|
7
|
+
inset: 0;
|
|
8
|
+
border-radius: $radius;
|
|
9
|
+
pointer-events: none;
|
|
10
|
+
background:
|
|
11
|
+
linear-gradient(var(--btn-platter-lighten), var(--btn-platter-lighten)),
|
|
12
|
+
linear-gradient(var(--btn-platter-dodge), var(--btn-platter-dodge));
|
|
13
|
+
background-blend-mode: lighten, color-dodge;
|
|
14
|
+
mix-blend-mode: screen;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@mixin fos-disabled-layers($radius) {
|
|
18
|
+
content: '';
|
|
19
|
+
position: absolute;
|
|
20
|
+
inset: 0;
|
|
21
|
+
border-radius: $radius;
|
|
22
|
+
pointer-events: none;
|
|
23
|
+
background:
|
|
24
|
+
linear-gradient(var(--btn-disabled-lighten), var(--btn-disabled-lighten)),
|
|
25
|
+
linear-gradient(var(--btn-disabled-dodge), var(--btn-disabled-dodge));
|
|
26
|
+
background-blend-mode: lighten, color-dodge;
|
|
27
|
+
mix-blend-mode: screen;
|
|
28
|
+
}
|
package/scss/_ink-baseline.scss
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
// ─── Inherited-ink baseline ──────────────────────────────────────────────────
|
|
2
|
-
//
|
|
3
|
-
// Establishes the document's inherited `color`. Until this existed, NOTHING in
|
|
4
|
-
// the shell ever set `color` on `html` or `body` — `_theme-light.scss` and
|
|
5
|
-
// `_theme-dark.scss` declare custom properties only. So the inherited colour was
|
|
6
|
-
// the user-agent default, **black**, and every component that says
|
|
7
|
-
// `color: inherit` (fly-tree-nav rows, fly-breadcrumb's current crumb,
|
|
8
|
-
// fly-search-input's typed text, fly-card's meta slot, and any consumer markup
|
|
9
|
-
// that simply doesn't mention colour) rendered black ink on the smoked glass.
|
|
10
|
-
//
|
|
11
|
-
// That is why the "text is invisible in light theme" bug kept coming back app
|
|
12
|
-
// after app: it was never really an app's mistake. There was no baseline to
|
|
13
|
-
// inherit, so *forgetting* to set a colour was the failure mode, and the fix
|
|
14
|
-
// kept being applied one component at a time. `--text-color` is white in BOTH
|
|
15
|
-
// themes since the
|
|
16
|
-
//
|
|
17
|
-
// Scoped to the two theme classes on purpose — NOT `:root`. A business app
|
|
18
|
-
// running STANDALONE renders on its own opaque paper (`--bg`, near-black
|
|
19
|
-
// `--ink`) and does not load `fly-theme` at all; keying on the class means a
|
|
20
|
-
// surface that never opted into a shell theme is never repainted white.
|
|
21
|
-
//
|
|
22
|
-
// A deliberate paper surface inside the shell (the documents editor) still
|
|
23
|
-
// brings its own dark ink locally — a component rule beats this baseline, which
|
|
24
|
-
// is exactly the intended precedence.
|
|
25
|
-
|
|
26
|
-
html.light-theme,
|
|
27
|
-
html.dark-theme {
|
|
28
|
-
color: var(--text-color);
|
|
29
|
-
}
|
|
1
|
+
// ─── Inherited-ink baseline ──────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Establishes the document's inherited `color`. Until this existed, NOTHING in
|
|
4
|
+
// the shell ever set `color` on `html` or `body` — `_theme-light.scss` and
|
|
5
|
+
// `_theme-dark.scss` declare custom properties only. So the inherited colour was
|
|
6
|
+
// the user-agent default, **black**, and every component that says
|
|
7
|
+
// `color: inherit` (fly-tree-nav rows, fly-breadcrumb's current crumb,
|
|
8
|
+
// fly-search-input's typed text, fly-card's meta slot, and any consumer markup
|
|
9
|
+
// that simply doesn't mention colour) rendered black ink on the smoked glass.
|
|
10
|
+
//
|
|
11
|
+
// That is why the "text is invisible in light theme" bug kept coming back app
|
|
12
|
+
// after app: it was never really an app's mistake. There was no baseline to
|
|
13
|
+
// inherit, so *forgetting* to set a colour was the failure mode, and the fix
|
|
14
|
+
// kept being applied one component at a time. `--text-color` is white in BOTH
|
|
15
|
+
// themes since the vibrancy flip, so one declaration settles it everywhere.
|
|
16
|
+
//
|
|
17
|
+
// Scoped to the two theme classes on purpose — NOT `:root`. A business app
|
|
18
|
+
// running STANDALONE renders on its own opaque paper (`--bg`, near-black
|
|
19
|
+
// `--ink`) and does not load `fly-theme` at all; keying on the class means a
|
|
20
|
+
// surface that never opted into a shell theme is never repainted white.
|
|
21
|
+
//
|
|
22
|
+
// A deliberate paper surface inside the shell (the documents editor) still
|
|
23
|
+
// brings its own dark ink locally — a component rule beats this baseline, which
|
|
24
|
+
// is exactly the intended precedence.
|
|
25
|
+
|
|
26
|
+
html.light-theme,
|
|
27
|
+
html.dark-theme {
|
|
28
|
+
color: var(--text-color);
|
|
29
|
+
}
|