@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.
@@ -0,0 +1,188 @@
1
+ // ─── Nova type roles (UX v2) ─────────────────────────────────────────────────
2
+ // Stage 1 task S1.10 of the UX-refresh program.
3
+ // Canon: `skills/desktop-design-language.md` §7, which cites
4
+ // `.workflow/plans/ux-refresh/analysis/D1-home-desktop.md` §6 for the exact
5
+ // numbers below. Never re-derive a value from the `UX/**/*.dc.html`
6
+ // prototypes — the prototypes drift, the skill is the contract.
7
+
8
+ // This partial is `@use`d exactly once, from `_fly-theme.scss` — it emits
9
+ // GLOBAL CSS (custom properties + two utility classes) and defines NO mixins.
10
+ // That is deliberate, not an omission: `_nova-motion.scss` already hit this
11
+ // exact wall (its own header warns "never `@use` it a second time" because a
12
+ // component-level `@use` re-emits its `:root` block and all 16 `@keyframes`
13
+ // into that component's own compiled stylesheet — harmless in content,
14
+ // wasteful in bytes) and `motion-panel.component.ts` had to replicate
15
+ // `nova-stagger`'s math in TypeScript rather than pay that cost. A role here
16
+ // is therefore exposed ONLY as `var(--nova-font-*)` / `var(--nova-tracking-*)`
17
+ // — plain global custom properties, read directly by any component's own
18
+ // `.scss` exactly the way `--w95` or `--font-title1` already are, with no
19
+ // `@use` of this file required or permitted a second time. Full reasoning for
20
+ // choosing tokens over mixins: `.workflow/plans/ux-refresh/notes/
21
+ // S1.10-typography.md` § "Token vs mixin".
22
+
23
+ // ── The Display/Text split ───────────────────────────────────────────────────
24
+ // The design distinguishes a Display face (headings ≥ ~13.5px bold) from Text
25
+ // (everything else), but D8 (skill §7, ratified) forbids ever shipping a
26
+ // second face for it — no SF fonts, ever. Geist is ONE family for both; the
27
+ // split rides WEIGHT and LETTER-SPACING only, never `font-family`. Proof
28
+ // point: `--nova-font-menu-item` and `--nova-font-section-title` share the
29
+ // exact same 13.5px SIZE and sit on opposite sides of the split (500/normal
30
+ // Text vs 700/-0.01em Display) — that pairing IS the split in action.
31
+
32
+ // ── Snap vs mint ─────────────────────────────────────────────────────────────
33
+ // Each role is annotated SNAP (aliases an existing `--font-*` ramp step from
34
+ // `_tokens.scss` verbatim, via `var()`, so a future ramp change propagates) or
35
+ // MINT (a new literal — no ramp step is close enough on size, line-height
36
+ // ratio, weight or letter-spacing to reuse without a visible fidelity loss).
37
+ // The full per-role reasoning lives in the handoff note; the short form
38
+ // travels here as a comment next to each declaration so the two can't drift
39
+ // apart silently.
40
+
41
+ // ── Letter-spacing rides a SEPARATE token, never the `font` shorthand ───────
42
+ // CSS `font:` cannot carry `letter-spacing` — it is not one of the shorthand's
43
+ // component properties (weight/size/line-height/family only). A role that
44
+ // needs tracking therefore gets a companion `--nova-tracking-<role>` token,
45
+ // and EVERY role gets one — even the ones the design leaves untracked
46
+ // (`normal`) — so a caller always writes the same two-line pattern regardless
47
+ // of role, instead of having to remember which roles carry tracking.
48
+
49
+ :root {
50
+ // ── Hero / marquee — bespoke, animated, never reused elsewhere ────────────
51
+ // MINT. 28px sits between --font-title1 (24px) and --font-large-title
52
+ // (29px); the closer neighbour (large-title) carries a 38/29 ≈ 1.31 line-
53
+ // height ratio against this role's specified 1.1 — snapping would make a
54
+ // compact hero headline read visibly looser than the design. Pair with the
55
+ // `greetShift` keyframe (`_nova-motion.scss`) for the animated gradient
56
+ // fill; the fill itself is the caller's concern, not baked in here.
57
+ --nova-font-page-headline: 700 28px/1.1 var(--font-family);
58
+ --nova-tracking-page-headline: -0.022em;
59
+
60
+ // MINT. Size coincides with --font-title1 (24px), but that step's 32/24 ≈
61
+ // 1.333 line-height ratio is a third looser than this role's 1.15 — the
62
+ // assistant's hero greeting would visibly loosen on snap.
63
+ --nova-font-hero-greeting: 700 24px/1.15 var(--font-family);
64
+ --nova-tracking-hero-greeting: -0.018em;
65
+
66
+ // MINT. No ramp step sits at 26px, and none pairs a semibold (600) weight
67
+ // with a sub-30px size. Colour/text-shadow (the D1 extraction notes
68
+ // "#fff + text-shadow") are the caller's concern, not part of the type role.
69
+ --nova-font-hero-card-name: 600 26px/1.14 var(--font-family);
70
+ --nova-tracking-hero-card-name: -0.01em;
71
+
72
+ // ── Chrome / structural headings (Display) ────────────────────────────────
73
+ // MINT. --font-headline (17px/700) is the nearest ramp step; its 22/17 ≈
74
+ // 1.294 line-height ratio is far looser than this role's 1.1, and the ramp
75
+ // carries no letter-spacing vocabulary at all.
76
+ --nova-font-drawer-title: 700 16px/1.1 var(--font-family);
77
+ --nova-tracking-drawer-title: -0.016em;
78
+
79
+ // MINT. Nothing in the ramp sits near 14.5px at weight 700.
80
+ --nova-font-dialog-title: 700 14.5px/1.1 var(--font-family);
81
+ --nova-tracking-dialog-title: -0.014em;
82
+
83
+ // MINT. The design gives a 500-600 WEIGHT RANGE at 14px; --font-footnote
84
+ // (13px/510) and --font-callout (15px/590) are each 1px off in opposite
85
+ // directions with no clear winner either way. Minted rather than snapped
86
+ // because this role lives in the always-visible header chrome — a singular,
87
+ // high-scrutiny use, not a repeated list primitive — so the vocabulary cost
88
+ // of one more token buys exact fidelity where it is most seen. Picked 600,
89
+ // the range's upper bound, for legibility over the translucent header
90
+ // material.
91
+ --nova-font-header-context: 600 14px/1.3 var(--font-family);
92
+ --nova-tracking-header-context: normal;
93
+
94
+ // MINT. Explicitly tagged Display in the source, with a tracking value the
95
+ // ramp has no vocabulary for at all. Shares its 13.5px SIZE with
96
+ // --nova-font-menu-item below on the opposite side of the Display/Text
97
+ // split — see the file header.
98
+ --nova-font-section-title: 700 13.5px/1.2 var(--font-family);
99
+ --nova-tracking-section-title: -0.01em;
100
+
101
+ // ── Workhorse text — repeated everywhere, snapped where fidelity allows ───
102
+ // SNAP → --font-footnote (510 13px/18px). Δsize 0.5px, Δweight 10 (510 vs
103
+ // 500) — both imperceptible. No Display tag, no tracking in the source:
104
+ // plain Text-family, unlike section-title above despite the shared 13.5px
105
+ // size.
106
+ --nova-font-menu-item: var(--font-footnote);
107
+ --nova-tracking-menu-item: normal;
108
+
109
+ // SNAP → --font-footnote (510 13px/18px, ratio 1.385). Exact size match.
110
+ // The source spec asks for weight 400, but every other body-ish ramp step
111
+ // already uses ~510 as a deliberate on-screen optical correction
112
+ // (--font-body, --font-caption1/2 all do the same) — reusing --font-footnote
113
+ // keeps body/chat/input text consistent with that platform-wide convention
114
+ // instead of introducing a lone literal-400 outlier.
115
+ --nova-font-body: var(--font-footnote);
116
+ --nova-tracking-body: normal;
117
+
118
+ // MINT. Weight 600 is a real, perceptible step up from every ramp option
119
+ // near this size (nearest is --font-caption1 at 510) — row labels / tile
120
+ // names are scannable list-and-grid primitives where that weight jump IS
121
+ // the point of the role, so snapping would flatten the exact distinction
122
+ // the design is making.
123
+ --nova-font-row-label: 600 12.5px/1.3 var(--font-family);
124
+ --nova-tracking-row-label: normal;
125
+
126
+ // MINT. Same weight-fidelity argument as row-label, reinforced by the
127
+ // skill's own field-kit section (§6) independently ratifying this exact
128
+ // 600/11.5px/`--w6` pairing as a load-bearing forms convention. Colour is
129
+ // NOT baked into this token — apply `color: var(--w6)` alongside it, the
130
+ // same as every other ramp-step colour decision.
131
+ --nova-font-field-label: 600 11.5px/1.2 var(--font-family);
132
+ --nova-tracking-field-label: normal;
133
+
134
+ // SNAP → --font-caption1 (510 12px/16px, ratio 1.333). Δsize 0.5px, Δweight
135
+ // 10, Δline-height-ratio ~6.6% against the target 1.25 — all within
136
+ // tolerance for an ancillary role.
137
+ --nova-font-tooltip: var(--font-caption1);
138
+ --nova-tracking-tooltip: normal;
139
+
140
+ // MINT. The smallest heading-adjacent role, and the ONLY one with a
141
+ // POSITIVE tracking direction in the whole table (the ramp has no
142
+ // letter-spacing vocabulary to snap to regardless). Snapped UP to 11px
143
+ // rather than the source's 10.5px floor: skill §7's de-facto size floor
144
+ // reserves 10.5px for the caption-tier exception (timestamps, below), not
145
+ // eyebrows — 11px is the honest reading of "de-facto floor 11px". Tracking
146
+ // is the midpoint of the source's 0.02-0.04em range. Colour defaults to
147
+ // `--w42`; the source also names `--w4` as a context-dependent alternate
148
+ // with no rule for choosing between them, so that choice stays with the
149
+ // caller rather than being baked in here.
150
+ --nova-font-eyebrow: 600 11px/1.3 var(--font-family);
151
+ --nova-tracking-eyebrow: 0.03em;
152
+
153
+ // MINT. The one sanctioned exception to the 11px floor (skill §7:
154
+ // "10.5px caption-only"). Base weight 400; the source's 400-700 range
155
+ // covers a bold badge-count variant, applied by the caller with a local
156
+ // `font-weight: 700` override rather than a second minted role, since size,
157
+ // tracking and family are identical either way.
158
+ --nova-font-timestamp: 400 10.5px/1.3 var(--font-family);
159
+ --nova-tracking-timestamp: normal;
160
+ }
161
+
162
+ // ── Rule A: tabular numerals (skill §7, mechanical) ─────────────────────────
163
+ // "font-variant-numeric: tabular-nums on every count, timestamp and pagination
164
+ // number." `_app-surface-utilities.scss` already ships `.mono` bundling this
165
+ // same property with a switch to the monospace family; `.nova-tabular-nums`
166
+ // is the tabular-nums-ONLY half, for the proportional-font contexts these
167
+ // roles live in (a row count sitting inside --nova-font-row-label, a
168
+ // timestamp inside --nova-font-timestamp) where switching to Geist Mono is
169
+ // not wanted. One class is the one-liner the rule calls for.
170
+ // Reviewer check: grep the diff for a numeric interpolation — a count binding
171
+ // (`{{ …Count }}`), a date/time pipe, a "page N of M" string — with no
172
+ // `nova-tabular-nums` class alongside it in the same template. That gap IS
173
+ // the violation; there is no way to detect it from the SCSS side alone, since
174
+ // the rule is about which ELEMENTS carry the class, not whether the class
175
+ // itself exists (`nova-type.spec.ts` only guards the latter).
176
+ .nova-tabular-nums {
177
+ font-variant-numeric: tabular-nums;
178
+ }
179
+
180
+ // ── Rule B: RTL runs keep embedded Latin its own direction (skill §7) ──────
181
+ // "RTL text runs carry unicode-bidi: plaintext so embedded English keeps its
182
+ // own direction." Same one-class mechanism and the same reviewer check: grep
183
+ // the diff for a translated or mixed-script string (a product name, an email
184
+ // address, a file extension embedded in Arabic/Urdu copy) with no
185
+ // `nova-bidi-plaintext` class in the same template.
186
+ .nova-bidi-plaintext {
187
+ unicode-bidi: plaintext;
188
+ }
@@ -1,81 +1,113 @@
1
- // ─── Shell-embed bridge — app-surface tokens re-tinted onto shell glass ──────
2
- // MIXIN ONLY. This partial emits no CSS on import; it exists so the mapping
3
- // below lives in ONE place instead of being retyped per feature app.
4
- //
5
- // WHY IT IS NEEDED
6
- // The DS carries two token families on purpose (see `_app-surface-tokens.scss`'s
7
- // header). `--ink-*`/`--bg-*`/`--line-*` dress BUSINESS-APP CONTENT: an opaque
8
- // reading surface, dark ink on white in light mode. `--label-*`/`--surface-*`
9
- // dress SHELL CHROME: white ink on translucent glass over the user's wallpaper.
10
- //
11
- // A desktop-shell feature app renders inside a glass window, so its chrome is
12
- // necessarily the second family — but the moment it mounts a component from the
13
- // `fly-*` app-surface kit (or `@flyos/design-system-board`), that component
14
- // themes itself from the FIRST family and paints an opaque white slab with
15
- // near-black ink. One region, two families: the app's own header ink is white,
16
- // the slab beneath it is white, and the header goes invisible.
17
- //
18
- // That is not hypothetical. It shipped in canvas-boards and was measured live:
19
- // `.cbe__export-btn` computed `color: rgb(255,255,255)` sitting on a board whose
20
- // `--_surface` resolved to `oklch(100% 0 0deg)`. `core/theme/feature-app-surface-tokens.ts`
21
- // documents the same class of trap ("a dark-mode-only review passes while light
22
- // mode renders a white slab") — and note the failure is LIGHT-mode-only, because
23
- // in dark mode both families happen to agree on light-ink-on-dark.
24
- //
25
- // THE FIX is the seam the tokens file already names: an app that embeds in the
26
- // shell re-tints the content family onto glass. help-center (`.hc-shell`) and
27
- // dashboard-app hand-rolled identical copies of this map before it lived here.
28
- //
29
- // ALIASES ONLY — every value is another token, never a literal, so the whole map
30
- // follows the shell's theme automatically. The ONE exception is `--on-ink`, and
31
- // its reason is written at the declaration.
32
- @mixin app-surface-on-glass {
33
- // Surfaces: the window chrome supplies the material, so the app's own page
34
- // background is nothing at all; cards/tracks are lifts ON that plate.
35
- --bg: transparent;
36
- --bg-2: var(--surface-card);
37
- --bg-3: var(--fill-tertiary);
38
- --bg-hover: var(--surface-active);
39
-
40
- // Hairlines.
41
- --line: var(--separator);
42
- --line-2: var(--surface-border);
43
- --line-3: var(--separator);
44
-
45
- // Ink — opaque label tokens, never a surface token. Aliasing a translucent
46
- // surface onto an ink slot is what turns a control into an empty coloured
47
- // square (see the note in help-center.component.scss).
48
- --ink: var(--label-primary);
49
- --ink-2: var(--label-secondary);
50
- --ink-3: var(--label-secondary);
51
- --ink-4: var(--label-tertiary);
52
-
53
- // CONTRACT (`_app-surface-tokens.scss`): anything that redefines `--ink` MUST
54
- // redefine `--on-ink`, and it must be OPAQUE. `--on-ink` labels a fill of
55
- // `--ink` itself — the primary button. Above we pin `--ink` to the shell's
56
- // white label ink in BOTH themes, so its counter-ink is a fixed DARK value in
57
- // both. It cannot alias a shell token: the shell is glass and owns no
58
- // dark-opaque ink. This literal is the same value `--ink` carries in the
59
- // app-surface light theme, i.e. the colour a white pill is designed to label.
60
- //
61
- // `app-surface-conventions.spec.ts` can only police the DS token file itself;
62
- // its doc says consumers that remap `--ink` "carry the same obligation".
63
- // Routing every consumer through this mixin is how that obligation is met.
64
- --on-ink: oklch(18% 0.005 250deg);
65
-
66
- // `--ink-inverse` carries the SAME obligation as `--on-ink` and was missed.
67
- //
68
- // The app-surface family defines it once, as "text on dark / gradient fills" — near-white, which
69
- // is correct while `--ink` is near-black. This mixin flips `--ink` to the shell's WHITE label ink
70
- // and updated `--on-ink` accordingly, but left `--ink-inverse` at its near-white resting value.
71
- // Every `background: var(--ink); color: var(--ink-inverse)` pair inside an embedded app therefore
72
- // became WHITE ON WHITE at a contrast ratio of about 1:1.
73
- //
74
- // That is not hypothetical: it is what made `[data-tooltip]` (the CSS tooltip behind every
75
- // `fly-icon-button`'s `tooltipKey`) invisible across the board editor measured live at
76
- // background rgb(255,255,255) with colour oklch(0.99 0 0). The same pair is used by the button,
77
- // icon-button, checkbox and card components, so this was never a Boards-only bug.
78
- //
79
- // Pinned to `--on-ink` rather than restated, so the two counter-inks cannot drift apart again.
80
- --ink-inverse: var(--on-ink);
81
- }
1
+ // ─── Shell-embed bridge — app-surface tokens re-tinted onto shell glass ──────
2
+ // MIXIN ONLY. This partial emits no CSS on import; it exists so the mapping
3
+ // below lives in ONE place instead of being retyped per feature app.
4
+ //
5
+ // WHY IT IS NEEDED
6
+ // The DS carries two token families on purpose (see `_app-surface-tokens.scss`'s
7
+ // header). `--ink-*`/`--bg-*`/`--line-*` dress BUSINESS-APP CONTENT: an opaque
8
+ // reading surface, dark ink on white in light mode. `--label-*`/`--surface-*`
9
+ // dress SHELL CHROME: white ink on translucent glass over the user's wallpaper.
10
+ //
11
+ // A desktop-shell feature app renders inside a glass window, so its chrome is
12
+ // necessarily the second family — but the moment it mounts a component from the
13
+ // `fly-*` app-surface kit (or `@flyos/design-system-board`), that component
14
+ // themes itself from the FIRST family and paints an opaque white slab with
15
+ // near-black ink. One region, two families: the app's own header ink is white,
16
+ // the slab beneath it is white, and the header goes invisible.
17
+ //
18
+ // That is not hypothetical. It shipped in canvas-boards and was measured live:
19
+ // `.cbe__export-btn` computed `color: rgb(255,255,255)` sitting on a board whose
20
+ // `--_surface` resolved to `oklch(100% 0 0deg)`. `core/theme/feature-app-surface-tokens.ts`
21
+ // documents the same class of trap ("a dark-mode-only review passes while light
22
+ // mode renders a white slab") — and note the failure is LIGHT-mode-only, because
23
+ // in dark mode both families happen to agree on light-ink-on-dark.
24
+ //
25
+ // THE FIX is the seam the tokens file already names: an app that embeds in the
26
+ // shell re-tints the content family onto glass. help-center (`.hc-shell`) and
27
+ // dashboard-app hand-rolled identical copies of this map before it lived here.
28
+ //
29
+ // ALIASES ONLY — every value is another token, never a literal, so the whole map
30
+ // follows the shell's theme automatically. The ONE exception is `--on-ink`, and
31
+ // its reason is written at the declaration.
32
+ @mixin app-surface-on-glass {
33
+ // Surfaces: the window chrome supplies the material, so the app's own page
34
+ // background is nothing at all; cards/tracks are lifts ON that plate.
35
+ --bg: transparent;
36
+ --bg-2: var(--surface-card);
37
+ --bg-3: var(--fill-tertiary);
38
+ --bg-hover: var(--surface-active);
39
+
40
+ // Hairlines.
41
+ --line: var(--separator);
42
+ --line-2: var(--surface-border);
43
+ --line-3: var(--separator);
44
+
45
+ // Ink — opaque label tokens, never a surface token. Aliasing a translucent
46
+ // surface onto an ink slot is what turns a control into an empty coloured
47
+ // square (see the note in help-center.component.scss).
48
+ --ink: var(--label-primary);
49
+ --ink-2: var(--label-secondary);
50
+ --ink-3: var(--label-secondary);
51
+ --ink-4: var(--label-tertiary);
52
+
53
+ // CONTRACT (`_app-surface-tokens.scss`): anything that redefines `--ink` MUST
54
+ // redefine `--on-ink`, and it must be OPAQUE. `--on-ink` labels a fill of
55
+ // `--ink` itself — the primary button. Above we pin `--ink` to the shell's
56
+ // white label ink in BOTH themes, so its counter-ink is a fixed DARK value in
57
+ // both. It cannot alias a shell token: the shell is glass and owns no
58
+ // dark-opaque ink. This literal is the same value `--ink` carries in the
59
+ // app-surface light theme, i.e. the colour a white pill is designed to label.
60
+ //
61
+ // `app-surface-conventions.spec.ts` can only police the DS token file itself;
62
+ // its doc says consumers that remap `--ink` "carry the same obligation".
63
+ // Routing every consumer through this mixin is how that obligation is met.
64
+ // (`app-surface-nova-bridge.spec.ts` extends that gate to THIS file.)
65
+
66
+ // AT THE DS 2.0 FLIP THIS LINE MUST CHANGE — it is the one value in this mixin
67
+ // that does not follow the shell automatically. The premise above ("`--ink` is
68
+ // the shell's white label ink in BOTH themes") holds only while light chrome is
69
+ // the vibrancy smoked plate. Nova's light chrome is genuinely light with
70
+ // DARK ink (`--chrome-ink: #353537`; design `--ink` light `#000`, folded into
71
+ // `--label-primary` per D1), so post-flip `--ink` flips per theme here too and
72
+ // a single dark literal is wrong in light mode the same white-slab failure,
73
+ // arriving from the opposite side.
74
+
75
+ // The flip value is `var(--check-ink)`: Nova's only counter-ink that flips the
76
+ // right way (`#fff` light, `#0f1013` dark), which is exactly "the ink on a
77
+ // plate that carries the theme's own primary ink". Do NOT reach for a `--w*`
78
+ // step — the ramp flips polarity (`--w95` is 95% black in light, 95% WHITE in
79
+ // dark), so no step is a counter-ink in both themes; and do NOT reach for
80
+ // `--on-accent`, which is `#fff` in both and therefore right only in light.
81
+ --on-ink: oklch(18% 0.005 250deg);
82
+
83
+ // `--ink-inverse` carries the SAME obligation as `--on-ink` and was missed.
84
+ //
85
+ // The app-surface family defines it once, as "text on dark / gradient fills" — near-white, which
86
+ // is correct while `--ink` is near-black. This mixin flips `--ink` to the shell's WHITE label ink
87
+ // and updated `--on-ink` accordingly, but left `--ink-inverse` at its near-white resting value.
88
+ // Every `background: var(--ink); color: var(--ink-inverse)` pair inside an embedded app therefore
89
+ // became WHITE ON WHITE at a contrast ratio of about 1:1.
90
+ //
91
+ // That is not hypothetical: it is what made `[data-tooltip]` (the CSS tooltip behind every
92
+ // `fly-icon-button`'s `tooltipKey`) invisible across the board editor — measured live at
93
+ // background rgb(255,255,255) with colour oklch(0.99 0 0). The same pair is used by the button,
94
+ // icon-button, checkbox and card components, so this was never a Boards-only bug.
95
+ //
96
+ // Pinned to `--on-ink` rather than restated, so the two counter-inks cannot drift apart again.
97
+
98
+ // KNOWN COLLATERAL, recorded not fixed (S1.11 — the fix is a token re-value, and DS 1.x is
99
+ // additive-only). `_app-surface-tokens.scss` aliases `--on-accent-fill: var(--ink-inverse)`,
100
+ // so this pin also re-points the label on a SOLID ACCENT plate from near-white to near-black
101
+ // wherever the mixin applies. That contradicts the guarantee its header gives — "--accent-fill
102
+ // is a SOLID plate we own outright, so its ink is knowable regardless of backdrop — always
103
+ // white … their contrast cannot be broken by a host token override" — and this mixin is such
104
+ // an override. Live example inside the DS itself: `detail-shell.component.scss` paints
105
+ // `.ds__tab--active` as `background: var(--accent-fill); color: var(--on-accent-fill)`, which
106
+ // under the bridge is oklch(18%) ink on `#9333EA`, roughly 2.5:1. Also reached by
107
+ // `_mixins.scss`'s `badge()` default and `-board`'s canvas-diagram / canvas-fishbone.
108
+
109
+ // The 2.0 fix is one line in `_app-surface-tokens.scss` — `--on-accent-fill: var(--on-accent)`,
110
+ // Nova's theme-invariant `#fff` — which also makes the pair immune to any future host remap of
111
+ // `--ink-inverse`, rather than immune only to the remaps that exist today.
112
+ --ink-inverse: var(--on-ink);
113
+ }
@@ -1,6 +1,6 @@
1
1
  // ─── Dark theme (`html.dark-theme`, persisted `dark`) ────────────────────────
