@ksp-gonogo/ui-kit 0.1.0 → 0.2.0-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/LICENSE +21 -596
  2. package/README.md +151 -0
  3. package/dist/chunk-G3PMV62Z.js +36 -0
  4. package/dist/chunk-G3PMV62Z.js.map +1 -0
  5. package/dist/chunk-KPESGDJJ.js +8611 -0
  6. package/dist/chunk-KPESGDJJ.js.map +1 -0
  7. package/dist/chunk-SHAX2WJJ.js +18 -0
  8. package/dist/chunk-SHAX2WJJ.js.map +1 -0
  9. package/dist/dist-JSEJWZKX.js +546 -0
  10. package/dist/dist-JSEJWZKX.js.map +1 -0
  11. package/dist/gridUnits.d.ts +34 -0
  12. package/dist/gridUnits.js +14 -0
  13. package/dist/gridUnits.js.map +1 -0
  14. package/dist/guards.d.ts +110 -0
  15. package/dist/guards.js +171 -0
  16. package/dist/guards.js.map +1 -0
  17. package/dist/index.d.ts +9318 -25
  18. package/dist/index.js +8098 -28
  19. package/dist/index.js.map +1 -1
  20. package/dist/magic-string.es-J7BYFTTJ.js +1309 -0
  21. package/dist/magic-string.es-J7BYFTTJ.js.map +1 -0
  22. package/dist/testing.d.ts +470 -0
  23. package/dist/testing.js +16747 -0
  24. package/dist/testing.js.map +1 -0
  25. package/dist/tokens.css +1063 -29
  26. package/dist/units-Cl-8ZdJc.d.ts +270 -0
  27. package/package.json +57 -15
  28. package/dist/ActionButton.d.ts +0 -18
  29. package/dist/ActionButton.d.ts.map +0 -1
  30. package/dist/ActionButton.js +0 -56
  31. package/dist/ActionButton.js.map +0 -1
  32. package/dist/Badge.d.ts +0 -15
  33. package/dist/Badge.d.ts.map +0 -1
  34. package/dist/Badge.js +0 -60
  35. package/dist/Badge.js.map +0 -1
  36. package/dist/Box.d.ts +0 -19
  37. package/dist/Box.d.ts.map +0 -1
  38. package/dist/Box.js +0 -27
  39. package/dist/Box.js.map +0 -1
  40. package/dist/Card.d.ts +0 -11
  41. package/dist/Card.d.ts.map +0 -1
  42. package/dist/Card.js +0 -13
  43. package/dist/Card.js.map +0 -1
  44. package/dist/Cluster.d.ts +0 -18
  45. package/dist/Cluster.d.ts.map +0 -1
  46. package/dist/Cluster.js +0 -24
  47. package/dist/Cluster.js.map +0 -1
  48. package/dist/EmptyState.d.ts +0 -16
  49. package/dist/EmptyState.d.ts.map +0 -1
  50. package/dist/EmptyState.js +0 -37
  51. package/dist/EmptyState.js.map +0 -1
  52. package/dist/GonogoTokens.d.ts +0 -16
  53. package/dist/GonogoTokens.d.ts.map +0 -1
  54. package/dist/GonogoTokens.js +0 -108
  55. package/dist/GonogoTokens.js.map +0 -1
  56. package/dist/Grid.d.ts +0 -25
  57. package/dist/Grid.d.ts.map +0 -1
  58. package/dist/Grid.js +0 -23
  59. package/dist/Grid.js.map +0 -1
  60. package/dist/Inline.d.ts +0 -20
  61. package/dist/Inline.d.ts.map +0 -1
  62. package/dist/Inline.js +0 -17
  63. package/dist/Inline.js.map +0 -1
  64. package/dist/Panel.d.ts +0 -14
  65. package/dist/Panel.d.ts.map +0 -1
  66. package/dist/Panel.js +0 -145
  67. package/dist/Panel.js.map +0 -1
  68. package/dist/ProgressBar.d.ts +0 -15
  69. package/dist/ProgressBar.d.ts.map +0 -1
  70. package/dist/ProgressBar.js +0 -31
  71. package/dist/ProgressBar.js.map +0 -1
  72. package/dist/Readout.d.ts +0 -35
  73. package/dist/Readout.d.ts.map +0 -1
  74. package/dist/Readout.js +0 -114
  75. package/dist/Readout.js.map +0 -1
  76. package/dist/Row.d.ts +0 -22
  77. package/dist/Row.d.ts.map +0 -1
  78. package/dist/Row.js +0 -32
  79. package/dist/Row.js.map +0 -1
  80. package/dist/Section.d.ts +0 -15
  81. package/dist/Section.d.ts.map +0 -1
  82. package/dist/Section.js +0 -21
  83. package/dist/Section.js.map +0 -1
  84. package/dist/Spinner.d.ts +0 -18
  85. package/dist/Spinner.d.ts.map +0 -1
  86. package/dist/Spinner.js +0 -22
  87. package/dist/Spinner.js.map +0 -1
  88. package/dist/Stack.d.ts +0 -15
  89. package/dist/Stack.d.ts.map +0 -1
  90. package/dist/Stack.js +0 -17
  91. package/dist/Stack.js.map +0 -1
  92. package/dist/StatusIndicator.d.ts +0 -27
  93. package/dist/StatusIndicator.d.ts.map +0 -1
  94. package/dist/StatusIndicator.js +0 -78
  95. package/dist/StatusIndicator.js.map +0 -1
  96. package/dist/Truncate.d.ts +0 -7
  97. package/dist/Truncate.d.ts.map +0 -1
  98. package/dist/Truncate.js +0 -14
  99. package/dist/Truncate.js.map +0 -1
  100. package/dist/Value.d.ts +0 -24
  101. package/dist/Value.d.ts.map +0 -1
  102. package/dist/Value.js +0 -42
  103. package/dist/Value.js.map +0 -1
  104. package/dist/WidgetHeader.d.ts +0 -16
  105. package/dist/WidgetHeader.d.ts.map +0 -1
  106. package/dist/WidgetHeader.js +0 -40
  107. package/dist/WidgetHeader.js.map +0 -1
  108. package/dist/defaultDarkTheme.d.ts +0 -16
  109. package/dist/defaultDarkTheme.d.ts.map +0 -1
  110. package/dist/defaultDarkTheme.js +0 -96
  111. package/dist/defaultDarkTheme.js.map +0 -1
  112. package/dist/format.d.ts +0 -11
  113. package/dist/format.d.ts.map +0 -1
  114. package/dist/format.js +0 -12
  115. package/dist/format.js.map +0 -1
  116. package/dist/index.d.ts.map +0 -1
  117. package/dist/science/ScienceExperimentRow.d.ts +0 -43
  118. package/dist/science/ScienceExperimentRow.d.ts.map +0 -1
  119. package/dist/science/ScienceExperimentRow.js +0 -61
  120. package/dist/science/ScienceExperimentRow.js.map +0 -1
  121. package/dist/test/axe.d.ts +0 -3
  122. package/dist/test/axe.d.ts.map +0 -1
  123. package/dist/test/axe.js +0 -5
  124. package/dist/test/axe.js.map +0 -1
  125. package/dist/test/setup.d.ts +0 -2
  126. package/dist/test/setup.d.ts.map +0 -1
  127. package/dist/test/setup.js +0 -12
  128. package/dist/test/setup.js.map +0 -1
  129. package/dist/theme.d.ts +0 -116
  130. package/dist/theme.d.ts.map +0 -1
  131. package/dist/theme.js +0 -15
  132. package/dist/theme.js.map +0 -1
