@orbytes/astrolab 0.3.0 → 0.4.0-next.2

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 (82) hide show
  1. package/README.md +261 -116
  2. package/bin/pin-gallery.mjs +53 -19
  3. package/defaults.mjs +72 -20
  4. package/dist/core/virtual-module/virtual-routes.js +12 -1
  5. package/docs/PIN-CONTRACT.md +84 -10
  6. package/docs/PIN.md +117 -37
  7. package/index.d.ts +41 -23
  8. package/index.mjs +46 -91
  9. package/package.json +7 -3
  10. package/src/Home.astro +166 -264
  11. package/src/LabHead.astro +37 -1047
  12. package/src/chrome/ActionsMenu.astro +97 -0
  13. package/src/chrome/ComponentCard.astro +76 -0
  14. package/src/chrome/Icon.astro +21 -0
  15. package/src/chrome/LICENSE-icons +43 -0
  16. package/src/chrome/Nav.astro +131 -0
  17. package/src/chrome/Panel.astro +117 -0
  18. package/src/chrome/Properties.astro +104 -0
  19. package/src/chrome/SectionsTree.astro +128 -0
  20. package/src/chrome/Shell.astro +120 -0
  21. package/src/chrome/Sprite.astro +23 -0
  22. package/src/chrome/StoryView.astro +192 -0
  23. package/src/chrome/Tree.astro +86 -0
  24. package/src/chrome/ViewportControls.astro +173 -0
  25. package/src/chrome/ViewportStage.astro +55 -0
  26. package/src/chrome/fonts/OFL.txt +93 -0
  27. package/src/chrome/fonts/inter-latin-wght-normal.woff2 +0 -0
  28. package/src/chrome/icons.ts +68 -0
  29. package/src/chrome/marks-client.ts +75 -0
  30. package/src/chrome/model.ts +156 -0
  31. package/src/chrome/navbar-client.ts +324 -0
  32. package/src/chrome/params-client.ts +434 -0
  33. package/src/chrome/pins-data.ts +63 -0
  34. package/src/chrome/shell-client.ts +468 -0
  35. package/src/chrome/site-data.ts +230 -0
  36. package/src/chrome/trees.ts +257 -0
  37. package/src/chrome/viewport-client.ts +405 -0
  38. package/src/chrome/views/Assets.astro +125 -0
  39. package/src/chrome/views/Pages.astro +214 -0
  40. package/src/chrome/views/Placeholder.astro +37 -0
  41. package/src/chrome/views/Tasks.astro +79 -0
  42. package/src/core/LICENSE-astrobook +21 -0
  43. package/src/core/lib/components/home.astro +4 -2
  44. package/src/core/lib/pages/story.astro +12 -10
  45. package/src/core/utils/kebab-case.ts +2 -2
  46. package/src/core/virtual-module/virtual-routes.ts +20 -4
  47. package/src/pin/board.mjs +414 -190
  48. package/src/pin/index.mjs +67 -22
  49. package/src/pin/tickets.mjs +6 -5
  50. package/src/pin/toolbar.js +82 -4
  51. package/src/shell/Browse.astro +131 -353
  52. package/src/shell/Viewport.astro +22 -1315
  53. package/src/shell/lab-index.ts +28 -18
  54. package/src/shell/lab-params.ts +113 -6
  55. package/src/shell/live-files.mjs +212 -10
  56. package/src/shell/marks.mjs +17 -41
  57. package/src/ui/components/app.astro +5 -7
  58. package/src/ui/components/preview-layout.astro +17 -0
  59. package/src/ui/components/theme-script.astro +17 -5
  60. package/src/ui/lab.css +3754 -371
  61. package/virtual.d.ts +13 -4
  62. package/bin/lab-cull.mjs +0 -401
  63. package/src/shell/CardGrid.astro +0 -297
  64. package/src/ui/components/build-path.ts +0 -13
  65. package/src/ui/components/build-tree.ts +0 -108
  66. package/src/ui/components/collapse-duration.ts +0 -28
  67. package/src/ui/components/compress-terms.ts +0 -10
  68. package/src/ui/components/dashboard-layout.astro +0 -39
  69. package/src/ui/components/home.astro +0 -65
  70. package/src/ui/components/layout.astro +0 -110
  71. package/src/ui/components/sidebar-button-fullscreen.astro +0 -38
  72. package/src/ui/components/sidebar-button-search.astro +0 -23
  73. package/src/ui/components/sidebar-button-theme.astro +0 -9
  74. package/src/ui/components/sidebar-button.astro +0 -24
  75. package/src/ui/components/sidebar-resize-handle.astro +0 -74
  76. package/src/ui/components/sidebar-search-panel.astro +0 -41
  77. package/src/ui/components/sidebar-search-script.ts +0 -103
  78. package/src/ui/components/sidebar-title.astro +0 -17
  79. package/src/ui/components/sidebar-tree-node.astro +0 -143
  80. package/src/ui/components/sidebar-tree.astro +0 -84
  81. package/src/ui/components/sidebar.astro +0 -29
  82. package/src/ui/components/theme-toggle.astro +0 -63
package/src/ui/lab.css CHANGED
@@ -9,541 +9,3924 @@
9
9
 
10
10
  So the line runs between CHROME and PREVIEW, and it runs exactly there:
11
11
 
12
- CHROME the lab's own furniture — layout, sidebar, search, toolbar, the home page, the
13
- folder pages, the viewport configurator. Styled HERE and nowhere else. It never
14
- receives the consumer's `css` list and never receives the consumer's `head`.
15
- PREVIEW the story itself (/lab/stories/<id>) and the story rendered inside the dashboard.
16
- It receives the consumer's `css` list and `head` — that is how the site's tokens
17
- and fonts arrive, and seeing a component as the real site renders it is the whole
18
- point of the lab.
19
-
20
- ISOLATION RUNS BOTH WAYS, and that is why this file looks the way it does:
21
-
22
- 1. EVERY TOKEN IS `--lab-*`. The consumer owns `--color-background`, `--font-body`,
23
- `--radius-pill` and the rest; a lab that read those names would wear the client's brand,
24
- which is the exact thing the ruling forbids. Nothing here reads a name the consumer could
25
- also define, so the two palettes cannot collide even where both stylesheets are in one
26
- document (the dashboard, which must carry both).
27
- 2. NOTHING HERE IS A BARE ELEMENT SELECTOR that could reach a story. Every rule is anchored
28
- on a chrome class, a chrome id, or a chrome custom element. `html`/`body` carry the
29
- `.lab-shell` class rather than being styled as elements, and the chrome's font, colour
30
- and font-size are set on the chrome's own boxes — never inherited down from `body`, which
31
- on a dashboard page is shared with the preview.
32
- 3. NO CASCADE LAYER. The previous build put the lab's token fallbacks in
33
- `@layer orbytes-lab-fallback` so the consumer would win; that was the leak, expressed as
34
- a policy. The lab wins now.
35
-
36
- WHAT THIS FILE IS NOT. This is the FUNCTION pass, not the design pass: plain, legible,
37
- working, dark mode intact. Semantic class names (`.lab-sidebar`, `.lab-tree-row`) rather than
38
- a hand-rolled utility set, precisely so the next pass is a restyle and not a rewrite. It
39
- replaces the UnoCSS atomic classes the upstream `@astrobook/ui` was built from — this package
40
- ships source and has no build step, so Uno was never an option here.
41
-
42
- Typography ships with the package: a system font stack, no webfont, no build step, no
43
- consumer dependency.
12
+ CHROME the lab's own furniture — both sidebar levels, the navbar, the home page, the folder,
13
+ page, asset and task views, the viewport stage. Styled HERE and nowhere else. It
14
+ never receives the consumer's `css` list and never receives the consumer's `head`.
15
+ PREVIEW the story (/lab/stories/<id>) and the site's own pages, which the chrome only ever
16
+ shows inside an <iframe>. They receive the consumer's CSS and head — that is how the
17
+ site's tokens and fonts arrive, and seeing a component as the real site renders it is
18
+ the whole point of the lab.
19
+
20
+ THE REDESIGN (2026-09-24) MADE THE LINE PHYSICAL. Until then the dashboard rendered the story
21
+ INLINE, beside the chrome, so one document had to carry both stylesheets and this file had to
22
+ survive sharing a <body> with the site. Every preview is now an <iframe> — its own document —
23
+ so no chrome page carries a byte of consumer CSS, and nothing here can reach a story even by
24
+ accident. The rules below still hold, because they are what keeps that true if an inline
25
+ render ever comes back:
26
+
27
+ 1. EVERY TOKEN IS `--lab-*`. The consumer owns `--color-background`, `--font-body` and the
28
+ rest; a lab that read those names would wear the client's brand.
29
+ 2. NOTHING HERE IS A BARE ELEMENT SELECTOR. Every rule is anchored on a chrome class, id or
30
+ attribute. `html`/`body` carry the `.lab-shell` class rather than being styled as elements.
31
+ 3. NO CASCADE LAYER. A fallback layer that lets the consumer win was the leak, written as a
32
+ policy; it is deleted and stays deleted.
33
+
34
+ THE LOOK is the Figma file "Astrolab", frame `Desktop` (20036:40696): the Untitled UI neutral
35
+ ramp, Inter at 14/20, 6–12px radii, a brand violet for "you are here" and a lime footer. Every
36
+ value below that came from a Figma variable says so in its name's comment; the dark ramp is not
37
+ in the file and is derived from the same neutral scale.
38
+
39
+ Typography ships with the package: Inter's variable latin subset (./../chrome/fonts, SIL OFL),
40
+ 48 KB, served by the consumer's own dev server — nothing is fetched from the network.
44
41
  ============================================================================================= */
45
42
 
43
+ @font-face {
44
+ font-family: "Lab Inter";
45
+ font-style: normal;
46
+ font-display: swap;
47
+ font-weight: 100 900;
48
+ src: url("../chrome/fonts/inter-latin-wght-normal.woff2") format("woff2-variations");
49
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
50
+ U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
51
+ U+FEFF, U+FFFD;
52
+ }
53
+
46
54
  /* ---------------------------------------------------------------------------------------------
47
55
  Tokens. Light is the default; `.dark` on <html> is set by the theme script before paint
48
- (src/ui/components/theme-script.astro) and is the hook LabHead's own styles key off too.
56
+ (./components/theme-script.astro).
49
57
  --------------------------------------------------------------------------------------------- */
50
58
  :root {
51
- --lab-font-body:
52
- system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
53
- "Apple Color Emoji", "Segoe UI Emoji";
59
+ --lab-font: "Lab Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
54
60
  --lab-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
55
- /* The chrome has one face today. The name exists so phase 2 can split display from body
56
- without touching every rule that asks for it. */
57
- --lab-font-heading: var(--lab-font-body);
58
-
59
- --lab-color-bg: #ffffff;
60
- --lab-color-bg-sunken: #f9fafb;
61
- --lab-color-surface: #f4f4f5;
62
- --lab-color-surface-raised: #ffffff;
63
- --lab-color-border: #e4e4e7;
64
- --lab-color-border-strong: #d4d4d8;
65
-
66
- --lab-color-text: #3f3f46;
67
- --lab-color-text-strong: #18181b;
68
- --lab-color-text-muted: #71717a;
69
- --lab-color-text-faint: #a1a1aa;
70
61
 
71
- --lab-color-accent: #2563eb;
72
- --lab-color-accent-text: #ffffff;
73
- /* The accent that reads on the INVERTED active tree row, where the ground flips to the other
74
- theme's. LabHead.astro's live pill is the only thing that needs it. */
75
- --lab-color-accent-inverse: #60a5fa;
76
- --lab-color-focus: #18181b;
77
-
78
- /* The inverted active row in the tree. */
79
- --lab-color-active-bg: #27272a;
80
- --lab-color-active-text: #ffffff;
81
-
82
- --lab-radius: 4px;
83
- --lab-radius-pill: 100px;
84
- --lab-radius-square: 0;
85
- --lab-transition: 150ms ease;
86
-
87
- --lab-sidebar-width: attr(data-sidebar-width px, 300px);
62
+ --lab-bg-primary: #ffffff; /* Colors/Background/bg-primary */
63
+ --lab-bg-secondary: #fafafa; /* Colors/Background/bg-secondary */
64
+ --lab-bg-hover: #f5f5f5;
65
+ --lab-bg-sunken: #f0f0f0;
66
+ --lab-bg-active: #f9f5ff; /* Component colors/Utility/Brand/utility-brand-50 */
67
+ --lab-border-primary: #d4d4d4; /* Colors/Border/border-primary */
68
+ --lab-border-secondary: #e5e5e5; /* Colors/Border/border-secondary */
69
+
70
+ --lab-text-primary: #171717; /* Colors/Text/text-primary (900) */
71
+ --lab-text-secondary: #404040; /* Colors/Text/text-secondary (700) */
72
+ --lab-text-tertiary: #525252; /* Colors/Text/text-tertiary (600) */
73
+ --lab-text-quaternary: #737373; /* Colors/Text/text-quaternary (500) */
74
+ --lab-text-placeholder: #737373; /* Colors/Text/text-placeholder */
75
+ --lab-fg-quaternary: #a3a3a3; /* Colors/Foreground/fg-quaternary (400) — icons at rest */
76
+ --lab-fg-muted: #d4d4d4; /* Component colors/Utility/Neutral/utility-neutral-300 — separators */
77
+
78
+ --lab-fg-brand: #9e77ed; /* Colors/Foreground/fg-brand-secondary_alt — the active icon */
79
+ --lab-text-brand-primary: #42307d; /* Colors/Text/text-brand-primary (900) — the active label */
80
+ --lab-text-brand-secondary: #6941c6; /* Colors/Text/text-brand-secondary (700) — breadcrumb */
81
+ --lab-brand-solid: #7f56d9;
82
+ --lab-brand-solid-hover: #6941c6;
83
+
84
+ --lab-coffee-bg: #ecfccb; /* Colors/Lime/100 */
85
+ --lab-coffee-text: #3f6212; /* Colors/Lime/800 */
86
+ --lab-coffee-icon: #65a30d; /* Colors/Lime/600 */
87
+
88
+ /* Status. Live is the lab's one claim that matters, so it is the one green on the screen. */
89
+ --lab-success-bg: #ecfdf3;
90
+ --lab-success-border: #abefc6;
91
+ --lab-success-text: #067647;
92
+ --lab-success-dot: #17b26a;
93
+ --lab-info-bg: #eff8ff;
94
+ --lab-info-border: #b2ddff;
95
+ --lab-info-text: #175cd3;
96
+ --lab-info-dot: #2e90fa;
97
+ --lab-warning-bg: #fffaeb;
98
+ --lab-warning-border: #fedf89;
99
+ --lab-warning-text: #b54708;
100
+ --lab-warning-dot: #f79009;
101
+ --lab-error-bg: #fef3f2;
102
+ --lab-error-border: #fecdca;
103
+ --lab-error-text: #b42318;
104
+ --lab-error-dot: #f04438;
105
+ --lab-neutral-bg: #fafafa;
106
+ --lab-neutral-border: #e5e5e5;
107
+ --lab-neutral-text: #404040;
108
+
109
+ --lab-shadow-xs: 0 1px 2px rgba(10, 13, 18, 0.05); /* Shadows/shadow-xs */
110
+ --lab-shadow-sm: 0 1px 3px rgba(10, 13, 18, 0.1), 0 1px 2px -1px rgba(10, 13, 18, 0.1);
111
+ --lab-shadow-lg: 0 12px 16px -4px rgba(10, 13, 18, 0.08), 0 4px 6px -2px rgba(10, 13, 18, 0.03);
112
+ --lab-focus-ring: 0 0 0 2px var(--lab-bg-primary), 0 0 0 4px var(--lab-fg-brand);
113
+
114
+ --lab-radius-xs: 4px;
115
+ --lab-radius-sm: 6px; /* radius-sm */
116
+ --lab-radius-md: 8px; /* radius-md */
117
+ --lab-radius-lg: 10px;
118
+ --lab-radius-xl: 12px; /* radius-xl */
119
+ --lab-radius-full: 9999px;
120
+
121
+ --lab-space-xs: 4px; /* spacing-xs */
122
+ --lab-space-sm: 6px; /* spacing-sm */
123
+ --lab-space-md: 8px; /* spacing-md */
124
+ --lab-space-lg: 12px; /* spacing-lg */
125
+ --lab-space-xl: 16px; /* spacing-xl */
126
+ --lab-space-2xl: 20px; /* spacing-2xl */
127
+ --lab-space-3xl: 24px;
128
+
129
+ --lab-l1-w: 280px;
130
+ --lab-l1-rail: 64px; /* the file's own rail variant; 68 with level 1's 4px padding */
131
+ --lab-l2-w: 280px;
132
+ --lab-navbar-h: 60px;
133
+ --lab-ease: 160ms cubic-bezier(0.2, 0, 0, 1);
88
134
  }
89
135
 
90
136
  :root.dark {
91
- --lab-color-bg: #18181b;
92
- --lab-color-bg-sunken: #111113;
93
- --lab-color-surface: #1f1f23;
94
- --lab-color-surface-raised: #27272a;
95
- --lab-color-border: #3f3f46;
96
- --lab-color-border-strong: #52525b;
97
-
98
- --lab-color-text: #d4d4d8;
99
- --lab-color-text-strong: #fafafa;
100
- --lab-color-text-muted: #a1a1aa;
101
- --lab-color-text-faint: #71717a;
102
-
103
- --lab-color-accent: #60a5fa;
104
- --lab-color-accent-text: #09090b;
105
- --lab-color-accent-inverse: #2563eb;
106
- --lab-color-focus: #fafafa;
107
-
108
- --lab-color-active-bg: #e4e4e7;
109
- --lab-color-active-text: #18181b;
137
+ --lab-bg-primary: #0f0f0f;
138
+ --lab-bg-secondary: #171717;
139
+ --lab-bg-hover: #222222;
140
+ --lab-bg-sunken: #0a0a0a;
141
+ --lab-bg-active: #231b36;
142
+ --lab-border-primary: #404040;
143
+ --lab-border-secondary: #292929;
144
+
145
+ --lab-text-primary: #fafafa;
146
+ --lab-text-secondary: #d4d4d4;
147
+ --lab-text-tertiary: #b5b5b5;
148
+ --lab-text-quaternary: #a3a3a3;
149
+ --lab-text-placeholder: #8a8a8a;
150
+ --lab-fg-quaternary: #7a7a7a;
151
+ --lab-fg-muted: #525252;
152
+
153
+ --lab-fg-brand: #b692f6;
154
+ --lab-text-brand-primary: #e9d7fe;
155
+ --lab-text-brand-secondary: #d6bbfb;
156
+ --lab-brand-solid: #7f56d9;
157
+ --lab-brand-solid-hover: #9e77ed;
158
+
159
+ --lab-coffee-bg: #1a2e05;
160
+ --lab-coffee-text: #d9f99d;
161
+ --lab-coffee-icon: #a3e635;
162
+
163
+ --lab-success-bg: #053321;
164
+ --lab-success-border: #085d3a;
165
+ --lab-success-text: #75e0a7;
166
+ --lab-info-bg: #102a56;
167
+ --lab-info-border: #194185;
168
+ --lab-info-text: #84caff;
169
+ --lab-warning-bg: #4e1d09;
170
+ --lab-warning-border: #93370d;
171
+ --lab-warning-text: #fec84b;
172
+ --lab-error-bg: #55160c;
173
+ --lab-error-border: #912018;
174
+ --lab-error-text: #fda29b;
175
+ --lab-neutral-bg: #1f1f1f;
176
+ --lab-neutral-border: #333333;
177
+ --lab-neutral-text: #d4d4d4;
178
+
179
+ --lab-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
180
+ --lab-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.5);
181
+ --lab-shadow-lg: 0 16px 32px -8px rgba(0, 0, 0, 0.7);
110
182
  }
111
183
 
112
184
  /* ---------------------------------------------------------------------------------------------
113
- The document frame. Structure only — no font, no colour, nothing that would inherit into a
114
- story rendered in <main> on a dashboard page.
185
+ The document. `.lab-shell` sits on <html> and <body> of every chrome page and nowhere else.
115
186
  --------------------------------------------------------------------------------------------- */
116
187
  html.lab-shell {
117
188
  height: 100%;
118
- max-height: 100vh;
119
- overflow: hidden;
189
+ color-scheme: light;
120
190
  -webkit-text-size-adjust: 100%;
121
191
  }
122
192
 
193
+ :root.dark.lab-shell {
194
+ color-scheme: dark;
195
+ }
196
+
123
197
  body.lab-shell {
198
+ height: 100%;
124
199
  margin: 0;
125
- padding: 0;
126
- display: flex;
127
- flex-direction: row;
128
- align-items: stretch;
129
- justify-content: flex-start;
130
- min-height: 100%;
131
- max-height: 100%;
200
+ overflow: hidden;
201
+ background-color: var(--lab-bg-secondary);
202
+ color: var(--lab-text-primary);
203
+ font-family: var(--lab-font);
204
+ font-size: 14px;
205
+ line-height: 20px;
206
+ -webkit-font-smoothing: antialiased;
207
+ -moz-osx-font-smoothing: grayscale;
132
208
  }
133
209
 