2
2
  // Shell Appearance label "Dark" / i18n `settings.theme.dark`.
3
- // VisionOS-style translucent dark glass: semi-transparent surfaces + backdrop blur.
3
+ // Translucent dark glass: semi-transparent surfaces + backdrop blur.
4
4
  html.dark-theme {
5
5
  /* Native selects / scrollbars follow OS form appearance; keep in sync with light-on-glass labels. */
6
6
  color-scheme: dark;
@@ -181,7 +181,7 @@ html.light-theme {
181
181
  --sidebar-radius: 28px;
182
182
  --focus-ring: color-mix(in srgb, var(--accent) 50%, transparent);
183
183
 
184
- // Segmented `.vos-btn.selected` / `.active` (Settings theme row, clock, weather, etc.): dark pill on light UI.
184
+ // Segmented `.fos-btn.selected` / `.active` (Settings theme row, clock, weather, etc.): dark pill on light UI.
185
185
  --btn-selected-bg: #111827;
186
186
  --btn-selected-text: #fff;
187
187
 
@@ -207,7 +207,7 @@ html.light-theme {
207
207
  --material-glass-specular: inset 0 1px 0 rgb(255 255 255 / 60%),
208
208
  inset 0 -1px 0 rgb(17 24 39 / 4%);
209
209
 
210
- // Aliases used by some Business App SCSS (not part of core visionOS tokens).
210
+ // Aliases used by some Business App SCSS (not part of core FlyOS tokens).
211
211
  --fly-color-surface: #fff;
212
212
  --fly-color-surface-alt: #f3f4f6;
213
213
  --fly-color-border: #e5e7eb;
package/scss/_tokens.scss CHANGED
@@ -1,4 +1,4 @@
1
- // ─── visionOS System Colors & Design Tokens ─────────────────────────────────
1
+ // ─── FlyOS System Colors & Design Tokens ─────────────────────────────────
2
2
  :root {
3
3
  --system-red: #FF453A;
4
4
  --system-orange: #FF9F0A;
@@ -21,7 +21,7 @@
21
21
  --status-info: var(--system-blue);
22
22
  --status-pending: var(--system-yellow);
23
23
 
24
- // ── visionOS Typography ──
24
+ // ── FlyOS Typography ──
25
25
  // Geist is the primary face (self-hosted, see styles/_geist-fonts.scss).
26
26
  // SF Pro / Inter / system fonts remain as fallbacks; the cascade also picks
27
27
  // them up for scripts Geist doesn't cover (ar / ur).
@@ -41,12 +41,12 @@
41
41
  --font-caption1: 510 12px/16px var(--font-family);
42
42
  --font-caption2: 510 12px/16px var(--font-family);
43
43
 
44
- // ── visionOS Label Colors ──
44
+ // ── FlyOS Label Colors ──
45
45
  --label-primary: rgb(255 255 255 / 96%);
46
46
  --label-secondary: rgb(255 255 255 / 56%);
47
47
  --label-tertiary: rgb(255 255 255 / 36%);
48
48
 
49
- // ── visionOS Materials ──
49
+ // ── FlyOS Materials ──
50
50
  --material-glass: rgb(128 128 128 / 30%);
51
51
  --material-glass-blur: 50px;
52
52
  --material-glass-stroke: rgb(255 255 255 / 40%);
@@ -70,7 +70,7 @@
70
70
  --separator: rgb(255 255 255 / 12%);
71
71
  --blur-shadow-small: 0 2px 4px rgb(0 0 0 / 10%);
72
72
 
73
- // ── visionOS Button Tokens ──
73
+ // ── FlyOS Button Tokens ──
74
74
  --btn-font-weight: 590;
75
75
  --btn-symbol-font-weight: 510;
76
76
  --btn-radius-capsule: 500px;