package/dist/tokens.css CHANGED
@@ -1,13 +1,231 @@
1
+ /*
2
+ * Every box measures its padding and border INSIDE the width it was given.
3
+ *
4
+ * Without this, a rule pairing `width: 100%` with horizontal padding resolves
5
+ * to 100% plus that padding and hangs over its parent's edge by exactly the
6
+ * inset. The panel toolbar measured 542px inside a 510px row, at every tile
7
+ * width and in every widget that drew one, and a fill empty state was 32px
8
+ * wider than the panel it was centred in, so its sentence was sliced by the
9
+ * border in four widgets at once. Neither is visible from the source: the rule
10
+ * that overflows reads as if it asks for exactly the space it has.
11
+ *
12
+ * It lives here because this file is what a page loads to be a gonogo page:
13
+ * the app imports it, and every render probe injects it verbatim. A page that
14
+ * draws kit primitives without it draws them at a size their authors never
15
+ * measured.
16
+ */
17
+ *,
18
+ *::before,
19
+ *::after {
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ /*
24
+ * Native form controls take their font, colour and background from the UA
25
+ * stylesheet rather than from the page, so a bare `<button>` draws in the
26
+ * browser's sans-serif with black words on a light grey box, and a bare input
27
+ * is a white box, on a dark panel. Every control takes the theme's type, text
28
+ * colour and raised surface instead, a pair that clears the text floor on
29
+ * its own, so a control no kit rule has styled is still readable. Kit
30
+ * controls set their own colours over it. The root carries the theme's text
31
+ * colour for the same reason: an inherited colour always ends at the theme
32
+ * and never at a browser default.
33
+ *
34
+ * It lives here for the same reason as the reset above: this file is what
35
+ * every page, render probe and Uplink docs page loads.
36
+ */
37
+ html {
38
+ color: var(--color-text-primary);
39
+ }
40
+
41
+ button,
42
+ input,
43
+ select,
44
+ textarea {
45
+ font: inherit;
46
+ color: var(--color-text-primary);
47
+ background-color: var(--color-surface-raised);
48
+ }
49
+
1
50
  :root {
51
+ /*
52
+ * NAMING ACROSS FAMILIES. A convention, not a rule: a new token need not
53
+ * declare a category, and a name in one family needs no counterpart in
54
+ * another. The categories describe what the families already are.
55
+ *
56
+ * container externals --z-*, --color-* (boundary and ground), --radius-*
57
+ * container internals --color-* (content), --line-height-*, --gap-*,
58
+ * --inset-*
59
+ * animation --duration-*, --ease-*
60
+ *
61
+ * `--color-*` sits in both of the first two: a colour is either what a
62
+ * container is against what lies behind it (border, ground) or the
63
+ * treatment of what it holds (text, marks). It is one family because the
64
+ * names are not separable by prefix, not because the jobs are the same.
65
+ *
66
+ * A modifier such as `status` varies a token of an existing category and is
67
+ * never a category of its own. The set of three is closed: a fourth is a
68
+ * structural claim about the whole system and is made deliberately, not by
69
+ * adding a token.
70
+ *
71
+ * Reusing a term across families is the part with teeth. A shared word
72
+ * promises a pairing: `--radius-floating` names the corner of the boxes that
73
+ * sit on the `--z-*` rungs above the app (modal, fab), so a reader who reaches
74
+ * for one should reach for the other, and the two must actually belong
75
+ * together. Where that does not hold, use a different word
76
+ * rather than a familiar one borrowed by coincidence, because a coincidental
77
+ * match teaches a pairing nobody checked.
78
+ *
79
+ * This sits beside the bar for whether a name is earned at all (it must
80
+ * resolve differently by context or express a shape the rung ladder cannot),
81
+ * and governs only how names relate across families.
82
+ */
2
83
  --font-family-mono:
3
84
  "JetBrains Mono", ui-monospace, "IBM Plex Mono", Menlo, Consolas, monospace;
85
+ /*
86
+ * Type scale. `2xs` is the one step the census added: 103 of the 394
87
+ * hardcoded px font-sizes sit BELOW the old 11px floor (69 at 10px, 34
88
+ * at 9px), so the scale had no rung for the micro-labels this dense
89
+ * mission-control UI is built out of. Everything else maps onto the
90
+ * four rungs that were already here.
91
+ *
92
+ * The scale deliberately stops at `lg`. Every hardcoded size above
93
+ * 16px in the codebase (18/19/20/22/23/24/28) is either a fluid
94
+ * `clamp()`, a JS-computed fit, or a value comment-locked to a
95
+ * specific box width. A fixed display rung would freeze behaviour
96
+ * those widgets depend on, so they stay literal.
97
+ */
98
+ --font-size-2xs: 10px;
4
99
  --font-size-xs: 11px;
5
100
  --font-size-sm: 12px;
6
101
  --font-size-base: 14px;
7
102
  --font-size-lg: 16px;
8
103
 
9
104
  /*
10
- * Colour tokens — names match `theme.colors.*` paths in
105
+ * SEMANTIC TYPE, over the rungs above. One name per JOB, where a job
106
+ * is what the text IS rather than how big it is.
107
+ *
108
+ * `caption` The annotation that names a value it does not contain.
109
+ * Small caps and tracked, set apart from the reading it
110
+ * points at. 165 sites, and the job has a ceiling the rung
111
+ * ladder never described: it reaches 2xs, xs and sm and
112
+ * never base or lg.
113
+ * `compact` The working text of a surface: rows, cells, control
114
+ * labels, and the commentary beside them. 297 sites, 53% of
115
+ * the whole population, so this is the app's real default
116
+ * and the document's own 14px never was.
117
+ * `value` What the caption points at. The name of a row or a card,
118
+ * the reading in a labelled line, the text a form control
119
+ * holds.
120
+ * `prose` Consecutive sentences, read start to finish.
121
+ * `figure` The number a widget exists to show.
122
+ *
123
+ * COMPACT AND PROSE ARE REGISTERS, NOT SIZES. The test is not length,
124
+ * it is whether a person reads the words in order or picks one out at
125
+ * a glance. The boundary is disjoint rather than a judgement at the
126
+ * margin: of the 297 compact sites, 2 set a max-width in em or ch and
127
+ * none sets `--line-height-prose`, so almost nothing outside `prose`
128
+ * holds a wrapped sentence at all.
129
+ *
130
+ * CAPTION AND VALUE ARE A PAIR, and colour says so independently of
131
+ * size: 85% of captions sit on a muted, faint or dim text colour and
132
+ * 69% of values on `--color-text-primary`. Reach for one and you are
133
+ * reaching for the other.
134
+ *
135
+ * THERE IS NO SIXTH NAME FOR SUPPORTING TEXT, and the reason is here
136
+ * because the question comes back. Split `compact` by what its
137
+ * declarations are called and the four strands (supporting text,
138
+ * control labels, small readings, the rest) come out at 11.22, 11.47,
139
+ * 10.71 and 11.24 mean px, which is one size. Supporting text IS a
140
+ * real job, and the design already marks it with COLOUR: 68% muted
141
+ * against 2% primary, where the rest of `compact` is 37% and 18%. A
142
+ * second size name would invent a distinction the design does not
143
+ * make and say a second time what `--color-text-muted` already says.
144
+ *
145
+ * NOT NAMED, and each absence is a decision:
146
+ * a glyph's box 14 sites size an icon glyph THROUGH `font-size`
147
+ * rather than sizing text: a dismiss cross, a
148
+ * caret, a chevron centred in a button. That is a
149
+ * box dimension wearing a type property, it belongs
150
+ * to an icon family, and a type name offered for it
151
+ * would be reached for as text.
152
+ * a display tier Every size above 16px is a fluid `clamp()`, a
153
+ * JS-computed fit or a value locked to a box width,
154
+ * as the scale's own note above records. `figure`
155
+ * is the top of the type scale, not the bottom of a
156
+ * display one.
157
+ */
158
+ --font-size-caption: var(--font-size-2xs);
159
+ --font-size-compact: var(--font-size-xs);
160
+ --font-size-value: var(--font-size-sm);
161
+ --font-size-prose: var(--font-size-base);
162
+ --font-size-figure: var(--font-size-lg);
163
+
164
+ /*
165
+ * ICON SIZE. A separate family from the type scale on purpose: an icon's
166
+ * size is a BOX dimension, and a name offered on the type scale for it
167
+ * gets reached for as text.
168
+ *
169
+ * `control` The glyph inside a control the operator presses. 46 of the
170
+ * 63 icon sites, and the number is not arbitrary: both
171
+ * control tokens below already do their arithmetic against a
172
+ * 14px glyph, in writing. `--inset-control-compact` derives
173
+ * an 18px header from 2 inset + 2 border + a 14px glyph, and
174
+ * `--control-height` derives its 28 from 12 + 2 + 14. Sizing
175
+ * icons at anything else falsifies both.
176
+ * `standalone` The glyph that IS its control: no label, nothing beside it,
177
+ * on a target hit at a glance. Every `Fab`, and the
178
+ * processor picker.
179
+ *
180
+ * THESE STEP ON A COARSE POINTER and a raw `size={14}` cannot. The step
181
+ * is the reason the family is custom properties rather than constants: a
182
+ * prop is a JS number and a number cannot read a media query, so today
183
+ * the two halves of one population disagree on a touch screen. A `×`
184
+ * drawn with `font-size` grows and the `<CloseIcon>` beside it does not.
185
+ *
186
+ * REACHED THROUGH THE `size` PROP, not through a CSS rule, because a
187
+ * `var()` resolves inside an SVG presentation attribute. Measured in
188
+ * chromium, firefox and webkit, with the negative controls that make the
189
+ * measurement worth having: `width="banana"` is rejected in all three and
190
+ * `width="var(--absent, 33px)"` renders at 33, so the probe can tell a
191
+ * failure from a success. Anyone tempted to "fix" a `size="var(...)"`
192
+ * back to a number should reverse that reasoning, not the code.
193
+ *
194
+ * NOT NAMED:
195
+ * an overlay tier A dismiss on a surface that covers the screen is a
196
+ * real candidate and its own population disagrees:
197
+ * 16, 16, 14 and 18 across four instances of one job.
198
+ * Naming it would write one of the three down and call
199
+ * it a decision.
200
+ * a 12px tier The observed mode, and not a job. It appears on
201
+ * `--inset-control-compact` AND on the 28px control
202
+ * floor, and sits beside a 14 in the same row twice.
203
+ */
204
+ --icon-size-control: 14px;
205
+ --icon-size-standalone: 20px;
206
+
207
+ /*
208
+ * Line height, unitless, named by ROLE rather than by number. The
209
+ * census showed the values split cleanly by job, not by size: all 25
210
+ * `line-height: 1` sites are single-line chrome and glyph buttons that
211
+ * collapse the glyph box, and all 16 `1.5` sites are prose blocks with
212
+ * no vertical padding. Four roles cover 55 of the 68 declarations
213
+ * exactly.
214
+ *
215
+ * `flush` glyph and single-line chrome. Collapses the line box so an
216
+ * icon or a one-character button centres in a fixed height.
217
+ * Never use on anything that can wrap.
218
+ * `tight` dense multi-line readouts, label-over-value stacks.
219
+ * `body` default for widget text.
220
+ * `prose` running paragraphs, help text, banners.
221
+ */
222
+ --line-height-flush: 1;
223
+ --line-height-tight: 1.2;
224
+ --line-height-body: 1.4;
225
+ --line-height-prose: 1.5;
226
+
227
+ /*
228
+ * Colour tokens: names match `theme.colors.*` paths in
11
229
  * `packages/ui-kit/src/defaultDarkTheme.ts`. The CSS-variable layer is
12
230
  * the single source of truth; the JS theme object is a typed handle
13
231
  * that reads from these vars (e.g. `var(--color-text-primary)`).
@@ -15,8 +233,8 @@
15
233
  */
16
234
  --color-text-primary: #ccc;
17
235
  --color-text-muted: #888;
18
- --color-text-dim: #808080;
19
- --color-text-faint: #7a7a7a;
236
+ --color-text-dim: #858585;
237
+ --color-text-faint: #838383;
20
238
  --color-text-inverse: #050505;
21
239
 
22
240
  --color-surface-app: #050505;
@@ -30,41 +248,103 @@
30
248
  --color-accent-fg: #00ff88;
31
249
  --color-accent-bg: #00ff88;
32
250
 
33
- --color-status-go-fg: #cfe;
34
- --color-status-go-bg: #2e5a2e;
35
- --color-status-nogo-fg: #ffdede;
36
- --color-status-nogo-bg: #ff4d4d;
37
- --color-status-warning-fg: #1a1a1a;
38
- --color-status-warning-bg: #ff8c00;
39
- --color-status-info-fg: #7cf;
40
- --color-status-info-bg: #0d0d0d;
41
-
42
251
  /*
43
- * Muted variants for status surfaces — used when the alert/warning
44
- * state needs a *darker* fill (e.g. error-button background, banner
45
- * inset). Distinct from the primary status colours, which are full-
46
- * brightness. `alert-muted` covers nogo-tone darks; `warning-bg-muted`
47
- * covers warning-tone darks (warm browns / olives).
252
+ * The tone hues. A palette rung is named nowhere but this file: everything
253
+ * else reaches a hue through the role token below that says what it is for.
48
254
  */
49
- --color-status-alert-muted: #4a0e0e;
50
- --color-status-warning-bg-muted: #3a2a0a;
255
+ --palette-green-100: #cfe;
256
+ --palette-green-500: #3c8050;
257
+ --palette-green-700: #2e5a2e;
258
+ --palette-green-900: #0d180d;
259
+ --palette-red-100: #ffdede;
260
+ --palette-red-400: #ff4d4d;
261
+ --palette-red-900: #4a0e0e;
262
+ --palette-amber-200: #ffd68a;
263
+ --palette-amber-400: #ff8c00;
264
+ --palette-amber-600: #8a6a28;
265
+ --palette-amber-900: #3a2a0a;
266
+ --palette-blue-300: #7cf;
267
+ --palette-ink: #1a1a1a;
268
+
51
269
  /*
52
- * Warning text + border for warning chips on a *dark* surface — the
53
- * `-fg` and `-bg` pair above is for bright orange chips (dark text on
54
- * orange); these are the inverse case (light cream text on dark amber).
270
+ * One tone scale, five roles per tone, so a colour is chosen by the job it
271
+ * does rather than by which token happened to look right:
272
+ *
273
+ * -text the tone's words on a surface or on its own muted ground
274
+ * -mark a dot, fill, edge or stroke standing alone, with no text of
275
+ * its own: held to 3:1 on the surfaces
276
+ * -status a fill that carries its own text, which is -on-status
277
+ * -on-status the text on that fill
278
+ * -muted a quiet ground under ordinary text
279
+ *
280
+ * Text takes the pale tint of a hue and marks the saturated one, since a
281
+ * saturated hue that clears 3:1 as a mark is too loud to read as prose, and
282
+ * the pale tint is too faint to carry meaning as a lone dot. go's mark is a
283
+ * mid green, distinct from both its muted fill and the accent, which means
284
+ * focus and interaction and never state.
55
285
  */
56
- --color-status-warning-fg-muted: #ffd68a;
57
- --color-status-warning-border-muted: #8a6a28;
286
+ --color-neutral-text: var(--color-text-primary);
287
+ --color-neutral-mark: var(--color-text-dim);
288
+ --color-neutral-status: var(--color-surface-raised);
289
+ --color-neutral-on-status: var(--color-text-primary);
290
+ --color-neutral-muted: var(--color-surface-raised);
291
+
292
+ --color-info-text: var(--palette-blue-300);
293
+ --color-info-mark: var(--palette-blue-300);
294
+ --color-info-status: var(--palette-blue-300);
295
+ --color-info-on-status: var(--palette-ink);
296
+ --color-info-muted: var(--color-surface-panel);
297
+
298
+ --color-go-text: var(--palette-green-100);
299
+ --color-go-mark: var(--palette-green-500);
300
+ --color-go-status: var(--palette-green-700);
301
+ --color-go-on-status: var(--palette-green-100);
302
+ /* Kept this dark so --color-text-dim body text stays above 4.5:1 on it; a go card's green is carried by its edge. */
303
+ --color-go-muted: var(--palette-green-900);
304
+
305
+ --color-caution-text: var(--palette-amber-200);
306
+ --color-caution-mark: var(--palette-amber-200);
307
+ --color-caution-status: var(--palette-amber-200);
308
+ --color-caution-on-status: var(--palette-ink);
309
+ --color-caution-muted: var(--palette-amber-900);
310
+
311
+ --color-warn-text: var(--palette-amber-200);
312
+ --color-warn-mark: var(--palette-amber-400);
313
+
314
+ /* The mark of a figure a model carried: held keeps the warn square, a modelled figure draws this blue as a triangle, so the two differ by shape and by hue. */
315
+ --color-modelled-mark: var(--palette-blue-300);
316
+ /* The apsides, on an orbit drawing and beside their readouts. Neither is the held amber nor the modelled blue, so an apsis never reads as a mark about how current a figure is. */
317
+ --color-apoapsis-mark: var(--color-data-12);
318
+ --color-periapsis-mark: var(--color-data-15);
319
+ /* The mark of a figure measured now, but of something other than what its label names. Not the held amber, the modelled blue or a go green, so it says nothing about how current the figure is. */
320
+ --color-elsewhere-mark: var(--color-data-15);
321
+ --color-warn-status: var(--palette-amber-400);
322
+ --color-warn-on-status: var(--palette-ink);
323
+ --color-warn-muted: var(--palette-amber-900);
324
+ /* The edge of a warn box drawn on its muted ground: decorative, the box's text carries the tone. */
325
+ --color-warn-muted-edge: var(--palette-amber-600);
326
+
327
+ --color-nogo-text: var(--palette-red-100);
328
+ --color-nogo-mark: var(--palette-red-400);
329
+ --color-nogo-status: var(--palette-red-400);
330
+ --color-nogo-on-status: var(--palette-ink);
331
+ --color-nogo-muted: var(--palette-red-900);
332
+
333
+ --color-offline-text: var(--color-text-dim);
334
+ --color-offline-mark: var(--color-text-dim);
335
+ --color-offline-status: var(--color-surface-raised);
336
+ --color-offline-on-status: var(--color-text-dim);
337
+ --color-offline-muted: var(--color-surface-sunken);
58
338
 
59
339
  /*
60
- * Tag palette — used for *labeled state* with a paired fg / bg /
340
+ * Tag palette: used for *labeled state* with a paired fg / bg /
61
341
  * border surface. Examples: a purple "prediction" chip, a blue
62
342
  * station-link badge. Each tag has a fg / bg / border triple.
63
343
  *
64
344
  * Not for categorical data (chart series, telemetry rows, body
65
- * markers) — use the `--color-data-*` palette below for that.
345
+ * markers): use the `--color-data-*` palette below for that.
66
346
  *
67
- * Not currently typed in the theme schema — promote when a widely-
347
+ * Not currently typed in the theme schema, promote when a widely-
68
348
  * shared widget begins consuming them programmatically.
69
349
  */
70
350
  --color-tag-blue-fg: #4488ff;
@@ -83,7 +363,28 @@
83
363
  --color-tag-red-fg: #ff4466;
84
364
 
85
365
  /*
86
- * Categorical data palette — used for chart series, telemetry rows,
366
+ * Attitude and manoeuvre marker palette: the hue KSP paints each navball
367
+ * marker, so an operator who knows a marker by its colour is not retrained.
368
+ * These are FACTS about the game, not a design choice, and are not to be
369
+ * tuned for the surface: the marker glyphs in `@ksp-gonogo/ui-kit`
370
+ * (`MarkerIcons.tsx`) carry a `currentColor` keyline that supplies the
371
+ * luminance contrast against either ground, so the hue is free to be the
372
+ * game's. Stock ships the glyph atlas as a white alpha mask
373
+ * (`Squad/Props/IVANavBallNoBase/navBall_vectors_IVA.png`) and tints it from
374
+ * the NavBall prefab; the values here are RasterPropMonitor's stock-matching
375
+ * tints (`JSIPrimaryFlightDisplay.cs`), converted from Unity `Color` floats.
376
+ * A pair (prograde/retrograde, normal/anti-normal, radial in/out,
377
+ * target/anti-target) shares one hue in game and shares one token here:
378
+ * within a pair, SHAPE carries the distinction.
379
+ */
380
+ --color-marker-prograde: #d6fa00;
381
+ --color-marker-normal: #ed00ff;
382
+ --color-marker-radial: #00fff4;
383
+ --color-marker-maneuver: #001dff;
384
+ --color-marker-target: #ff00ff;
385
+
386
+ /*
387
+ * Categorical data palette: used for chart series, telemetry rows,
87
388
  * body markers, and anything where the role is "make this category
88
389
  * visually distinct from neighbours". Cycle by index (see
89
390
  * packages/components/src/shared/dataPalette.ts).
@@ -92,7 +393,7 @@
92
393
  * ~70%+ HSL lightness with high saturation so it reads on
93
394
  * `--color-surface-app` through `--color-surface-raised`. Hues span
94
395
  * ~360° with adjacent pairs visually distinct. None overlap the
95
- * semantic palette (accent green, status orange/red/nogo) — a chart
396
+ * semantic palette (accent green, status orange/red/nogo): a chart
96
397
  * series should never be mistaken for a state indicator.
97
398
  */
98
399
  --color-data-1: #6cb4ff; /* steel blue */
@@ -121,13 +422,746 @@
121
422
  --color-data-24: #f5e07c; /* canary */
122
423
 
123
424
  --color-focus: #00ff88;
425
+
426
+ /*
427
+ * SPACING LADDER. One ladder for padding, margin AND gap, on a 2px grid
428
+ * with a 1px `hair` step below it.
429
+ *
430
+ * The rungs are raw material for the semantic names below and for nothing
431
+ * else. Only this file reads them, and only to define a name; every other
432
+ * file in the repo reads the name, which is enforced by the spacing gate in
433
+ * packages/core/src/styleguide-widget-spacing.test.ts. A rung says how many
434
+ * pixels. A name says which job, and the job is what a reader needs and
435
+ * what a container can change.
436
+ */
437
+ --space-hair: 1px;
438
+ --space-2: 2px;
439
+ --space-4: 4px;
440
+ --space-6: 6px;
441
+ --space-8: 8px;
442
+ --space-10: 10px;
443
+ --space-12: 12px;
444
+ --space-16: 16px;
445
+ --space-24: 24px;
446
+
447
+ /*
448
+ * SEMANTIC SPACING. Every gap, inset, offset, indent and spacing-derived
449
+ * size in the app, named for its job.
450
+ *
451
+ * A site whose job has no name here gets one: add it to the right family,
452
+ * defined from the rungs or from another name, and read it. Two sites share
453
+ * a name only when they do the same job at the same size, so a name is safe
454
+ * to change for everything that reads it.
455
+ *
456
+ * Families, by the axis the space sits on:
457
+ * --gap-* between siblings, as a `gap` or as the margin that stands
458
+ * in for one in flow layout
459
+ * --inset-* inside an edge, as `padding`. Two- and three-value insets
460
+ * are ordered vertical/horizontal and belong in the
461
+ * `padding` shorthand, never `padding-inline`
462
+ * --outset-* outside an edge, as `margin`, where the box and not its
463
+ * siblings decides the distance
464
+ * --gutter-* one axis of an inset that several boxes share so their
465
+ * content lines up down a column
466
+ * --indent-* a leading inset nesting a child under its parent
467
+ * --offset-* the distance an absolutely positioned box keeps from the
468
+ * edge it is pinned to
469
+ * --size-* a box dimension that sits on the spacing grid
470
+ *
471
+ * The two context names, --gap-related and --gap-section, STEP: a
472
+ * container re-declares them from its tier (`Card` is compact, `Panel`
473
+ * and `Notice` comfortable) and everything inside inherits the new value.
474
+ * The related tiers run comfortable, compact, dense and packed.
475
+ * Every other name is a constant. A tier constant read directly
476
+ * (--gap-related-compact) is the related gap of that tier wherever it
477
+ * sits, which is how a widget spaces blocks in flow without inheriting a
478
+ * card's step.
479
+ *
480
+ * Every name is declared HERE, at `:root`, and a container only ever
481
+ * overrides. A name that resolves nowhere computes to nothing and the
482
+ * declaration is dropped, which for a gap is a silently collapsed layout.
483
+ */
484
+
485
+ /* Context gaps, and the tiers a container steps them to. */
486
+ --gap-related-comfortable: var(--space-8);
487
+ --gap-related-compact: var(--space-6);
488
+ --gap-related-dense: var(--space-4);
489
+ --gap-related-packed: var(--space-2);
490
+ --gap-section-comfortable: var(--space-16);
491
+ --gap-section-compact: var(--space-12);
492
+ /* Items of the same kind. THE DEFAULT gap. */
493
+ --gap-related: var(--gap-related-comfortable);
494
+ /* Groups of different kinds. */
495
+ --gap-section: var(--gap-section-comfortable);
496
+
497
+ /* Lines and readouts. */
498
+ /* The seam that stops two stacked lines, rows or glyph boxes touching. */
499
+ --gap-line: var(--space-hair);
500
+ /* Stacked rows that each carry their own inset. */
501
+ --gap-rows: var(--space-2);
502
+ /* A caption, note, detail or wrapped second line directly under its owner. */
503
+ --gap-caption: var(--space-2);
504
+ /* A subordinate readout, chip or line under a figure, one step looser. */
505
+ --gap-sub-readout: var(--space-4);
506
+ /* Row seam in a label/value readout grid. */
507
+ --gap-readout-row: var(--space-2);
508
+ /* Column gap between a label and its value. */
509
+ --gap-label-value: var(--space-8);
510
+ /* The same, in a readout squeezed to fit a narrow tile. */
511
+ --gap-label-value-tight: var(--space-6);
512
+ /* A value and the tag, chip, unit or caption running beside it. */
513
+ --gap-value-tag: var(--space-6);
514
+ /* A label and the figure, separator or suffix it runs straight into. */
515
+ --gap-lead-figure: var(--space-2);
516
+ /* A unit symbol set off the figure it qualifies. */
517
+ --gap-unit-suffix: var(--space-2);
518
+ /* The parts of one figure: value and unit, or both ends of a range. */
519
+ --gap-figure-parts: var(--space-4);
520
+ /* Text and a trailing count, value or distance. */
521
+ --gap-trailing-figure: var(--space-4);
522
+ /* Text and a trailing unit, dot, bullet or pill. */
523
+ --gap-trailing-mark: var(--space-6);
524
+ /* The horizontal margin either side of a hairline rule between fields. */
525
+ --gap-rule: var(--space-4);
526
+ /* A title and the hairline rule drawn under it. */
527
+ --gap-title-rule: var(--space-4);
528
+ /* A title or caption and the block it heads. */
529
+ --gap-under-title: var(--space-4);
530
+ /* A row's name and its trailing content. */
531
+ --gap-row: var(--space-8);
532
+ /* The lines of a row allowed to wrap. */
533
+ --gap-row-wrap: var(--space-4);
534
+ /* A headline name and the figure or meta beside it. */
535
+ --gap-headline: var(--space-10);
536
+ /* Consecutive readout groups, each already carrying its own section seam. */
537
+ --gap-readout-groups: var(--space-10);
538
+ /* Lines of the centred content a tiny tile shows, and a hero value's caption. */
539
+ --gap-tiny-content: var(--space-4);
540
+ /* A row meter group stacked on one MeterStack's columns. */
541
+ --gap-meter-rows: var(--space-2);
542
+ /* A meter's label, bar and figure columns. */
543
+ --gap-meter-columns: var(--space-8);
544
+ /* The four bars of one signal-strength glyph. */
545
+ --gap-signal-bars: var(--space-2);
546
+ /* A data line: wrapped rows, then label to value. */
547
+ --gap-data-line: var(--space-4) var(--space-6);
548
+ /* A delay legend: wrapped rows, then entries. */
549
+ --gap-legend: var(--space-4) var(--space-12);
550
+ /* A contract's reward chips: wrapped rows, then chips. */
551
+ --gap-rewards: var(--space-2) var(--space-12);
552
+ /* The two columns of a tag suggestion. */
553
+ --gap-suggestion-columns: var(--space-12);
554
+
555
+ /* Glyphs and controls. */
556
+ /* A glyph and its label in a compact control, a legend or a tick. */
557
+ --gap-glyph: var(--space-4);
558
+ /* A glyph and its label in a full-size control. */
559
+ --gap-glyph-control: var(--space-6);
560
+ /* A glyph and its text inside a bordered status or message box. */
561
+ --gap-glyph-box: var(--space-8);
562
+ /* Adjacent glyph buttons in a tile header's tray. */
563
+ --gap-glyph-tray: var(--space-2);
564
+ /* A pill's glyph and label, and a FAB's label and button. */
565
+ --gap-pill: var(--space-10);
566
+ /* A file or screenshot and the name or caption beside it. */
567
+ --gap-attachment: var(--space-10);
568
+ /* Controls side by side in a row: form actions, a toolbar, a modal footer. */
569
+ --gap-control-row: var(--space-8);
570
+ /* Buttons packed into a grid. */
571
+ --gap-chips: var(--space-4);
572
+ /* A stepper's minus, value and plus. */
573
+ --gap-stepper: var(--space-4);
574
+ /* A cluster set apart from the cluster before it on one line. */
575
+ --gap-inline-cluster: var(--space-6);
576
+ /* A tab strip's tabs. */
577
+ --gap-tab: var(--space-2);
578
+ /* A tab strip and the panel it switches. */
579
+ --gap-tabs-panel: var(--space-12);
580
+ /* A disclosure trigger and the panel it opens. */
581
+ --gap-disclosure: var(--space-2);
582
+ /* A record's lead-in and its name. */
583
+ --gap-record-lead: var(--space-6);
584
+ /* The lines inside a free-standing card on a sheet. */
585
+ --gap-card-lines: var(--space-4);
586
+
587
+ /* Forms. */
588
+ /* A field's label and its input, stacked. */
589
+ --gap-field-label: var(--space-6);
590
+ /* The same in a compact field. */
591
+ --gap-field-label-compact: var(--space-4);
592
+ /* A label, input and suffix laid out in a row. */
593
+ --gap-field-label-inline: var(--space-8);
594
+ /* A read-only field's term and its value. */
595
+ --gap-field-term: var(--space-12);
596
+ /* The fields of a form section. */
597
+ --gap-form-field: var(--space-16);
598
+ /* The fields of a boxed form. */
599
+ --gap-form-field-boxed: var(--space-6);
600
+ /* An extra row hung under a field: a tag list, a screenshot, a listening state. */
601
+ --gap-under-field: var(--space-6);
602
+ /* A unit input's stepped parts. */
603
+ --gap-unit-parts: var(--space-6);
604
+ /* A heading and the hint line under it. */
605
+ --gap-heading-hint: var(--space-6);
606
+
607
+ /* Blocks in flow. */
608
+ /* A row of actions closing the block above it. */
609
+ --gap-actions: var(--space-4);
610
+ /* The actions at the foot of a card. */
611
+ --gap-card-actions: var(--space-6);
612
+ /* A control row closing a list or an editor. */
613
+ --gap-list-footer: var(--space-2);
614
+ /* A heading opening the next group in a list. */
615
+ --gap-list-heading: var(--space-4);
616
+ /* The next section of the maneuver planner. */
617
+ --gap-planner-section: var(--space-4);
618
+ /* A graph series' configuration group. */
619
+ --gap-config-group: var(--space-4);
620
+ /* A crew row's contributed meters and the next row. */
621
+ --gap-crew-meters: var(--space-4);
622
+ /* A toolbar's rule and the table under it. */
623
+ --gap-toolbar: var(--space-4);
624
+ /* A section opening below a rule inside a popover. */
625
+ --gap-popover-section: var(--space-12);
626
+ /* A footnote below the list it closes. */
627
+ --gap-footnote: var(--space-10);
628
+ /* The mission bar and the screen under it. */
629
+ --gap-mission-bar: var(--space-8);
630
+ /* Prose blocks. */
631
+ --gap-prose: var(--space-10);
632
+ /* Items of a prose list. */
633
+ --gap-prose-item: var(--space-4);
634
+
635
+ /* Panels. */
636
+ /* A panel's horizontal gutter, shared by its title, toolbar, body, sidebar
637
+ and footer so their content lines up down one column. */
638
+ --gutter-panel: var(--space-16);
639
+ /* How far a horizontal strip (a tab bar, a wide table) may reach past its
640
+ box to the panel's edge. A context name: a panel body steps it to its
641
+ gutter, and a bordered block inside resets it, so nothing bleeds out of a
642
+ card. */
643
+ --bleed-inline: 0px;
644
+ --inset-panel-top: var(--space-8);
645
+ --inset-panel-bottom: var(--space-12);
646
+ --inset-panel-body: var(--inset-panel-top) var(--gutter-panel)
647
+ var(--inset-panel-bottom);
648
+ /* The inset a panel steps down to when it has little room to give: a lone
649
+ framed drawing under a narrow container (a hairline of panel surface
650
+ either side of the frame's own border, so the panel's edge and the
651
+ frame's read as two lines rather than one thick one), and the tiny
652
+ hover-title panel's own edge (the same narrow-panel case; it is always
653
+ that narrow, so it takes the step unconditionally rather than under a
654
+ container query). */
655
+ --inset-tiny: var(--space-6);
656
+ /* A sidebar's top inset when it sits UNDER the body: the body's bottom
657
+ inset is already above it, so the full panel top would double the gap
658
+ between a drawing and the readouts that annotate it. */
659
+ --inset-panel-sidebar-under: var(--space-4);
660
+ /* No bottom inset: the body's own gap is the only space between the header and the content. */
661
+ --inset-panel-header: 0 var(--gutter-panel);
662
+ /* Above a hand-composed header row; inside a Panel the rail band is that inset. */
663
+ --inset-panel-header-top: var(--space-6);
664
+ --inset-panel-toolbar: var(--space-6) var(--gutter-panel) 0;
665
+ --inset-panel-footer: var(--space-6) var(--gutter-panel) var(--space-8);
666
+ /* A panel header's title and its aside. */
667
+ --gap-panel-header: var(--space-8);
668
+ /* Items in a panel header's aside. */
669
+ --gap-panel-aside: var(--space-4);
670
+ /* Sections side by side in a panel body. */
671
+ --gap-panel-columns: var(--space-16);
672
+ /* Sections stacked in a panel body's grid. */
673
+ --gap-panel-sections: var(--space-12);
674
+ /* The height of a panel's full-bleed trend strip along its bottom edge. */
675
+ --size-panel-trend: var(--space-24);
676
+ /* The least height a filling Section keeps when its siblings take the rest; the panel body scrolls past it. */
677
+ --size-section-fill-floor: calc(var(--space-24) * 4);
678
+ /* The band every panel reserves at its top edge for the delay rail. */
679
+ --panel-rail-band: var(--space-16);
680
+
681
+ /* Command delay. */
682
+ /* The strips around a panel's commands, on the panel gutter. */
683
+ --outset-command-strip: var(--space-4) var(--gutter-panel);
684
+ /* The delay stream's legend, on the panel gutter. */
685
+ --outset-delay-legend: 0 var(--gutter-panel) var(--space-4);
686
+ --gap-delay-stream: var(--space-4);
687
+ --gap-delay-rail: var(--space-8);
688
+ --gap-rail-summaries: var(--space-8);
689
+ --offset-rail-hint: var(--space-4);
690
+ /* How far the collapse hint's click target reaches past its glyph. */
691
+ --outset-rail-hint-target: var(--space-8);
692
+ --gap-message-stack: var(--space-4);
693
+ --inset-command-list: var(--space-4) var(--space-8);
694
+ --gap-command-list-column: var(--space-8);
695
+ --gap-command-list-row: var(--space-2);
696
+ --gap-command-row: var(--space-6);
697
+
698
+ /* Surfaces. */
699
+ /* A bordered or filled box that holds content: a card, a record, a popover body. */
700
+ --inset-surface: var(--space-6) var(--space-8);
701
+ /* A card that IS its screen, holding its own controls. */
702
+ --inset-surface-standalone: var(--space-10) var(--space-12);
703
+ /* A label sized by its text: a badge, a pill, an inline code span. */
704
+ --inset-chip: var(--space-hair) var(--space-6);
705
+ /* The same on a larger badge. */
706
+ --inset-chip-roomy: var(--space-hair) var(--space-8);
707
+ /* A chip that carries a live reading. */
708
+ --inset-chip-readout: var(--space-2) var(--space-8);
709
+ /* A single-token status pill, and a message pill that shares its shape. */
710
+ --inset-pill: var(--space-6) var(--space-12);
711
+ /* A floating or full-width banner on the 16px gutter. */
712
+ --inset-banner: var(--space-8) var(--space-16);
713
+ /* An alert band that spans the app or a menu. */
714
+ --inset-alert-band: var(--space-10) var(--space-16);
715
+ /* A bordered status box sized to --control-height. */
716
+ --inset-status-box: var(--space-4) var(--space-8);
717
+ /* A popover's content. */
718
+ --inset-popover: var(--space-8);
719
+ /* A tooltip's content. */
720
+ --inset-tooltip: var(--space-4) var(--space-8);
721
+ /* The drop between an anchor and the popover it opens. */
722
+ --offset-popover: var(--space-4);
723
+ /* A popover shadow's offset and blur. */
724
+ --shadow-popover-drop: 0 var(--space-4) var(--space-12);
725
+ /* Visual content held inside a FramedDisplay. */
726
+ --inset-framed-display: var(--space-4);
727
+ /* A panel sharing a FramedDisplay slot, whose frame draws the edge. */
728
+ --inset-frame-panel: var(--space-8) var(--space-10);
729
+ /* A box pinned into its container's corner. */
730
+ --offset-corner: var(--space-4);
731
+ /* A graph notice pinned over the plot's bottom-left corner, and its inset. */
732
+ --offset-graph-notice-bottom: var(--space-4);
733
+ --offset-graph-notice-left: var(--space-8);
734
+ --inset-notice-pill: var(--space-2) var(--space-4);
735
+ /* A caption inlaid on a FramedDisplay's own top-left corner. */
736
+ --offset-frame-caption-top: var(--space-6);
737
+ --offset-frame-caption-left: var(--space-8);
738
+ /* A footer line inlaid on a FramedDisplay's own bottom-left corner. */
739
+ --offset-frame-footer-bottom: var(--space-6);
740
+ --offset-frame-footer-left: var(--space-8);
741
+
742
+ /* Controls. */
743
+ /* A box you press or type into, on the --control-height floor. */
744
+ --inset-control: var(--space-6) var(--space-12);
745
+ /* The same class in a box that cannot reach the floor. */
746
+ --inset-control-compact: var(--space-hair) var(--space-4);
747
+ /* The same class standing on its own, one tier above the floor. */
748
+ --inset-control-prominent: var(--space-8) var(--space-12);
749
+ /* A small button, toggle or chip control. */
750
+ --inset-control-small: var(--space-2) var(--space-8);
751
+ /* The coarse-pointer widening of a control. */
752
+ --inset-control-touch: var(--space-8) var(--space-16);
753
+ /* The coarse-pointer widening of a small control. */
754
+ --inset-control-small-touch: var(--space-6) var(--space-10);
755
+ /* A chromeless glyph button. */
756
+ --inset-glyph-button: var(--space-2) var(--space-4);
757
+ /* A glyph button in a row, padded across only. */
758
+ --inset-glyph: 0 var(--space-4);
759
+ /* The same, packed tight against the row's text. */
760
+ --inset-glyph-tight: 0 var(--space-2);
761
+ /* A square icon-only button. */
762
+ --inset-icon-button: var(--space-6);
763
+ --inset-icon-button-compact: var(--space-4);
764
+ --inset-icon-button-tight: var(--space-2);
765
+ --inset-icon-button-touch: var(--space-8);
766
+ /* A command group's commit button. */
767
+ --inset-commit-button: var(--space-4) var(--space-12);
768
+ /* A compact button in a form's footer. */
769
+ --inset-form-footer-button: var(--space-4) var(--space-10);
770
+ /* A glyph button in a dense grid, taller than wide. */
771
+ --inset-warp-button: var(--space-6) var(--space-4);
772
+ /* A text button inside a FAB prompt. */
773
+ --inset-prompt-button: 0 var(--space-12);
774
+ /* A file picker's button on a coarse pointer. */
775
+ --inset-file-button-touch: var(--space-10) var(--space-16);
776
+ /* A momentary button on the virtual pad. */
777
+ --inset-pad-button: var(--space-10) 0;
778
+ /* A stepper's buttons and value. */
779
+ --inset-stepper: var(--space-4) var(--space-8);
780
+ --size-stepper-button: var(--space-24);
781
+ /* A switch label's touch target. */
782
+ --inset-switch-touch: 0 var(--space-6);
783
+ /* A segmented control's track. */
784
+ --inset-segment-track: var(--space-2);
785
+ /* A tab strip's track, and the selection blob inside it. */
786
+ --inset-tab-track: var(--space-4);
787
+ --inset-tab-compact: var(--space-4) var(--space-6);
788
+ --inset-tab-compact-touch: var(--space-4) var(--space-8);
789
+ /* A jog wheel's content, and its value label. */
790
+ --inset-jog-wheel: var(--space-4);
791
+ --inset-jog-wheel-compact: var(--space-2);
792
+ --inset-jog-wheel-label: 0 var(--space-2);
793
+ /* The room a Meter figure keeps for the held mark drawn outside it. */
794
+ --inset-meter-mark: var(--space-6);
795
+
796
+ /* Fields. */
797
+ --inset-field: var(--space-6) var(--space-8);
798
+ --inset-field-compact: var(--space-4) var(--space-6);
799
+ --inset-field-touch: var(--space-10) var(--space-12);
800
+ /* The trailing room a search field keeps for its clear control. */
801
+ --inset-field-clearable-end: calc(var(--icon-size-control) + var(--space-12));
802
+ /* The trailing room a select keeps for its drawn arrow. */
803
+ --inset-field-select-end: var(--space-24);
804
+ /* The two strokes of a select's arrow: each one's size, and how far each sits from the field's end. */
805
+ --size-select-arrow: var(--space-4);
806
+ --offset-select-arrow-near: var(--space-12);
807
+ --offset-select-arrow-far: var(--space-16);
808
+ /* A boxed form's surface. */
809
+ --inset-form-box: var(--space-8) var(--space-10);
810
+ /* A search input flush in its band. */
811
+ --inset-search-input: var(--space-4) 0;
812
+
813
+ /* Menus, lists and tables. */
814
+ --inset-menu-list: var(--space-4) 0;
815
+ --inset-menu-item: var(--space-6) var(--space-8);
816
+ /* A group label, hugging the items under it. */
817
+ --inset-menu-group-label: var(--space-8) var(--space-8) var(--space-4);
818
+ --inset-empty-menu: var(--space-12) var(--space-8);
819
+ --inset-row: var(--space-2) 0;
820
+ --inset-row-pressable: var(--space-4) var(--space-6);
821
+ /* A row picked from a select-one list. */
822
+ --inset-selectable-row: var(--space-2) var(--space-4);
823
+ /* A nested row under its parent. */
824
+ --indent-row: var(--space-12);
825
+ /* A line of text flush in its container, given a little vertical room. */
826
+ --inset-line: var(--space-2) 0;
827
+ /* A part row in a dense parts list. */
828
+ --inset-part-row: var(--space-hair) 0;
829
+ /* A label/value row in an expanded detail. */
830
+ --inset-detail-row: var(--space-4) 0;
831
+ /* A section header lined up with the toggles beside it. */
832
+ --inset-list-heading: var(--space-2) var(--space-4);
833
+ --gutter-table-cell: var(--space-8);
834
+ --inset-table-cell: var(--space-4) var(--gutter-table-cell);
835
+ --inset-table-section: var(--space-6) var(--gutter-table-cell);
836
+ --inset-table-detail: 0 var(--gutter-table-cell) var(--space-6);
837
+ --inset-table-empty: var(--space-8);
838
+ --inset-table-footer: var(--space-10) var(--gutter-table-cell) var(--space-4);
839
+ /* A narrow column header, taller than wide. */
840
+ --inset-table-narrow-header: var(--space-6) var(--space-4);
841
+ /* A reach table's cells. */
842
+ --inset-reach-cell: var(--space-2) var(--space-4);
843
+ /* A fleet roster cell, and the contributed lines hung under its row. */
844
+ --gutter-roster-cell: var(--space-6);
845
+ --inset-roster-cell: 0 var(--gutter-roster-cell);
846
+ /* A transfer window row and the expander under it, sharing one gutter. */
847
+ --gutter-window-row: var(--space-12);
848
+ --inset-window-row: var(--space-8) var(--gutter-window-row);
849
+ --inset-window-expander: var(--space-8) var(--gutter-window-row)
850
+ var(--space-4);
851
+ /* A tech node's expanded body, under the node row's own inset. */
852
+ --inset-node-body: var(--space-4) var(--space-10) var(--space-8);
853
+ /* A tech-graph card, sized against its fixed card height. */
854
+ --inset-graph-card: var(--space-4) var(--space-8);
855
+ /* The vessel switch panel. */
856
+ --inset-switch-panel: var(--space-2);
857
+ /* The spacing around a reveal toggle at a list header. */
858
+ --outset-reveal-toggle: var(--space-2) var(--space-2) var(--space-4);
859
+
860
+ /* Rules. */
861
+ /* Content opening below a divider rule. */
862
+ --inset-below-rule: var(--space-6);
863
+ /* A footnote below its rule. */
864
+ --inset-footnote-rule: var(--space-8);
865
+ /* A toolbar above its bottom rule. */
866
+ --inset-toolbar-bottom: var(--space-10);
867
+
868
+ /* Messages and empty states. */
869
+ /* A message that fills its container. */
870
+ --inset-empty-state: var(--space-16);
871
+ /* A message that fills a tile: an error, a placeholder, a no-data note. */
872
+ --inset-tile-message: var(--space-12);
873
+ /* A one-line "nothing here" note in a list. */
874
+ --inset-empty-note: var(--space-8) 0;
875
+ --inset-empty-roster: var(--space-6) 0;
876
+ --inset-empty-modal-list: var(--space-12) 0;
877
+ --inset-empty-popover: var(--space-16) 0;
878
+ --inset-chart-empty: var(--space-24) 0;
879
+ /* A widget's refusal to draw. */
880
+ --inset-refusal: var(--space-8);
881
+ /* A message standing in for a whole screen. */
882
+ --inset-screen-message: var(--space-24);
883
+ /* A stand-in box with no content of its own. */
884
+ --inset-placeholder: var(--space-8);
885
+ /* Contributed plots, framed by themselves. */
886
+ --inset-contributed-plots: var(--space-8) 0;
887
+ /* The maneuver planner's shortfall banner. */
888
+ --inset-feasibility-banner: var(--space-6) var(--space-10);
889
+ /* A bordered inline confirmation bar. */
890
+ --inset-confirm-bar: var(--space-10);
891
+
892
+ /* Screens, overlays and modals. */
893
+ /* A screen's outer gutter, before any safe-area inset. */
894
+ --gutter-screen: var(--space-24);
895
+ /* The gutter of a widget screen whose children draw no edge of their own; the panel pads the sides. */
896
+ --gutter-screen-body: var(--space-8) 0;
897
+ /* The dashboard grid's gutter between tiles. */
898
+ --gutter-dashboard: var(--space-8);
899
+ /* A tile's drag header. */
900
+ --inset-tile-header: 0 var(--space-4);
901
+ /* A dialog that stands alone over the app. */
902
+ --inset-dialog: var(--space-24);
903
+ --inset-modal-bar: var(--space-12) var(--space-16);
904
+ --inset-modal-body: var(--space-16);
905
+ /* The bands of a full-surface overlay, on one gutter. */
906
+ --gutter-overlay: var(--space-16);
907
+ --inset-overlay-band: var(--space-12) var(--gutter-overlay);
908
+ --inset-overlay-heading: var(--space-6) var(--gutter-overlay);
909
+ --inset-overlay-chips: var(--space-10) var(--gutter-overlay);
910
+ --inset-overlay-empty: var(--space-24) var(--gutter-overlay);
911
+ /* A reference sheet's header and body. */
912
+ --inset-reference-sheet: var(--space-10) var(--space-16);
913
+ /* The mission bar. */
914
+ --inset-mission-bar: 0 var(--space-2);
915
+ /* The Go/No-Go board. */
916
+ --inset-gonogo-board: var(--space-12);
917
+ /* A QR code's quiet zone. */
918
+ --inset-qr: var(--space-12);
919
+ /* A settings row, and a nested settings row. */
920
+ --inset-settings-row: var(--space-6) 0;
921
+ --indent-settings: var(--space-16);
922
+ /* The chapter editor's band. */
923
+ --inset-chapters-band: var(--space-10) var(--space-12) var(--space-12);
924
+ /* A device-picker row. */
925
+ --inset-picker-row: var(--space-6) 0;
926
+ /* A wizard, and the boxes inside it. */
927
+ --inset-wizard: var(--space-12);
928
+ --inset-wizard-box: var(--space-8);
929
+ /* A virtual device's frame buffer. */
930
+ --inset-device-display: var(--space-8);
931
+ /* The virtual pad's centre mark, off its track's ends. */
932
+ --offset-pad-mark: var(--space-4);
933
+ /* An inline code run in a banner. */
934
+ --inset-code-inline: 0 var(--space-2);
935
+ /* A free-standing sheet of cards, and a card on it. */
936
+ --inset-sheet: var(--space-12);
937
+ --inset-sheet-card: var(--space-8) var(--space-12);
938
+
939
+ /* Consoles and logs. */
940
+ /* A scrolling log's inner inset. */
941
+ --inset-log: var(--space-8);
942
+ /* A console's foot, and the chips pinned to its edges. */
943
+ --inset-console-foot: var(--space-8);
944
+ /* The foot's top when a reading straddles the composer's border. */
945
+ --inset-console-foot-straddled: var(--space-16);
946
+ --gap-console-foot: var(--space-6);
947
+ --gap-console-readings: var(--space-6);
948
+ --gap-composer: var(--space-6);
949
+ /* The prompt glyph and the line typed after it. */
950
+ --gap-prompt-glyph: var(--space-8);
951
+
952
+ /* Indents and marks. */
953
+ /* A contributed aside or detail under the row it belongs to. */
954
+ --indent-aside: var(--space-8);
955
+ /* The burn axis, off the row's leading edge. */
956
+ --indent-burn-axis: var(--space-8);
957
+ --size-burn-axis: var(--space-12);
958
+ --size-burn-mark: var(--space-6);
959
+ /* A short mark rather than a rule: a card's identity tab, a threshold tick. */
960
+ --size-mark: var(--space-24);
961
+ /* The height a comms hop keeps with no detail to show. */
962
+ --size-comms-hop: var(--space-16);
963
+ /* The gauge bar under a tiny tile's figure: the figure and the bar share a content box barely taller than the figure. */
964
+ --size-tiny-gauge: var(--space-2);
965
+
966
+ /*
967
+ * RADIUS. Three jobs plus two shape sentinels.
968
+ *
969
+ * The ladder was `xs`/`sm`/`md`/`lg` at 2/3/4/6px, which answered
970
+ * "which number" and nothing else, the same t-shirt complaint already
971
+ * ruled on for spacing. Measured over its 515 sites: `xs` (236) and
972
+ * `sm` (140) do not separate at all, buttons and inputs take both, and
973
+ * `md` (28) mostly joins them. One name covers all three, and every
974
+ * site moves by at most a pixel.
975
+ *
976
+ * regular Every ordinary corner in the app: chips, badges, buttons,
977
+ * inputs, rows, cells, swatches, cards, menus, dropdowns.
978
+ * 400+ sites, and there was never a rule distinguishing
979
+ * them.
980
+ * floating A box that sits ABOVE the app rather than in it. Eight of
981
+ * `lg`'s nine sites were already exactly that population
982
+ * (`Modal`, three consent/reference modals, `Fab`,
983
+ * `HostedLanding`, `GoNoGo`, the in-flight queue panel), so
984
+ * this names a job the ladder was already being used for by
985
+ * accident. It pairs deliberately with the `z` ladder's
986
+ * floating rungs: a thing that floats wants both, and a
987
+ * reader reaching for one should reach for the other.
988
+ * display-frame
989
+ * `FramedDisplay`'s corner, and only that. Bigger than
990
+ * everything else on purpose: it is what separates a box
991
+ * that is LOOKED AT from a box that is READ, which the kit
992
+ * asserted in a doc comment and nowhere in its pixels while
993
+ * a frame and a card were both drawn at 3px.
994
+ *
995
+ * Named for the frame rather than for "visual", so the next
996
+ * ambiguous box cannot reach for it and feel justified. It
997
+ * is a container external belonging to ONE container.
998
+ *
999
+ * It STEPS, which is what makes it a custom property rather
1000
+ * than a constant: `Block`'s side asides re-declare it, so
1001
+ * a frame holding a 40px avatar in a record's left aside
1002
+ * takes a corner proportioned to that box without the frame
1003
+ * knowing its own size. Content asks, context answers, the
1004
+ * same mechanism `--gap-related` uses.
1005
+ *
1006
+ * `pill` and `circle` are SHAPES, not sizes. 50% is a circle on a
1007
+ * square box; 999px clamps to exactly half the shorter side, so it
1008
+ * renders identically to a hand-computed half-height while surviving a
1009
+ * change to that height. Every stadium in the codebase (the 7px switch
1010
+ * track, the 10px chip, the 12px filter chip, the 20px and 24px FAB
1011
+ * prompts) should reach for `pill` rather than recomputing the half.
1012
+ * Neither sentinel belongs on the numeric ramp.
1013
+ */
1014
+ --radius-regular: 3px;
1015
+ --radius-floating: 6px;
1016
+ --radius-display-frame: 8px;
1017
+ /* What `Block`'s side asides step --radius-display-frame to. */
1018
+ --radius-display-frame-aside: 4px;
1019
+ --radius-pill: 999px;
1020
+ --radius-circle: 50%;
1021
+
1022
+ /*
1023
+ * CONTROL HEIGHT. One rung, and one is the point of it.
1024
+ *
1025
+ * Every boxed control in the kit used to take its height from whatever was
1026
+ * inside it: a small toggle around a 14px glyph came out at 20px, the same
1027
+ * toggle around a word at 17, a bordered status readout at 27 and a default
1028
+ * button at 30. Put four of them in a row, which is what a widget bar IS, and
1029
+ * the row is visibly ragged with no one declaration to blame. Commcast's
1030
+ * radio bar is where the operator finally said so.
1031
+ *
1032
+ * So `size` on a control chooses its type scale and its inset, never its
1033
+ * height: a control is a control-shaped box. Read it as a `min-height` so
1034
+ * genuinely taller content (a wrapped label, a coarse-pointer target) still
1035
+ * grows the box rather than clipping inside it, and pair it with
1036
+ * `--line-height-flush` so a line box's unused descender headroom is not what
1037
+ * decides whether a control clears the floor.
1038
+ *
1039
+ * 28px is where the two commonest controls in the app already sat, so this
1040
+ * lifts the small ones to meet them rather than moving everything. The check
1041
+ * that it is high enough, worth writing down because a floor a control
1042
+ * OVERSHOOTS is a floor that does nothing for it: a default Button is
1043
+ * 12 inset + 2 border + a 14px glyph = 28, a small ToggleButton 4 + 2 + 14 =
1044
+ * 20, and a StatusIndicator 8 + 2 + 11px text at a 1.4 line height = 25.4.
1045
+ * Every one of them lands on the floor. The indicator is why its own inset
1046
+ * came down a rung; at the old one it cleared 28 and stayed the odd box out.
1047
+ * Coarse pointers ignore all of this and take the 44px touch target.
1048
+ *
1049
+ * A box that cannot reach this floor at all is a different case from one
1050
+ * that undershoots it. Ten pressable glyphs and chips would OVERFLOW what
1051
+ * holds them at 28: an 18px drag header, a note whose whole one-line body is
1052
+ * about that tall, a fixed overlay with a logged history of swallowing the
1053
+ * clicks underneath it. They take `--inset-control-compact` above, and each
1054
+ * names the box it is stuck inside.
1055
+ */
1056
+ --control-height: 28px;
1057
+
1058
+ /*
1059
+ * Z-INDEX, as named layers rather than a numeric ramp.
1060
+ *
1061
+ * Only app-global chrome belongs here. Roughly half the z-index
1062
+ * declarations in the repo (the 0/1/2, 5/6 and 10/20 families) are
1063
+ * LOCAL sibling ordering inside one component's own stacking context,
1064
+ * where the absolute number is meaningless and only the relative order
1065
+ * matters. Those stay literal; pulling them onto this ladder would
1066
+ * collapse several independent contexts into one and can invert either
1067
+ * pair.
1068
+ *
1069
+ * The order below is derived from what the app does today, with two
1070
+ * deliberate corrections where today's numbers are already inverted:
1071
+ *
1072
+ * base 0 default flow.
1073
+ * sticky 100 in-flow chrome that pins while content scrolls
1074
+ * (the replay session bar, sticky table headers).
1075
+ * dropdown 200 popovers anchored to a control (combobox, tag
1076
+ * autocomplete, the science-augment menu). MUST
1077
+ * outrank sticky: a select opened next to a pinned
1078
+ * bar has to render over it. One of these menus
1079
+ * currently sits at 20 and renders BEHIND the
1080
+ * sticky bar; this rung is the fix.
1081
+ * overlay 800 full-surface overlays that replace the dashboard
1082
+ * (pushed dashboard, station overlay).
1083
+ * fab 900 the bottom-right floating control cluster.
1084
+ * Strictly BELOW backdrop: the FAB opens the
1085
+ * backdrop and must be covered by it.
1086
+ * backdrop 950 the scrim behind a modal. Covers the FAB, sits
1087
+ * under the dialog surface.
1088
+ * modal 1000 dialog surfaces.
1089
+ * toast 1100 transient notifications. ABOVE modal on purpose:
1090
+ * an alarm or offline banner has to reach the
1091
+ * operator even while a dialog is open. The banner
1092
+ * stack sits at 90 today, which renders it behind
1093
+ * the FAB it is positioned beside.
1094
+ * critical 10000 consent gates. Deliberately far off the ramp;
1095
+ * a consent surface may never be occluded.
1096
+ */
1097
+ --z-base: 0;
1098
+ --z-sticky: 100;
1099
+ --z-dropdown: 200;
1100
+ --z-overlay: 800;
1101
+ --z-fab: 900;
1102
+ --z-backdrop: 950;
1103
+ --z-modal: 1000;
1104
+ --z-toast: 1100;
1105
+ --z-critical: 10000;
1106
+
1107
+ /*
1108
+ * MOTION. Durations and easings for UI transitions only.
1109
+ *
1110
+ * The census splits cleanly into two populations. 52 declarations are
1111
+ * UI transitions (hover, focus, open, close) in the 60ms to 400ms
1112
+ * band; 19 are PHYSICAL animation, where the duration encodes
1113
+ * something real: a resource bar tied to the telemetry sample cadence,
1114
+ * a reticle chasing a moving target, a 1Hz terminal caret, a spinner,
1115
+ * an indicator whose 1s-versus-2s period is how the operator reads
1116
+ * connection state. Those keep their literal values and their
1117
+ * `linear` / `step-end` timing; folding them onto a UI scale would
1118
+ * destroy information the widget is deliberately carrying.
1119
+ *
1120
+ * instant 80ms binary state flips, the visual echo of a press.
1121
+ * fast 120ms small property changes: colour, border, opacity.
1122
+ * base 150ms THE DEFAULT for a UI transition.
1123
+ * slow 200ms larger moves: a panel expanding, a row sliding.
1124
+ * entrance 320ms paired with `--ease-entrance`; the banner
1125
+ * entrance recipe, always used together.
1126
+ *
1127
+ * standard the default curve for a UI transition.
1128
+ * emphasis symmetric, for looping attention pulses.
1129
+ * linear continuous physical motion. Required, not stylistic: an
1130
+ * eased bar fill appears to stall between samples.
1131
+ * entrance an overshoot tuned to a 40px slide with
1132
+ * `transform-origin: right center`. Do not substitute a
1133
+ * generic ease-out; it changes the physical read.
1134
+ *
1135
+ * These tokens do NOT opt anything out of the reduced-motion damper in
1136
+ * `packages/app/src/styles/global.css`, which clamps every duration to
1137
+ * 0.01ms under `prefers-reduced-motion: reduce`. Indefinite animations
1138
+ * still need their own `@media (prefers-reduced-motion: no-preference)`
1139
+ * guard, and the `animation` shorthand must stay INSIDE that guard
1140
+ * with its keyframes.
1141
+ */
1142
+ --duration-instant: 80ms;
1143
+ --duration-fast: 120ms;
1144
+ --duration-base: 150ms;
1145
+ --duration-slow: 200ms;
1146
+ --duration-entrance: 320ms;
1147
+
1148
+ --ease-standard: ease;
1149
+ --ease-emphasis: ease-in-out;
1150
+ --ease-linear: linear;
1151
+ --ease-entrance: cubic-bezier(0.22, 1, 0.36, 1);
124
1152
  }
125
1153
 
126
1154
  @media (pointer: coarse) {
127
1155
  :root {
1156
+ --font-size-2xs: 11px;
128
1157
  --font-size-xs: 12px;
129
1158
  --font-size-sm: 13px;
130
1159
  --font-size-base: 15px;
131
1160
  --font-size-lg: 17px;
1161
+
1162
+ /* The step a raw size prop cannot have. A Fab already grows its own box
1163
+ 40 -> 48 here; without this its glyph stayed behind. */
1164
+ --icon-size-control: 15px;
1165
+ --icon-size-standalone: 24px;
132
1166
  }
133
1167
  }