134
- .lab-main {
135
- flex: 1;
136
- min-width: 0;
137
- overflow: auto;
210
+ .lab-shell *,
211
+ .lab-shell *::before,
212
+ .lab-shell *::after {
138
213
  box-sizing: border-box;
139
214
  }
140
215
 
141
- /* Box-sizing is scoped to the chrome for the same reason: on a dashboard page a `*` selector
142
- would cross into the story. */
143
- .lab-chrome,
144
- .lab-chrome *,
145
- .lab-chrome *::before,
146
- .lab-chrome *::after {
147
- box-sizing: border-box;
216
+ /* `hidden` means hidden, whatever display a chrome class gives the element. */
217
+ .lab-shell [hidden] {
218
+ display: none !important;
148
219
  }
149
220
 
150
- /* The chrome's own typographic ground. Declared on the chrome boxes, not on `body`, because the
151
- consumer's `body { font-family; font-size }` is in the same document on a dashboard page — a
152
- class here beats an element selector there, and nothing inherits into the preview from us. */
153
- .lab-chrome {
154
- font-family: var(--lab-font-body);
155
- font-size: 14px;
156
- line-height: 1.45;
157
- color: var(--lab-color-text);
158
- -webkit-font-smoothing: antialiased;
159
- -moz-osx-font-smoothing: grayscale;
221
+ /* Cross-document view transitions: the old page stays painted until the new one is ready, so a
222
+ click in the tree never flashes white. No script — the browser does it, where it can. */
223
+ @view-transition {
224
+ navigation: auto;
225
+ }
226
+
227
+ .lab-icon {
228
+ display: block;
229
+ flex: none;
230
+ width: 20px;
231
+ height: 20px;
232
+ }
233
+
234
+ .lab-icon--sm {
235
+ width: 16px;
236
+ height: 16px;
160
237
  }
161
238
 
162
- .lab-sr-only {
239
+ .lab-visually-hidden {
163
240
  position: absolute;
164
241
  width: 1px;
165
242
  height: 1px;
166
- padding: 0;
167
- margin: -1px;
168
243
  overflow: hidden;
169
- clip-path: inset(50%);
244
+ clip: rect(0 0 0 0);
170
245
  white-space: nowrap;
171
- border: 0;
172
246
  }
173
247
 
174
- .lab-focus-ring:focus-visible {
175
- outline: 2px solid var(--lab-color-focus);
176
- outline-offset: -1px;
177
- z-index: 20;
248
+ .lab-focusable:focus-visible,
249
+ .lab-shell a:focus-visible,
250
+ .lab-shell button:focus-visible,
251
+ .lab-shell summary:focus-visible,
252
+ .lab-shell input:focus-visible,
253
+ .lab-shell select:focus-visible {
254
+ outline: none;
255
+ box-shadow: var(--lab-focus-ring);
256
+ }
257
+
258
+ .lab-shell code,
259
+ .lab-mono {
260
+ font-family: var(--lab-font-mono);
261
+ font-size: 0.92em;
178
262
  }
179
263
 
180
264
  /* ---------------------------------------------------------------------------------------------
181
- Sidebar. `#astrobook-sidebar` is a DOM contract, not a style choice: LabHead.astro's collapse
182
- script and pill decorator both query it by that id. Same for the resize-handle element.
265
+ The frame: level 1 (the main nav), level 2 (the panel), and main.
266
+
267
+ State lives on <html>, set before paint by the inline script in ../chrome/Shell.astro so a
268
+ collapsed sidebar never flashes open:
269
+ data-lab-l1="rail" level 1 is the 64px icon rail
270
+ data-lab-l2="closed" level 2 is gone; the stage gets its width
271
+ data-lab-peek level 2 is closed but showing over the stage, below the navbar, for
272
+ as long as the pointer stays on it — it overlays rather than pushes,
273
+ because pushing would resize the preview and trip its breakpoints
274
+ data-lab-sort="name" the Sections tree is sorted by name rather than by slot
183
275
  --------------------------------------------------------------------------------------------- */
184
- #astrobook-sidebar {
185
- display: flex;
186
- flex-direction: column;
187
- flex-shrink: 0;
188
- width: var(--lab-sidebar-width, 300px);
189
- min-width: 100px;
190
- max-width: calc(100vw - 100px);
191
- border-right: 1px solid var(--lab-color-border);
192
- background-color: var(--lab-color-bg-sunken);
276
+
277
+ /* The sidebars' own tokens (Figma `Astrolab`, 2026-09-24): the floating surfaces' edge and
278
+ shadow, the rail's hover on a group it holds, and the Sections tree's slot badge. Dark values
279
+ are the file's own Dark mode where it defines them. */
280
+ :root {
281
+ --lab-border-secondary-alt: rgba(0, 0, 0, 0.1); /* Colors/Border/border-secondary_alt */
282
+ --lab-shadow-lg-3: 0 12px 16px -4px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.03),
283
+ 0 2px 2px -1px rgba(0, 0, 0, 0.04); /* Shadows/shadow-lg */
284
+ --lab-shadow-xl: 0 20px 24px -4px rgba(0, 0, 0, 0.08), 0 8px 8px -4px rgba(0, 0, 0, 0.03),
285
+ 0 3px 3px -1.5px rgba(0, 0, 0, 0.04); /* Shadows/shadow-xl */
286
+ --lab-bg-active-hover: #f4ebff; /* Component colors/Utility/Brand/utility-brand-100 */
287
+ --lab-tip-bg: #0a0a0a; /* Colors/Background/bg-primary-solid */
288
+ --lab-tip-text: #ffffff;
289
+ --lab-tip-sub: #d4d4d4; /* tooltip-supporting-text */
290
+
291
+ --lab-slot-bg: #f0fdf4; /* utility-green-50 */
292
+ --lab-slot-border: #bbf7d0; /* utility-green-200 */
293
+ --lab-slot-text: #15803d; /* utility-green-700 */
294
+ --lab-slot-none-bg: #fafafa; /* utility-neutral-50 */
295
+ --lab-slot-none-border: #e5e5e5; /* utility-neutral-200 */
296
+ --lab-slot-none-text: #404040; /* utility-neutral-700 */
297
+ --lab-slot-here-bg: #f9f5ff; /* utility-brand-50 */
298
+ --lab-slot-here-border: #e9d7fe; /* utility-brand-200 */
299
+ --lab-slot-here-text: #7f56d9; /* utility-brand-600 */
193
300
  }
194
301
 
195
- :root[data-sidebar-resizing] {
196
- cursor: ew-resize;
197
- user-select: none;
302
+ :root.dark {
303
+ --lab-border-secondary-alt: rgba(255, 255, 255, 0.1);
304
+ --lab-shadow-lg-3: 0 16px 32px -8px rgba(0, 0, 0, 0.7);
305
+ --lab-shadow-xl: 0 20px 40px -8px rgba(0, 0, 0, 0.75);
306
+ --lab-bg-active-hover: #2c1c5f;
307
+ --lab-tip-bg: #fafafa;
308
+ --lab-tip-text: #0a0a0a;
309
+ --lab-tip-sub: #404040;
310
+
311
+ --lab-slot-bg: #052e16;
312
+ --lab-slot-border: #166534;
313
+ --lab-slot-text: #86efac;
314
+ --lab-slot-none-bg: #171717;
315
+ --lab-slot-none-border: #404040;
316
+ --lab-slot-none-text: #d4d4d4;
317
+ --lab-slot-here-bg: #2c1c5f;
318
+ --lab-slot-here-border: #53389e;
319
+ --lab-slot-here-text: #d6bbfb;
320
+ }
321
+
322
+ /* In the dark sidebars the file's Dark mode keeps "you are here" as a violet fill and turns the
323
+ brand-coloured text neutral (Figma `Lab / Story / dark`). Scoped to the two sidebars, so the
324
+ navbar and the views keep the lab-wide tokens. */
325
+ :root.dark .lab-l1,
326
+ :root.dark .lab-l2 {
327
+ --lab-bg-active: #2c1c5f; /* utility-brand-50, dark */
328
+ --lab-text-brand-primary: #fafafa; /* text-brand-primary (900), dark */
329
+ --lab-text-brand-secondary: #d4d4d4; /* text-brand-secondary (700), dark */
330
+ }
331
+
332
+ /* The coffee footer is lime primitives in the file, so it stays a light slab in the dark sidebar —
333
+ left that way on purpose while its link waits on a decision. */
334
+ :root.dark .lab-l2__footer {
335
+ --lab-coffee-bg: #ecfccb;
336
+ --lab-coffee-text: #3f6212;
337
+ --lab-coffee-icon: #65a30d;
338
+ }
339
+
340
+ .lab-app {
341
+ display: flex;
342
+ height: 100vh;
343
+ height: 100dvh;
344
+ min-width: 0;
198
345
  }
199
346
 
200
- astrobook-sidebar-resize-handle {
347
+ /* ---- level 1 ------------------------------------------------------------------------------ */
348
+ .lab-l1 {
201
349
  position: relative;
202
- left: -2px;
203
- display: block;
204
- width: 4px;
350
+ z-index: 4;
205
351
  flex: none;
206
- cursor: ew-resize;
207
- transition: background-color var(--lab-transition);
352
+ display: flex;
353
+ padding: var(--lab-space-xs) 0 var(--lab-space-xs) var(--lab-space-xs);
208
354
  }
209
355
 
210
- astrobook-sidebar-resize-handle:hover {
211
- background-color: var(--lab-color-border-strong);
356
+ .lab-l1__card {
357
+ display: flex;
358
+ flex-direction: column;
359
+ justify-content: space-between;
360
+ width: var(--lab-l1-w);
361
+ height: 100%;
362
+ background-color: var(--lab-bg-primary);
363
+ border-right: 1px solid var(--lab-border-secondary);
364
+ border-radius: var(--lab-radius-xl);
365
+ transition: width var(--lab-ease);
212
366
  }
213
367
 
214
- .lab-sidebar__nav {
368
+ .lab-l1__top {
369
+ min-height: 0;
215
370
  display: flex;
216
371
  flex-direction: column;
217
- min-height: 100%;
218
- max-height: 100%;
219
- overflow-y: hidden;
220
- padding: 0;
221
- margin: 0;
222
372
  }
223
373
 
224
- .lab-sidebar__header {
374
+ .lab-l1__header {
225
375
  display: flex;
226
376
  align-items: center;
227
- gap: 0.125rem;
228
- padding: 0.75rem 0.5rem;
229
- border-bottom: 1px solid var(--lab-color-border);
377
+ gap: var(--lab-space-md);
378
+ padding: var(--lab-space-xl) var(--lab-space-lg) var(--lab-space-xl) var(--lab-space-2xl);
230
379
  }
231
380
 
232
- .lab-sidebar__title {
381
+ .lab-brand {
382
+ display: flex;
383
+ align-items: center;
384
+ gap: var(--lab-space-md);
233
385
  min-width: 0;
234
- flex: 0 1 auto;
386
+ flex: 1;
387
+ color: var(--lab-text-primary);
388
+ text-decoration: none;
389
+ border-radius: var(--lab-radius-sm);
390
+ }
391
+
392
+ /* The logo is a placeholder until one is designed. */
393
+ .lab-brand__mark {
394
+ flex: none;
395
+ display: grid;
396
+ place-items: center;
397
+ width: 24px;
398
+ height: 24px;
399
+ border-radius: var(--lab-radius-sm);
400
+ background: linear-gradient(26.5deg, #53389e 8%, #6941c6 92%);
401
+ box-shadow: var(--lab-shadow-xs), inset 0 -0.5px 0.5px rgba(0, 0, 0, 0.1);
402
+ }
403
+
404
+ .lab-brand__mark::after {
405
+ content: "";
406
+ width: 12px;
407
+ height: 12px;
408
+ border-radius: 50%;
409
+ background: radial-gradient(circle at 50% 30%, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0.25) 70%);
410
+ }
411
+
412
+ .lab-brand__name {
413
+ font-size: 16px;
414
+ line-height: 24px;
415
+ font-weight: 650;
416
+ letter-spacing: -0.01em;
417
+ white-space: nowrap;
235
418
  overflow: hidden;
236
419
  text-overflow: ellipsis;
237
- white-space: nowrap;
238
- padding: 0.35rem 0.5rem;
239
- border-radius: var(--lab-radius);
240
- font-weight: 600;
241
- color: var(--lab-color-text-muted);
242
- text-decoration: none;
243
- transition: color var(--lab-transition);
244
420
  }
245
421
 
246
- .lab-sidebar__title:hover {
247
- color: var(--lab-color-text-strong);
422
+ .lab-l1__nav {
423
+ display: flex;
424
+ flex-direction: column;
425
+ padding: 0 var(--lab-space-xl);
426
+ overflow-y: auto;
427
+ scrollbar-width: none;
248
428
  }
249
429
 
250
- .lab-sidebar__spacer {
251
- flex: 1 1 auto;
430
+ .lab-l1__footer {
431
+ display: flex;
432
+ flex-direction: column;
433
+ padding: 0 var(--lab-space-xl) var(--lab-space-2xl);
434
+ }
435
+
436
+ .lab-divider {
437
+ height: 1px;
438
+ margin: 0;
439
+ border: 0;
440
+ background-color: var(--lab-border-secondary);
441
+ }
442
+
443
+ .lab-l1__nav .lab-divider {
444
+ margin: 0;
252
445
  }
253
446
 
254
- /* The button class name is a DOM contract too: LabHead.astro creates the collapse, cull,
255
- viewport, staging and code buttons and sets `className = "astrobook-sidebar-button"` on each,
256
- so every injected control inherits this shape without knowing anything about the stylesheet. */
257
- .astrobook-sidebar-button {
447
+ /* One row of level 1. Figma `_Nav item base`: 8px padding, 12px between text and trailing icon,
448
+ 8px between icon and text, a 36px row, 1px of air above and below. */
449
+ .lab-nav-item {
450
+ position: relative;
258
451
  display: flex;
259
452
  align-items: center;
260
- justify-content: center;
261
- flex: none;
262
- width: 2rem;
263
- height: 2rem;
264
- padding: 0.375rem;
453
+ gap: var(--lab-space-md);
454
+ width: 100%;
455
+ min-height: 36px;
456
+ margin: 1px 0;
457
+ padding: var(--lab-space-md);
265
458
  border: 0;
266
- border-radius: var(--lab-radius);
459
+ border-radius: var(--lab-radius-sm);
267
460
  background: transparent;
268
- color: var(--lab-color-text-muted);
461
+ color: var(--lab-text-secondary);
269
462
  font: inherit;
270
- font-size: 1rem;
463
+ font-weight: 600;
464
+ text-align: left;
271
465
  text-decoration: none;
272
466
  cursor: pointer;
273
- transition:
274
- color var(--lab-transition),
275
- background-color var(--lab-transition);
467
+ list-style: none;
468
+ transition: background-color var(--lab-ease), color var(--lab-ease);
276
469
  }
277
470
 
278
- .astrobook-sidebar-button:hover {
279
- color: var(--lab-color-text-strong);
280
- background-color: var(--lab-color-surface);
471
+ .lab-nav-item::-webkit-details-marker {
472
+ display: none;
281
473
  }
282
474
 
283
- .astrobook-sidebar-button[data-active] {
284
- color: var(--lab-color-text-strong);
285
- background-color: var(--lab-color-surface);
475
+ .lab-nav-item > .lab-icon {
476
+ color: var(--lab-fg-quaternary);
477
+ transition: color var(--lab-ease);
286
478
  }
287
479
 
288
- .astrobook-sidebar-button svg {
289
- display: block;
290
- width: 1rem;
291
- height: 1rem;
480
+ .lab-nav-item__label {
481
+ flex: 1;
482
+ min-width: 0;
483
+ white-space: nowrap;
484
+ overflow: hidden;
485
+ text-overflow: ellipsis;
292
486
  }
293
487
 
294
- /* ---------------------------------------------------------------------------------------------
295
- Search. The ids are the contract (LabHead widens matches by appending terms to
296
- `data-search-text`, and opens the panel on ⌘K by clicking the toggle).
297
- --------------------------------------------------------------------------------------------- */
298
- #astrobook-search-panel {
299
- interpolate-size: allow-keywords;
300
- height: 0;
301
- overflow: clip;
302
- border-bottom: 1px solid transparent;
303
- transition: height var(--lab-transition);
488
+ .lab-nav-item__count {
489
+ flex: none;
490
+ min-width: 22px;
491
+ padding: 0 7px;
492
+ border-radius: var(--lab-radius-full);
493
+ background-color: var(--lab-bg-hover);
494
+ border: 1px solid var(--lab-border-secondary);
495
+ color: var(--lab-text-secondary);
496
+ font-size: 12px;
497
+ line-height: 18px;
498
+ font-weight: 500;
499
+ text-align: center;
304
500
  }
305
501
 
306
- #astrobook-search-panel[data-open] {
307
- height: max-content;
308
- border-bottom-color: var(--lab-color-border);
502
+ .lab-nav-item:hover {
503
+ background-color: var(--lab-bg-hover);
504
+ color: var(--lab-text-primary);
309
505
  }
310
506
 
311
- .lab-search__row {
312
- display: flex;
313
- align-items: center;
314
- padding: 0.5rem;
507
+ .lab-nav-item:hover > .lab-icon {
508
+ color: var(--lab-text-quaternary);
315
509
  }
316
510
 
317
- .lab-search__field {
318
- position: relative;
319
- display: flex;
320
- flex: 1;
321
- align-items: center;
511
+ .lab-nav-item[aria-current="page"],
512
+ .lab-nav-item[aria-current="true"] {
513
+ background-color: var(--lab-bg-active);
514
+ color: var(--lab-text-brand-primary);
322
515
  }
323
516
 
324
- .lab-search__icon {
325
- position: absolute;
326
- left: 0.5rem;
327
- display: block;
328
- width: 0.875rem;
329
- height: 0.875rem;
330
- pointer-events: none;
331
- color: var(--lab-color-text-faint);
517
+ .lab-nav-item[aria-current="page"] > .lab-icon,
518
+ .lab-nav-item[aria-current="true"] > .lab-icon {
519
+ color: var(--lab-fg-brand);
332
520
  }
333
521
 
334
- #astrobook-search-input {
335
- width: 100%;
336
- padding: 0.3rem 0.5rem 0.3rem 1.75rem;
337
- border: 1px solid var(--lab-color-border);
338
- border-radius: var(--lab-radius);
339
- background-color: var(--lab-color-surface-raised);
340
- font-family: var(--lab-font-body);
341
- font-size: 0.875rem;
342
- color: var(--lab-color-text-strong);
343
- outline: none;
522
+ .lab-nav-item[aria-disabled="true"] {
523
+ cursor: default;
344
524
  }
345
525
 
346
- #astrobook-search-input::placeholder {
347
- color: var(--lab-color-text-faint);
526
+ /* Group headers (Site, Tasks) read one step stronger than their items. */
527
+ .lab-nav-group > .lab-nav-item {
528
+ color: var(--lab-text-primary);
348
529
  }
349
530
 
350
- #astrobook-search-input:focus {
351
- border-color: var(--lab-color-border-strong);
531
+ .lab-nav-group__chevron {
532
+ width: 16px;
533
+ height: 16px;
534
+ color: var(--lab-fg-quaternary);
535
+ transform: rotate(180deg);
536
+ transition: transform var(--lab-ease);
352
537
  }
353
538
 
354
- /* ---------------------------------------------------------------------------------------------
355
- The tree. Every selector below is one LabHead.astro also queries — see its DOM contract block.
356
- --------------------------------------------------------------------------------------------- */
357
- astrobook-sidebar-tree {
539
+ .lab-nav-group[data-collapsed] .lab-nav-group__chevron {
540
+ transform: rotate(0deg);
541
+ }
542
+
543
+ .lab-nav-group__items {
358
544
  display: flex;
359
545
  flex-direction: column;
360
- gap: 2px;
361
- overflow-y: auto;
362
- padding: 0.75rem 0.5rem 2rem;
546
+ padding-bottom: 2px;
363
547
  }
364
548
 
365
- astrobook-sidebar-node {
366
- display: contents;
549
+ .lab-nav-group[data-collapsed] > .lab-nav-group__items {
550
+ display: none;
367
551
  }
368
552
 
369
- .astrobook-sidebar-link {
370
- display: flex;
371
- align-items: center;
372
- gap: 0.5rem;
373
- list-style: none;
374
- padding: 0.25rem 0.5rem;
375
- border-radius: var(--lab-radius);
376
- font-size: 0.875rem;
377
- text-decoration: none;
378
- cursor: pointer;
379
- user-select: none;
380
- transition:
381
- color var(--lab-transition),
382
- background-color var(--lab-transition);
553
+ /* The group header is a toggle, not a place: it never takes the active fill, even when the page
554
+ is one of its items (that item carries it). aria-current stays on it for the rail, where the
555
+ header IS the only visible trace of the group. */
556
+ .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"] {
557
+ background-color: transparent;
558
+ color: var(--lab-text-primary);
559
+ }
560
+
561
+ .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"] > .lab-icon {
562
+ color: var(--lab-fg-quaternary);
383
563
  }
384
564
 
385
- .astrobook-sidebar-link::-webkit-details-marker {
565
+ .lab-l1__nav .lab-nav-group > .lab-nav-item:hover {
566
+ background-color: var(--lab-bg-hover);
567
+ }
568
+
569
+ .lab-l1-btn__expand,
570
+ :root[data-lab-l1="rail"] .lab-l1-btn__collapse {
386
571
  display: none;
387
572
  }
388
573
 
389
- /* Folder and module rows. */
390
- .lab-tree-row--group {
391
- color: var(--lab-color-text-muted);
574
+ :root[data-lab-l1="rail"] .lab-l1-btn__expand {
575
+ display: block;
392
576
  }
393
577
 
394
- .lab-tree-row--group:hover {
395
- color: var(--lab-color-text-strong);
396
- background-color: var(--lab-color-surface);
578
+ :root[data-lab-l1="rail"] .lab-tip__title .lab-l1-btn__expand {
579
+ display: inline;
397
580
  }
398
581
 
399
- /* Story rows. The two state classes are toggled by the inline script in sidebar-tree.astro and
400
- are styled by LabHead.astro as well — do not rename either. */
401
- .astrobook-sidebar-story-link-inactive {
402
- color: var(--lab-color-text);
582
+ /* The header's two buttons — collapse, then the lab's theme — are 32px ghost buttons with a 16px
583
+ icon at text-quaternary, not the kit's paler fg-quaternary: an icon-only control needs 3:1. */
584
+ .lab-icon-btn.lab-l1__btn {
585
+ color: var(--lab-text-quaternary);
403
586
  }
404
587
 
405
- .astrobook-sidebar-story-link-inactive:hover {
406
- color: var(--lab-color-text-strong);
407
- background-color: var(--lab-color-surface);
588
+ .lab-icon-btn.lab-l1__btn:hover {
589
+ color: var(--lab-text-secondary);
408
590
  }
409
591
 
410
- .astrobook-sidebar-story-link-active,
411
- .astrobook-sidebar-story-link-active:hover {
412
- color: var(--lab-color-active-text);
413
- background-color: var(--lab-color-active-bg);
592
+ .lab-icon-btn.lab-l1__btn .lab-icon {
593
+ width: 16px;
594
+ height: 16px;
414
595
  }
415
596
 
416
- .lab-tree-icon {
417
- display: block;
418
- flex: none;
419
- width: 0.875rem;
420
- height: 0.875rem;
421
- color: var(--lab-color-text-faint);
597
+ /* The moon is a toggle for the lab's dark mode. It stays a ghost button either way. */
598
+ .lab-icon-btn.lab-theme-btn[aria-pressed="true"] {
599
+ background-color: transparent;
600
+ color: var(--lab-text-quaternary);
422
601
  }
423
602
 
424
- .astrobook-sidebar-story-link-active .lab-tree-icon {
425
- color: inherit;
603
+ .lab-icon-btn.lab-theme-btn:hover {
604
+ background-color: var(--lab-bg-hover);
605
+ color: var(--lab-text-secondary);
606
+ }
607
+
608
+ /* ---- tooltips (Figma `Tooltip`, with supporting text) ------------------------------------- */
609
+ /* A shortcut tooltip that shows at once on hover or keyboard focus — `title` takes a second and
610
+ cannot hold three lines. The tooltip is inside its button, which names itself with aria-label
611
+ and points at the tooltip with aria-describedby. */
612
+ .lab-tipped {
613
+ position: relative;
426
614
  }
427
615
 
428
- .lab-tree-children {
616
+ .lab-tip {
617
+ position: absolute;
618
+ z-index: 20;
429
619
  display: flex;
430
620
  flex-direction: column;
431
621
  gap: 2px;
432
- padding-left: 0.75rem;
622
+ width: max-content;
623
+ max-width: 296px;
624
+ padding: var(--lab-space-lg);
625
+ border-radius: var(--lab-radius-md);
626
+ background-color: var(--lab-tip-bg);
627
+ box-shadow: var(--lab-shadow-lg-3);
628
+ color: var(--lab-tip-sub);
629
+ font-size: 12px;
630
+ line-height: 18px;
631
+ font-weight: 500;
632
+ text-align: left;
633
+ white-space: nowrap;
634
+ pointer-events: none;
635
+ visibility: hidden;
636
+ opacity: 0;
637
+ transition: opacity 120ms ease, visibility 0s linear 120ms;
433
638
  }
434
639
 
435
- /* The collapse animation, lifted verbatim in behaviour from upstream's scoped block so the
436
- chevron and the details height still move together. */
437
- details[data-astrobook-collapsible] {
438
- --lab-collapse-ease: cubic-bezier(0, 0, 0.2, 1);
439
- interpolate-size: allow-keywords;
640
+ .lab-tip__title {
641
+ color: var(--lab-tip-text);
642
+ font-weight: 600;
440
643
  }
441
644
 
442
- details[data-astrobook-collapsible]::details-content {
443
- height: 0;
444
- overflow: clip;
445
- transition:
446
- height var(--collapse-duration, 150ms) var(--lab-collapse-ease),
447
- content-visibility var(--collapse-duration, 150ms) var(--lab-collapse-ease) allow-discrete;
645
+ /* The arrow: a 6px notch on the side facing the button. */
646
+ .lab-tip::before {
647
+ content: "";
648
+ position: absolute;
649
+ width: 10px;
650
+ height: 10px;
651
+ background-color: inherit;
652
+ transform: rotate(45deg);
653
+ border-radius: 1px;
448
654
  }
449
655
 
450
- details[data-astrobook-collapsible][open]::details-content {
451
- height: auto;
656
+ .lab-tipped:hover > .lab-tip,
657
+ .lab-tipped:focus-visible > .lab-tip {
658
+ visibility: visible;
659
+ opacity: 1;
660
+ transition-delay: 300ms, 300ms;
661
+ transition-duration: 120ms, 0s;
452
662
  }
453
663
 
454
- [data-astrobook-chevron] {
455
- transform: rotate(-90deg);
456
- transition: transform var(--collapse-duration, 150ms) var(--lab-collapse-ease);
664
+ /* Below its button, arrow up (level 1 open). */
665
+ .lab-tip--l1 {
666
+ top: calc(100% + 8px);
667
+ left: 50%;
668
+ transform: translateX(-50%);
457
669
  }
458
670
 
459
- details[data-astrobook-collapsible][open] > summary > [data-astrobook-chevron] {
460
- transform: rotate(0deg);
671
+ .lab-tip--l1::before {
672
+ top: -4px;
673
+ left: calc(50% - 5px);
461
674
  }
462
675
 
463
- /* ---------------------------------------------------------------------------------------------
464
- Theme toggle (vendored — see components/theme-script.astro for why the dependency went).
465
- --------------------------------------------------------------------------------------------- */
466
- .lab-theme-toggle {
467
- display: flex;
468
- align-items: center;
469
- justify-content: center;
676
+ /* Right of its button, arrow left (the rail's expand button, the navbar's panel button). */
677
+ :root[data-lab-l1="rail"] .lab-tip--l1,
678
+ .lab-tip--panel {
679
+ top: 0;
680
+ left: calc(100% + 10px);
681
+ transform: none;
470
682
  }
471
683
 
472
- .lab-theme-toggle .lab-theme-icon--dark {
473
- display: none;
684
+ :root[data-lab-l1="rail"] .lab-tip--l1::before,
685
+ .lab-tip--panel::before {
686
+ top: 11px;
687
+ left: -4px;
474
688
  }
475
689
 
476
- :root.dark .lab-theme-toggle .lab-theme-icon--light {
690
+ /* The navbar's own "open the panel" button exists only while the panel is closed. */
691
+ .lab-navbar .lab-navbar__panel-btn {
477
692
  display: none;
478
693
  }
479
694
 
480
- :root.dark .lab-theme-toggle .lab-theme-icon--dark {
481
- display: block;
695
+ :root[data-lab-l2="closed"] .lab-navbar .lab-navbar__panel-btn {
696
+ display: inline-grid;
482
697
  }
483
698
 
484
- /* ---------------------------------------------------------------------------------------------
485
- The built-in splash. Reachable only when a consumer leaves `home` at its default; this package
486
- always passes its own (src/Home.astro), so this is the fallback path, kept working.
487
- --------------------------------------------------------------------------------------------- */
488
- .lab-splash {
489
- display: flex;
699
+ .lab-nav-group__items .lab-nav-item {
700
+ padding-left: 36px;
701
+ }
702
+
703
+ /* The header's own buttons (theme, collapse): the tertiary icon button. */
704
+ .lab-icon-btn {
705
+ flex: none;
706
+ display: inline-grid;
707
+ place-items: center;
708
+ width: 32px;
709
+ height: 32px;
710
+ padding: 0;
711
+ border: 0;
712
+ border-radius: var(--lab-radius-md);
713
+ background: transparent;
714
+ color: var(--lab-fg-quaternary);
715
+ cursor: pointer;
716
+ text-decoration: none;
717
+ transition: background-color var(--lab-ease), color var(--lab-ease);
718
+ }
719
+
720
+ .lab-icon-btn:hover {
721
+ background-color: var(--lab-bg-hover);
722
+ color: var(--lab-text-secondary);
723
+ }
724
+
725
+ .lab-icon-btn[aria-pressed="true"] {
726
+ background-color: var(--lab-bg-active);
727
+ color: var(--lab-fg-brand);
728
+ }
729
+
730
+ .lab-icon-btn .lab-icon {
731
+ width: 18px;
732
+ height: 18px;
733
+ }
734
+
735
+ /* ---- level 1 as a rail -------------------------------------------------------------------- */
736
+ /* Figma `Lab / Rail + flyout`: a 64px card (68 with level 1's padding), bordered all round and
737
+ lifted, with the logomark, the theme moon and the expand button stacked in its header. */
738
+ :root[data-lab-l1="rail"] .lab-l1__card {
739
+ width: var(--lab-l1-rail);
740
+ border: 1px solid var(--lab-border-secondary);
741
+ box-shadow: var(--lab-shadow-xs);
742
+ }
743
+
744
+ :root[data-lab-l1="rail"] .lab-l1__header {
490
745
  flex-direction: column;
746
+ gap: var(--lab-space-lg);
747
+ padding: var(--lab-space-2xl) 0;
748
+ }
749
+
750
+ :root[data-lab-l1="rail"] .lab-brand {
751
+ flex: none;
752
+ }
753
+
754
+ /* Under the logomark the moon comes first, then the expand button. */
755
+ :root[data-lab-l1="rail"] .lab-l1__header .lab-theme-btn {
756
+ order: 1;
757
+ }
758
+
759
+ :root[data-lab-l1="rail"] .lab-l1__header [data-lab-l1-btn] {
760
+ order: 2;
761
+ }
762
+
763
+ :root[data-lab-l1="rail"] .lab-l1 .lab-brand__name,
764
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-item__label,
765
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-item__count,
766
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-group__chevron {
767
+ display: none;
768
+ }
769
+
770
+ :root[data-lab-l1="rail"] .lab-l1__nav,
771
+ :root[data-lab-l1="rail"] .lab-l1__footer {
491
772
  align-items: center;
773
+ padding-left: var(--lab-space-lg);
774
+ padding-right: var(--lab-space-lg);
775
+ overflow: visible;
776
+ }
777
+
778
+ :root[data-lab-l1="rail"] .lab-l1__footer {
779
+ padding-bottom: var(--lab-space-xl);
780
+ }
781
+
782
+ :root[data-lab-l1="rail"] .lab-l1__nav,
783
+ :root[data-lab-l1="rail"] .lab-l1__footer {
784
+ gap: 2px;
785
+ }
786
+
787
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-item {
492
788
  justify-content: center;
493
- height: 100%;
494
- width: 100%;
495
- padding: 1.5rem;
496
- background-color: var(--lab-color-bg);
789
+ width: 36px;
790
+ margin: 0;
791
+ padding: var(--lab-space-md);
497
792
  }
498
793
 
499
- .lab-splash__title {
794
+ /* The rail has no dividers between its groups — the icons carry the grouping. */
795
+ :root[data-lab-l1="rail"] .lab-l1__nav > .lab-divider {
796
+ display: none;
797
+ }
798
+
799
+ /* In the rail a group is its icon; its items open as a flyout beside it, on hover or on focus,
800
+ with no script. The flyout is the same list the expanded nav shows, repositioned, under the
801
+ group's name so it reads as the group rather than as a context menu for the icon. */
802
+ :root[data-lab-l1="rail"] .lab-nav-group {
803
+ position: relative;
804
+ }
805
+
806
+ /* In the rail the group's own header marks where you are, since its items are out of sight. */
807
+ :root[data-lab-l1="rail"] .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"] {
808
+ background-color: var(--lab-bg-active);
809
+ }
810
+
811
+ /* ...and still answers the pointer: one step deeper on hover, since it is already tinted. */
812
+ :root[data-lab-l1="rail"] .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"]:hover {
813
+ background-color: var(--lab-bg-active-hover);
814
+ }
815
+
816
+ :root[data-lab-l1="rail"] .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"] > .lab-icon {
817
+ color: var(--lab-fg-brand);
818
+ }
819
+
820
+ .lab-nav-group__flyout-label {
821
+ display: none;
500
822
  margin: 0;
501
- font-size: 2rem;
502
- font-weight: 700;
503
- letter-spacing: -0.02em;
504
- text-align: center;
505
- color: var(--lab-color-text-strong);
823
+ padding: var(--lab-space-sm) var(--lab-space-md) 2px;
824
+ color: var(--lab-text-quaternary);
825
+ font-size: 12px;
826
+ line-height: 18px;
827
+ font-weight: 600;
506
828
  }
507
829
 
508
- .lab-splash__subtitle {
509
- margin: 0.75rem 0 0;
510
- font-size: 1rem;
511
- text-align: center;
512
- color: var(--lab-color-text-muted);
830
+ :root[data-lab-l1="rail"] .lab-nav-group__flyout-label {
831
+ display: block;
513
832
  }
514
833
 
515
- .lab-splash__badges {
516
- display: flex;
517
- flex-wrap: wrap;
518
- align-items: center;
519
- justify-content: center;
520
- gap: 0.75rem;
521
- margin-top: 1.5rem;
834
+ /* 8px right of the rail's edge: the group is 36px wide, centred in the 62px inside the card. */
835
+ :root[data-lab-l1="rail"] .lab-nav-group > .lab-nav-group__items,
836
+ :root[data-lab-l1="rail"] .lab-nav-group[data-collapsed] > .lab-nav-group__items {
837
+ display: none;
838
+ position: absolute;
839
+ top: -5px;
840
+ left: calc(100% + 13px + 1px + 8px);
841
+ z-index: 10;
842
+ min-width: 216px;
843
+ padding: var(--lab-space-xs);
844
+ background-color: var(--lab-bg-primary);
845
+ border: 1px solid var(--lab-border-secondary-alt);
846
+ border-radius: var(--lab-radius-md);
847
+ box-shadow: var(--lab-shadow-lg-3);
522
848
  }
523
849
 
524
- .lab-splash__badge {
525
- display: inline-flex;
526
- align-items: center;
527
- gap: 0.5rem;
528
- padding: 0.25rem 0.75rem;
529
- border: 1px solid var(--lab-color-border);
530
- border-radius: var(--lab-radius-pill);
531
- font-family: var(--lab-font-mono);
532
- font-size: 0.8125rem;
533
- color: var(--lab-color-text-muted);
534
- text-decoration: none;
535
- transition:
536
- color var(--lab-transition),
537
- border-color var(--lab-transition);
850
+ /* A bridge across the gap, so the pointer can travel from the icon to the flyout. */
851
+ :root[data-lab-l1="rail"] .lab-nav-group__items::before {
852
+ content: "";
853
+ position: absolute;
854
+ top: 0;
855
+ bottom: 0;
856
+ left: -24px;
857
+ width: 24px;
858
+ }
859
+
860
+ :root[data-lab-l1="rail"] .lab-nav-group:hover > .lab-nav-group__items,
861
+ :root[data-lab-l1="rail"] .lab-nav-group:focus-within > .lab-nav-group__items {
862
+ display: flex;
538
863
  }
539
864
 
540
- .lab-splash__badge:hover {
541
- color: var(--lab-color-text-strong);
542
- border-color: var(--lab-color-border-strong);
865
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-group__items .lab-nav-item {
866
+ justify-content: flex-start;
867
+ width: 100%;
868
+ margin: 1px 0;
869
+ padding: var(--lab-space-md) var(--lab-space-md);
543
870
  }
544
871
 
545
- .lab-splash__badge svg {
872
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-group__items .lab-nav-item__label {
546
873
  display: block;
547
- width: 0.875rem;
548
- height: 0.875rem;
874
+ }
875
+
876
+ /* Rail tooltips for the ungrouped items — a CSS tooltip, because `title` takes a second to show. */
877
+ :root[data-lab-l1="rail"] .lab-l1 [data-tip]:hover::after {
878
+ content: attr(data-tip);
879
+ position: absolute;
880
+ top: 50%;
881
+ left: calc(100% + 10px);
882
+ z-index: 10;
883
+ transform: translateY(-50%);
884
+ padding: 6px 10px;
885
+ border-radius: var(--lab-radius-md);
886
+ background-color: var(--lab-text-primary);
887
+ color: var(--lab-bg-primary);
888
+ font-size: 12px;
889
+ line-height: 18px;
890
+ font-weight: 600;
891
+ white-space: nowrap;
892
+ pointer-events: none;
893
+ }
894
+
895
+ :root[data-lab-l1="rail"] .lab-nav-group:hover > .lab-nav-item[data-tip]::after {
896
+ display: none;
897
+ }
898
+
899
+ /* ---- level 2 ------------------------------------------------------------------------------ */
900
+ .lab-l2 {
901
+ position: relative;
902
+ z-index: 3;
903
+ flex: none;
904
+ display: flex;
905
+ flex-direction: column;
906
+ width: var(--lab-l2-w);
907
+ height: 100%;
908
+ background-color: var(--lab-bg-secondary);
909
+ border-right: 1px solid var(--lab-border-secondary);
910
+ }
911
+
912
+ /* The tighter padding, 12 / 8, from the design pass (2026-09-24): the header's contents sit 12px
913
+ from the panel's edges, the tree 8px (plus its own 4). */
914
+ .lab-l2__header {
915
+ flex: none;
916
+ display: flex;
917
+ flex-direction: column;
918
+ gap: var(--lab-space-lg);
919
+ padding: var(--lab-space-md) var(--lab-space-lg);
920
+ }
921
+
922
+ .lab-l2__crumbs-row {
923
+ display: flex;
924
+ align-items: center;
925
+ gap: var(--lab-space-md);
926
+ min-height: 20px;
927
+ padding-top: var(--lab-space-xl);
928
+ }
929
+
930
+ .lab-l2__body {
931
+ flex: 1;
932
+ min-height: 0;
933
+ overflow-y: auto;
934
+ padding: 0 var(--lab-space-md) var(--lab-space-xl);
935
+ scrollbar-width: thin;
936
+ }
937
+
938
+ .lab-l2__footer {
939
+ flex: none;
940
+ padding: var(--lab-space-2xl) var(--lab-space-xl);
941
+ background-color: var(--lab-coffee-bg);
942
+ }
943
+
944
+ .lab-coffee {
945
+ color: var(--lab-coffee-text);
946
+ }
947
+
948
+ .lab-coffee > .lab-icon,
949
+ .lab-coffee:hover > .lab-icon {
950
+ color: var(--lab-coffee-icon);
951
+ }
952
+
953
+ .lab-coffee:hover {
954
+ background-color: color-mix(in srgb, var(--lab-coffee-icon) 14%, transparent);
955
+ color: var(--lab-coffee-text);
956
+ }
957
+
958
+ :root[data-lab-l2="closed"] .lab-l2 {
959
+ display: none;
960
+ }
961
+
962
+ /* Peek: the closed panel, shown over the STAGE for as long as the pointer is on it — below the
963
+ navbar, never over it, so the navbar's own panel button stays in reach (Figma `Lab / Peek`).
964
+ --lab-peek-top is the navbar's measured height (../chrome/shell-client.ts); it floats over a
965
+ stage of the same grey, so it keeps its border and lifts with shadow-xl. */
966
+ :root[data-lab-l2="closed"][data-lab-peek] .lab-l2 {
967
+ display: flex;
968
+ position: absolute;
969
+ top: var(--lab-peek-top, var(--lab-navbar-h));
970
+ bottom: 0;
971
+ left: calc(var(--lab-l1-w) + var(--lab-space-xs));
972
+ height: auto;
973
+ box-shadow: var(--lab-shadow-xl);
974
+ animation: lab-peek-in var(--lab-ease);
975
+ }
976
+
977
+ :root[data-lab-l2="closed"][data-lab-l1="rail"][data-lab-peek] .lab-l2 {
978
+ left: calc(var(--lab-l1-rail) + var(--lab-space-xs));
979
+ }
980
+
981
+ @keyframes lab-peek-in {
982
+ from {
983
+ opacity: 0;
984
+ transform: translateX(-12px);
985
+ }
986
+ }
987
+
988
+ /* The strip along the stage's left edge that opens the peek. Only exists while level 2 is closed,
989
+ and like the peek it starts under the navbar. */
990
+ .lab-peek-zone {
991
+ display: none;
992
+ position: absolute;
993
+ top: var(--lab-peek-top, var(--lab-navbar-h));
994
+ bottom: 0;
995
+ left: 0;
996
+ z-index: 3;
997
+ width: 10px;
998
+ }
999
+
1000
+ :root[data-lab-l2="closed"] .lab-peek-zone {
1001
+ display: block;
1002
+ }
1003
+
1004
+ /* ---- breadcrumbs (Figma `Breadcrumbs`) ----------------------------------------------------- */
1005
+ .lab-crumbs {
1006
+ display: flex;
1007
+ align-items: center;
1008
+ gap: var(--lab-space-md);
1009
+ min-width: 0;
1010
+ margin: 0;
1011
+ padding: 0;
1012
+ list-style: none;
1013
+ }
1014
+
1015
+ .lab-crumbs__item {
1016
+ display: flex;
1017
+ align-items: center;
1018
+ gap: var(--lab-space-md);
1019
+ min-width: 0;
1020
+ color: var(--lab-text-quaternary);
1021
+ font-size: 12px;
1022
+ line-height: 18px;
1023
+ font-weight: 600;
1024
+ white-space: nowrap;
1025
+ }
1026
+
1027
+ .lab-crumbs__item + .lab-crumbs__item::before {
1028
+ content: "";
1029
+ flex: none;
1030
+ width: 16px;
1031
+ height: 16px;
1032
+ background-color: var(--lab-fg-muted);
1033
+ -webkit-mask: var(--lab-chevron-right) center / 16px no-repeat;
1034
+ mask: var(--lab-chevron-right) center / 16px no-repeat;
1035
+ }
1036
+
1037
+ .lab-crumbs__item a {
1038
+ display: flex;
1039
+ color: inherit;
1040
+ text-decoration: none;
1041
+ border-radius: var(--lab-radius-xs);
1042
+ }
1043
+
1044
+ .lab-crumbs__item a:hover {
1045
+ color: var(--lab-text-secondary);
1046
+ }
1047
+
1048
+ .lab-crumbs__item .lab-icon {
1049
+ color: var(--lab-fg-quaternary);
1050
+ }
1051
+
1052
+ .lab-crumbs__item:last-child {
1053
+ color: var(--lab-text-brand-secondary);
1054
+ overflow: hidden;
1055
+ text-overflow: ellipsis;
1056
+ }
1057
+
1058
+ :root {
1059
+ --lab-chevron-right: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.33' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 12l4-4-4-4'/%3E%3C/svg%3E");
1060
+ }
1061
+
1062
+ /* Level 2's own hide / keep-open button. It overhangs the 20px crumbs row rather than growing it,
1063
+ so the header keeps the design's rhythm. */
1064
+ .lab-l2__close {
1065
+ margin: -6px -6px -6px auto;
1066
+ }
1067
+
1068
+ .lab-l2__close .lab-icon {
1069
+ width: 16px;
1070
+ height: 16px;
1071
+ }
1072
+
1073
+ .lab-l2__close-dock,
1074
+ :root[data-lab-peek] .lab-l2__close-hide {
1075
+ display: none;
1076
+ }
1077
+
1078
+ :root[data-lab-peek] .lab-l2__close-dock {
1079
+ display: block;
1080
+ }
1081
+
1082
+ /* ---- inputs (Figma `Input`) ---------------------------------------------------------------- */
1083
+ .lab-input {
1084
+ display: flex;
1085
+ align-items: center;
1086
+ gap: var(--lab-space-md);
1087
+ padding: var(--lab-space-md) var(--lab-space-md) var(--lab-space-md) var(--lab-space-lg);
1088
+ background-color: var(--lab-bg-primary);
1089
+ border: 1px solid var(--lab-border-primary);
1090
+ border-radius: var(--lab-radius-md);
1091
+ box-shadow: var(--lab-shadow-xs);
1092
+ color: var(--lab-fg-quaternary);
1093
+ transition: border-color var(--lab-ease), box-shadow var(--lab-ease);
1094
+ }
1095
+
1096
+ .lab-input:focus-within {
1097
+ border-color: var(--lab-fg-brand);
1098
+ box-shadow: var(--lab-shadow-xs), 0 0 0 3px color-mix(in srgb, var(--lab-fg-brand) 22%, transparent);
1099
+ }
1100
+
1101
+ .lab-input__field {
1102
+ flex: 1;
1103
+ min-width: 0;
1104
+ padding: 0;
1105
+ border: 0;
1106
+ outline: none;
1107
+ background: transparent;
1108
+ color: var(--lab-text-primary);
1109
+ font: inherit;
1110
+ }
1111
+
1112
+ .lab-shell .lab-input__field:focus-visible {
1113
+ box-shadow: none;
1114
+ }
1115
+
1116
+ .lab-input__field::placeholder {
1117
+ color: var(--lab-text-placeholder);
1118
+ opacity: 1;
1119
+ }
1120
+
1121
+ .lab-input__field::-webkit-search-cancel-button {
1122
+ -webkit-appearance: none;
1123
+ }
1124
+
1125
+ .lab-input__kbd {
1126
+ flex: none;
1127
+ padding: 0 5px;
1128
+ border: 1px solid var(--lab-border-secondary);
1129
+ border-radius: var(--lab-radius-xs);
1130
+ color: var(--lab-text-quaternary);
1131
+ font-size: 12px;
1132
+ line-height: 18px;
1133
+ font-weight: 500;
1134
+ }
1135
+
1136
+ .lab-input:focus-within .lab-input__kbd {
1137
+ display: none;
1138
+ }
1139
+
1140
+ .lab-input .lab-icon-btn {
1141
+ width: 24px;
1142
+ height: 24px;
1143
+ margin: -4px -2px -4px 0;
1144
+ }
1145
+
1146
+ .lab-input .lab-icon-btn .lab-icon {
1147
+ width: 16px;
1148
+ height: 16px;
1149
+ }
1150
+
1151
+ /* ---- menus (popover) -----------------------------------------------------------------------
1152
+ Figma `Astrolab` › Menu: every navbar dropdown, the sidebars' filter and sort menus, and the one
1153
+ hover panel (Properties). A row is 6px in from the menu's edge with 8px of padding inside, a 16px
1154
+ leading slot (`__mark`: an icon, a tick or a checkbox), the label (`__text`), and a trailing hint
1155
+ or chevron. Section labels are text-tertiary, not violet, because violet means "you are here";
1156
+ row hover is bg-hover, because the kit's #fafafa is invisible on a white menu. */
1157
+ .lab-menu {
1158
+ margin: 0;
1159
+ min-width: 220px;
1160
+ max-width: 320px;
1161
+ padding: var(--lab-space-xs) 0;
1162
+ border: 1px solid var(--lab-border-secondary);
1163
+ border-radius: var(--lab-radius-md);
1164
+ background-color: var(--lab-bg-primary);
1165
+ color: var(--lab-text-primary);
1166
+ box-shadow: var(--lab-shadow-lg);
1167
+ font-family: var(--lab-font);
1168
+ font-size: 14px;
1169
+ line-height: 20px;
1170
+ inset: auto;
1171
+ }
1172
+
1173
+ .lab-menu__label {
1174
+ margin: 0;
1175
+ padding: var(--lab-space-sm) var(--lab-space-xl) 2px;
1176
+ color: var(--lab-text-tertiary);
1177
+ font-size: 12px;
1178
+ line-height: 18px;
1179
+ font-weight: 600;
1180
+ }
1181
+
1182
+ .lab-menu__item {
1183
+ display: flex;
1184
+ align-items: center;
1185
+ gap: var(--lab-space-md);
1186
+ width: calc(100% - 12px);
1187
+ margin: 1px 6px;
1188
+ padding: var(--lab-space-md) var(--lab-space-sm) var(--lab-space-md) 10px;
1189
+ border: 0;
1190
+ border-radius: var(--lab-radius-sm);
1191
+ background: transparent;
1192
+ color: var(--lab-text-secondary);
1193
+ font: inherit;
1194
+ font-weight: 600;
1195
+ text-align: left;
1196
+ text-decoration: none;
1197
+ cursor: pointer;
1198
+ }
1199
+
1200
+ .lab-menu__item:hover {
1201
+ background-color: var(--lab-bg-hover);
1202
+ color: var(--lab-text-primary);
1203
+ }
1204
+
1205
+ .lab-menu__item[aria-disabled="true"] {
1206
+ cursor: default;
1207
+ opacity: 0.6;
1208
+ }
1209
+
1210
+ .lab-menu__item[aria-disabled="true"]:hover {
1211
+ background-color: transparent;
1212
+ }
1213
+
1214
+ .lab-menu__item > .lab-icon {
1215
+ width: 16px;
1216
+ height: 16px;
1217
+ color: var(--lab-fg-quaternary);
1218
+ }
1219
+
1220
+ .lab-menu__item[aria-checked="true"] {
1221
+ color: var(--lab-text-primary);
1222
+ }
1223
+
1224
+ /* A tick on the right — the sidebars' filter menus. */
1225
+ .lab-menu__item .lab-menu__tick {
1226
+ margin-left: auto;
1227
+ color: var(--lab-fg-brand);
1228
+ visibility: hidden;
1229
+ }
1230
+
1231
+ .lab-menu__item[aria-checked="true"] .lab-menu__tick {
1232
+ visibility: visible;
1233
+ }
1234
+
1235
+ /* The 16px leading slot. A tick in it marks the current choice (menuitemradio); a checkbox box in
1236
+ it marks a switch (menuitemcheckbox); anything else is the row's icon. */
1237
+ .lab-menu__mark {
1238
+ flex: none;
1239
+ display: grid;
1240
+ place-items: center;
1241
+ width: 16px;
1242
+ height: 16px;
1243
+ color: var(--lab-text-quaternary);
1244
+ }
1245
+
1246
+ .lab-menu__mark > .lab-icon {
1247
+ width: 16px;
1248
+ height: 16px;
1249
+ }
1250
+
1251
+ .lab-menu__mark .lab-menu__tick {
1252
+ margin: 0;
1253
+ }
1254
+
1255
+ .lab-menu__text {
1256
+ flex: 1;
1257
+ min-width: 0;
1258
+ overflow: hidden;
1259
+ text-overflow: ellipsis;
1260
+ white-space: nowrap;
1261
+ }
1262
+
1263
+ .lab-menu__sub {
1264
+ color: var(--lab-text-quaternary);
1265
+ font-weight: 500;
1266
+ }
1267
+
1268
+ .lab-menu__hint {
1269
+ flex: none;
1270
+ margin-left: auto;
1271
+ padding-right: var(--lab-space-xs);
1272
+ color: var(--lab-text-quaternary);
1273
+ font-family: inherit;
1274
+ font-size: 12px;
1275
+ line-height: 18px;
1276
+ font-weight: 500;
1277
+ font-variant-numeric: tabular-nums;
1278
+ }
1279
+
1280
+ .lab-menu__trail {
1281
+ flex: none;
1282
+ width: 16px;
1283
+ height: 16px;
1284
+ color: var(--lab-text-quaternary);
1285
+ }
1286
+
1287
+ .lab-menu hr {
1288
+ height: 1px;
1289
+ margin: var(--lab-space-xs) 0;
1290
+ border: 0;
1291
+ background-color: var(--lab-border-secondary);
1292
+ }
1293
+
1294
+ .lab-menu__note {
1295
+ margin: 0;
1296
+ padding: var(--lab-space-md) var(--lab-space-xl);
1297
+ color: var(--lab-text-quaternary);
1298
+ font-size: 12px;
1299
+ line-height: 18px;
1300
+ }
1301
+
1302
+ /* The checkbox a switch row draws in its slot (Figma `_Checkbox base`, sm), for rows that are
1303
+ buttons with role="menuitemcheckbox" rather than real inputs. */
1304
+ .lab-menu__box {
1305
+ width: 16px;
1306
+ height: 16px;
1307
+ border: 1px solid var(--lab-border-primary);
1308
+ border-radius: var(--lab-radius-xs);
1309
+ background-color: var(--lab-bg-primary);
1310
+ }
1311
+
1312
+ .lab-menu__item[aria-checked="true"] .lab-menu__box {
1313
+ border-color: var(--lab-brand-solid);
1314
+ background: var(--lab-brand-solid) no-repeat center / 12px;
1315
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
1316
+ }
1317
+
1318
+ /* The real checkboxes in the Properties panel — the responsive marks. */
1319
+ .lab-checkbox {
1320
+ width: 16px;
1321
+ height: 16px;
1322
+ margin: 0;
1323
+ accent-color: var(--lab-brand-solid);
1324
+ cursor: pointer;
1325
+ }
1326
+
1327
+ .lab-menu__item:has(.lab-checkbox:disabled) {
1328
+ cursor: default;
1329
+ opacity: 0.7;
1330
+ }
1331
+
1332
+ .lab-menu--variants {
1333
+ width: 256px;
1334
+ }
1335
+
1336
+ .lab-menu--presets {
1337
+ width: 304px;
1338
+ }
1339
+
1340
+ /* The design's 312px, grown to fit "Copy link to this exact size" and its size beside it. */
1341
+ .lab-menu--actions {
1342
+ width: max-content;
1343
+ min-width: 312px;
1344
+ max-width: 360px;
1345
+ }
1346
+
1347
+ .lab-menu--components {
1348
+ width: 288px;
1349
+ }
1350
+
1351
+ /* The zoom row in the presets menu (Figma `Story tab`, Type=Tab): a segmented choice. It fills the
1352
+ menu between its 12px margins and splits that into seven equal segments. Sized by its labels
1353
+ instead, it came to 283px, which with both margins is 5px more than the 304px menu holds, so the
1354
+ menu scrolled sideways. */
1355
+ .lab-zoom {
1356
+ display: flex;
1357
+ gap: 2px;
1358
+ margin: 0 var(--lab-space-lg) var(--lab-space-md);
1359
+ padding: 2px;
1360
+ border: 1px solid var(--lab-border-secondary);
1361
+ border-radius: var(--lab-radius-md);
1362
+ background-color: var(--lab-bg-secondary);
1363
+ }
1364
+
1365
+ .lab-zoom__btn {
1366
+ flex: 1 1 0;
1367
+ min-width: 0;
1368
+ display: inline-flex;
1369
+ align-items: center;
1370
+ justify-content: center;
1371
+ height: 32px;
1372
+ padding: var(--lab-space-sm) 0;
1373
+ border: 1px solid transparent;
1374
+ border-radius: var(--lab-radius-sm);
1375
+ background: transparent;
1376
+ color: var(--lab-text-quaternary);
1377
+ font: inherit;
1378
+ font-weight: 600;
1379
+ font-variant-numeric: tabular-nums;
1380
+ cursor: pointer;
1381
+ }
1382
+
1383
+ .lab-zoom__btn:hover {
1384
+ color: var(--lab-text-secondary);
1385
+ }
1386
+
1387
+ .lab-zoom__btn[aria-checked="true"] {
1388
+ border-color: var(--lab-border-primary);
1389
+ background-color: var(--lab-bg-primary);
1390
+ box-shadow: var(--lab-shadow-xs);
1391
+ color: var(--lab-text-secondary);
1392
+ }
1393
+
1394
+ /* ---- the Properties panel (Figma `Menu`, Type=Properties) -----------------------------------
1395
+ Not a menu: the eye's hover panel, a manual popover placed by ../chrome/navbar-client.ts. */
1396
+ .lab-props {
1397
+ width: 288px;
1398
+ }
1399
+
1400
+ .lab-props__facts {
1401
+ display: flex;
1402
+ flex-direction: column;
1403
+ gap: 2px;
1404
+ padding: var(--lab-space-xs) var(--lab-space-xl) var(--lab-space-md);
1405
+ }
1406
+
1407
+ .lab-props__fact {
1408
+ display: flex;
1409
+ align-items: center;
1410
+ gap: var(--lab-space-md);
1411
+ margin: 0;
1412
+ color: var(--lab-text-tertiary);
1413
+ }
1414
+
1415
+ .lab-props__dot {
1416
+ flex: none;
1417
+ width: 16px;
1418
+ height: 16px;
1419
+ background: radial-gradient(circle, var(--lab-fg-quaternary) 3.5px, transparent 4px);
1420
+ }
1421
+
1422
+ .lab-props__dot--live {
1423
+ background: radial-gradient(circle, var(--lab-success-dot) 3.5px, transparent 4px);
1424
+ }
1425
+
1426
+ .lab-props__pin {
1427
+ color: var(--lab-pin-open);
1428
+ }
1429
+
1430
+ .lab-props__source {
1431
+ margin: 0;
1432
+ padding: var(--lab-space-xs) var(--lab-space-xl) var(--lab-space-md);
1433
+ color: var(--lab-text-tertiary);
1434
+ overflow-wrap: anywhere;
1435
+ }
1436
+
1437
+ .lab-props__note {
1438
+ margin: 0;
1439
+ padding: var(--lab-space-md) var(--lab-space-xl);
1440
+ color: var(--lab-text-quaternary);
1441
+ font-size: 12px;
1442
+ line-height: 18px;
1443
+ }
1444
+
1445
+ /* ---------------------------------------------------------------------------------------------
1446
+ The tree (Figma `Tree view`, sm, as condensed on 2026-09-24): no icons, 12px text, 30px rows
1447
+ with no gap between them, a 16px connector column per level, connector lines in neutral-300
1448
+ with a curved end on the last child.
1449
+ --------------------------------------------------------------------------------------------- */
1450
+ .lab-tree,
1451
+ .lab-tree__group {
1452
+ display: flex;
1453
+ flex-direction: column;
1454
+ margin: 0;
1455
+ padding: 0;
1456
+ list-style: none;
1457
+ }
1458
+
1459
+ .lab-tree {
1460
+ padding: var(--lab-space-xs);
1461
+ }
1462
+
1463
+ .lab-tree__node {
1464
+ position: relative;
1465
+ --lab-indent: calc(var(--lab-d, 0) * 24px);
1466
+ }
1467
+
1468
+ .lab-tree__node > details > summary {
1469
+ list-style: none;
1470
+ }
1471
+
1472
+ .lab-tree__node > details > summary::-webkit-details-marker {
1473
+ display: none;
1474
+ }
1475
+
1476
+ .lab-tree__row {
1477
+ position: relative;
1478
+ display: flex;
1479
+ align-items: center;
1480
+ gap: var(--lab-space-sm);
1481
+ min-height: 30px;
1482
+ padding: var(--lab-space-sm) var(--lab-space-md);
1483
+ padding-left: calc(var(--lab-space-md) + var(--lab-indent));
1484
+ border-radius: var(--lab-radius-sm);
1485
+ color: var(--lab-text-tertiary);
1486
+ font-size: 12px;
1487
+ line-height: 18px;
1488
+ font-weight: 600;
1489
+ text-decoration: none;
1490
+ cursor: pointer;
1491
+ user-select: none;
1492
+ transition: background-color var(--lab-ease), color var(--lab-ease);
1493
+ }
1494
+
1495
+ .lab-tree__row:hover {
1496
+ background-color: var(--lab-bg-hover);
1497
+ color: var(--lab-text-primary);
1498
+ }
1499
+
1500
+ .lab-tree__row[aria-current="page"] {
1501
+ background-color: var(--lab-bg-active);
1502
+ color: var(--lab-text-brand-primary);
1503
+ }
1504
+
1505
+ .lab-tree__row[aria-current="page"] > .lab-icon {
1506
+ color: var(--lab-fg-brand);
1507
+ }
1508
+
1509
+ .lab-tree__row[aria-disabled="true"] {
1510
+ color: var(--lab-text-quaternary);
1511
+ cursor: default;
1512
+ }
1513
+
1514
+ .lab-tree__row > .lab-icon {
1515
+ width: 16px;
1516
+ height: 16px;
1517
+ color: var(--lab-fg-quaternary);
1518
+ }
1519
+
1520
+ .lab-tree__label {
1521
+ flex: 1;
1522
+ min-width: 0;
1523
+ overflow: hidden;
1524
+ text-overflow: ellipsis;
1525
+ white-space: nowrap;
1526
+ }
1527
+
1528
+ /* A group row whose label is also a page (Pages › Services): the label is the link, the rest of
1529
+ the row toggles. */
1530
+ .lab-tree__label > a {
1531
+ color: inherit;
1532
+ text-decoration: none;
1533
+ }
1534
+
1535
+ .lab-tree__label > a::after {
1536
+ content: "";
1537
+ position: absolute;
1538
+ inset: 0 32px 0 0;
1539
+ }
1540
+
1541
+ .lab-tree__chevron {
1542
+ width: 16px;
1543
+ height: 16px;
1544
+ color: var(--lab-fg-quaternary);
1545
+ transition: transform var(--lab-ease);
1546
+ }
1547
+
1548
+ details:not([open]) > summary .lab-tree__chevron {
1549
+ transform: rotate(-90deg);
1550
+ }
1551
+
1552
+ /* The status dot — the tree's one-glyph version of the live / used-by pills. */
1553
+ .lab-dot {
1554
+ flex: none;
1555
+ width: 8px;
1556
+ height: 8px;
1557
+ border-radius: 50%;
1558
+ }
1559
+
1560
+ .lab-dot--live {
1561
+ background-color: var(--lab-success-dot);
1562
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--lab-success-dot) 18%, transparent);
1563
+ }
1564
+
1565
+ .lab-dot--used {
1566
+ border: 2px solid var(--lab-info-dot);
1567
+ }
1568
+
1569
+ .lab-dot--open {
1570
+ background-color: var(--lab-brand-solid);
1571
+ }
1572
+
1573
+ .lab-dot--muted {
1574
+ border: 1.5px dashed var(--lab-fg-quaternary);
1575
+ }
1576
+
1577
+ .lab-tree__count {
1578
+ flex: none;
1579
+ color: var(--lab-text-quaternary);
1580
+ font-size: 12px;
1581
+ font-weight: 500;
1582
+ }
1583
+
1584
+ /* Connectors. Each child draws the line of its own level in the 16px column its parent's icon
1585
+ sits over; the last child ends it in a curve that turns towards its own row. */
1586
+ .lab-tree__group > .lab-tree__node::before {
1587
+ content: "";
1588
+ position: absolute;
1589
+ top: 0;
1590
+ bottom: 0;
1591
+ left: calc(var(--lab-space-md) + var(--lab-indent) - 24px + 7.5px);
1592
+ width: 1px;
1593
+ background-color: var(--lab-fg-muted);
1594
+ pointer-events: none;
1595
+ }
1596
+
1597
+ .lab-tree__group > .lab-tree__node:last-child::before {
1598
+ bottom: auto;
1599
+ height: 15px;
1600
+ width: 9px;
1601
+ background: none;
1602
+ border-left: 1px solid var(--lab-fg-muted);
1603
+ border-bottom: 1px solid var(--lab-fg-muted);
1604
+ border-bottom-left-radius: 8px;
1605
+ }
1606
+
1607
+ .lab-tree__empty {
1608
+ margin: var(--lab-space-md) var(--lab-space-md);
1609
+ color: var(--lab-text-quaternary);
1610
+ }
1611
+
1612
+ /* Search: rows that do not match are hidden by the tree script; this is the hook. */
1613
+ .lab-tree [data-lab-hidden] {
1614
+ display: none;
1615
+ }
1616
+
1617
+ .lab-tree mark {
1618
+ background: transparent;
1619
+ color: var(--lab-text-brand-secondary);
1620
+ }
1621
+
1622
+ /* ---- the Sections tree (Figma `Sections tree`, ../chrome/SectionsTree.astro) ---------------- */
1623
+ /* The count and the Sort by button, above the rows. */
1624
+ .lab-stree-head {
1625
+ display: flex;
1626
+ align-items: center;
1627
+ justify-content: space-between;
1628
+ padding: var(--lab-space-xs) var(--lab-space-xs) var(--lab-space-xs) calc(var(--lab-space-xs) + var(--lab-space-md));
1629
+ }
1630
+
1631
+ .lab-stree-head__count {
1632
+ color: var(--lab-text-quaternary);
1633
+ font-size: 12px;
1634
+ line-height: 18px;
1635
+ font-weight: 500;
1636
+ }
1637
+
1638
+ .lab-stree-head__sort {
1639
+ display: inline-flex;
1640
+ align-items: center;
1641
+ gap: var(--lab-space-xs);
1642
+ height: 28px;
1643
+ padding: 0 var(--lab-space-sm) 0 var(--lab-space-md);
1644
+ border: 0;
1645
+ border-radius: var(--lab-radius-sm);
1646
+ background: transparent;
1647
+ color: var(--lab-text-quaternary);
1648
+ font: inherit;
1649
+ font-size: 12px;
1650
+ line-height: 18px;
1651
+ font-weight: 500;
1652
+ cursor: pointer;
1653
+ }
1654
+
1655
+ .lab-stree-head__sort b {
1656
+ color: var(--lab-text-secondary);
1657
+ font-weight: 600;
1658
+ }
1659
+
1660
+ .lab-stree-head__sort .lab-icon {
1661
+ width: 12px;
1662
+ height: 12px;
1663
+ transition: transform var(--lab-ease);
1664
+ }
1665
+
1666
+ .lab-stree-head__sort:hover,
1667
+ .lab-stree-head:has(+ .lab-menu:popover-open) .lab-stree-head__sort {
1668
+ background-color: var(--lab-bg-sunken);
1669
+ }
1670
+
1671
+ .lab-stree-head:has(+ .lab-menu:popover-open) .lab-stree-head__sort .lab-icon {
1672
+ transform: rotate(180deg);
1673
+ }
1674
+
1675
+ .lab-stree-head__by-name,
1676
+ :root[data-lab-sort="name"] .lab-stree-head__by-slot {
1677
+ display: none;
1678
+ }
1679
+
1680
+ :root[data-lab-sort="name"] .lab-stree-head__by-name {
1681
+ display: inline;
1682
+ }
1683
+
1684
+ /* The sort menu (Figma `Menu`, Type=Sort): 200px, the tick leading its row. */
1685
+ .lab-menu.lab-menu--sort {
1686
+ min-width: 200px;
1687
+ width: 200px;
1688
+ padding: var(--lab-space-xs) 0;
1689
+ border-color: var(--lab-border-secondary-alt);
1690
+ box-shadow: var(--lab-shadow-lg-3);
1691
+ }
1692
+
1693
+ .lab-menu--sort .lab-menu__item {
1694
+ width: calc(100% - 12px);
1695
+ margin: 1px var(--lab-space-sm);
1696
+ padding: var(--lab-space-md) var(--lab-space-sm) var(--lab-space-md) 10px;
1697
+ color: var(--lab-text-secondary);
1698
+ font-weight: 600;
1699
+ }
1700
+
1701
+ .lab-menu--sort .lab-menu__item .lab-menu__tick {
1702
+ order: -1;
1703
+ margin-left: 0;
1704
+ }
1705
+
1706
+ /* The rows. A remembered "Name" sort is drawn by `order` from the first paint; the script then
1707
+ moves the rows to match (../chrome/shell-client.ts). */
1708
+ .lab-stree > li {
1709
+ order: var(--lab-o-slot, 0);
1710
+ }
1711
+
1712
+ :root[data-lab-sort="name"] .lab-stree > li {
1713
+ order: var(--lab-o-name, 0);
1714
+ }
1715
+
1716
+ .lab-stree__heading {
1717
+ margin: var(--lab-space-xs) 0;
1718
+ padding: var(--lab-space-lg) 0 var(--lab-space-xs) var(--lab-space-md);
1719
+ border-top: 1px solid var(--lab-border-secondary);
1720
+ color: var(--lab-text-quaternary);
1721
+ font-size: 12px;
1722
+ line-height: 18px;
1723
+ font-weight: 500;
1724
+ }
1725
+
1726
+ .lab-stree .lab-tree__row {
1727
+ gap: var(--lab-space-sm);
1728
+ }
1729
+
1730
+ /* The row you are on reads violet, without a fill — the badge carries "you are here". */
1731
+ .lab-stree .lab-tree__row[aria-current="page"] {
1732
+ background-color: transparent;
1733
+ color: var(--lab-text-brand-secondary);
1734
+ }
1735
+
1736
+ .lab-stree .lab-tree__row[aria-current="page"]:hover {
1737
+ background-color: var(--lab-bg-hover);
1738
+ }
1739
+
1740
+ /* A section's versions: the connector runs straight down the badge column, no curve. */
1741
+ .lab-stree .lab-tree__group > .lab-tree__node::before,
1742
+ .lab-stree .lab-tree__group > .lab-tree__node:last-child::before {
1743
+ top: 0;
1744
+ bottom: 0;
1745
+ height: auto;
1746
+ width: 1px;
1747
+ border: 0;
1748
+ border-radius: 0;
1749
+ background-color: var(--lab-fg-muted);
1750
+ }
1751
+
1752
+ .lab-stree .lab-tree__group > .lab-tree__node > .lab-tree__row {
1753
+ gap: var(--lab-space-md);
1754
+ }
1755
+
1756
+ /* The slot badge (Figma `Slot badge`): green with its slot, a grey dash when no page mounts it,
1757
+ violet on the row you are on. Two digits widen it. */
1758
+ .lab-slot {
1759
+ flex: none;
1760
+ display: inline-grid;
1761
+ place-items: center;
1762
+ min-width: 20px;
1763
+ height: 16px;
1764
+ padding: 0 3px;
1765
+ border: 1px solid var(--lab-slot-border);
1766
+ border-radius: var(--lab-radius-xs);
1767
+ background-color: var(--lab-slot-bg);
1768
+ color: var(--lab-slot-text);
1769
+ font-size: 12px;
1770
+ line-height: 14px;
1771
+ font-weight: 600;
1772
+ font-variant-numeric: tabular-nums;
1773
+ }
1774
+
1775
+ .lab-slot[data-state="unslotted"] {
1776
+ border-color: var(--lab-slot-none-border);
1777
+ background-color: var(--lab-slot-none-bg);
1778
+ color: var(--lab-slot-none-text);
1779
+ }
1780
+
1781
+ .lab-slot[data-state="current"] {
1782
+ border-color: var(--lab-slot-here-border);
1783
+ background-color: var(--lab-slot-here-bg);
1784
+ color: var(--lab-slot-here-text);
1785
+ }
1786
+
1787
+ /* ---------------------------------------------------------------------------------------------
1788
+ Main: the navbar above, the content below.
1789
+ --------------------------------------------------------------------------------------------- */
1790
+ .lab-main {
1791
+ position: relative;
1792
+ z-index: 1;
1793
+ flex: 1;
1794
+ min-width: 0;
1795
+ display: flex;
1796
+ flex-direction: column;
1797
+ height: 100%;
1798
+ }
1799
+
1800
+ .lab-navbar {
1801
+ flex: none;
1802
+ display: flex;
1803
+ align-items: center;
1804
+ flex-wrap: wrap;
1805
+ gap: var(--lab-space-md) var(--lab-space-lg);
1806
+ min-height: var(--lab-navbar-h);
1807
+ padding: var(--lab-space-md) var(--lab-space-2xl);
1808
+ border-bottom: 1px solid var(--lab-border-secondary);
1809
+ background-color: var(--lab-bg-primary);
1810
+ }
1811
+
1812
+ .lab-navbar__panel-btn {
1813
+ margin-left: calc(-1 * var(--lab-space-sm));
1814
+ }
1815
+
1816
+ .lab-navbar__id {
1817
+ display: flex;
1818
+ align-items: center;
1819
+ gap: var(--lab-space-md) var(--lab-space-lg);
1820
+ flex-wrap: wrap;
1821
+ min-width: 0;
1822
+ flex: 1 1 280px;
1823
+ }
1824
+
1825
+ .lab-navbar__title {
1826
+ margin: 0;
1827
+ min-width: 0;
1828
+ color: var(--lab-text-primary);
1829
+ font-size: 16px;
1830
+ line-height: 24px;
1831
+ font-weight: 600;
1832
+ white-space: nowrap;
1833
+ overflow: hidden;
1834
+ text-overflow: ellipsis;
1835
+ }
1836
+
1837
+ .lab-navbar__title small {
1838
+ margin-left: var(--lab-space-sm);
1839
+ color: var(--lab-text-quaternary);
1840
+ font-size: 14px;
1841
+ font-weight: 500;
1842
+ }
1843
+
1844
+ .lab-navbar__meta {
1845
+ display: flex;
1846
+ align-items: center;
1847
+ flex-wrap: wrap;
1848
+ gap: var(--lab-space-sm);
1849
+ color: var(--lab-text-quaternary);
1850
+ }
1851
+
1852
+ .lab-navbar__tools {
1853
+ display: flex;
1854
+ align-items: center;
1855
+ flex-wrap: wrap;
1856
+ gap: var(--lab-space-md);
1857
+ }
1858
+
1859
+ .lab-navbar__sep {
1860
+ width: 1px;
1861
+ height: 24px;
1862
+ background-color: var(--lab-border-secondary);
1863
+ }
1864
+
1865
+ /* ---- the navbar of a component or a page (Figma `Astrolab` › Lab / Components › Navbar) --------
1866
+ One grid, `.lab-nb`, laid out by a container query on the navbar's own width — the sidebars
1867
+ change the main area, never the viewport, so a media query would read the wrong number.
1868
+ ONE row, 59px, when the navbar is 1040px wide or more:
1869
+ identity · variants · previous / next · device switch + Viewport field · Comment · Parameters · ⋯
1870
+ otherwise TWO rows, 99px, the viewport row always last:
1871
+ identity · variants · previous / next · ⋯ / device switch + Viewport field | Comment · Parameters
1872
+ and under 552px THREE or FOUR rows — "The narrow navbar", after the Viewport field's rules.
1873
+ The query's number is the navbar's content box: 1040 less its 16px side padding. Row 1 needs
1874
+ about 468px and row 2 about 480 to 520px (Parameters is the difference), so the two rows hold
1875
+ down to a 552px navbar. Where row 2 still runs out (the long "phone landscape" band, the panel
1876
+ button taking its 44px), Comment and Parameters wrap under the viewport group, to the right,
1877
+ rather than overflow. Every control is 32px tall; the device switch's outer border makes the
1878
+ one-row bar 34px of content, which is where 59 comes from.
1879
+ The page must never scroll sideways. Every layout fits down to a navbar of about 200px (measured
1880
+ 2026-09-24); below that nothing can, and the navbar clips its right edge instead
1881
+ (`overflow-x: clip` — the menus and the Properties panel are popovers in the top layer, so they
1882
+ are not clipped with it). */
1883
+ .lab-navbar:has(> .lab-nb) {
1884
+ container: lab-navbar / inline-size;
1885
+ align-items: flex-start;
1886
+ flex-wrap: nowrap;
1887
+ gap: var(--lab-space-lg);
1888
+ min-height: 0;
1889
+ padding: var(--lab-space-lg) var(--lab-space-xl);
1890
+ overflow-x: clip;
1891
+ }
1892
+
1893
+ .lab-navbar:has(> .lab-nb) .lab-navbar__panel-btn {
1894
+ margin-left: 0;
1895
+ }
1896
+
1897
+ .lab-nb {
1898
+ flex: 1;
1899
+ min-width: 0;
1900
+ display: grid;
1901
+ grid-template-columns: minmax(0, 1fr) auto auto;
1902
+ grid-template-areas:
1903
+ "id variants actions"
1904
+ "tools tools tools";
1905
+ align-items: center;
1906
+ gap: var(--lab-space-md);
1907
+ }
1908
+
1909
+ @container lab-navbar (min-width: 1008px) {
1910
+ .lab-nb {
1911
+ grid-template-columns: minmax(0, 1fr) auto auto auto;
1912
+ grid-template-areas: "id variants tools actions";
1913
+ column-gap: var(--lab-space-lg);
1914
+ }
1915
+
1916
+ .lab-nb .lab-nb__id {
1917
+ padding-right: 0;
1918
+ }
1919
+
1920
+ .lab-nb .lab-nb__tools {
1921
+ column-gap: var(--lab-space-lg);
1922
+ }
1923
+ }
1924
+
1925
+ .lab-nb__id {
1926
+ grid-area: id;
1927
+ display: flex;
1928
+ flex-wrap: wrap;
1929
+ align-items: center;
1930
+ gap: var(--lab-space-xs) 10px;
1931
+ min-width: 0;
1932
+ padding-right: var(--lab-space-xs);
1933
+ }
1934
+
1935
+ .lab-nb__line {
1936
+ display: flex;
1937
+ align-items: center;
1938
+ gap: var(--lab-space-md);
1939
+ min-width: 0;
1940
+ max-width: 100%;
1941
+ }
1942
+
1943
+ .lab-nb__name {
1944
+ margin: 0;
1945
+ min-width: 0;
1946
+ overflow: hidden;
1947
+ color: var(--lab-text-primary);
1948
+ font-size: 16px;
1949
+ line-height: 24px;
1950
+ font-weight: 600;
1951
+ text-overflow: ellipsis;
1952
+ white-space: nowrap;
1953
+ }
1954
+
1955
+ /* The version (`V1`) or, on a page, the route (`/services/web-design`). */
1956
+ .lab-nb__tag {
1957
+ flex: none;
1958
+ padding: 0 var(--lab-space-sm);
1959
+ border: 1px solid var(--lab-border-primary);
1960
+ border-radius: var(--lab-radius-sm);
1961
+ color: var(--lab-text-tertiary);
1962
+ font-size: 12px;
1963
+ line-height: 18px;
1964
+ font-weight: 500;
1965
+ white-space: nowrap;
1966
+ }
1967
+
1968
+ .lab-nb__tag--route {
1969
+ flex-shrink: 1;
1970
+ min-width: 0;
1971
+ overflow: hidden;
1972
+ text-overflow: ellipsis;
1973
+ background-color: var(--lab-bg-secondary);
1974
+ }
1975
+
1976
+ /* A page's second identity line: its source file, then the components it mounts. */
1977
+ .lab-nb__facts {
1978
+ display: flex;
1979
+ align-items: center;
1980
+ gap: var(--lab-space-md);
1981
+ min-width: 0;
1982
+ max-width: 100%;
1983
+ color: var(--lab-text-quaternary);
1984
+ font-size: 12px;
1985
+ line-height: 18px;
1986
+ }
1987
+
1988
+ .lab-nb__source {
1989
+ display: inline-flex;
1990
+ align-items: center;
1991
+ gap: var(--lab-space-xs);
1992
+ min-width: 0;
1993
+ overflow: hidden;
1994
+ white-space: nowrap;
1995
+ text-overflow: ellipsis;
1996
+ }
1997
+
1998
+ .lab-nb__source .lab-icon {
1999
+ width: 14px;
2000
+ height: 14px;
2001
+ }
2002
+
2003
+ /* The components chip: 22px to look at, 32px to hit. */
2004
+ .lab-nb__chip {
2005
+ position: relative;
2006
+ flex: none;
2007
+ display: inline-flex;
2008
+ align-items: center;
2009
+ gap: var(--lab-space-xs);
2010
+ padding: 1px var(--lab-space-xs) 1px var(--lab-space-sm);
2011
+ border: 1px solid var(--lab-border-primary);
2012
+ border-radius: var(--lab-radius-sm);
2013
+ background: transparent;
2014
+ color: var(--lab-text-secondary);
2015
+ font: inherit;
2016
+ font-size: 12px;
2017
+ line-height: 18px;
2018
+ font-weight: 500;
2019
+ white-space: nowrap;
2020
+ cursor: pointer;
2021
+ }
2022
+
2023
+ .lab-nb__chip::after {
2024
+ content: "";
2025
+ position: absolute;
2026
+ inset: -5px 0;
2027
+ }
2028
+
2029
+ .lab-nb__chip:hover {
2030
+ background-color: var(--lab-bg-secondary);
2031
+ }
2032
+
2033
+ .lab-nb__chip .lab-icon {
2034
+ width: 12px;
2035
+ height: 12px;
2036
+ color: var(--lab-text-quaternary);
2037
+ }
2038
+
2039
+ .lab-nb__variants {
2040
+ grid-area: variants;
2041
+ display: flex;
2042
+ align-items: center;
2043
+ gap: var(--lab-space-md);
2044
+ }
2045
+
2046
+ .lab-nb__step {
2047
+ display: flex;
2048
+ }
2049
+
2050
+ /* On two rows the space between the viewport group and the overlays is space-between's, so the gap
2051
+ is only the least it may be before the overlays wrap: 8, which is what keeps Parameters on the row
2052
+ at 552px. The one-row bar sizes this to its content, where the gap is the spacing, and sets 12. */
2053
+ .lab-nb__tools {
2054
+ grid-area: tools;
2055
+ display: flex;
2056
+ flex-wrap: wrap;
2057
+ align-items: center;
2058
+ justify-content: space-between;
2059
+ gap: var(--lab-space-md);
2060
+ min-width: 0;
2061
+ }
2062
+
2063
+ .lab-nb__viewport {
2064
+ display: flex;
2065
+ align-items: center;
2066
+ gap: var(--lab-space-md);
2067
+ }
2068
+
2069
+ /* margin-left: auto keeps the pair at the right when it wraps onto a line of its own. */
2070
+ .lab-nb__overlays {
2071
+ display: flex;
2072
+ align-items: center;
2073
+ gap: var(--lab-space-xs);
2074
+ margin-left: auto;
2075
+ }
2076
+
2077
+ .lab-nb__actions {
2078
+ grid-area: actions;
2079
+ display: flex;
2080
+ }
2081
+
2082
+ /* The navbar's buttons (Figma `Tool button`): 32px, ghost at rest. Icons at rest are
2083
+ text-quaternary (#737373, 4.7:1) rather than the kit's #a3a3a3 — an icon-only button needs 3:1.
2084
+ Outline for a standalone action, Pressed for a toggle that is on, and the blue Pins pressed for
2085
+ Comment while pin mode is on, because pins are blue. */
2086
+ .lab-tool {
2087
+ flex: none;
2088
+ display: inline-flex;
2089
+ align-items: center;
2090
+ justify-content: center;
2091
+ gap: var(--lab-space-xs);
2092
+ height: 32px;
2093
+ padding: 7px;
2094
+ border: 1px solid transparent;
2095
+ border-radius: var(--lab-radius-md);
2096
+ background: transparent;
2097
+ color: var(--lab-text-quaternary);
2098
+ font: inherit;
2099
+ font-weight: 600;
2100
+ white-space: nowrap;
2101
+ text-decoration: none;
2102
+ cursor: pointer;
2103
+ transition: background-color var(--lab-ease), color var(--lab-ease), border-color var(--lab-ease);
2104
+ }
2105
+
2106
+ .lab-tool .lab-icon {
2107
+ width: 16px;
2108
+ height: 16px;
2109
+ }
2110
+
2111
+ .lab-tool__label {
2112
+ padding: 0 2px;
2113
+ color: var(--lab-text-tertiary);
2114
+ }
2115
+
2116
+ .lab-tool:hover {
2117
+ background-color: var(--lab-bg-secondary);
2118
+ color: var(--lab-text-secondary);
2119
+ }
2120
+
2121
+ .lab-tool:hover .lab-tool__label {
2122
+ color: var(--lab-text-secondary);
2123
+ }
2124
+
2125
+ .lab-tool--icon {
2126
+ width: 32px;
2127
+ padding: 0;
2128
+ }
2129
+
2130
+ .lab-tool--outline {
2131
+ border-color: var(--lab-border-primary);
2132
+ background-color: var(--lab-bg-primary);
2133
+ box-shadow: var(--lab-shadow-xs);
2134
+ }
2135
+
2136
+ .lab-tool--outline .lab-tool__label {
2137
+ color: var(--lab-text-secondary);
2138
+ }
2139
+
2140
+ .lab-tool[aria-pressed="true"],
2141
+ .lab-tool[data-pinned] {
2142
+ border-color: var(--lab-border-secondary);
2143
+ background-color: var(--lab-bg-hover);
2144
+ color: var(--lab-text-secondary);
2145
+ }
2146
+
2147
+ .lab-tool[aria-pressed="true"]:hover,
2148
+ .lab-tool[data-pinned]:hover {
2149
+ border-color: var(--lab-border-primary);
2150
+ }
2151
+
2152
+ .lab-tool--pins[aria-pressed="true"] {
2153
+ border-color: var(--lab-pins-border);
2154
+ background-color: var(--lab-pins-bg);
2155
+ color: var(--lab-pins-text);
2156
+ }
2157
+
2158
+ .lab-tool--pins[aria-pressed="true"] .lab-tool__label {
2159
+ color: var(--lab-pins-text);
2160
+ }
2161
+
2162
+ .lab-tool--pins[aria-pressed="true"]:hover {
2163
+ border-color: var(--lab-pins-border);
2164
+ background-color: var(--lab-pins-bg-hover);
2165
+ }
2166
+
2167
+ .lab-tool:disabled {
2168
+ opacity: 0.45;
2169
+ cursor: default;
2170
+ }
2171
+
2172
+ .lab-tool:disabled:hover {
2173
+ background-color: transparent;
2174
+ }
2175
+
2176
+ /* The variant dropdown (Figma `Story tab`, Type=Dropdown) — at every count. */
2177
+ .lab-variant-btn {
2178
+ display: inline-flex;
2179
+ align-items: center;
2180
+ gap: var(--lab-space-sm);
2181
+ max-width: 260px;
2182
+ height: 32px;
2183
+ padding: var(--lab-space-sm) 10px var(--lab-space-sm) var(--lab-space-lg);
2184
+ border: 1px solid var(--lab-border-primary);
2185
+ border-radius: var(--lab-radius-md);
2186
+ background-color: var(--lab-bg-primary);
2187
+ box-shadow: var(--lab-shadow-xs);
2188
+ color: var(--lab-text-secondary);
2189
+ font: inherit;
2190
+ font-weight: 600;
2191
+ white-space: nowrap;
2192
+ cursor: pointer;
2193
+ }
2194
+
2195
+ .lab-variant-btn:hover {
2196
+ background-color: var(--lab-bg-secondary);
2197
+ }
2198
+
2199
+ .lab-variant-btn__name {
2200
+ min-width: 0;
2201
+ overflow: hidden;
2202
+ text-overflow: ellipsis;
2203
+ }
2204
+
2205
+ .lab-variant-btn__count {
2206
+ color: var(--lab-text-quaternary);
2207
+ font-weight: 500;
2208
+ font-variant-numeric: tabular-nums;
2209
+ }
2210
+
2211
+ .lab-variant-btn .lab-icon {
2212
+ width: 16px;
2213
+ height: 16px;
2214
+ color: var(--lab-text-quaternary);
2215
+ }
2216
+
2217
+ /* The device switch (Figma `Device switch`): four 34 × 32 segments. The active one is violet —
2218
+ "you are here"; a width that is none of the four presses none. */
2219
+ .lab-devsw {
2220
+ flex: none;
2221
+ display: inline-flex;
2222
+ border: 1px solid var(--lab-border-primary);
2223
+ border-radius: var(--lab-radius-md);
2224
+ background-color: var(--lab-bg-primary);
2225
+ box-shadow: var(--lab-shadow-xs);
2226
+ overflow: hidden;
2227
+ }
2228
+
2229
+ .lab-devsw__btn {
2230
+ display: grid;
2231
+ place-items: center;
2232
+ width: 34px;
2233
+ height: 32px;
2234
+ padding: 0;
2235
+ border: 0;
2236
+ border-right: 1px solid var(--lab-border-primary);
2237
+ background: transparent;
2238
+ color: var(--lab-text-quaternary);
2239
+ cursor: pointer;
2240
+ }
2241
+
2242
+ .lab-devsw__btn:last-child {
2243
+ border-right: 0;
2244
+ }
2245
+
2246
+ .lab-devsw__btn:hover {
2247
+ background-color: var(--lab-bg-secondary);
2248
+ color: var(--lab-text-secondary);
2249
+ }
2250
+
2251
+ .lab-devsw__btn[aria-pressed="true"] {
2252
+ background-color: var(--lab-bg-active);
2253
+ color: var(--lab-brand-solid);
2254
+ }
2255
+
2256
+ :root.dark .lab-devsw__btn[aria-pressed="true"] {
2257
+ color: var(--lab-fg-brand);
2258
+ }
2259
+
2260
+ .lab-shell .lab-devsw__btn:focus-visible {
2261
+ box-shadow: inset 0 0 0 2px var(--lab-fg-brand);
2262
+ }
2263
+
2264
+ /* The Viewport field (Figma `Viewport field`): W × H typed in place, then the band and zoom as the
2265
+ button that opens the presets. While a frame edge is dragged the numbers follow it, in violet. */
2266
+ .lab-vpf {
2267
+ flex: none;
2268
+ display: inline-flex;
2269
+ align-items: center;
2270
+ gap: var(--lab-space-xs);
2271
+ height: 32px;
2272
+ padding-left: 10px;
2273
+ border: 1px solid var(--lab-border-primary);
2274
+ border-radius: var(--lab-radius-md);
2275
+ background-color: var(--lab-bg-primary);
2276
+ box-shadow: var(--lab-shadow-xs);
2277
+ overflow: hidden;
2278
+ color: var(--lab-text-secondary);
2279
+ font-weight: 500;
2280
+ font-variant-numeric: tabular-nums;
2281
+ transition: border-color var(--lab-ease), box-shadow var(--lab-ease);
2282
+ }
2283
+
2284
+ .lab-vpf:has(.lab-vpf__num:focus) {
2285
+ border-color: var(--lab-fg-brand);
2286
+ box-shadow: var(--lab-shadow-xs), 0 0 0 3px color-mix(in srgb, var(--lab-fg-brand) 22%, transparent);
2287
+ }
2288
+
2289
+ .lab-vpf__num {
2290
+ width: 36px;
2291
+ padding: 0;
2292
+ border: 0;
2293
+ outline: none;
2294
+ background: transparent;
2295
+ color: var(--lab-text-secondary);
2296
+ font: inherit;
2297
+ appearance: textfield;
2298
+ -moz-appearance: textfield;
2299
+ }
2300
+
2301
+ .lab-vpf__num--w {
2302
+ text-align: right;
2303
+ }
2304
+
2305
+ .lab-vpf__num::-webkit-outer-spin-button,
2306
+ .lab-vpf__num::-webkit-inner-spin-button {
2307
+ -webkit-appearance: none;
2308
+ margin: 0;
2309
+ }
2310
+
2311
+ .lab-shell .lab-vpf__num:focus-visible {
2312
+ box-shadow: none;
2313
+ color: var(--lab-text-brand-secondary);
2314
+ }
2315
+
2316
+ .lab-vpf[data-dragging] .lab-vpf__num {
2317
+ color: var(--lab-text-brand-secondary);
2318
+ }
2319
+
2320
+ .lab-vpf__x {
2321
+ color: var(--lab-text-quaternary);
2322
+ }
2323
+
2324
+ .lab-vpf__menu {
2325
+ display: inline-flex;
2326
+ align-items: center;
2327
+ gap: var(--lab-space-sm);
2328
+ height: 100%;
2329
+ padding: 0 var(--lab-space-md) 0 10px;
2330
+ border: 0;
2331
+ border-left: 1px solid var(--lab-border-secondary);
2332
+ background: transparent;
2333
+ font: inherit;
2334
+ white-space: nowrap;
2335
+ cursor: pointer;
2336
+ }
2337
+
2338
+ .lab-vpf__menu:hover {
2339
+ background-color: var(--lab-bg-secondary);
2340
+ }
2341
+
2342
+ .lab-shell .lab-vpf__menu:focus-visible {
2343
+ box-shadow: inset 0 0 0 2px var(--lab-fg-brand);
2344
+ }
2345
+
2346
+ .lab-vpf__band {
2347
+ color: var(--lab-text-secondary);
2348
+ font-weight: 600;
2349
+ }
2350
+
2351
+ .lab-vpf__zoom {
2352
+ color: var(--lab-text-quaternary);
2353
+ font-weight: 500;
2354
+ }
2355
+
2356
+ .lab-vpf__menu .lab-icon {
2357
+ width: 16px;
2358
+ height: 16px;
2359
+ color: var(--lab-text-quaternary);
2360
+ }
2361
+
2362
+ /* ---- the narrow navbar (no Figma frame: the design stops at two rows) --------------------------
2363
+ Under 520px of content box — a navbar under 552px — row 1's variants and ⋯ squeeze the name to
2364
+ nothing and row 2 is wider than the bar. So the identity gets a row of its own, with ⋯:
2365
+ identity · ⋯ / variants · previous / next | Comment · Parameters / device switch + Viewport field
2366
+ THREE rows, and FOUR under about 400px, where the device switch and the Viewport field stop fitting
2367
+ side by side and stack. Nothing is folded into a menu: the four device icons stay in view (the
2368
+ ruling that condensed the viewport group kept them), and so does every other control. Comment
2369
+ drops its word and keeps its icon, its tooltip and its accessible name, which is what lets the
2370
+ variants row hold at 300px. The name, the variant and the band shorten with an ellipsis; the
2371
+ name's title carries it in full. */
2372
+ @container lab-navbar (width < 520px) {
2373
+ .lab-nb {
2374
+ grid-template-columns: minmax(0, 1fr) auto;
2375
+ grid-template-areas:
2376
+ "id actions"
2377
+ "variants overlays"
2378
+ "viewport viewport";
2379
+ }
2380
+
2381
+ /* Its two halves become grid items of their own; nothing reads this element's box. */
2382
+ .lab-nb__tools {
2383
+ display: contents;
2384
+ }
2385
+
2386
+ .lab-nb__viewport {
2387
+ grid-area: viewport;
2388
+ flex-wrap: wrap;
2389
+ min-width: 0;
2390
+ }
2391
+
2392
+ .lab-nb__overlays {
2393
+ grid-area: overlays;
2394
+ }
2395
+
2396
+ .lab-nb__variants,
2397
+ .lab-variant-btn,
2398
+ .lab-vpf__menu {
2399
+ min-width: 0;
2400
+ }
2401
+
2402
+ .lab-vpf {
2403
+ flex-shrink: 1;
2404
+ min-width: 0;
2405
+ }
2406
+
2407
+ .lab-vpf__band {
2408
+ min-width: 0;
2409
+ overflow: hidden;
2410
+ text-overflow: ellipsis;
2411
+ }
2412
+
2413
+ .lab-tool--pins .lab-tool__label {
2414
+ position: absolute;
2415
+ width: 1px;
2416
+ height: 1px;
2417
+ overflow: hidden;
2418
+ clip: rect(0 0 0 0);
2419
+ white-space: nowrap;
2420
+ }
2421
+ }
2422
+
2423
+ .lab-content {
2424
+ position: relative;
2425
+ flex: 1;
2426
+ min-height: 0;
2427
+ overflow: auto;
2428
+ }
2429
+
2430
+ /* A listing's content (Figma `Lab / Listing`): Main's own padding, 32 on top and at the sides,
2431
+ 48 at the foot. No dot grid — a listing is a page of cards, not a stage. */
2432
+ .lab-page {
2433
+ max-width: 1280px;
2434
+ padding: 32px 32px 48px;
2435
+ }
2436
+
2437
+ /* ---- the listing title bar (Figma `Navbar`, listing: one row, title · counts · filter chips) -- */
2438
+ .lab-navbar.lab-navbar--listing {
2439
+ min-height: 0;
2440
+ padding: var(--lab-space-lg) var(--lab-space-xl);
2441
+ }
2442
+
2443
+ .lab-listing-bar {
2444
+ display: flex;
2445
+ align-items: center;
2446
+ flex-wrap: wrap;
2447
+ gap: var(--lab-space-md) var(--lab-space-lg);
2448
+ flex: 1 1 auto;
2449
+ min-width: 0;
2450
+ min-height: 38px;
2451
+ }
2452
+
2453
+ .lab-listing-bar__title {
2454
+ margin: 0;
2455
+ color: var(--lab-text-primary);
2456
+ font-size: 16px;
2457
+ line-height: 24px;
2458
+ font-weight: 600;
2459
+ white-space: nowrap;
2460
+ }
2461
+
2462
+ .lab-listing-bar__counts {
2463
+ margin: 0;
2464
+ min-width: 0;
2465
+ color: var(--lab-text-quaternary);
2466
+ font-size: 14px;
2467
+ line-height: 20px;
2468
+ font-weight: 500;
2469
+ overflow: hidden;
2470
+ text-overflow: ellipsis;
2471
+ white-space: nowrap;
2472
+ }
2473
+
2474
+ .lab-listing-bar > .lab-chips,
2475
+ .lab-listing-bar__end {
2476
+ margin-left: auto;
2477
+ }
2478
+
2479
+ /* Filter chips: a segmented control whose choices set the same filter as level 2's menu. */
2480
+ .lab-chips {
2481
+ display: inline-flex;
2482
+ align-items: center;
2483
+ gap: 2px;
2484
+ padding: 2px;
2485
+ border: 1px solid var(--lab-border-secondary);
2486
+ border-radius: var(--lab-radius-md);
2487
+ background-color: var(--lab-bg-secondary);
2488
+ }
2489
+
2490
+ .lab-chip {
2491
+ display: inline-flex;
2492
+ align-items: center;
2493
+ height: 32px;
2494
+ padding: var(--lab-space-sm) 10px;
2495
+ border: 1px solid transparent;
2496
+ border-radius: var(--lab-radius-sm);
2497
+ background: transparent;
2498
+ color: var(--lab-text-quaternary);
2499
+ font: inherit;
2500
+ font-weight: 600;
2501
+ white-space: nowrap;
2502
+ cursor: pointer;
2503
+ transition: background-color var(--lab-ease), color var(--lab-ease);
2504
+ }
2505
+
2506
+ .lab-chip:hover {
2507
+ color: var(--lab-text-secondary);
2508
+ }
2509
+
2510
+ .lab-chip[aria-checked="true"] {
2511
+ border-color: var(--lab-border-primary);
2512
+ background-color: var(--lab-bg-primary);
2513
+ box-shadow: var(--lab-shadow-xs);
2514
+ color: var(--lab-text-secondary);
2515
+ }
2516
+
2517
+ /* What waits on you — the review count on Tasks. Amber, the design's colour for it. */
2518
+ .lab-pill.lab-pill--waiting {
2519
+ border-color: var(--lab-warning-border);
2520
+ background-color: var(--lab-warning-bg);
2521
+ color: var(--lab-warning-text);
2522
+ }
2523
+
2524
+ .lab-pill.lab-pill--waiting::before {
2525
+ content: "";
2526
+ width: 6px;
2527
+ height: 6px;
2528
+ border-radius: 50%;
2529
+ background-color: var(--lab-warning-dot);
2530
+ }
2531
+
2532
+ /* ---- the Tasks views' layout (no level 2 — main takes the width beside level 1) ------------ */
2533
+ /* In the lab the board scrolls sideways inside itself, which makes the board — not the page — the
2534
+ sticky container of its column heads. Their offset below the page's toolbar then pushed every
2535
+ head ~70px down its own column; inside the board they stick at its top. */
2536
+ .pin-board.pin-board--lab .board .col-head {
2537
+ top: 0;
2538
+ }
2539
+
2540
+ .lab-page__head {
2541
+ margin-bottom: var(--lab-space-3xl);
2542
+ }
2543
+
2544
+ .lab-page__title {
2545
+ margin: 0 0 var(--lab-space-xs);
2546
+ color: var(--lab-text-primary);
2547
+ font-size: 24px;
2548
+ line-height: 32px;
2549
+ font-weight: 600;
2550
+ letter-spacing: -0.01em;
2551
+ }
2552
+
2553
+ .lab-page__lede {
2554
+ max-width: 68ch;
2555
+ margin: 0;
2556
+ color: var(--lab-text-tertiary);
2557
+ font-size: 15px;
2558
+ line-height: 24px;
2559
+ }
2560
+
2561
+ .lab-section-title {
2562
+ display: flex;
2563
+ align-items: baseline;
2564
+ gap: var(--lab-space-md);
2565
+ margin: var(--lab-space-3xl) 0 var(--lab-space-lg);
2566
+ color: var(--lab-text-primary);
2567
+ font-size: 16px;
2568
+ line-height: 24px;
2569
+ font-weight: 600;
2570
+ }
2571
+
2572
+ .lab-section-title small {
2573
+ color: var(--lab-text-quaternary);
2574
+ font-size: 14px;
2575
+ font-weight: 500;
2576
+ }
2577
+
2578
+ .lab-section-title a {
2579
+ color: inherit;
2580
+ text-decoration: none;
2581
+ }
2582
+
2583
+ .lab-section-title a:hover {
2584
+ color: var(--lab-text-brand-secondary);
2585
+ }
2586
+
2587
+ /* ---- buttons ------------------------------------------------------------------------------- */
2588
+ .lab-btn {
2589
+ display: inline-flex;
2590
+ align-items: center;
2591
+ justify-content: center;
2592
+ gap: var(--lab-space-xs);
2593
+ min-height: 36px;
2594
+ padding: var(--lab-space-md) var(--lab-space-lg);
2595
+ border: 1px solid var(--lab-border-primary);
2596
+ border-radius: var(--lab-radius-md);
2597
+ background-color: var(--lab-bg-primary);
2598
+ box-shadow: var(--lab-shadow-xs);
2599
+ color: var(--lab-text-secondary);
2600
+ font: inherit;
2601
+ font-weight: 600;
2602
+ white-space: nowrap;
2603
+ text-decoration: none;
2604
+ cursor: pointer;
2605
+ transition: background-color var(--lab-ease), color var(--lab-ease), border-color var(--lab-ease);
2606
+ }
2607
+
2608
+ .lab-btn:hover {
2609
+ background-color: var(--lab-bg-hover);
2610
+ color: var(--lab-text-primary);
2611
+ }
2612
+
2613
+ .lab-btn > .lab-icon {
2614
+ width: 16px;
2615
+ height: 16px;
2616
+ color: var(--lab-fg-quaternary);
2617
+ }
2618
+
2619
+ .lab-btn--icon {
2620
+ width: 36px;
2621
+ padding: 0;
2622
+ }
2623
+
2624
+ .lab-btn--sm {
2625
+ min-height: 32px;
2626
+ padding: var(--lab-space-sm) var(--lab-space-md);
2627
+ font-size: 13px;
2628
+ }
2629
+
2630
+ .lab-btn--sm.lab-btn--icon {
2631
+ width: 32px;
2632
+ padding: 0;
2633
+ }
2634
+
2635
+ .lab-btn[aria-pressed="true"],
2636
+ .lab-btn[data-active] {
2637
+ background-color: var(--lab-bg-active);
2638
+ border-color: color-mix(in srgb, var(--lab-fg-brand) 55%, var(--lab-border-primary));
2639
+ color: var(--lab-text-brand-primary);
2640
+ }
2641
+
2642
+ .lab-btn[aria-pressed="true"] > .lab-icon,
2643
+ .lab-btn[data-active] > .lab-icon {
2644
+ color: var(--lab-fg-brand);
2645
+ }
2646
+
2647
+ .lab-btn:disabled,
2648
+ .lab-btn[aria-disabled="true"] {
2649
+ opacity: 0.5;
2650
+ cursor: not-allowed;
2651
+ }
2652
+
2653
+ /* A row of joined buttons — the device switch. */
2654
+ .lab-seg {
2655
+ display: inline-flex;
2656
+ border: 1px solid var(--lab-border-primary);
2657
+ border-radius: var(--lab-radius-md);
2658
+ box-shadow: var(--lab-shadow-xs);
2659
+ overflow: hidden;
2660
+ background-color: var(--lab-bg-primary);
2661
+ }
2662
+
2663
+ .lab-seg > .lab-btn {
2664
+ border: 0;
2665
+ border-radius: 0;
2666
+ box-shadow: none;
2667
+ }
2668
+
2669
+ .lab-seg > .lab-btn + .lab-btn {
2670
+ border-left: 1px solid var(--lab-border-primary);
2671
+ }
2672
+
2673
+ .lab-seg > .lab-btn[data-active] {
2674
+ background-color: var(--lab-bg-hover);
2675
+ color: var(--lab-text-primary);
2676
+ }
2677
+
2678
+ .lab-seg > .lab-btn[data-active] > .lab-icon {
2679
+ color: var(--lab-text-secondary);
2680
+ }
2681
+
2682
+ .lab-select {
2683
+ min-height: 36px;
2684
+ padding: var(--lab-space-md) 32px var(--lab-space-md) var(--lab-space-lg);
2685
+ border: 1px solid var(--lab-border-primary);
2686
+ border-radius: var(--lab-radius-md);
2687
+ background: var(--lab-bg-primary) no-repeat right 10px center / 16px;
2688
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23a3a3a3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
2689
+ box-shadow: var(--lab-shadow-xs);
2690
+ color: var(--lab-text-secondary);
2691
+ font: inherit;
2692
+ font-weight: 600;
2693
+ appearance: none;
2694
+ cursor: pointer;
2695
+ }
2696
+
2697
+ .lab-select--sm {
2698
+ min-height: 32px;
2699
+ padding-top: var(--lab-space-sm);
2700
+ padding-bottom: var(--lab-space-sm);
2701
+ font-size: 13px;
2702
+ }
2703
+
2704
+ /* ---- pills (Figma/Untitled `Badge`, sm) ---------------------------------------------------- */
2705
+ .lab-pill {
2706
+ display: inline-flex;
2707
+ align-items: center;
2708
+ gap: var(--lab-space-xs);
2709
+ padding: 1px 8px;
2710
+ border: 1px solid var(--lab-neutral-border);
2711
+ border-radius: var(--lab-radius-full);
2712
+ background-color: var(--lab-neutral-bg);
2713
+ color: var(--lab-neutral-text);
2714
+ font-size: 12px;
2715
+ line-height: 18px;
2716
+ font-weight: 500;
2717
+ white-space: nowrap;
2718
+ }
2719
+
2720
+ .lab-pill--live {
2721
+ border-color: var(--lab-success-border);
2722
+ background-color: var(--lab-success-bg);
2723
+ color: var(--lab-success-text);
2724
+ }
2725
+
2726
+ .lab-pill--live::before {
2727
+ content: "";
2728
+ width: 6px;
2729
+ height: 6px;
2730
+ border-radius: 50%;
2731
+ background-color: var(--lab-success-dot);
2732
+ }
2733
+
2734
+ .lab-pill--used {
2735
+ border-color: var(--lab-info-border);
2736
+ background-color: var(--lab-info-bg);
2737
+ color: var(--lab-info-text);
2738
+ }
2739
+
2740
+ .lab-pill--responsive {
2741
+ border-color: var(--lab-info-border);
2742
+ background-color: var(--lab-info-bg);
2743
+ color: var(--lab-info-text);
2744
+ }
2745
+
2746
+ .lab-pill--unresponsive {
2747
+ border-style: dashed;
2748
+ border-color: var(--lab-warning-border);
2749
+ background-color: var(--lab-warning-bg);
2750
+ color: var(--lab-warning-text);
2751
+ }
2752
+
2753
+ .lab-pill--error {
2754
+ border-color: var(--lab-error-border);
2755
+ background-color: var(--lab-error-bg);
2756
+ color: var(--lab-error-text);
2757
+ }
2758
+
2759
+ .lab-pill--brand {
2760
+ border-color: color-mix(in srgb, var(--lab-fg-brand) 40%, transparent);
2761
+ background-color: var(--lab-bg-active);
2762
+ color: var(--lab-text-brand-secondary);
2763
+ }
2764
+
2765
+ .lab-pill--mono {
2766
+ font-family: var(--lab-font-mono);
2767
+ font-size: 11.5px;
2768
+ }
2769
+
2770
+ /* ---- stat cards (the home page) ------------------------------------------------------------ */
2771
+ .lab-stats {
2772
+ display: grid;
2773
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
2774
+ gap: var(--lab-space-xl);
2775
+ }
2776
+
2777
+ .lab-stat {
2778
+ display: flex;
2779
+ flex-direction: column;
2780
+ gap: var(--lab-space-lg);
2781
+ padding: var(--lab-space-2xl);
2782
+ background-color: var(--lab-bg-primary);
2783
+ border: 1px solid var(--lab-border-secondary);
2784
+ border-radius: var(--lab-radius-xl);
2785
+ box-shadow: var(--lab-shadow-xs);
2786
+ color: var(--lab-text-primary);
2787
+ text-decoration: none;
2788
+ transition: border-color var(--lab-ease), box-shadow var(--lab-ease);
2789
+ }
2790
+
2791
+ .lab-stat:hover {
2792
+ border-color: var(--lab-border-primary);
2793
+ box-shadow: var(--lab-shadow-sm);
2794
+ }
2795
+
2796
+ .lab-stat__head {
2797
+ display: flex;
2798
+ align-items: center;
2799
+ gap: var(--lab-space-md);
2800
+ color: var(--lab-text-tertiary);
2801
+ font-weight: 600;
2802
+ }
2803
+
2804
+ .lab-stat__head .lab-icon {
2805
+ color: var(--lab-fg-quaternary);
2806
+ }
2807
+
2808
+ .lab-stat__value {
2809
+ font-size: 30px;
2810
+ line-height: 38px;
2811
+ font-weight: 600;
2812
+ letter-spacing: -0.02em;
2813
+ }
2814
+
2815
+ .lab-stat__foot {
2816
+ display: flex;
2817
+ flex-wrap: wrap;
2818
+ gap: var(--lab-space-sm);
2819
+ }
2820
+
2821
+ /* ---- the "on this page" slot list (the home page) ------------------------------------------ */
2822
+ .lab-slots {
2823
+ display: flex;
2824
+ flex-direction: column;
2825
+ margin: 0;
2826
+ padding: 0;
2827
+ list-style: none;
2828
+ background-color: var(--lab-bg-primary);
2829
+ border: 1px solid var(--lab-border-secondary);
2830
+ border-radius: var(--lab-radius-xl);
2831
+ box-shadow: var(--lab-shadow-xs);
2832
+ overflow: hidden;
2833
+ }
2834
+
2835
+ .lab-slots__row {
2836
+ display: grid;
2837
+ grid-template-columns: 32px minmax(160px, 260px) 1fr;
2838
+ align-items: center;
2839
+ gap: var(--lab-space-lg);
2840
+ padding: var(--lab-space-md) var(--lab-space-xl);
2841
+ }
2842
+
2843
+ .lab-slots__row + .lab-slots__row {
2844
+ border-top: 1px solid var(--lab-border-secondary);
2845
+ }
2846
+
2847
+ .lab-slots__n {
2848
+ color: var(--lab-text-quaternary);
2849
+ font-variant-numeric: tabular-nums;
2850
+ font-weight: 500;
2851
+ }
2852
+
2853
+ .lab-slots__name {
2854
+ display: flex;
2855
+ align-items: baseline;
2856
+ gap: var(--lab-space-sm);
2857
+ min-width: 0;
2858
+ color: var(--lab-text-primary);
2859
+ font-weight: 600;
2860
+ text-decoration: none;
2861
+ }
2862
+
2863
+ a.lab-slots__name:hover {
2864
+ color: var(--lab-text-brand-secondary);
2865
+ }
2866
+
2867
+ .lab-slots__name--bare {
2868
+ color: var(--lab-text-tertiary);
2869
+ font-weight: 500;
2870
+ }
2871
+
2872
+ .lab-slots__file {
2873
+ min-width: 0;
2874
+ overflow: hidden;
2875
+ text-overflow: ellipsis;
2876
+ white-space: nowrap;
2877
+ color: var(--lab-text-quaternary);
2878
+ font-size: 12px;
2879
+ }
2880
+
2881
+ /* ---- story and page cards ------------------------------------------------------------------ */
2882
+ .lab-cards {
2883
+ display: grid;
2884
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
2885
+ gap: var(--lab-space-2xl);
2886
+ }
2887
+
2888
+ .lab-card {
2889
+ position: relative;
2890
+ display: flex;
2891
+ flex-direction: column;
2892
+ min-width: 0;
2893
+ background-color: var(--lab-bg-primary);
2894
+ border: 1px solid var(--lab-border-secondary);
2895
+ border-radius: var(--lab-radius-xl);
2896
+ box-shadow: var(--lab-shadow-xs);
2897
+ overflow: hidden;
2898
+ transition: border-color var(--lab-ease), box-shadow var(--lab-ease);
2899
+ }
2900
+
2901
+ .lab-card:hover,
2902
+ .lab-card:focus-within {
2903
+ border-color: var(--lab-border-primary);
2904
+ box-shadow: var(--lab-shadow-sm);
2905
+ }
2906
+
2907
+ .lab-card[data-lab-hidden] {
2908
+ display: none;
2909
+ }
2910
+
2911
+ .lab-card__main {
2912
+ display: flex;
2913
+ flex-direction: column;
2914
+ color: inherit;
2915
+ text-decoration: none;
2916
+ }
2917
+
2918
+ .lab-card__main::after {
2919
+ content: "";
2920
+ position: absolute;
2921
+ inset: 0;
2922
+ z-index: 1;
2923
+ border-radius: inherit;
2924
+ }
2925
+
2926
+ .lab-card__thumb {
2927
+ position: relative;
2928
+ display: block;
2929
+ overflow: hidden;
2930
+ aspect-ratio: 16 / 10;
2931
+ background-color: var(--lab-bg-sunken);
2932
+ border-bottom: 1px solid var(--lab-border-secondary);
2933
+ }
2934
+
2935
+ .lab-card__thumb iframe,
2936
+ .lab-card__thumb img {
2937
+ position: absolute;
2938
+ top: 0;
2939
+ left: 0;
2940
+ border: 0;
2941
+ transform-origin: 0 0;
2942
+ pointer-events: none;
2943
+ background-color: #fff;
2944
+ }
2945
+
2946
+ .lab-card__thumb img {
2947
+ width: 100%;
2948
+ height: 100%;
2949
+ object-fit: contain;
2950
+ background: repeating-conic-gradient(var(--lab-bg-sunken) 0 25%, var(--lab-bg-primary) 0 50%) 0 0 / 16px 16px;
2951
+ }
2952
+
2953
+ .lab-card__thumb--note {
2954
+ display: grid;
2955
+ place-items: center;
2956
+ padding: var(--lab-space-xl);
2957
+ color: var(--lab-text-quaternary);
2958
+ font-size: 13px;
2959
+ text-align: center;
2960
+ }
2961
+
2962
+ .lab-card__body {
2963
+ display: flex;
2964
+ flex-direction: column;
2965
+ gap: var(--lab-space-sm);
2966
+ padding: var(--lab-space-lg) var(--lab-space-xl) var(--lab-space-xl);
2967
+ }
2968
+
2969
+ .lab-card__name {
2970
+ display: flex;
2971
+ align-items: baseline;
2972
+ gap: var(--lab-space-sm);
2973
+ min-width: 0;
2974
+ color: var(--lab-text-primary);
2975
+ font-weight: 600;
2976
+ }
2977
+
2978
+ .lab-card__name span:first-child {
2979
+ overflow: hidden;
2980
+ text-overflow: ellipsis;
2981
+ white-space: nowrap;
2982
+ }
2983
+
2984
+ .lab-card__version {
2985
+ flex: none;
2986
+ color: var(--lab-text-quaternary);
2987
+ font-size: 12px;
2988
+ font-weight: 500;
2989
+ }
2990
+
2991
+ .lab-card__sub {
2992
+ color: var(--lab-text-tertiary);
2993
+ overflow: hidden;
2994
+ text-overflow: ellipsis;
2995
+ white-space: nowrap;
2996
+ }
2997
+
2998
+ .lab-card__pills {
2999
+ display: flex;
3000
+ flex-wrap: wrap;
3001
+ gap: var(--lab-space-xs);
3002
+ margin-top: var(--lab-space-xs);
3003
+ }
3004
+
3005
+ /* VS Code and staging, over the thumbnail's corner, above the card's own link. */
3006
+ .lab-card__links {
3007
+ position: absolute;
3008
+ top: var(--lab-space-md);
3009
+ right: var(--lab-space-md);
3010
+ z-index: 2;
3011
+ display: flex;
3012
+ gap: var(--lab-space-xs);
3013
+ opacity: 0;
3014
+ transition: opacity var(--lab-ease);
3015
+ }
3016
+
3017
+ .lab-card:hover .lab-card__links,
3018
+ .lab-card:focus-within .lab-card__links {
3019
+ opacity: 1;
3020
+ }
3021
+
3022
+ .lab-card__links .lab-icon-btn {
3023
+ width: 28px;
3024
+ height: 28px;
3025
+ background-color: var(--lab-bg-primary);
3026
+ border: 1px solid var(--lab-border-secondary);
3027
+ box-shadow: var(--lab-shadow-xs);
3028
+ }
3029
+
3030
+ .lab-card__links .lab-icon-btn .lab-icon {
3031
+ width: 16px;
3032
+ height: 16px;
3033
+ }
3034
+
3035
+ .lab-empty {
3036
+ padding: 48px var(--lab-space-3xl);
3037
+ border: 1px dashed var(--lab-border-primary);
3038
+ border-radius: var(--lab-radius-xl);
3039
+ color: var(--lab-text-quaternary);
3040
+ text-align: center;
3041
+ }
3042
+
3043
+ .lab-empty strong {
3044
+ display: block;
3045
+ margin-bottom: var(--lab-space-xs);
3046
+ color: var(--lab-text-primary);
3047
+ font-size: 16px;
3048
+ line-height: 24px;
3049
+ }
3050
+
3051
+ /* ---------------------------------------------------------------------------------------------
3052
+ The viewport stage — the story or page in an <iframe> whose logical size you set. Zoom is a
3053
+ transform on the wrapper, so the frame's own width (and so its breakpoints) never changes.
3054
+ --------------------------------------------------------------------------------------------- */
3055
+ .lab-vp {
3056
+ position: absolute;
3057
+ inset: 0;
3058
+ }
3059
+
3060
+ /* The preview's own light/dark, apart from the chrome's (decided 2026-09-24). The iframe's
3061
+ color-scheme is what `prefers-color-scheme` reads inside it, so it is pinned here — or a dark
3062
+ chrome would quietly turn every preview dark — and the preview theme switch sets it. */
3063
+ .lab-vp-wrapper iframe,
3064
+ .lab-card__thumb iframe {
3065
+ color-scheme: light;
3066
+ }
3067
+
3068
+ .lab-vp-wrapper iframe[data-theme="dark"] {
3069
+ color-scheme: dark;
3070
+ }
3071
+
3072
+ /* The live size while a handle is being dragged. */
3073
+ .lab-vp-live {
3074
+ position: absolute;
3075
+ right: 22px;
3076
+ bottom: 22px;
3077
+ z-index: 2;
3078
+ display: none;
3079
+ padding: 2px 8px;
3080
+ border-radius: var(--lab-radius-sm);
3081
+ background-color: var(--lab-text-primary);
3082
+ color: var(--lab-bg-primary);
3083
+ font-size: 12px;
3084
+ line-height: 18px;
3085
+ font-weight: 600;
3086
+ font-variant-numeric: tabular-nums;
3087
+ pointer-events: none;
3088
+ }
3089
+
3090
+ .lab-vp[data-dragging] .lab-vp-live {
3091
+ display: block;
3092
+ }
3093
+
3094
+ .lab-vp-stage {
3095
+ position: absolute;
3096
+ inset: 0;
3097
+ display: flex;
3098
+ overflow: auto;
3099
+ background-color: var(--lab-bg-secondary);
3100
+ background-image: radial-gradient(color-mix(in srgb, var(--lab-fg-quaternary) 45%, transparent) 1px, transparent 1px);
3101
+ background-size: 16px 16px;
3102
+ }
3103
+
3104
+ .lab-vp-box {
3105
+ position: relative;
3106
+ flex: none;
3107
+ margin: auto;
3108
+ padding: var(--lab-space-xl);
3109
+ box-sizing: content-box;
3110
+ overflow: hidden;
3111
+ }
3112
+
3113
+ .lab-vp-wrapper {
3114
+ position: relative;
3115
+ transform-origin: 0 0;
3116
+ border-radius: 2px;
3117
+ outline: 1px solid var(--lab-border-primary);
3118
+ background-color: #fff;
3119
+ box-shadow: var(--lab-shadow-lg);
3120
+ }
3121
+
3122
+ .lab-vp-wrapper iframe {
3123
+ display: block;
3124
+ width: 100%;
3125
+ height: 100%;
3126
+ border: 0;
3127
+ background-color: #fff;
3128
+ }
3129
+
3130
+ .lab-vp[data-dragging] .lab-vp-stage {
3131
+ user-select: none;
3132
+ }
3133
+
3134
+ .lab-vp-handle {
3135
+ position: absolute;
3136
+ touch-action: none;
3137
+ }
3138
+
3139
+ .lab-vp-handle::after {
3140
+ content: "";
3141
+ position: absolute;
3142
+ border-radius: var(--lab-radius-full);
3143
+ background-color: var(--lab-text-quaternary);
3144
+ transition: background-color var(--lab-ease);
3145
+ }
3146
+
3147
+ .lab-vp-handle:hover::after,
3148
+ .lab-vp[data-dragging="x"] .lab-vp-handle--e::after,
3149
+ .lab-vp[data-dragging="y"] .lab-vp-handle--s::after,
3150
+ .lab-vp[data-dragging="xy"] .lab-vp-handle--se::after {
3151
+ background-color: var(--lab-fg-brand);
3152
+ }
3153
+
3154
+ .lab-vp-handle--e {
3155
+ top: 16px;
3156
+ right: 0;
3157
+ bottom: 16px;
3158
+ width: 16px;
3159
+ cursor: ew-resize;
3160
+ }
3161
+
3162
+ .lab-vp-handle--e::after {
3163
+ top: 50%;
3164
+ left: 5px;
3165
+ width: 5px;
3166
+ height: 40px;
3167
+ transform: translateY(-50%);
3168
+ }
3169
+
3170
+ .lab-vp-handle--s {
3171
+ left: 16px;
3172
+ right: 16px;
3173
+ bottom: 0;
3174
+ height: 16px;
3175
+ cursor: ns-resize;
3176
+ }
3177
+
3178
+ .lab-vp-handle--s::after {
3179
+ left: 50%;
3180
+ top: 5px;
3181
+ width: 40px;
3182
+ height: 5px;
3183
+ transform: translateX(-50%);
3184
+ }
3185
+
3186
+ .lab-vp-handle--se {
3187
+ right: 0;
3188
+ bottom: 0;
3189
+ width: 20px;
3190
+ height: 20px;
3191
+ cursor: nwse-resize;
3192
+ }
3193
+
3194
+ .lab-vp-handle--se::after {
3195
+ top: 4px;
3196
+ left: 4px;
3197
+ width: 9px;
3198
+ height: 9px;
3199
+ }
3200
+
3201
+ /* ---- the pins on the frame, and the Comment hint --------------------------------------------
3202
+ Figma `Pin`: 24px at every zoom, its bottom-left corner the anchor, set on the element's top-left.
3203
+ Open pins are blue and resolved pins grey (ruled). The number and the ring are bg-primary, which
3204
+ passes on the blue in both themes where white would not in dark. Drawn by
3205
+ ../chrome/navbar-client.ts, in the chrome over the frame — never inside it. */
3206
+ :root {
3207
+ --lab-pin-open: #2563eb; /* Component colors/Utility/Blue/utility-blue-600 */
3208
+ --lab-pin-resolved: #737373; /* Colors/Foreground/fg-quaternary_hover */
3209
+ --lab-pins-bg: #eff6ff; /* utility-blue-50 — Comment while pin mode is on */
3210
+ --lab-pins-bg-hover: #dbeafe; /* utility-blue-100 */
3211
+ --lab-pins-border: #bfdbfe; /* utility-blue-200 */
3212
+ --lab-pins-text: #1d4ed8; /* utility-blue-700 */
3213
+ }
3214
+
3215
+ :root.dark {
3216
+ --lab-pin-open: #60a5fa; /* utility-blue-600, Dark mode */
3217
+ --lab-pin-resolved: #a3a3a3;
3218
+ --lab-pins-bg: #172554;
3219
+ --lab-pins-bg-hover: #1e3a8a;
3220
+ --lab-pins-border: #1e40af;
3221
+ --lab-pins-text: #93c5fd;
3222
+ }
3223
+
3224
+ .lab-vp-pins {
3225
+ position: absolute;
3226
+ inset: 0;
3227
+ z-index: 1;
3228
+ pointer-events: none;
3229
+ }
3230
+
3231
+ .lab-pin {
3232
+ position: absolute;
3233
+ display: grid;
3234
+ place-items: center;
3235
+ width: 24px;
3236
+ height: 24px;
3237
+ border: 2px solid var(--lab-bg-primary);
3238
+ border-radius: 12px 12px 12px 2px;
3239
+ background-color: var(--lab-pin-open);
3240
+ color: var(--lab-bg-primary);
3241
+ font-size: 12px;
3242
+ line-height: 18px;
3243
+ font-weight: 600;
3244
+ font-variant-numeric: tabular-nums;
3245
+ text-decoration: none;
3246
+ filter: drop-shadow(0 4px 3px rgba(0, 0, 0, 0.1)) drop-shadow(0 2px 2px rgba(0, 0, 0, 0.06));
3247
+ pointer-events: auto;
3248
+ cursor: pointer;
3249
+ }
3250
+
3251
+ .lab-pin[data-state="resolved"] {
3252
+ background-color: var(--lab-pin-resolved);
3253
+ }
3254
+
3255
+ /* While Comment is picking: what a click does, and how to stop. */
3256
+ .lab-vp-hint {
3257
+ position: absolute;
3258
+ top: var(--lab-space-sm);
3259
+ left: 50%;
3260
+ z-index: 3;
3261
+ margin: 0;
3262
+ padding: 3px var(--lab-space-lg);
3263
+ border-radius: var(--lab-radius-full);
3264
+ background-color: var(--lab-text-primary);
3265
+ color: var(--lab-bg-primary);
3266
+ font-size: 12px;
3267
+ line-height: 18px;
3268
+ font-weight: 600;
3269
+ white-space: nowrap;
3270
+ transform: translateX(-50%);
3271
+ pointer-events: none;
3272
+ }
3273
+
3274
+
3275
+ /* ---- the parameters drawer ---------------------------------------------------------------------
3276
+ On the stage's right edge, present only when the story registered parameters through
3277
+ @orbytes/astrolab/params. Built from the Figma components Parameters drawer (20074:3489), Drawer
3278
+ group (20072:259700) and Param row (20072:259490); the markup is ../chrome/params-client.ts.
3279
+ It overlays the canvas and never resizes it — resizing would change the preview's width and trip
3280
+ its breakpoints. Every rule here is anchored on .lab-params. */
3281
+ .lab-params {
3282
+ --lab-params-shadow: 0 20px 24px -4px rgba(0, 0, 0, 0.08), 0 8px 8px -4px rgba(0, 0, 0, 0.03),
3283
+ 0 3px 3px -1.5px rgba(0, 0, 0, 0.04); /* Shadows/shadow-xl */
3284
+ --lab-params-track: #e5e5e5; /* Colors/Background/bg-quaternary — the slider's empty track */
3285
+ --lab-params-switch-off: #f5f5f5; /* Colors/Background/bg-tertiary — a switch that is off */
3286
+ --lab-params-swatch-edge: rgba(0, 0, 0, 0.1); /* Component colors/Alpha/alpha-black-10 */
3287
+ --lab-params-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23a3a3a3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
3288
+ position: absolute;
3289
+ top: 0;
3290
+ right: 0;
3291
+ bottom: 0;
3292
+ z-index: 2;
3293
+ display: flex;
3294
+ flex-direction: column;
3295
+ width: 340px;
3296
+ max-width: 100%;
3297
+ background-color: var(--lab-bg-primary);
3298
+ border-left: 1px solid var(--lab-border-secondary);
3299
+ box-shadow: var(--lab-params-shadow);
3300
+ color: var(--lab-text-secondary);
3301
+ font-size: 14px;
3302
+ line-height: 20px;
3303
+ }
3304
+
3305
+ :root.dark .lab-params {
3306
+ --lab-params-shadow: var(--lab-shadow-lg);
3307
+ --lab-params-track: #333333;
3308
+ --lab-params-switch-off: #262626;
3309
+ --lab-params-swatch-edge: rgba(255, 255, 255, 0.14);
3310
+ }
3311
+
3312
+ .lab-params[hidden] {
3313
+ display: none;
3314
+ }
3315
+
3316
+ /* The header: title, the control count, close. */
3317
+ .lab-params .vpp__header {
3318
+ display: flex;
3319
+ flex: none;
3320
+ align-items: center;
3321
+ justify-content: space-between;
3322
+ gap: var(--lab-space-md);
3323
+ padding: var(--lab-space-lg) var(--lab-space-lg) var(--lab-space-lg) var(--lab-space-xl);
3324
+ border-bottom: 1px solid var(--lab-border-secondary);
3325
+ }
3326
+
3327
+ .lab-params .vpp__heading {
3328
+ display: flex;
3329
+ align-items: center;
3330
+ gap: var(--lab-space-md);
3331
+ min-width: 0;
3332
+ }
3333
+
3334
+ .lab-params .vpp__heading-title {
3335
+ margin: 0;
3336
+ color: var(--lab-text-primary);
3337
+ font-size: 16px;
3338
+ line-height: 24px;
3339
+ font-weight: 600;
3340
+ }
3341
+
3342
+ .lab-params .vpp__count {
3343
+ padding: 2px 8px;
3344
+ border: 1px solid var(--lab-neutral-border);
3345
+ border-radius: var(--lab-radius-full);
3346
+ background-color: var(--lab-neutral-bg);
3347
+ color: var(--lab-neutral-text);
3348
+ font-size: 12px;
3349
+ line-height: 18px;
3350
+ font-weight: 500;
3351
+ font-variant-numeric: tabular-nums;
3352
+ }
3353
+
3354
+ /* Close, Reset and Copy: the ghost tool button, 32 × 32. At rest the icon is #737373 (4.7:1), not
3355
+ the lighter fg-quaternary — an icon-only button needs 3:1. */
3356
+ .lab-params .vpp__icon-btn {
3357
+ display: inline-grid;
3358
+ flex: none;
3359
+ place-items: center;
3360
+ width: 32px;
3361
+ height: 32px;
3362
+ padding: 0;
3363
+ border: 0;
3364
+ border-radius: var(--lab-radius-md);
3365
+ background: transparent;
3366
+ color: var(--lab-text-quaternary);
3367
+ cursor: pointer;
3368
+ transition: background-color var(--lab-ease), color var(--lab-ease);
3369
+ }
3370
+
3371
+ .lab-params .vpp__icon-btn:hover {
3372
+ background-color: var(--lab-bg-hover);
3373
+ color: var(--lab-text-secondary);
3374
+ }
3375
+
3376
+ /* The body scrolls; the header and footer stay put. */
3377
+ .lab-params .vpp__scroll {
3378
+ flex: 1 1 auto;
3379
+ min-height: 0;
3380
+ padding: 0 var(--lab-space-xl);
3381
+ overflow-y: auto;
3382
+ overscroll-behavior: contain;
3383
+ }
3384
+
3385
+ /* One registered group — a <details>. */
3386
+ .lab-params .vpp__group {
3387
+ position: relative;
3388
+ padding: var(--lab-space-lg) 0;
3389
+ }
3390
+
3391
+ .lab-params .vpp__group + .vpp__group {
3392
+ border-top: 1px solid var(--lab-border-secondary);
3393
+ }
3394
+
3395
+ .lab-params .vpp__summary {
3396
+ display: flex;
3397
+ align-items: center;
3398
+ gap: var(--lab-space-md);
3399
+ min-height: 32px;
3400
+ border-radius: var(--lab-radius-sm);
3401
+ list-style: none;
3402
+ cursor: pointer;
3403
+ }
3404
+
3405
+ .lab-params .vpp__summary::-webkit-details-marker {
3406
+ display: none;
3407
+ }
3408
+
3409
+ .lab-params .vpp__group[open] > .vpp__summary {
3410
+ padding-right: 72px; /* room for Reset and Copy, which sit over this row */
3411
+ }
3412
+
3413
+ /* With a source link the header row is 38 tall: the title on top, the link tucked under it. */
3414
+ .lab-params .vpp__group[open][data-source] > .vpp__summary {
3415
+ min-height: 38px;
3416
+ }
3417
+
3418
+ .lab-params .vpp__group[open][data-source] .vpp__title {
3419
+ align-self: flex-start;
3420
+ }
3421
+
3422
+ .lab-params .vpp__chev {
3423
+ color: var(--lab-fg-quaternary);
3424
+ transition: transform var(--lab-ease);
3425
+ }
3426
+
3427
+ .lab-params .vpp__group:not([open]) .vpp__chev {
3428
+ transform: rotate(-90deg);
3429
+ }
3430
+
3431
+ .lab-params .vpp__title {
3432
+ flex: 1 1 auto;
3433
+ min-width: 0;
3434
+ color: var(--lab-text-primary);
3435
+ font-size: 14px;
3436
+ line-height: 20px;
3437
+ font-weight: 600;
3438
+ overflow-wrap: anywhere;
3439
+ }
3440
+
3441
+ .lab-params .vpp__summary-count {
3442
+ flex: none;
3443
+ color: var(--lab-text-quaternary);
3444
+ font-size: 12px;
3445
+ line-height: 18px;
3446
+ font-weight: 500;
3447
+ }
3448
+
3449
+ .lab-params .vpp__group[open] .vpp__summary-count {
3450
+ display: none;
3451
+ }
3452
+
3453
+ .lab-params .vpp__source {
3454
+ position: relative;
3455
+ display: block;
3456
+ width: fit-content;
3457
+ max-width: calc(100% - 96px);
3458
+ margin: -18px 0 0 24px;
3459
+ overflow: hidden;
3460
+ color: var(--lab-text-brand-secondary);
3461
+ font-size: 12px;
3462
+ line-height: 18px;
3463
+ font-weight: 500;
3464
+ text-decoration: none;
3465
+ text-overflow: ellipsis;
3466
+ white-space: nowrap;
3467
+ }
3468
+
3469
+ .lab-params .vpp__source:hover {
3470
+ text-decoration: underline;
3471
+ }
3472
+
3473
+ .lab-params .vpp__actions {
3474
+ position: absolute;
3475
+ top: var(--lab-space-lg);
3476
+ right: 0;
3477
+ display: flex;
3478
+ }
3479
+
3480
+ .lab-params .vpp__group[data-source] > .vpp__actions {
3481
+ top: calc(var(--lab-space-lg) + 3px); /* centred on the 38px header row */
3482
+ }
3483
+
3484
+ .lab-params .vpp__note {
3485
+ margin: var(--lab-space-md) 0 0;
3486
+ padding-left: 24px;
3487
+ color: var(--lab-text-tertiary);
3488
+ font-size: 12px;
3489
+ line-height: 18px;
3490
+ }
3491
+
3492
+ .lab-params .vpp__controls {
3493
+ display: flex;
3494
+ flex-direction: column;
3495
+ gap: var(--lab-space-md);
3496
+ margin-top: var(--lab-space-md);
3497
+ padding-top: var(--lab-space-xs);
3498
+ }
3499
+
3500
+ .lab-params .vpp__status {
3501
+ margin: var(--lab-space-md) 0 0;
3502
+ color: var(--lab-text-tertiary);
3503
+ font-size: 12px;
3504
+ line-height: 18px;
3505
+ white-space: pre-wrap;
3506
+ overflow-wrap: anywhere;
3507
+ }
3508
+
3509
+ /* One row per control: the label on the left, the control in a fixed 144px column so every
3510
+ control starts on one edge. The label wraps rather than truncates. */
3511
+ .lab-params .vpp__row {
3512
+ display: flex;
3513
+ align-items: center;
3514
+ gap: var(--lab-space-lg);
3515
+ min-height: 32px;
3516
+ }
3517
+
3518
+ .lab-params .vpp__label {
3519
+ display: flex;
3520
+ flex: 1 1 0;
3521
+ align-items: center;
3522
+ gap: var(--lab-space-xs);
3523
+ min-width: 0;
3524
+ }
3525
+
3526
+ .lab-params .vpp__label-text {
3527
+ max-width: 136px;
3528
+ color: var(--lab-text-secondary);
3529
+ overflow-wrap: anywhere;
3530
+ }
3531
+
3532
+ .lab-params .vpp__help {
3533
+ width: 12px;
3534
+ height: 12px;
3535
+ color: var(--lab-fg-quaternary);
3536
+ }
3537
+
3538
+ .lab-params .vpp__control {
3539
+ display: flex;
3540
+ flex: none;
3541
+ align-items: center;
3542
+ width: 144px;
3543
+ }
3544
+
3545
+ .lab-params .vpp__row--range .vpp__control {
3546
+ gap: var(--lab-space-md);
3547
+ }
3548
+
3549
+ /* Slider — unitless tuning values only. Track 6px, fill brand, a 16px handle. */
3550
+ .lab-params .vpp__range {
3551
+ flex: 1 1 0;
3552
+ min-width: 0;
3553
+ height: 20px;
3554
+ margin: 0;
3555
+ background: transparent;
3556
+ cursor: pointer;
3557
+ -webkit-appearance: none;
3558
+ appearance: none;
3559
+ }
3560
+
3561
+ .lab-params .vpp__range::-webkit-slider-runnable-track {
3562
+ height: 6px;
3563
+ border-radius: var(--lab-radius-full);
3564
+ background: linear-gradient(
3565
+ to right,
3566
+ var(--lab-brand-solid) var(--lab-params-fill, 0%),
3567
+ var(--lab-params-track) var(--lab-params-fill, 0%)
3568
+ );
3569
+ }
3570
+
3571
+ .lab-params .vpp__range::-webkit-slider-thumb {
3572
+ box-sizing: border-box;
3573
+ width: 16px;
3574
+ height: 16px;
3575
+ margin-top: -5px;
3576
+ border: 2px solid var(--lab-brand-solid);
3577
+ border-radius: 50%;
3578
+ background-color: #ffffff;
3579
+ box-shadow: var(--lab-shadow-sm);
3580
+ -webkit-appearance: none;
3581
+ appearance: none;
3582
+ }
3583
+
3584
+ .lab-params .vpp__range::-moz-range-track {
3585
+ height: 6px;
3586
+ border-radius: var(--lab-radius-full);
3587
+ background-color: var(--lab-params-track);
3588
+ }
3589
+
3590
+ .lab-params .vpp__range::-moz-range-progress {
3591
+ height: 6px;
3592
+ border-radius: var(--lab-radius-full);
3593
+ background-color: var(--lab-brand-solid);
3594
+ }
3595
+
3596
+ .lab-params .vpp__range::-moz-range-thumb {
3597
+ box-sizing: border-box;
3598
+ width: 16px;
3599
+ height: 16px;
3600
+ border: 2px solid var(--lab-brand-solid);
3601
+ border-radius: 50%;
3602
+ background-color: #ffffff;
3603
+ box-shadow: var(--lab-shadow-sm);
3604
+ }
3605
+
3606
+ .lab-params .vpp__range:focus-visible {
3607
+ box-shadow: none;
3608
+ }
3609
+
3610
+ .lab-params .vpp__range:focus-visible::-webkit-slider-thumb {
3611
+ box-shadow: var(--lab-focus-ring);
3612
+ }
3613
+
3614
+ .lab-params .vpp__range:focus-visible::-moz-range-thumb {
3615
+ box-shadow: var(--lab-focus-ring);
3616
+ }
3617
+
3618
+ .lab-params .vpp__readout {
3619
+ min-width: 40px;
3620
+ color: var(--lab-text-primary);
3621
+ font-size: 12px;
3622
+ line-height: 18px;
3623
+ font-weight: 500;
3624
+ font-variant-numeric: tabular-nums;
3625
+ text-align: right;
3626
+ white-space: nowrap;
3627
+ }
3628
+
3629
+ /* The outline field every other control sits in: 32px, border-primary, radius 8, shadow-xs. */
3630
+ .lab-params .vpp__field,
3631
+ .lab-params .vpp__select {
3632
+ box-sizing: border-box;
3633
+ flex: 1 1 0;
3634
+ min-width: 0;
3635
+ height: 32px;
3636
+ border: 1px solid var(--lab-border-primary);
3637
+ border-radius: var(--lab-radius-md);
3638
+ background-color: var(--lab-bg-primary);
3639
+ box-shadow: var(--lab-shadow-xs);
3640
+ color: var(--lab-text-primary);
3641
+ font: inherit;
3642
+ transition: border-color var(--lab-ease), box-shadow var(--lab-ease);
3643
+ }
3644
+
3645
+ .lab-params .vpp__field {
3646
+ display: flex;
3647
+ align-items: center;
3648
+ overflow: hidden;
3649
+ }
3650
+
3651
+ .lab-params .vpp__field:focus-within,
3652
+ .lab-params .vpp__select:focus-visible {
3653
+ border-color: var(--lab-fg-brand);
3654
+ box-shadow: var(--lab-shadow-xs), 0 0 0 3px color-mix(in srgb, var(--lab-fg-brand) 22%, transparent);
3655
+ }
3656
+
3657
+ .lab-params .vpp__select {
3658
+ padding: 0 30px 0 12px;
3659
+ background: var(--lab-bg-primary) var(--lab-params-chevron) no-repeat right 10px center / 16px;
3660
+ font-weight: 500;
3661
+ text-overflow: ellipsis;
3662
+ cursor: pointer;
3663
+ -webkit-appearance: none;
3664
+ appearance: none;
3665
+ }
3666
+
3667
+ /* Number + unit — every size. The value is typed (↑ / ↓ step it); the unit is a select joined
3668
+ on its right. */
3669
+ .lab-params .vpp__number {
3670
+ flex: 1 1 0;
3671
+ min-width: 0;
3672
+ height: 100%;
3673
+ padding: 0 8px 0 12px;
3674
+ border: 0;
3675
+ background: transparent;
3676
+ color: var(--lab-text-primary);
3677
+ font: inherit;
3678
+ font-weight: 500;
3679
+ font-variant-numeric: tabular-nums;
3680
+ -moz-appearance: textfield;
3681
+ appearance: textfield;
3682
+ }
3683
+
3684
+ .lab-params .vpp__number::-webkit-inner-spin-button,
3685
+ .lab-params .vpp__number::-webkit-outer-spin-button {
3686
+ margin: 0;
3687
+ -webkit-appearance: none;
3688
+ }
3689
+
3690
+ .lab-params .vpp__number:focus-visible {
3691
+ outline: none;
3692
+ box-shadow: none;
3693
+ }
3694
+
3695
+ .lab-params .vpp__unit {
3696
+ flex: none;
3697
+ width: 64px;
3698
+ height: 100%;
3699
+ padding: 0 22px 0 10px;
3700
+ border: 0;
3701
+ border-left: 1px solid var(--lab-border-primary);
3702
+ border-radius: 0;
3703
+ background: transparent var(--lab-params-chevron) no-repeat right 6px center / 16px;
3704
+ color: var(--lab-text-secondary);
3705
+ font: inherit;
3706
+ cursor: pointer;
3707
+ -webkit-appearance: none;
3708
+ appearance: none;
3709
+ }
3710
+
3711
+ .lab-params .vpp__unit:focus-visible {
3712
+ outline: none;
3713
+ box-shadow: none;
3714
+ background-color: var(--lab-bg-hover);
3715
+ }
3716
+
3717
+ .lab-params .vpp__unit:disabled {
3718
+ background-image: none;
3719
+ color: var(--lab-text-secondary);
3720
+ opacity: 1;
3721
+ cursor: default;
3722
+ }
3723
+
3724
+ /* Colour — the native picker drawn as a 20px swatch, and the hex beside it. */
3725
+ .lab-params .vpp__field--color {
3726
+ gap: var(--lab-space-md);
3727
+ padding: 0 10px 0 6px;
3728
+ cursor: pointer;
3729
+ }
3730
+
3731
+ .lab-params .vpp__swatch {
3732
+ box-sizing: border-box;
3733
+ flex: none;
3734
+ width: 20px;
3735
+ height: 20px;
3736
+ padding: 0;
3737
+ border: 1px solid var(--lab-params-swatch-edge);
3738
+ border-radius: var(--lab-radius-full);
3739
+ background: none;
3740
+ overflow: hidden;
3741
+ cursor: pointer;
3742
+ -webkit-appearance: none;
3743
+ appearance: none;
3744
+ }
3745
+
3746
+ .lab-params .vpp__swatch::-webkit-color-swatch-wrapper {
3747
+ padding: 0;
3748
+ }
3749
+
3750
+ .lab-params .vpp__swatch::-webkit-color-swatch {
3751
+ border: 0;
3752
+ border-radius: var(--lab-radius-full);
3753
+ }
3754
+
3755
+ .lab-params .vpp__swatch::-moz-color-swatch {
3756
+ border: 0;
3757
+ border-radius: var(--lab-radius-full);
3758
+ }
3759
+
3760
+ .lab-params .vpp__swatch:focus-visible {
3761
+ outline: none;
3762
+ box-shadow: none;
3763
+ }
3764
+
3765
+ .lab-params .vpp__hex {
3766
+ flex: 1 1 0;
3767
+ min-width: 0;
3768
+ overflow: hidden;
3769
+ color: var(--lab-text-primary);
3770
+ font-variant-numeric: tabular-nums;
3771
+ text-overflow: ellipsis;
3772
+ text-transform: uppercase;
3773
+ white-space: nowrap;
3774
+ }
3775
+
3776
+ /* Toggle — <input type="checkbox" role="switch">, the kit's switch at sm: 36 × 20. */
3777
+ .lab-params .vpp__switch {
3778
+ position: relative;
3779
+ box-sizing: border-box;
3780
+ flex: none;
3781
+ width: 36px;
3782
+ height: 20px;
3783
+ margin: 0;
3784
+ border: 0.5px solid var(--lab-border-secondary);
3785
+ border-radius: var(--lab-radius-full);
3786
+ background-color: var(--lab-params-switch-off);
3787
+ cursor: pointer;
3788
+ transition: background-color var(--lab-ease);
3789
+ -webkit-appearance: none;
3790
+ appearance: none;
3791
+ }
3792
+
3793
+ .lab-params .vpp__switch::before {
3794
+ content: "";
3795
+ position: absolute;
3796
+ top: 1.5px;
3797
+ left: 1.5px;
3798
+ width: 16px;
3799
+ height: 16px;
3800
+ border-radius: 50%;
3801
+ background-color: #ffffff;
3802
+ box-shadow: var(--lab-shadow-sm);
3803
+ transition: transform var(--lab-ease);
3804
+ }
3805
+
3806
+ .lab-params .vpp__switch:checked {
3807
+ border-color: transparent;
3808
+ background-color: var(--lab-brand-solid);
3809
+ }
3810
+
3811
+ .lab-params .vpp__switch:checked::before {
3812
+ transform: translateX(16px);
3813
+ }
3814
+
3815
+ /* The footer: one line saying what the last action did. Hidden until there is something to say. */
3816
+ .lab-params .vpp__footer {
3817
+ display: flex;
3818
+ flex: none;
3819
+ align-items: center;
3820
+ gap: var(--lab-space-md);
3821
+ padding: 10px var(--lab-space-xl);
3822
+ border-top: 1px solid var(--lab-border-secondary);
3823
+ }
3824
+
3825
+ .lab-params .vpp__footer[hidden] {
3826
+ display: none;
3827
+ }
3828
+
3829
+ .lab-params .vpp__footer-line {
3830
+ flex: 1 1 0;
3831
+ min-width: 0;
3832
+ margin: 0;
3833
+ overflow: hidden;
3834
+ color: var(--lab-text-quaternary);
3835
+ font-size: 12px;
3836
+ line-height: 18px;
3837
+ font-weight: 500;
3838
+ text-overflow: ellipsis;
3839
+ white-space: nowrap;
3840
+ }
3841
+
3842
+ /* ---- the asset grid ------------------------------------------------------------------------ */
3843
+ .lab-asset {
3844
+ scroll-margin-top: var(--lab-space-3xl);
3845
+ }
3846
+
3847
+ .lab-asset:target {
3848
+ border-color: var(--lab-fg-brand);
3849
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--lab-fg-brand) 25%, transparent);
3850
+ }
3851
+
3852
+ .lab-asset .lab-card__thumb {
3853
+ aspect-ratio: 4 / 3;
3854
+ }
3855
+
3856
+ .lab-kv {
3857
+ display: grid;
3858
+ grid-template-columns: auto 1fr;
3859
+ gap: 2px var(--lab-space-md);
3860
+ margin: 0;
3861
+ color: var(--lab-text-quaternary);
3862
+ font-size: 12px;
3863
+ line-height: 18px;
3864
+ }
3865
+
3866
+ .lab-kv dt {
3867
+ font-weight: 500;
3868
+ }
3869
+
3870
+ .lab-kv dd {
3871
+ margin: 0;
3872
+ min-width: 0;
3873
+ color: var(--lab-text-secondary);
3874
+ overflow: hidden;
3875
+ text-overflow: ellipsis;
3876
+ white-space: nowrap;
3877
+ }
3878
+
3879
+ /* ---- placeholder pages (Support, Settings) ------------------------------------------------- */
3880
+ .lab-placeholder {
3881
+ display: grid;
3882
+ place-items: center;
3883
+ min-height: 100%;
3884
+ padding: 48px;
3885
+ }
3886
+
3887
+ .lab-placeholder__box {
3888
+ max-width: 440px;
3889
+ text-align: center;
3890
+ color: var(--lab-text-tertiary);
3891
+ }
3892
+
3893
+ .lab-placeholder__icon {
3894
+ display: inline-grid;
3895
+ place-items: center;
3896
+ width: 48px;
3897
+ height: 48px;
3898
+ margin-bottom: var(--lab-space-xl);
3899
+ border: 1px solid var(--lab-border-secondary);
3900
+ border-radius: var(--lab-radius-lg);
3901
+ background-color: var(--lab-bg-primary);
3902
+ box-shadow: var(--lab-shadow-xs);
3903
+ color: var(--lab-text-quaternary);
3904
+ }
3905
+
3906
+ .lab-placeholder h1 {
3907
+ margin: 0 0 var(--lab-space-md);
3908
+ color: var(--lab-text-primary);
3909
+ font-size: 18px;
3910
+ line-height: 28px;
3911
+ font-weight: 600;
3912
+ }
3913
+
3914
+ .lab-placeholder p {
3915
+ margin: 0;
3916
+ }
3917
+
3918
+ /* ---- small screens ------------------------------------------------------------------------- */
3919
+ @media (max-width: 1023px) {
3920
+ .lab-page {
3921
+ padding: var(--lab-space-2xl);
3922
+ }
3923
+ }
3924
+
3925
+ @media (prefers-reduced-motion: reduce) {
3926
+ .lab-shell *,
3927
+ .lab-shell *::before,
3928
+ .lab-shell *::after {
3929
+ transition-duration: 0ms !important;
3930
+ animation-duration: 0ms !important;
3931
+ }
549
3932
  }