@flyos/design-system 3.3.0 → 3.5.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 +1131 -310
- package/fesm2022/flyos-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/scss/_fly-theme.scss +5 -0
- package/scss/_nova-tokens.scss +22 -0
- package/scss/_nova-type.scss +51 -12
- package/scss/_sf-arabic-font.scss +32 -0
- package/scss/_tokens.scss +56 -20
- package/types/flyos-design-system.d.ts +413 -63
- package/types/flyos-design-system.d.ts.map +1 -1
package/package.json
CHANGED
package/scss/_fly-theme.scss
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
// stylePreprocessorOptions.includePaths → design-system src folder
|
|
4
4
|
// @use 'scss/fly-theme' or @use 'fly-theme' when scss is on the path.
|
|
5
5
|
@use 'tokens';
|
|
6
|
+
|
|
7
|
+
// The Arabic face the RTL half of `--font-family` names. Emits an @font-face and
|
|
8
|
+
// nothing else, and must be @used from here alone — see the partial's header for
|
|
9
|
+
// why it is a design-system concern rather than a shell one.
|
|
10
|
+
@use 'sf-arabic-font';
|
|
6
11
|
@use 'theme-light';
|
|
7
12
|
@use 'theme-dark';
|
|
8
13
|
|
package/scss/_nova-tokens.scss
CHANGED
|
@@ -110,6 +110,19 @@ html.light-theme {
|
|
|
110
110
|
--sys-green: #1B7355;
|
|
111
111
|
--sys-purple: #6A3FC0;
|
|
112
112
|
|
|
113
|
+
// ── Greeting gradient ──────────────────────────────────────────────────────
|
|
114
|
+
// The three stops the Home greeting cycles through (`.mb-ctx__greeting`, via the
|
|
115
|
+
// `greetShift` keyframe; design §7 "Header context / greeting"). Ported from the
|
|
116
|
+
// design's own per-theme stop sets rather than re-pointed at --accent/--sys-*:
|
|
117
|
+
// those are a BRAND knob and STATUS hues, and borrowing them drew a saturated
|
|
118
|
+
// violet→cyan run where the design draws a deeper violet→teal on light glass.
|
|
119
|
+
// Deliberately NOT accent-derived, so a tenant `applyAccent` re-skin does not
|
|
120
|
+
// carry the greeting — that is the trade the design's fixed stops ask for.
|
|
121
|
+
// Tune HERE, never at the call site.
|
|
122
|
+
--greet-grad-1: #5B3FA8;
|
|
123
|
+
--greet-grad-2: #0A6E8F;
|
|
124
|
+
--greet-grad-3: #8A3FB0;
|
|
125
|
+
|
|
113
126
|
// ── System TINTS ───────────────────────────────────────────────────────────
|
|
114
127
|
// The status-wash step the hue family was missing. Before these existed, 75
|
|
115
128
|
// feature-app call sites minted their own Tailwind-palette literal at a fixed
|
|
@@ -332,6 +345,15 @@ html.dark-theme {
|
|
|
332
345
|
--sys-green: #4dd6a8;
|
|
333
346
|
--sys-purple: #a48ce0;
|
|
334
347
|
|
|
348
|
+
// ── Greeting gradient ──────────────────────────────────────────────────────
|
|
349
|
+
// Same role as the light block (see its comment for the full rationale). The
|
|
350
|
+
// design draws the dark greeting as a pale lavender → cyan → mauve wash: these
|
|
351
|
+
// stops sit near white on a near-black plate, which is why they are nowhere
|
|
352
|
+
// near the saturated hues the status family carries at this position.
|
|
353
|
+
--greet-grad-1: #E4DBFF;
|
|
354
|
+
--greet-grad-2: #BFF0FF;
|
|
355
|
+
--greet-grad-3: #F3DEFF;
|
|
356
|
+
|
|
335
357
|
// ── System TINTS ───────────────────────────────────────────────────────────
|
|
336
358
|
// Same two-step law as the light block (see its comment for the full
|
|
337
359
|
// rationale). Dark strengths are HIGHER on purpose: these hues are bright
|
package/scss/_nova-type.scss
CHANGED
|
@@ -22,12 +22,18 @@
|
|
|
22
22
|
|
|
23
23
|
// ── The Display/Text split ───────────────────────────────────────────────────
|
|
24
24
|
// The design distinguishes a Display face (headings ≥ ~13.5px bold) from Text
|
|
25
|
-
// (everything else)
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
25
|
+
// (everything else). Since the UI-owner's 2026-08-18 one-system ruling that
|
|
26
|
+
// split rides `font-family` as well as weight and tracking: the six title roles
|
|
27
|
+
// below resolve `var(--font-family-display)`, every other role resolves
|
|
28
|
+
// `var(--font-family)`. Those are the platform's only two families and both are
|
|
29
|
+
// device-resident system faces, so honouring the split still ships no font file
|
|
30
|
+
// — which is what the earlier D8 prohibition was actually protecting.
|
|
31
|
+
// ·
|
|
32
|
+
// Before that ruling the split was carried by WEIGHT and LETTER-SPACING alone,
|
|
33
|
+
// because D8 forbade a second face outright. The proof point that pairing left
|
|
34
|
+
// behind still reads: `--nova-font-menu-item` and `--nova-font-section-title`
|
|
35
|
+
// share the exact same 13.5px SIZE and sit on opposite sides (500/normal Text vs
|
|
36
|
+
// 700/-0.01em Display) — now they differ in family too.
|
|
31
37
|
|
|
32
38
|
// ── Snap vs mint ─────────────────────────────────────────────────────────────
|
|
33
39
|
// Each role is annotated SNAP (aliases an existing `--font-*` ramp step from
|
|
@@ -54,30 +60,30 @@
|
|
|
54
60
|
// compact hero headline read visibly looser than the design. Pair with the
|
|
55
61
|
// `greetShift` keyframe (`_nova-motion.scss`) for the animated gradient
|
|
56
62
|
// 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);
|
|
63
|
+
--nova-font-page-headline: 700 28px/1.1 var(--font-family-display);
|
|
58
64
|
--nova-tracking-page-headline: -0.022em;
|
|
59
65
|
|
|
60
66
|
// MINT. Size coincides with --font-title1 (24px), but that step's 32/24 ≈
|
|
61
67
|
// 1.333 line-height ratio is a third looser than this role's 1.15 — the
|
|
62
68
|
// assistant's hero greeting would visibly loosen on snap.
|
|
63
|
-
--nova-font-hero-greeting: 700 24px/1.15 var(--font-family);
|
|
69
|
+
--nova-font-hero-greeting: 700 24px/1.15 var(--font-family-display);
|
|
64
70
|
--nova-tracking-hero-greeting: -0.018em;
|
|
65
71
|
|
|
66
72
|
// MINT. No ramp step sits at 26px, and none pairs a semibold (600) weight
|
|
67
73
|
// with a sub-30px size. Colour/text-shadow (the D1 extraction notes
|
|
68
74
|
// "#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);
|
|
75
|
+
--nova-font-hero-card-name: 600 26px/1.14 var(--font-family-display);
|
|
70
76
|
--nova-tracking-hero-card-name: -0.01em;
|
|
71
77
|
|
|
72
78
|
// ── Chrome / structural headings (Display) ────────────────────────────────
|
|
73
79
|
// MINT. --font-headline (17px/700) is the nearest ramp step; its 22/17 ≈
|
|
74
80
|
// 1.294 line-height ratio is far looser than this role's 1.1, and the ramp
|
|
75
81
|
// carries no letter-spacing vocabulary at all.
|
|
76
|
-
--nova-font-drawer-title: 700 16px/1.1 var(--font-family);
|
|
82
|
+
--nova-font-drawer-title: 700 16px/1.1 var(--font-family-display);
|
|
77
83
|
--nova-tracking-drawer-title: -0.016em;
|
|
78
84
|
|
|
79
85
|
// 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);
|
|
86
|
+
--nova-font-dialog-title: 700 14.5px/1.1 var(--font-family-display);
|
|
81
87
|
--nova-tracking-dialog-title: -0.014em;
|
|
82
88
|
|
|
83
89
|
// MINT. The design gives a 500-600 WEIGHT RANGE at 14px; --font-footnote
|
|
@@ -95,7 +101,7 @@
|
|
|
95
101
|
// ramp has no vocabulary for at all. Shares its 13.5px SIZE with
|
|
96
102
|
// --nova-font-menu-item below on the opposite side of the Display/Text
|
|
97
103
|
// split — see the file header.
|
|
98
|
-
--nova-font-section-title: 700 13.5px/1.2 var(--font-family);
|
|
104
|
+
--nova-font-section-title: 700 13.5px/1.2 var(--font-family-display);
|
|
99
105
|
--nova-tracking-section-title: -0.01em;
|
|
100
106
|
|
|
101
107
|
// ── Workhorse text — repeated everywhere, snapped where fidelity allows ───
|
|
@@ -159,6 +165,39 @@
|
|
|
159
165
|
--nova-tracking-timestamp: normal;
|
|
160
166
|
}
|
|
161
167
|
|
|
168
|
+
// ── RTL: tracking off, everywhere, once ─────────────────────────────────────
|
|
169
|
+
// Latin display type is tightened (down to -0.022em on the page headline) because
|
|
170
|
+
// negative tracking is how a large Latin heading stops looking loose. Applied to
|
|
171
|
+
// Arabic the same declaration is not a tightening at all: `letter-spacing` inserts
|
|
172
|
+
// its value BETWEEN glyphs, and Arabic glyphs are joined — so any non-zero value,
|
|
173
|
+
// negative or positive, forces the shaper to break the cursive connections and the
|
|
174
|
+
// word visibly falls apart. That is why the eyebrow's POSITIVE 0.03em is reset here
|
|
175
|
+
// too; "reduce the negative ones" would be the wrong reading of the rule.
|
|
176
|
+
// ·
|
|
177
|
+
// Every role is listed, including the ones already `normal`, for the same reason the
|
|
178
|
+
// block above gives every role a tracking token at all: a caller writes one pattern
|
|
179
|
+
// regardless of role, and a reader can see at a glance that the reset is total
|
|
180
|
+
// rather than having to diff two lists. `nova-type.spec.ts` pins that completeness.
|
|
181
|
+
// ·
|
|
182
|
+
// Unlayered, like the `:root` it mirrors — see the RTL note in `_tokens.scss` for
|
|
183
|
+
// why a layered copy of this would silently never apply.
|
|
184
|
+
html[dir='rtl'] {
|
|
185
|
+
--nova-tracking-page-headline: normal;
|
|
186
|
+
--nova-tracking-hero-greeting: normal;
|
|
187
|
+
--nova-tracking-hero-card-name: normal;
|
|
188
|
+
--nova-tracking-drawer-title: normal;
|
|
189
|
+
--nova-tracking-dialog-title: normal;
|
|
190
|
+
--nova-tracking-header-context: normal;
|
|
191
|
+
--nova-tracking-section-title: normal;
|
|
192
|
+
--nova-tracking-menu-item: normal;
|
|
193
|
+
--nova-tracking-body: normal;
|
|
194
|
+
--nova-tracking-row-label: normal;
|
|
195
|
+
--nova-tracking-field-label: normal;
|
|
196
|
+
--nova-tracking-tooltip: normal;
|
|
197
|
+
--nova-tracking-eyebrow: normal;
|
|
198
|
+
--nova-tracking-timestamp: normal;
|
|
199
|
+
}
|
|
200
|
+
|
|
162
201
|
// ── Rule A: tabular numerals (skill §7, mechanical) ─────────────────────────
|
|
163
202
|
// "font-variant-numeric: tabular-nums on every count, timestamp and pagination
|
|
164
203
|
// number." `_app-surface-utilities.scss` already ships `.mono` bundling this
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// ─── 'SF Arabic' — the Arabic face, declared ONCE for the whole estate ───────
|
|
2
|
+
// ·
|
|
3
|
+
// This is a `local()`-ONLY @font-face: it names faces already installed on the
|
|
4
|
+
// device and downloads nothing. That is what keeps it inside the platform's
|
|
5
|
+
// "self-hosted only, no CDN" rule — the rule bans *fetching* a font from
|
|
6
|
+
// someone else's server, and this fetches nothing at all.
|
|
7
|
+
// ·
|
|
8
|
+
// It lives in the design system rather than in the shell's `src/styles/` on
|
|
9
|
+
// purpose: External App remotes (Circles, Thoughts, PPM) consume `fly-theme` and
|
|
10
|
+
// would otherwise inherit `--font-family`'s reference to a family no @font-face
|
|
11
|
+
// in their document ever declared — the exact dev/prod skew the Kufi partial's
|
|
12
|
+
// header records for `page-header.component.scss`. `_fly-theme.scss` is the one
|
|
13
|
+
// entry point that `@use`s this, so the block is emitted exactly once.
|
|
14
|
+
// ·
|
|
15
|
+
// WHERE IT DOES NOT RESOLVE — say it plainly, because it is half the estate:
|
|
16
|
+
// 'SF Arabic' and 'Geeza Pro' ship with macOS / iOS. On Windows and Linux none
|
|
17
|
+
// of the three `local()` names match, the @font-face contributes nothing, and
|
|
18
|
+
// Arabic falls through the stack to `system-ui` (Segoe UI on Windows). That is a
|
|
19
|
+
// deliberate consequence of the UI-owner's 2026-08-18 stack ruling, which retired
|
|
20
|
+
// the self-hosted Noto Kufi Arabic that had covered every OS equally.
|
|
21
|
+
// ·
|
|
22
|
+
// `font-weight: 400 700` declares a RANGE over the local faces so a 600 title
|
|
23
|
+
// and a 400 body both resolve here instead of falling out of the family at the
|
|
24
|
+
// first weight the single named face cannot supply.
|
|
25
|
+
|
|
26
|
+
@font-face {
|
|
27
|
+
font-family: 'SF Arabic';
|
|
28
|
+
src: local('SF Arabic'), local('SFArabic-Regular'), local('Geeza Pro');
|
|
29
|
+
font-weight: 400 700;
|
|
30
|
+
font-style: normal;
|
|
31
|
+
font-display: swap;
|
|
32
|
+
}
|
package/scss/_tokens.scss
CHANGED
|
@@ -22,27 +22,42 @@
|
|
|
22
22
|
--status-pending: var(--system-yellow);
|
|
23
23
|
|
|
24
24
|
// ── FlyOS Typography ──
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
25
|
+
// ONE typographic system, expressed as TWO stacks: Text carries body/UI, Display
|
|
26
|
+
// carries titles. The split rides `font-family` (UI-owner ruling 2026-08-18) — it
|
|
27
|
+
// used to ride weight + letter-spacing alone, because D8 forbade shipping a second
|
|
28
|
+
// face. Both stacks are resolved from faces already ON the device: nothing is
|
|
29
|
+
// downloaded, so "no CDN / self-hosted only" is satisfied by shipping no font file
|
|
30
|
+
// at all rather than by vendoring one. That is what makes naming SF here legal
|
|
31
|
+
// where the 2.0.0 removal of the SHIPPED SF Pro woff2 set was not.
|
|
32
|
+
// ·
|
|
33
|
+
// These two are the ONLY family declarations in the platform. Everything inherits
|
|
34
|
+
// from the root rule (`styles/_reset.scss`); the title stack reaches type through
|
|
35
|
+
// the `--font-*` title roles below and the `--nova-font-*` roles in `_nova-type.scss`.
|
|
36
|
+
// A component must never restate a stack — inheritance or a role token, never both.
|
|
37
|
+
// ·
|
|
38
|
+
// RTL: the `html[dir='rtl']` block after this one re-points BOTH names at their
|
|
39
|
+
// Arabic-leading variants. It sits unlayered, in this same cascade tier, because a
|
|
40
|
+
// layered override would lose to this `:root` — unlayered beats every `@layer`.
|
|
41
|
+
// 'SF Arabic' is declared `local()`-only in `styles/_sf-arabic-font.scss`; on a
|
|
42
|
+
// device without it (Windows, Linux) the stack falls through to `system-ui`.
|
|
43
|
+
--font-family: -apple-system, blinkmacsystemfont, 'SF Pro Text', system-ui, sans-serif;
|
|
44
|
+
--font-family-display: -apple-system, blinkmacsystemfont, 'SF Pro Display', system-ui, sans-serif;
|
|
45
|
+
|
|
46
|
+
// Monospace survives the one-system rule because it is a FUNCTION, not a voice:
|
|
47
|
+
// code blocks, diffs, ids and log output are unreadable proportionally spaced.
|
|
48
|
+
// 'Geist Mono' stays self-hosted (`styles/_geist-fonts.scss`) — the Geist SANS half
|
|
49
|
+
// is what the ruling above retires.
|
|
38
50
|
--font-family-mono: 'Geist Mono', ui-monospace, 'SF Mono', menlo, monospace;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
--font-title1: 700
|
|
43
|
-
--font-title2: 700
|
|
44
|
-
--font-
|
|
45
|
-
--font-
|
|
51
|
+
|
|
52
|
+
// Title roles → Display. Body roles → Text. The boundary is `--font-headline`:
|
|
53
|
+
// everything at or above it is a heading treatment, everything below is copy.
|
|
54
|
+
--font-xl-title1: 700 48px/56px var(--font-family-display);
|
|
55
|
+
--font-xl-title2: 700 38px/46px var(--font-family-display);
|
|
56
|
+
--font-large-title: 700 29px/38px var(--font-family-display);
|
|
57
|
+
--font-title1: 700 24px/32px var(--font-family-display);
|
|
58
|
+
--font-title2: 700 22px/28px var(--font-family-display);
|
|
59
|
+
--font-title3: 700 19px/24px var(--font-family-display);
|
|
60
|
+
--font-headline: 700 17px/22px var(--font-family-display);
|
|
46
61
|
--font-body: 510 17px/22px var(--font-family);
|
|
47
62
|
--font-callout: 590 15px/20px var(--font-family);
|
|
48
63
|
--font-subheadline: 400 15px/20px var(--font-family);
|
|
@@ -126,3 +141,24 @@
|
|
|
126
141
|
--link-height: 26px;
|
|
127
142
|
--link-font-size: 13px;
|
|
128
143
|
}
|
|
144
|
+
|
|
145
|
+
// ─── RTL: the whole typographic system, swapped once ─────────────────────────
|
|
146
|
+
// The ONLY direction-aware font rule in the platform. `dir="rtl"` is set on <html>
|
|
147
|
+
// by `I18nService` and nowhere else, so re-pointing the two family names here
|
|
148
|
+
// re-faces every surface — no per-component `[dir=rtl]` font rule exists or may be
|
|
149
|
+
// added.
|
|
150
|
+
// ·
|
|
151
|
+
// UNLAYERED, deliberately, and this is the trap that makes it non-obvious: the shell
|
|
152
|
+
// keeps its RTL overrides in `styles-rtl.scss` inside `@layer shell`, and a custom
|
|
153
|
+
// property redeclared there would LOSE to the `:root` block above — under Cascade
|
|
154
|
+
// Level 5 an unlayered declaration beats every layered one regardless of specificity.
|
|
155
|
+
// So the swap has to live in the token layer, beside the values it swaps.
|
|
156
|
+
// `styles.scss` already sets the RTL precedent for `--shell-screen-slide` the same way.
|
|
157
|
+
// ·
|
|
158
|
+
// Tracking is neutralised rather than merely reduced: negative letter-spacing pulls
|
|
159
|
+
// Arabic glyphs out of their joined forms, which is a legibility failure, not a
|
|
160
|
+
// tightening. `_nova-type.scss` carries the companion `--nova-tracking-*` reset.
|
|
161
|
+
html[dir='rtl'] {
|
|
162
|
+
--font-family: 'SF Arabic', -apple-system, blinkmacsystemfont, 'SF Pro Text', system-ui, sans-serif;
|
|
163
|
+
--font-family-display: 'SF Arabic', -apple-system, blinkmacsystemfont, 'SF Pro Display', system-ui, sans-serif;
|
|
164
|
+
}
|