@orbytes/astrolab 0.3.0 → 0.4.0-next.1

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 (69) hide show
  1. package/README.md +109 -64
  2. package/defaults.mjs +65 -0
  3. package/dist/core/virtual-module/virtual-routes.js +12 -1
  4. package/docs/PIN-CONTRACT.md +8 -0
  5. package/docs/PIN.md +29 -19
  6. package/index.d.ts +40 -16
  7. package/index.mjs +32 -10
  8. package/package.json +6 -2
  9. package/src/Home.astro +167 -264
  10. package/src/LabHead.astro +37 -1047
  11. package/src/chrome/ComponentCard.astro +69 -0
  12. package/src/chrome/Icon.astro +21 -0
  13. package/src/chrome/LICENSE-icons +43 -0
  14. package/src/chrome/Nav.astro +105 -0
  15. package/src/chrome/Panel.astro +104 -0
  16. package/src/chrome/Shell.astro +106 -0
  17. package/src/chrome/Sprite.astro +23 -0
  18. package/src/chrome/StoryView.astro +251 -0
  19. package/src/chrome/Tree.astro +83 -0
  20. package/src/chrome/ViewportControls.astro +98 -0
  21. package/src/chrome/ViewportStage.astro +32 -0
  22. package/src/chrome/fonts/OFL.txt +93 -0
  23. package/src/chrome/fonts/inter-latin-wght-normal.woff2 +0 -0
  24. package/src/chrome/icons.ts +59 -0
  25. package/src/chrome/marks-client.ts +102 -0
  26. package/src/chrome/model.ts +142 -0
  27. package/src/chrome/pins-data.ts +30 -0
  28. package/src/chrome/shell-client.ts +372 -0
  29. package/src/chrome/site-data.ts +230 -0
  30. package/src/chrome/trees.ts +152 -0
  31. package/src/chrome/viewport-client.ts +579 -0
  32. package/src/chrome/views/Assets.astro +110 -0
  33. package/src/chrome/views/Pages.astro +178 -0
  34. package/src/chrome/views/Placeholder.astro +37 -0
  35. package/src/chrome/views/Tasks.astro +107 -0
  36. package/src/core/LICENSE-astrobook +16 -0
  37. package/src/core/lib/components/home.astro +4 -2
  38. package/src/core/lib/pages/story.astro +12 -10
  39. package/src/core/virtual-module/virtual-routes.ts +20 -4
  40. package/src/pin/board.mjs +389 -175
  41. package/src/pin/index.mjs +33 -2
  42. package/src/pin/toolbar.js +1 -1
  43. package/src/shell/Browse.astro +106 -353
  44. package/src/shell/Viewport.astro +22 -1315
  45. package/src/shell/lab-index.ts +23 -14
  46. package/src/ui/components/app.astro +5 -7
  47. package/src/ui/components/theme-script.astro +13 -2
  48. package/src/ui/lab.css +2152 -370
  49. package/virtual.d.ts +13 -0
  50. package/src/shell/CardGrid.astro +0 -297
  51. package/src/ui/components/build-path.ts +0 -13
  52. package/src/ui/components/build-tree.ts +0 -108
  53. package/src/ui/components/collapse-duration.ts +0 -28
  54. package/src/ui/components/compress-terms.ts +0 -10
  55. package/src/ui/components/dashboard-layout.astro +0 -39
  56. package/src/ui/components/home.astro +0 -65
  57. package/src/ui/components/layout.astro +0 -110
  58. package/src/ui/components/sidebar-button-fullscreen.astro +0 -38
  59. package/src/ui/components/sidebar-button-search.astro +0 -23
  60. package/src/ui/components/sidebar-button-theme.astro +0 -9
  61. package/src/ui/components/sidebar-button.astro +0 -24
  62. package/src/ui/components/sidebar-resize-handle.astro +0 -74
  63. package/src/ui/components/sidebar-search-panel.astro +0 -41
  64. package/src/ui/components/sidebar-search-script.ts +0 -103
  65. package/src/ui/components/sidebar-title.astro +0 -17
  66. package/src/ui/components/sidebar-tree-node.astro +0 -143
  67. package/src/ui/components/sidebar-tree.astro +0 -84
  68. package/src/ui/components/sidebar.astro +0 -29
  69. package/src/ui/components/theme-toggle.astro +0 -63
package/src/ui/lab.css CHANGED
@@ -9,541 +9,2323 @@
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: 56px;
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 56px icon strip
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, for as long as the
272
+ pointer stays on it — it overlays rather than pushes, because pushing
273
+ would resize the preview and trip its breakpoints
183
274
  --------------------------------------------------------------------------------------------- */
184
- #astrobook-sidebar {
275
+ .lab-app {
185
276
  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);
193
- }
194
-
195
- :root[data-sidebar-resizing] {
196
- cursor: ew-resize;
197
- user-select: none;
277
+ height: 100vh;
278
+ height: 100dvh;
279
+ min-width: 0;
198
280
  }
199
281
 
200
- astrobook-sidebar-resize-handle {
282
+ /* ---- level 1 ------------------------------------------------------------------------------ */
283
+ .lab-l1 {
201
284
  position: relative;
202
- left: -2px;
203
- display: block;
204
- width: 4px;
285
+ z-index: 4;
205
286
  flex: none;
206
- cursor: ew-resize;
207
- transition: background-color var(--lab-transition);
287
+ display: flex;
288
+ padding: var(--lab-space-xs) 0 var(--lab-space-xs) var(--lab-space-xs);
208
289
  }
209
290
 
210
- astrobook-sidebar-resize-handle:hover {
211
- background-color: var(--lab-color-border-strong);
291
+ .lab-l1__card {
292
+ display: flex;
293
+ flex-direction: column;
294
+ justify-content: space-between;
295
+ width: var(--lab-l1-w);
296
+ height: 100%;
297
+ background-color: var(--lab-bg-primary);
298
+ border-right: 1px solid var(--lab-border-secondary);
299
+ border-radius: var(--lab-radius-xl);
300
+ transition: width var(--lab-ease);
212
301
  }
213
302
 
214
- .lab-sidebar__nav {
303
+ .lab-l1__top {
304
+ min-height: 0;
215
305
  display: flex;
216
306
  flex-direction: column;
217
- min-height: 100%;
218
- max-height: 100%;
219
- overflow-y: hidden;
220
- padding: 0;
221
- margin: 0;
222
307
  }
223
308
 
224
- .lab-sidebar__header {
309
+ .lab-l1__header {
225
310
  display: flex;
226
311
  align-items: center;
227
- gap: 0.125rem;
228
- padding: 0.75rem 0.5rem;
229
- border-bottom: 1px solid var(--lab-color-border);
312
+ gap: var(--lab-space-md);
313
+ padding: var(--lab-space-2xl) var(--lab-space-lg) var(--lab-space-2xl) var(--lab-space-2xl);
230
314
  }
231
315
 
232
- .lab-sidebar__title {
316
+ .lab-brand {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: var(--lab-space-md);
233
320
  min-width: 0;
234
- flex: 0 1 auto;
321
+ flex: 1;
322
+ color: var(--lab-text-primary);
323
+ text-decoration: none;
324
+ border-radius: var(--lab-radius-sm);
325
+ }
326
+
327
+ /* The logo is a placeholder until one is designed. */
328
+ .lab-brand__mark {
329
+ flex: none;
330
+ display: grid;
331
+ place-items: center;
332
+ width: 24px;
333
+ height: 24px;
334
+ border-radius: var(--lab-radius-sm);
335
+ background: linear-gradient(26.5deg, #53389e 8%, #6941c6 92%);
336
+ box-shadow: var(--lab-shadow-xs), inset 0 -0.5px 0.5px rgba(0, 0, 0, 0.1);
337
+ }
338
+
339
+ .lab-brand__mark::after {
340
+ content: "";
341
+ width: 12px;
342
+ height: 12px;
343
+ border-radius: 50%;
344
+ background: radial-gradient(circle at 50% 30%, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0.25) 70%);
345
+ }
346
+
347
+ .lab-brand__name {
348
+ font-size: 16px;
349
+ line-height: 24px;
350
+ font-weight: 650;
351
+ letter-spacing: -0.01em;
352
+ white-space: nowrap;
235
353
  overflow: hidden;
236
354
  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
355
  }
245
356
 
246
- .lab-sidebar__title:hover {
247
- color: var(--lab-color-text-strong);
357
+ .lab-l1__nav {
358
+ display: flex;
359
+ flex-direction: column;
360
+ padding: 0 var(--lab-space-xl);
361
+ overflow-y: auto;
362
+ scrollbar-width: none;
363
+ }
364
+
365
+ .lab-l1__footer {
366
+ display: flex;
367
+ flex-direction: column;
368
+ padding: 0 var(--lab-space-xl) var(--lab-space-2xl);
369
+ }
370
+
371
+ .lab-divider {
372
+ height: 1px;
373
+ margin: 0;
374
+ border: 0;
375
+ background-color: var(--lab-border-secondary);
248
376
  }
249
377
 
250
- .lab-sidebar__spacer {
251
- flex: 1 1 auto;
378
+ .lab-l1__nav .lab-divider {
379
+ margin: 1px 0;
252
380
  }
253
381
 
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 {
382
+ /* One row of level 1. Figma `_Nav item base`: 8px padding, 12px between text and trailing icon,
383
+ 8px between icon and text, a 36px row, 1px of air above and below. */
384
+ .lab-nav-item {
385
+ position: relative;
258
386
  display: flex;
259
387
  align-items: center;
260
- justify-content: center;
261
- flex: none;
262
- width: 2rem;
263
- height: 2rem;
264
- padding: 0.375rem;
388
+ gap: var(--lab-space-md);
389
+ width: 100%;
390
+ min-height: 36px;
391
+ margin: 1px 0;
392
+ padding: var(--lab-space-md);
265
393
  border: 0;
266
- border-radius: var(--lab-radius);
394
+ border-radius: var(--lab-radius-sm);
267
395
  background: transparent;
268
- color: var(--lab-color-text-muted);
396
+ color: var(--lab-text-secondary);
269
397
  font: inherit;
270
- font-size: 1rem;
398
+ font-weight: 600;
399
+ text-align: left;
271
400
  text-decoration: none;
272
401
  cursor: pointer;
273
- transition:
274
- color var(--lab-transition),
275
- background-color var(--lab-transition);
402
+ list-style: none;
403
+ transition: background-color var(--lab-ease), color var(--lab-ease);
276
404
  }
277
405
 
278
- .astrobook-sidebar-button:hover {
279
- color: var(--lab-color-text-strong);
280
- background-color: var(--lab-color-surface);
406
+ .lab-nav-item::-webkit-details-marker {
407
+ display: none;
281
408
  }
282
409
 
283
- .astrobook-sidebar-button[data-active] {
284
- color: var(--lab-color-text-strong);
285
- background-color: var(--lab-color-surface);
410
+ .lab-nav-item > .lab-icon {
411
+ color: var(--lab-fg-quaternary);
412
+ transition: color var(--lab-ease);
286
413
  }
287
414
 
288
- .astrobook-sidebar-button svg {
289
- display: block;
290
- width: 1rem;
291
- height: 1rem;
415
+ .lab-nav-item__label {
416
+ flex: 1;
417
+ min-width: 0;
418
+ white-space: nowrap;
419
+ overflow: hidden;
420
+ text-overflow: ellipsis;
292
421
  }
293
422
 
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);
423
+ .lab-nav-item__count {
424
+ flex: none;
425
+ min-width: 22px;
426
+ padding: 0 7px;
427
+ border-radius: var(--lab-radius-full);
428
+ background-color: var(--lab-bg-hover);
429
+ border: 1px solid var(--lab-border-secondary);
430
+ color: var(--lab-text-secondary);
431
+ font-size: 12px;
432
+ line-height: 18px;
433
+ font-weight: 500;
434
+ text-align: center;
304
435
  }
305
436
 
306
- #astrobook-search-panel[data-open] {
307
- height: max-content;
308
- border-bottom-color: var(--lab-color-border);
437
+ .lab-nav-item:hover {
438
+ background-color: var(--lab-bg-hover);
439
+ color: var(--lab-text-primary);
309
440
  }
310
441
 
311
- .lab-search__row {
312
- display: flex;
313
- align-items: center;
314
- padding: 0.5rem;
442
+ .lab-nav-item:hover > .lab-icon {
443
+ color: var(--lab-text-quaternary);
315
444
  }
316
445
 
317
- .lab-search__field {
318
- position: relative;
319
- display: flex;
320
- flex: 1;
321
- align-items: center;
446
+ .lab-nav-item[aria-current="page"],
447
+ .lab-nav-item[aria-current="true"] {
448
+ background-color: var(--lab-bg-active);
449
+ color: var(--lab-text-brand-primary);
322
450
  }
323
451
 
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);
452
+ .lab-nav-item[aria-current="page"] > .lab-icon,
453
+ .lab-nav-item[aria-current="true"] > .lab-icon {
454
+ color: var(--lab-fg-brand);
332
455
  }
333
456
 
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;
457
+ .lab-nav-item[aria-disabled="true"] {
458
+ cursor: default;
344
459
  }
345
460
 
346
- #astrobook-search-input::placeholder {
347
- color: var(--lab-color-text-faint);
461
+ /* Group headers (Site, Tasks) read one step stronger than their items. */
462
+ .lab-nav-group > .lab-nav-item {
463
+ color: var(--lab-text-primary);
348
464
  }
349
465
 
350
- #astrobook-search-input:focus {
351
- border-color: var(--lab-color-border-strong);
466
+ .lab-nav-group__chevron {
467
+ width: 16px;
468
+ height: 16px;
469
+ color: var(--lab-fg-quaternary);
470
+ transform: rotate(180deg);
471
+ transition: transform var(--lab-ease);
352
472
  }
353
473
 
354
- /* ---------------------------------------------------------------------------------------------
355
- The tree. Every selector below is one LabHead.astro also queries — see its DOM contract block.
356
- --------------------------------------------------------------------------------------------- */
357
- astrobook-sidebar-tree {
474
+ .lab-nav-group[data-collapsed] .lab-nav-group__chevron {
475
+ transform: rotate(0deg);
476
+ }
477
+
478
+ .lab-nav-group__items {
358
479
  display: flex;
359
480
  flex-direction: column;
360
- gap: 2px;
361
- overflow-y: auto;
362
- padding: 0.75rem 0.5rem 2rem;
481
+ padding-bottom: var(--lab-space-xs);
363
482
  }
364
483
 
365
- astrobook-sidebar-node {
366
- display: contents;
484
+ .lab-nav-group[data-collapsed] > .lab-nav-group__items {
485
+ display: none;
367
486
  }
368
487
 
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);
488
+ /* The group header is a toggle, not a place: it never takes the active fill, even when the page
489
+ is one of its items (that item carries it). aria-current stays on it for the rail, where the
490
+ header IS the only visible trace of the group. */
491
+ .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"] {
492
+ background-color: transparent;
493
+ color: var(--lab-text-primary);
494
+ }
495
+
496
+ .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"] > .lab-icon {
497
+ color: var(--lab-fg-quaternary);
498
+ }
499
+
500
+ .lab-l1__nav .lab-nav-group > .lab-nav-item:hover {
501
+ background-color: var(--lab-bg-hover);
502
+ }
503
+
504
+ .lab-l1-btn__expand,
505
+ :root[data-lab-l1="rail"] .lab-l1-btn__collapse {
506
+ display: none;
383
507
  }
384
508
 
385
- .astrobook-sidebar-link::-webkit-details-marker {
509
+ :root[data-lab-l1="rail"] .lab-l1-btn__expand {
510
+ display: block;
511
+ }
512
+
513
+ /* The navbar's own "open the panel" button exists only while the panel is closed. */
514
+ .lab-navbar .lab-navbar__panel-btn {
386
515
  display: none;
387
516
  }
388
517
 
389
- /* Folder and module rows. */
390
- .lab-tree-row--group {
391
- color: var(--lab-color-text-muted);
518
+ :root[data-lab-l2="closed"] .lab-navbar .lab-navbar__panel-btn {
519
+ display: inline-grid;
520
+ }
521
+
522
+ .lab-nav-group__items .lab-nav-item {
523
+ padding-left: 36px;
524
+ }
525
+
526
+ /* The header's own buttons (theme, collapse): the tertiary icon button. */
527
+ .lab-icon-btn {
528
+ flex: none;
529
+ display: inline-grid;
530
+ place-items: center;
531
+ width: 32px;
532
+ height: 32px;
533
+ padding: 0;
534
+ border: 0;
535
+ border-radius: var(--lab-radius-md);
536
+ background: transparent;
537
+ color: var(--lab-fg-quaternary);
538
+ cursor: pointer;
539
+ text-decoration: none;
540
+ transition: background-color var(--lab-ease), color var(--lab-ease);
392
541
  }
393
542
 
394
- .lab-tree-row--group:hover {
395
- color: var(--lab-color-text-strong);
396
- background-color: var(--lab-color-surface);
543
+ .lab-icon-btn:hover {
544
+ background-color: var(--lab-bg-hover);
545
+ color: var(--lab-text-secondary);
397
546
  }
398
547
 
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);
548
+ .lab-icon-btn[aria-pressed="true"] {
549
+ background-color: var(--lab-bg-active);
550
+ color: var(--lab-fg-brand);
403
551
  }
404
552
 
405
- .astrobook-sidebar-story-link-inactive:hover {
406
- color: var(--lab-color-text-strong);
407
- background-color: var(--lab-color-surface);
553
+ .lab-icon-btn .lab-icon {
554
+ width: 18px;
555
+ height: 18px;
408
556
  }
409
557
 
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);
558
+ .lab-theme-btn .lab-icon--moon,
559
+ :root.dark .lab-theme-btn .lab-icon--sun {
560
+ display: none;
414
561
  }
415
562
 
416
- .lab-tree-icon {
563
+ :root.dark .lab-theme-btn .lab-icon--moon {
417
564
  display: block;
418
- flex: none;
419
- width: 0.875rem;
420
- height: 0.875rem;
421
- color: var(--lab-color-text-faint);
422
565
  }
423
566
 
424
- .astrobook-sidebar-story-link-active .lab-tree-icon {
425
- color: inherit;
567
+ /* ---- level 1 as a rail -------------------------------------------------------------------- */
568
+ :root[data-lab-l1="rail"] .lab-l1__card {
569
+ width: var(--lab-l1-rail);
426
570
  }
427
571
 
428
- .lab-tree-children {
429
- display: flex;
572
+ :root[data-lab-l1="rail"] .lab-l1__header {
430
573
  flex-direction: column;
431
- gap: 2px;
432
- padding-left: 0.75rem;
574
+ padding: var(--lab-space-xl) 0 var(--lab-space-lg);
433
575
  }
434
576
 
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;
577
+ :root[data-lab-l1="rail"] .lab-brand {
578
+ flex: none;
440
579
  }
441
580
 
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;
581
+ :root[data-lab-l1="rail"] .lab-l1 .lab-brand__name,
582
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-item__label,
583
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-item__count,
584
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-group__chevron,
585
+ :root[data-lab-l1="rail"] .lab-l1__header .lab-theme-btn {
586
+ display: none;
448
587
  }
449
588
 
450
- details[data-astrobook-collapsible][open]::details-content {
451
- height: auto;
589
+ :root[data-lab-l1="rail"] .lab-l1__nav,
590
+ :root[data-lab-l1="rail"] .lab-l1__footer {
591
+ padding-left: var(--lab-space-md);
592
+ padding-right: var(--lab-space-md);
593
+ overflow: visible;
452
594
  }
453
595
 
454
- [data-astrobook-chevron] {
455
- transform: rotate(-90deg);
456
- transition: transform var(--collapse-duration, 150ms) var(--lab-collapse-ease);
596
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-item {
597
+ justify-content: center;
598
+ padding: var(--lab-space-md) 0;
457
599
  }
458
600
 
459
- details[data-astrobook-collapsible][open] > summary > [data-astrobook-chevron] {
460
- transform: rotate(0deg);
601
+ /* In the rail a group is its icon; its items open as a flyout beside it, on hover or on focus,
602
+ with no script. The flyout is the same list the expanded nav shows, repositioned. */
603
+ :root[data-lab-l1="rail"] .lab-nav-group {
604
+ position: relative;
461
605
  }
462
606
 
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;
607
+ /* In the rail the group's own header marks where you are, since its items are out of sight. */
608
+ :root[data-lab-l1="rail"] .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"] {
609
+ background-color: var(--lab-bg-active);
470
610
  }
471
611
 
472
- .lab-theme-toggle .lab-theme-icon--dark {
473
- display: none;
612
+ :root[data-lab-l1="rail"] .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"] > .lab-icon {
613
+ color: var(--lab-fg-brand);
474
614
  }
475
615
 
476
- :root.dark .lab-theme-toggle .lab-theme-icon--light {
616
+ :root[data-lab-l1="rail"] .lab-nav-group > .lab-nav-group__items,
617
+ :root[data-lab-l1="rail"] .lab-nav-group[data-collapsed] > .lab-nav-group__items {
477
618
  display: none;
619
+ position: absolute;
620
+ top: -5px;
621
+ left: calc(100% + 10px);
622
+ z-index: 10;
623
+ min-width: 200px;
624
+ padding: var(--lab-space-xs);
625
+ background-color: var(--lab-bg-primary);
626
+ border: 1px solid var(--lab-border-secondary);
627
+ border-radius: var(--lab-radius-md);
628
+ box-shadow: var(--lab-shadow-lg);
629
+ }
630
+
631
+ /* A bridge across the 10px gap, so the pointer can travel from the icon to the flyout. */
632
+ :root[data-lab-l1="rail"] .lab-nav-group__items::before {
633
+ content: "";
634
+ position: absolute;
635
+ top: 0;
636
+ bottom: 0;
637
+ left: -12px;
638
+ width: 12px;
639
+ }
640
+
641
+ :root[data-lab-l1="rail"] .lab-nav-group:hover > .lab-nav-group__items,
642
+ :root[data-lab-l1="rail"] .lab-nav-group:focus-within > .lab-nav-group__items {
643
+ display: flex;
478
644
  }
479
645
 
480
- :root.dark .lab-theme-toggle .lab-theme-icon--dark {
646
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-group__items .lab-nav-item {
647
+ justify-content: flex-start;
648
+ padding: var(--lab-space-md) var(--lab-space-md);
649
+ }
650
+
651
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-group__items .lab-nav-item__label {
481
652
  display: block;
482
653
  }
483
654
 
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 {
655
+ /* Rail tooltips for the ungrouped items — a CSS tooltip, because `title` takes a second to show. */
656
+ :root[data-lab-l1="rail"] .lab-l1 [data-tip]:hover::after {
657
+ content: attr(data-tip);
658
+ position: absolute;
659
+ top: 50%;
660
+ left: calc(100% + 10px);
661
+ z-index: 10;
662
+ transform: translateY(-50%);
663
+ padding: 6px 10px;
664
+ border-radius: var(--lab-radius-md);
665
+ background-color: var(--lab-text-primary);
666
+ color: var(--lab-bg-primary);
667
+ font-size: 12px;
668
+ line-height: 18px;
669
+ font-weight: 600;
670
+ white-space: nowrap;
671
+ pointer-events: none;
672
+ }
673
+
674
+ :root[data-lab-l1="rail"] .lab-nav-group:hover > .lab-nav-item[data-tip]::after {
675
+ display: none;
676
+ }
677
+
678
+ /* ---- level 2 ------------------------------------------------------------------------------ */
679
+ .lab-l2 {
680
+ position: relative;
681
+ z-index: 3;
682
+ flex: none;
489
683
  display: flex;
490
684
  flex-direction: column;
491
- align-items: center;
492
- justify-content: center;
685
+ width: var(--lab-l2-w);
493
686
  height: 100%;
494
- width: 100%;
495
- padding: 1.5rem;
496
- background-color: var(--lab-color-bg);
687
+ background-color: var(--lab-bg-secondary);
688
+ border-right: 1px solid var(--lab-border-secondary);
497
689
  }
498
690
 
499
- .lab-splash__title {
500
- 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);
691
+ .lab-l2__header {
692
+ flex: none;
693
+ display: flex;
694
+ flex-direction: column;
695
+ gap: var(--lab-space-lg);
696
+ padding: var(--lab-space-md) var(--lab-space-2xl);
506
697
  }
507
698
 
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);
699
+ .lab-l2__crumbs-row {
700
+ display: flex;
701
+ align-items: center;
702
+ gap: var(--lab-space-md);
703
+ padding-top: var(--lab-space-xl);
704
+ }
705
+
706
+ .lab-l2__body {
707
+ flex: 1;
708
+ min-height: 0;
709
+ overflow-y: auto;
710
+ padding: 0 var(--lab-space-xl) var(--lab-space-xl);
711
+ scrollbar-width: thin;
712
+ }
713
+
714
+ .lab-l2__footer {
715
+ flex: none;
716
+ padding: var(--lab-space-2xl) var(--lab-space-xl);
717
+ background-color: var(--lab-coffee-bg);
718
+ }
719
+
720
+ .lab-coffee {
721
+ color: var(--lab-coffee-text);
722
+ }
723
+
724
+ .lab-coffee > .lab-icon,
725
+ .lab-coffee:hover > .lab-icon {
726
+ color: var(--lab-coffee-icon);
727
+ }
728
+
729
+ .lab-coffee:hover {
730
+ background-color: color-mix(in srgb, var(--lab-coffee-icon) 14%, transparent);
731
+ color: var(--lab-coffee-text);
732
+ }
733
+
734
+ :root[data-lab-l2="closed"] .lab-l2 {
735
+ display: none;
513
736
  }
514
737
 
515
- .lab-splash__badges {
738
+ /* Peek: the closed panel, shown over the stage for as long as the pointer is on it. */
739
+ :root[data-lab-l2="closed"][data-lab-peek] .lab-l2 {
740
+ display: flex;
741
+ position: absolute;
742
+ top: 0;
743
+ bottom: 0;
744
+ left: calc(var(--lab-l1-w) + var(--lab-space-xs));
745
+ box-shadow: var(--lab-shadow-lg);
746
+ animation: lab-peek-in var(--lab-ease);
747
+ }
748
+
749
+ :root[data-lab-l2="closed"][data-lab-l1="rail"][data-lab-peek] .lab-l2 {
750
+ left: calc(var(--lab-l1-rail) + var(--lab-space-xs));
751
+ }
752
+
753
+ @keyframes lab-peek-in {
754
+ from {
755
+ opacity: 0;
756
+ transform: translateX(-12px);
757
+ }
758
+ }
759
+
760
+ /* The strip along the stage's left edge that opens the peek. Only exists while level 2 is closed. */
761
+ .lab-peek-zone {
762
+ display: none;
763
+ position: absolute;
764
+ top: 0;
765
+ bottom: 0;
766
+ left: 0;
767
+ z-index: 3;
768
+ width: 10px;
769
+ }
770
+
771
+ :root[data-lab-l2="closed"] .lab-peek-zone {
772
+ display: block;
773
+ }
774
+
775
+ /* ---- breadcrumbs (Figma `Breadcrumbs`) ----------------------------------------------------- */
776
+ .lab-crumbs {
516
777
  display: flex;
517
- flex-wrap: wrap;
518
778
  align-items: center;
519
- justify-content: center;
520
- gap: 0.75rem;
521
- margin-top: 1.5rem;
779
+ gap: var(--lab-space-md);
780
+ min-width: 0;
781
+ margin: 0;
782
+ padding: 0;
783
+ list-style: none;
522
784
  }
523
785
 
524
- .lab-splash__badge {
525
- display: inline-flex;
786
+ .lab-crumbs__item {
787
+ display: flex;
526
788
  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);
789
+ gap: var(--lab-space-md);
790
+ min-width: 0;
791
+ color: var(--lab-text-quaternary);
792
+ font-weight: 600;
793
+ white-space: nowrap;
794
+ }
795
+
796
+ .lab-crumbs__item + .lab-crumbs__item::before {
797
+ content: "";
798
+ flex: none;
799
+ width: 16px;
800
+ height: 16px;
801
+ background-color: var(--lab-fg-muted);
802
+ -webkit-mask: var(--lab-chevron-right) center / 16px no-repeat;
803
+ mask: var(--lab-chevron-right) center / 16px no-repeat;
804
+ }
805
+
806
+ .lab-crumbs__item a {
807
+ display: flex;
808
+ color: inherit;
534
809
  text-decoration: none;
535
- transition:
536
- color var(--lab-transition),
537
- border-color var(--lab-transition);
810
+ border-radius: var(--lab-radius-xs);
538
811
  }
539
812
 
540
- .lab-splash__badge:hover {
541
- color: var(--lab-color-text-strong);
542
- border-color: var(--lab-color-border-strong);
813
+ .lab-crumbs__item a:hover {
814
+ color: var(--lab-text-secondary);
543
815
  }
544
816
 
545
- .lab-splash__badge svg {
546
- display: block;
547
- width: 0.875rem;
548
- height: 0.875rem;
817
+ .lab-crumbs__item .lab-icon {
818
+ color: var(--lab-fg-quaternary);
819
+ }
820
+
821
+ .lab-crumbs__item:last-child {
822
+ color: var(--lab-text-brand-secondary);
823
+ overflow: hidden;
824
+ text-overflow: ellipsis;
825
+ }
826
+
827
+ :root {
828
+ --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");
829
+ }
830
+
831
+ .lab-l2__close {
832
+ margin-left: auto;
833
+ }
834
+
835
+ /* ---- inputs (Figma `Input`) ---------------------------------------------------------------- */
836
+ .lab-input {
837
+ display: flex;
838
+ align-items: center;
839
+ gap: var(--lab-space-md);
840
+ padding: var(--lab-space-md) var(--lab-space-md) var(--lab-space-md) var(--lab-space-lg);
841
+ background-color: var(--lab-bg-primary);
842
+ border: 1px solid var(--lab-border-primary);
843
+ border-radius: var(--lab-radius-md);
844
+ box-shadow: var(--lab-shadow-xs);
845
+ color: var(--lab-fg-quaternary);
846
+ transition: border-color var(--lab-ease), box-shadow var(--lab-ease);
847
+ }
848
+
849
+ .lab-input:focus-within {
850
+ border-color: var(--lab-fg-brand);
851
+ box-shadow: var(--lab-shadow-xs), 0 0 0 3px color-mix(in srgb, var(--lab-fg-brand) 22%, transparent);
852
+ }
853
+
854
+ .lab-input__field {
855
+ flex: 1;
856
+ min-width: 0;
857
+ padding: 0;
858
+ border: 0;
859
+ outline: none;
860
+ background: transparent;
861
+ color: var(--lab-text-primary);
862
+ font: inherit;
863
+ }
864
+
865
+ .lab-shell .lab-input__field:focus-visible {
866
+ box-shadow: none;
867
+ }
868
+
869
+ .lab-input__field::placeholder {
870
+ color: var(--lab-text-placeholder);
871
+ opacity: 1;
872
+ }
873
+
874
+ .lab-input__field::-webkit-search-cancel-button {
875
+ -webkit-appearance: none;
876
+ }
877
+
878
+ .lab-input__kbd {
879
+ flex: none;
880
+ padding: 0 5px;
881
+ border: 1px solid var(--lab-border-secondary);
882
+ border-radius: var(--lab-radius-xs);
883
+ color: var(--lab-text-quaternary);
884
+ font-size: 12px;
885
+ line-height: 18px;
886
+ font-weight: 500;
887
+ }
888
+
889
+ .lab-input:focus-within .lab-input__kbd {
890
+ display: none;
891
+ }
892
+
893
+ .lab-input .lab-icon-btn {
894
+ width: 24px;
895
+ height: 24px;
896
+ margin: -4px -2px -4px 0;
897
+ }
898
+
899
+ .lab-input .lab-icon-btn .lab-icon {
900
+ width: 16px;
901
+ height: 16px;
902
+ }
903
+
904
+ /* ---- menus (popover) ----------------------------------------------------------------------- */
905
+ .lab-menu {
906
+ margin: 0;
907
+ min-width: 220px;
908
+ max-width: 320px;
909
+ padding: var(--lab-space-xs);
910
+ border: 1px solid var(--lab-border-secondary);
911
+ border-radius: var(--lab-radius-md);
912
+ background-color: var(--lab-bg-primary);
913
+ color: var(--lab-text-primary);
914
+ box-shadow: var(--lab-shadow-lg);
915
+ font-family: var(--lab-font);
916
+ font-size: 14px;
917
+ line-height: 20px;
918
+ inset: auto;
919
+ }
920
+
921
+ .lab-menu__label {
922
+ margin: 0;
923
+ padding: var(--lab-space-sm) var(--lab-space-md) var(--lab-space-xs);
924
+ color: var(--lab-text-quaternary);
925
+ font-size: 12px;
926
+ line-height: 18px;
927
+ font-weight: 600;
928
+ }
929
+
930
+ .lab-menu__item {
931
+ display: flex;
932
+ align-items: center;
933
+ gap: var(--lab-space-md);
934
+ width: 100%;
935
+ padding: var(--lab-space-sm) var(--lab-space-md);
936
+ border: 0;
937
+ border-radius: var(--lab-radius-sm);
938
+ background: transparent;
939
+ color: var(--lab-text-secondary);
940
+ font: inherit;
941
+ font-weight: 500;
942
+ text-align: left;
943
+ text-decoration: none;
944
+ cursor: pointer;
945
+ }
946
+
947
+ .lab-menu__item:hover {
948
+ background-color: var(--lab-bg-hover);
949
+ color: var(--lab-text-primary);
950
+ }
951
+
952
+ .lab-menu__item > .lab-icon {
953
+ width: 16px;
954
+ height: 16px;
955
+ color: var(--lab-fg-quaternary);
956
+ }
957
+
958
+ .lab-menu__item[aria-checked="true"] {
959
+ color: var(--lab-text-primary);
960
+ }
961
+
962
+ .lab-menu__item .lab-menu__tick {
963
+ margin-left: auto;
964
+ color: var(--lab-fg-brand);
965
+ visibility: hidden;
966
+ }
967
+
968
+ .lab-menu__item[aria-checked="true"] .lab-menu__tick {
969
+ visibility: visible;
970
+ }
971
+
972
+ .lab-menu__hint {
973
+ margin-left: auto;
974
+ color: var(--lab-text-quaternary);
975
+ font-size: 12px;
976
+ }
977
+
978
+ .lab-menu hr {
979
+ height: 1px;
980
+ margin: var(--lab-space-xs) 0;
981
+ border: 0;
982
+ background-color: var(--lab-border-secondary);
983
+ }
984
+
985
+ /* ---------------------------------------------------------------------------------------------
986
+ The tree (Figma `Tree view`, sm): 32px rows 4px apart, a 16px connector column per level,
987
+ connector lines in neutral-300 with a curved end on the last child.
988
+ --------------------------------------------------------------------------------------------- */
989
+ .lab-tree,
990
+ .lab-tree__group {
991
+ display: flex;
992
+ flex-direction: column;
993
+ gap: var(--lab-space-xs);
994
+ margin: 0;
995
+ padding: 0;
996
+ list-style: none;
997
+ }
998
+
999
+ .lab-tree {
1000
+ padding: var(--lab-space-xs);
1001
+ }
1002
+
1003
+ .lab-tree__node {
1004
+ position: relative;
1005
+ --lab-indent: calc(var(--lab-d, 0) * 24px);
1006
+ }
1007
+
1008
+ .lab-tree__node > details > summary {
1009
+ list-style: none;
1010
+ }
1011
+
1012
+ .lab-tree__node > details > summary::-webkit-details-marker {
1013
+ display: none;
1014
+ }
1015
+
1016
+ .lab-tree__row {
1017
+ position: relative;
1018
+ display: flex;
1019
+ align-items: center;
1020
+ gap: var(--lab-space-sm);
1021
+ min-height: 32px;
1022
+ padding: var(--lab-space-sm) var(--lab-space-md);
1023
+ padding-left: calc(var(--lab-space-md) + var(--lab-indent));
1024
+ border-radius: var(--lab-radius-sm);
1025
+ color: var(--lab-text-tertiary);
1026
+ font-weight: 600;
1027
+ text-decoration: none;
1028
+ cursor: pointer;
1029
+ user-select: none;
1030
+ transition: background-color var(--lab-ease), color var(--lab-ease);
1031
+ }
1032
+
1033
+ .lab-tree__row:hover {
1034
+ background-color: var(--lab-bg-hover);
1035
+ color: var(--lab-text-primary);
1036
+ }
1037
+
1038
+ .lab-tree__row[aria-current="page"] {
1039
+ background-color: var(--lab-bg-active);
1040
+ color: var(--lab-text-brand-primary);
1041
+ }
1042
+
1043
+ .lab-tree__row[aria-current="page"] > .lab-icon {
1044
+ color: var(--lab-fg-brand);
1045
+ }
1046
+
1047
+ .lab-tree__row[aria-disabled="true"] {
1048
+ color: var(--lab-text-quaternary);
1049
+ cursor: default;
1050
+ }
1051
+
1052
+ .lab-tree__row > .lab-icon {
1053
+ width: 16px;
1054
+ height: 16px;
1055
+ color: var(--lab-fg-quaternary);
1056
+ }
1057
+
1058
+ .lab-tree__label {
1059
+ flex: 1;
1060
+ min-width: 0;
1061
+ overflow: hidden;
1062
+ text-overflow: ellipsis;
1063
+ white-space: nowrap;
1064
+ }
1065
+
1066
+ /* A group row whose label is also a page (Pages › Services): the label is the link, the rest of
1067
+ the row toggles. */
1068
+ .lab-tree__label > a {
1069
+ color: inherit;
1070
+ text-decoration: none;
1071
+ }
1072
+
1073
+ .lab-tree__label > a::after {
1074
+ content: "";
1075
+ position: absolute;
1076
+ inset: 0 32px 0 0;
1077
+ }
1078
+
1079
+ .lab-tree__chevron {
1080
+ width: 16px;
1081
+ height: 16px;
1082
+ color: var(--lab-fg-quaternary);
1083
+ transition: transform var(--lab-ease);
1084
+ }
1085
+
1086
+ details:not([open]) > summary .lab-tree__chevron {
1087
+ transform: rotate(-90deg);
1088
+ }
1089
+
1090
+ /* The status dot — the tree's one-glyph version of the live / used-by pills. */
1091
+ .lab-dot {
1092
+ flex: none;
1093
+ width: 8px;
1094
+ height: 8px;
1095
+ border-radius: 50%;
1096
+ }
1097
+
1098
+ .lab-dot--live {
1099
+ background-color: var(--lab-success-dot);
1100
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--lab-success-dot) 18%, transparent);
1101
+ }
1102
+
1103
+ .lab-dot--used {
1104
+ border: 2px solid var(--lab-info-dot);
1105
+ }
1106
+
1107
+ .lab-dot--open {
1108
+ background-color: var(--lab-brand-solid);
1109
+ }
1110
+
1111
+ .lab-dot--muted {
1112
+ border: 1.5px dashed var(--lab-fg-quaternary);
1113
+ }
1114
+
1115
+ .lab-tree__count {
1116
+ flex: none;
1117
+ color: var(--lab-text-quaternary);
1118
+ font-size: 12px;
1119
+ font-weight: 500;
1120
+ }
1121
+
1122
+ /* Connectors. Each child draws the line of its own level in the 16px column its parent's icon
1123
+ sits over; the last child ends it in a curve that turns towards its own row. */
1124
+ .lab-tree__group > .lab-tree__node::before {
1125
+ content: "";
1126
+ position: absolute;
1127
+ top: -4px;
1128
+ bottom: 0;
1129
+ left: calc(var(--lab-space-md) + var(--lab-indent) - 24px + 7.5px);
1130
+ width: 1px;
1131
+ background-color: var(--lab-fg-muted);
1132
+ pointer-events: none;
1133
+ }
1134
+
1135
+ .lab-tree__group > .lab-tree__node:last-child::before {
1136
+ bottom: auto;
1137
+ height: 20px;
1138
+ width: 9px;
1139
+ background: none;
1140
+ border-left: 1px solid var(--lab-fg-muted);
1141
+ border-bottom: 1px solid var(--lab-fg-muted);
1142
+ border-bottom-left-radius: 8px;
1143
+ }
1144
+
1145
+ .lab-tree__empty {
1146
+ margin: var(--lab-space-md) var(--lab-space-md);
1147
+ color: var(--lab-text-quaternary);
1148
+ }
1149
+
1150
+ /* Search: rows that do not match are hidden by the tree script; this is the hook. */
1151
+ .lab-tree [data-lab-hidden] {
1152
+ display: none;
1153
+ }
1154
+
1155
+ .lab-tree mark {
1156
+ background: transparent;
1157
+ color: var(--lab-text-brand-secondary);
1158
+ }
1159
+
1160
+ /* ---------------------------------------------------------------------------------------------
1161
+ Main: the navbar above, the content below.
1162
+ --------------------------------------------------------------------------------------------- */
1163
+ .lab-main {
1164
+ position: relative;
1165
+ z-index: 1;
1166
+ flex: 1;
1167
+ min-width: 0;
1168
+ display: flex;
1169
+ flex-direction: column;
1170
+ height: 100%;
1171
+ }
1172
+
1173
+ .lab-navbar {
1174
+ flex: none;
1175
+ display: flex;
1176
+ align-items: center;
1177
+ flex-wrap: wrap;
1178
+ gap: var(--lab-space-md) var(--lab-space-lg);
1179
+ min-height: var(--lab-navbar-h);
1180
+ padding: var(--lab-space-md) var(--lab-space-2xl);
1181
+ border-bottom: 1px solid var(--lab-border-secondary);
1182
+ background-color: var(--lab-bg-primary);
1183
+ }
1184
+
1185
+ .lab-navbar__panel-btn {
1186
+ margin-left: calc(-1 * var(--lab-space-sm));
1187
+ }
1188
+
1189
+ .lab-navbar__id {
1190
+ display: flex;
1191
+ align-items: center;
1192
+ gap: var(--lab-space-md) var(--lab-space-lg);
1193
+ flex-wrap: wrap;
1194
+ min-width: 0;
1195
+ flex: 1 1 280px;
1196
+ }
1197
+
1198
+ .lab-navbar__title {
1199
+ margin: 0;
1200
+ min-width: 0;
1201
+ color: var(--lab-text-primary);
1202
+ font-size: 16px;
1203
+ line-height: 24px;
1204
+ font-weight: 600;
1205
+ white-space: nowrap;
1206
+ overflow: hidden;
1207
+ text-overflow: ellipsis;
1208
+ }
1209
+
1210
+ .lab-navbar__title small {
1211
+ margin-left: var(--lab-space-sm);
1212
+ color: var(--lab-text-quaternary);
1213
+ font-size: 14px;
1214
+ font-weight: 500;
1215
+ }
1216
+
1217
+ .lab-navbar__meta {
1218
+ display: flex;
1219
+ align-items: center;
1220
+ flex-wrap: wrap;
1221
+ gap: var(--lab-space-sm);
1222
+ color: var(--lab-text-quaternary);
1223
+ }
1224
+
1225
+ .lab-navbar__tools {
1226
+ display: flex;
1227
+ align-items: center;
1228
+ flex-wrap: wrap;
1229
+ gap: var(--lab-space-md);
1230
+ }
1231
+
1232
+ .lab-navbar__sep {
1233
+ width: 1px;
1234
+ height: 24px;
1235
+ background-color: var(--lab-border-secondary);
1236
+ }
1237
+
1238
+ /* The pills after a component's name are one button: the trigger for its status menu. */
1239
+ .lab-status-btn {
1240
+ display: inline-flex;
1241
+ align-items: center;
1242
+ flex-wrap: wrap;
1243
+ gap: var(--lab-space-xs);
1244
+ padding: 2px 4px;
1245
+ margin: -2px -4px;
1246
+ border: 0;
1247
+ border-radius: var(--lab-radius-md);
1248
+ background: transparent;
1249
+ color: var(--lab-fg-quaternary);
1250
+ font: inherit;
1251
+ cursor: pointer;
1252
+ }
1253
+
1254
+ .lab-status-btn:hover {
1255
+ background-color: var(--lab-bg-hover);
1256
+ }
1257
+
1258
+ .lab-status-menu {
1259
+ width: 320px;
1260
+ }
1261
+
1262
+ .lab-status-menu__fact,
1263
+ .lab-status-menu__note {
1264
+ margin: 0;
1265
+ padding: 0 var(--lab-space-md) var(--lab-space-sm);
1266
+ color: var(--lab-text-tertiary);
1267
+ font-size: 13px;
1268
+ line-height: 18px;
1269
+ overflow-wrap: anywhere;
1270
+ }
1271
+
1272
+ .lab-status-menu__note {
1273
+ padding-top: var(--lab-space-sm);
1274
+ color: var(--lab-text-quaternary);
1275
+ font-size: 12px;
1276
+ }
1277
+
1278
+ .lab-menu__check {
1279
+ cursor: pointer;
1280
+ }
1281
+
1282
+ .lab-menu__check input {
1283
+ width: 16px;
1284
+ height: 16px;
1285
+ margin: 0;
1286
+ accent-color: var(--lab-brand-solid);
1287
+ }
1288
+
1289
+ .lab-menu__check:has(input:disabled) {
1290
+ cursor: default;
1291
+ opacity: 0.7;
1292
+ }
1293
+
1294
+ /* Story tabs (the Untitled "button minimal" tab). */
1295
+ .lab-tabs {
1296
+ display: flex;
1297
+ align-items: center;
1298
+ flex-wrap: wrap;
1299
+ gap: var(--lab-space-xs);
1300
+ }
1301
+
1302
+ .lab-tab {
1303
+ display: inline-flex;
1304
+ align-items: center;
1305
+ min-height: 32px;
1306
+ padding: var(--lab-space-sm) var(--lab-space-lg);
1307
+ border-radius: var(--lab-radius-sm);
1308
+ color: var(--lab-text-quaternary);
1309
+ font-weight: 600;
1310
+ text-decoration: none;
1311
+ white-space: nowrap;
1312
+ transition: background-color var(--lab-ease), color var(--lab-ease);
1313
+ }
1314
+
1315
+ .lab-tab:hover {
1316
+ background-color: var(--lab-bg-hover);
1317
+ color: var(--lab-text-secondary);
1318
+ }
1319
+
1320
+ .lab-tab[aria-current="page"] {
1321
+ background-color: var(--lab-bg-active);
1322
+ color: var(--lab-text-brand-primary);
1323
+ }
1324
+
1325
+ .lab-tabs__count {
1326
+ color: var(--lab-text-quaternary);
1327
+ font-weight: 500;
1328
+ font-variant-numeric: tabular-nums;
1329
+ }
1330
+
1331
+ a.lab-menu__item[aria-checked] .lab-menu__tick {
1332
+ margin-left: auto;
1333
+ }
1334
+
1335
+ .lab-content {
1336
+ position: relative;
1337
+ flex: 1;
1338
+ min-height: 0;
1339
+ overflow: auto;
1340
+ }
1341
+
1342
+ .lab-page {
1343
+ max-width: 1280px;
1344
+ padding: var(--lab-space-3xl) var(--lab-space-3xl) 48px;
1345
+ }
1346
+
1347
+ .lab-page__head {
1348
+ margin-bottom: var(--lab-space-3xl);
1349
+ }
1350
+
1351
+ .lab-page__title {
1352
+ margin: 0 0 var(--lab-space-xs);
1353
+ color: var(--lab-text-primary);
1354
+ font-size: 24px;
1355
+ line-height: 32px;
1356
+ font-weight: 600;
1357
+ letter-spacing: -0.01em;
1358
+ }
1359
+
1360
+ .lab-page__lede {
1361
+ max-width: 68ch;
1362
+ margin: 0;
1363
+ color: var(--lab-text-tertiary);
1364
+ font-size: 15px;
1365
+ line-height: 24px;
1366
+ }
1367
+
1368
+ .lab-section-title {
1369
+ display: flex;
1370
+ align-items: baseline;
1371
+ gap: var(--lab-space-md);
1372
+ margin: var(--lab-space-3xl) 0 var(--lab-space-lg);
1373
+ color: var(--lab-text-primary);
1374
+ font-size: 16px;
1375
+ line-height: 24px;
1376
+ font-weight: 600;
1377
+ }
1378
+
1379
+ .lab-section-title small {
1380
+ color: var(--lab-text-quaternary);
1381
+ font-size: 14px;
1382
+ font-weight: 500;
1383
+ }
1384
+
1385
+ .lab-section-title a {
1386
+ color: inherit;
1387
+ text-decoration: none;
1388
+ }
1389
+
1390
+ .lab-section-title a:hover {
1391
+ color: var(--lab-text-brand-secondary);
1392
+ }
1393
+
1394
+ /* ---- buttons ------------------------------------------------------------------------------- */
1395
+ .lab-btn {
1396
+ display: inline-flex;
1397
+ align-items: center;
1398
+ justify-content: center;
1399
+ gap: var(--lab-space-xs);
1400
+ min-height: 36px;
1401
+ padding: var(--lab-space-md) var(--lab-space-lg);
1402
+ border: 1px solid var(--lab-border-primary);
1403
+ border-radius: var(--lab-radius-md);
1404
+ background-color: var(--lab-bg-primary);
1405
+ box-shadow: var(--lab-shadow-xs);
1406
+ color: var(--lab-text-secondary);
1407
+ font: inherit;
1408
+ font-weight: 600;
1409
+ white-space: nowrap;
1410
+ text-decoration: none;
1411
+ cursor: pointer;
1412
+ transition: background-color var(--lab-ease), color var(--lab-ease), border-color var(--lab-ease);
1413
+ }
1414
+
1415
+ .lab-btn:hover {
1416
+ background-color: var(--lab-bg-hover);
1417
+ color: var(--lab-text-primary);
1418
+ }
1419
+
1420
+ .lab-btn > .lab-icon {
1421
+ width: 16px;
1422
+ height: 16px;
1423
+ color: var(--lab-fg-quaternary);
1424
+ }
1425
+
1426
+ .lab-btn--icon {
1427
+ width: 36px;
1428
+ padding: 0;
1429
+ }
1430
+
1431
+ .lab-btn--sm {
1432
+ min-height: 32px;
1433
+ padding: var(--lab-space-sm) var(--lab-space-md);
1434
+ font-size: 13px;
1435
+ }
1436
+
1437
+ .lab-btn--sm.lab-btn--icon {
1438
+ width: 32px;
1439
+ padding: 0;
1440
+ }
1441
+
1442
+ .lab-btn[aria-pressed="true"],
1443
+ .lab-btn[data-active] {
1444
+ background-color: var(--lab-bg-active);
1445
+ border-color: color-mix(in srgb, var(--lab-fg-brand) 55%, var(--lab-border-primary));
1446
+ color: var(--lab-text-brand-primary);
1447
+ }
1448
+
1449
+ .lab-btn[aria-pressed="true"] > .lab-icon,
1450
+ .lab-btn[data-active] > .lab-icon {
1451
+ color: var(--lab-fg-brand);
1452
+ }
1453
+
1454
+ .lab-btn:disabled,
1455
+ .lab-btn[aria-disabled="true"] {
1456
+ opacity: 0.5;
1457
+ cursor: not-allowed;
1458
+ }
1459
+
1460
+ /* A row of joined buttons — the device switch. */
1461
+ .lab-seg {
1462
+ display: inline-flex;
1463
+ border: 1px solid var(--lab-border-primary);
1464
+ border-radius: var(--lab-radius-md);
1465
+ box-shadow: var(--lab-shadow-xs);
1466
+ overflow: hidden;
1467
+ background-color: var(--lab-bg-primary);
1468
+ }
1469
+
1470
+ .lab-seg > .lab-btn {
1471
+ border: 0;
1472
+ border-radius: 0;
1473
+ box-shadow: none;
1474
+ }
1475
+
1476
+ .lab-seg > .lab-btn + .lab-btn {
1477
+ border-left: 1px solid var(--lab-border-primary);
1478
+ }
1479
+
1480
+ .lab-seg > .lab-btn[data-active] {
1481
+ background-color: var(--lab-bg-hover);
1482
+ color: var(--lab-text-primary);
1483
+ }
1484
+
1485
+ .lab-seg > .lab-btn[data-active] > .lab-icon {
1486
+ color: var(--lab-text-secondary);
1487
+ }
1488
+
1489
+ .lab-select {
1490
+ min-height: 36px;
1491
+ padding: var(--lab-space-md) 32px var(--lab-space-md) var(--lab-space-lg);
1492
+ border: 1px solid var(--lab-border-primary);
1493
+ border-radius: var(--lab-radius-md);
1494
+ background: var(--lab-bg-primary) no-repeat right 10px center / 16px;
1495
+ 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");
1496
+ box-shadow: var(--lab-shadow-xs);
1497
+ color: var(--lab-text-secondary);
1498
+ font: inherit;
1499
+ font-weight: 600;
1500
+ appearance: none;
1501
+ cursor: pointer;
1502
+ }
1503
+
1504
+ .lab-select--sm {
1505
+ min-height: 32px;
1506
+ padding-top: var(--lab-space-sm);
1507
+ padding-bottom: var(--lab-space-sm);
1508
+ font-size: 13px;
1509
+ }
1510
+
1511
+ /* ---- pills (Figma/Untitled `Badge`, sm) ---------------------------------------------------- */
1512
+ .lab-pill {
1513
+ display: inline-flex;
1514
+ align-items: center;
1515
+ gap: var(--lab-space-xs);
1516
+ padding: 1px 8px;
1517
+ border: 1px solid var(--lab-neutral-border);
1518
+ border-radius: var(--lab-radius-full);
1519
+ background-color: var(--lab-neutral-bg);
1520
+ color: var(--lab-neutral-text);
1521
+ font-size: 12px;
1522
+ line-height: 18px;
1523
+ font-weight: 500;
1524
+ white-space: nowrap;
1525
+ }
1526
+
1527
+ .lab-pill--live {
1528
+ border-color: var(--lab-success-border);
1529
+ background-color: var(--lab-success-bg);
1530
+ color: var(--lab-success-text);
1531
+ }
1532
+
1533
+ .lab-pill--live::before {
1534
+ content: "";
1535
+ width: 6px;
1536
+ height: 6px;
1537
+ border-radius: 50%;
1538
+ background-color: var(--lab-success-dot);
1539
+ }
1540
+
1541
+ .lab-pill--used {
1542
+ border-color: var(--lab-info-border);
1543
+ background-color: var(--lab-info-bg);
1544
+ color: var(--lab-info-text);
1545
+ }
1546
+
1547
+ .lab-pill--responsive {
1548
+ border-color: var(--lab-info-border);
1549
+ background-color: var(--lab-info-bg);
1550
+ color: var(--lab-info-text);
1551
+ }
1552
+
1553
+ .lab-pill--unresponsive {
1554
+ border-style: dashed;
1555
+ border-color: var(--lab-warning-border);
1556
+ background-color: var(--lab-warning-bg);
1557
+ color: var(--lab-warning-text);
1558
+ }
1559
+
1560
+ .lab-pill--marked,
1561
+ .lab-pill--error {
1562
+ border-color: var(--lab-error-border);
1563
+ background-color: var(--lab-error-bg);
1564
+ color: var(--lab-error-text);
1565
+ }
1566
+
1567
+ .lab-pill--brand {
1568
+ border-color: color-mix(in srgb, var(--lab-fg-brand) 40%, transparent);
1569
+ background-color: var(--lab-bg-active);
1570
+ color: var(--lab-text-brand-secondary);
1571
+ }
1572
+
1573
+ .lab-pill--mono {
1574
+ font-family: var(--lab-font-mono);
1575
+ font-size: 11.5px;
1576
+ }
1577
+
1578
+ /* ---- stat cards (the home page) ------------------------------------------------------------ */
1579
+ .lab-stats {
1580
+ display: grid;
1581
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
1582
+ gap: var(--lab-space-xl);
1583
+ }
1584
+
1585
+ .lab-stat {
1586
+ display: flex;
1587
+ flex-direction: column;
1588
+ gap: var(--lab-space-lg);
1589
+ padding: var(--lab-space-2xl);
1590
+ background-color: var(--lab-bg-primary);
1591
+ border: 1px solid var(--lab-border-secondary);
1592
+ border-radius: var(--lab-radius-xl);
1593
+ box-shadow: var(--lab-shadow-xs);
1594
+ color: var(--lab-text-primary);
1595
+ text-decoration: none;
1596
+ transition: border-color var(--lab-ease), box-shadow var(--lab-ease);
1597
+ }
1598
+
1599
+ .lab-stat:hover {
1600
+ border-color: var(--lab-border-primary);
1601
+ box-shadow: var(--lab-shadow-sm);
1602
+ }
1603
+
1604
+ .lab-stat__head {
1605
+ display: flex;
1606
+ align-items: center;
1607
+ gap: var(--lab-space-md);
1608
+ color: var(--lab-text-tertiary);
1609
+ font-weight: 600;
1610
+ }
1611
+
1612
+ .lab-stat__head .lab-icon {
1613
+ color: var(--lab-fg-quaternary);
1614
+ }
1615
+
1616
+ .lab-stat__value {
1617
+ font-size: 30px;
1618
+ line-height: 38px;
1619
+ font-weight: 600;
1620
+ letter-spacing: -0.02em;
1621
+ }
1622
+
1623
+ .lab-stat__foot {
1624
+ display: flex;
1625
+ flex-wrap: wrap;
1626
+ gap: var(--lab-space-sm);
1627
+ }
1628
+
1629
+ /* ---- the "on this page" slot list (the home page) ------------------------------------------ */
1630
+ .lab-slots {
1631
+ display: flex;
1632
+ flex-direction: column;
1633
+ margin: 0;
1634
+ padding: 0;
1635
+ list-style: none;
1636
+ background-color: var(--lab-bg-primary);
1637
+ border: 1px solid var(--lab-border-secondary);
1638
+ border-radius: var(--lab-radius-xl);
1639
+ box-shadow: var(--lab-shadow-xs);
1640
+ overflow: hidden;
1641
+ }
1642
+
1643
+ .lab-slots__row {
1644
+ display: grid;
1645
+ grid-template-columns: 32px minmax(160px, 260px) 1fr;
1646
+ align-items: center;
1647
+ gap: var(--lab-space-lg);
1648
+ padding: var(--lab-space-md) var(--lab-space-xl);
1649
+ }
1650
+
1651
+ .lab-slots__row + .lab-slots__row {
1652
+ border-top: 1px solid var(--lab-border-secondary);
1653
+ }
1654
+
1655
+ .lab-slots__n {
1656
+ color: var(--lab-text-quaternary);
1657
+ font-variant-numeric: tabular-nums;
1658
+ font-weight: 500;
1659
+ }
1660
+
1661
+ .lab-slots__name {
1662
+ display: flex;
1663
+ align-items: baseline;
1664
+ gap: var(--lab-space-sm);
1665
+ min-width: 0;
1666
+ color: var(--lab-text-primary);
1667
+ font-weight: 600;
1668
+ text-decoration: none;
1669
+ }
1670
+
1671
+ a.lab-slots__name:hover {
1672
+ color: var(--lab-text-brand-secondary);
1673
+ }
1674
+
1675
+ .lab-slots__name--bare {
1676
+ color: var(--lab-text-tertiary);
1677
+ font-weight: 500;
1678
+ }
1679
+
1680
+ .lab-slots__file {
1681
+ min-width: 0;
1682
+ overflow: hidden;
1683
+ text-overflow: ellipsis;
1684
+ white-space: nowrap;
1685
+ color: var(--lab-text-quaternary);
1686
+ font-size: 12px;
1687
+ }
1688
+
1689
+ /* ---- story and page cards ------------------------------------------------------------------ */
1690
+ .lab-cards {
1691
+ display: grid;
1692
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
1693
+ gap: var(--lab-space-2xl);
1694
+ }
1695
+
1696
+ .lab-card {
1697
+ position: relative;
1698
+ display: flex;
1699
+ flex-direction: column;
1700
+ min-width: 0;
1701
+ background-color: var(--lab-bg-primary);
1702
+ border: 1px solid var(--lab-border-secondary);
1703
+ border-radius: var(--lab-radius-xl);
1704
+ box-shadow: var(--lab-shadow-xs);
1705
+ overflow: hidden;
1706
+ transition: border-color var(--lab-ease), box-shadow var(--lab-ease);
1707
+ }
1708
+
1709
+ .lab-card:hover,
1710
+ .lab-card:focus-within {
1711
+ border-color: var(--lab-border-primary);
1712
+ box-shadow: var(--lab-shadow-sm);
1713
+ }
1714
+
1715
+ .lab-card[data-lab-hidden] {
1716
+ display: none;
1717
+ }
1718
+
1719
+ .lab-card__main {
1720
+ display: flex;
1721
+ flex-direction: column;
1722
+ color: inherit;
1723
+ text-decoration: none;
1724
+ }
1725
+
1726
+ .lab-card__main::after {
1727
+ content: "";
1728
+ position: absolute;
1729
+ inset: 0;
1730
+ z-index: 1;
1731
+ border-radius: inherit;
1732
+ }
1733
+
1734
+ .lab-card__thumb {
1735
+ position: relative;
1736
+ display: block;
1737
+ overflow: hidden;
1738
+ aspect-ratio: 16 / 10;
1739
+ background-color: var(--lab-bg-sunken);
1740
+ border-bottom: 1px solid var(--lab-border-secondary);
1741
+ }
1742
+
1743
+ .lab-card__thumb iframe,
1744
+ .lab-card__thumb img {
1745
+ position: absolute;
1746
+ top: 0;
1747
+ left: 0;
1748
+ border: 0;
1749
+ transform-origin: 0 0;
1750
+ pointer-events: none;
1751
+ background-color: #fff;
1752
+ }
1753
+
1754
+ .lab-card__thumb img {
1755
+ width: 100%;
1756
+ height: 100%;
1757
+ object-fit: contain;
1758
+ background: repeating-conic-gradient(var(--lab-bg-sunken) 0 25%, var(--lab-bg-primary) 0 50%) 0 0 / 16px 16px;
1759
+ }
1760
+
1761
+ .lab-card__thumb--note {
1762
+ display: grid;
1763
+ place-items: center;
1764
+ padding: var(--lab-space-xl);
1765
+ color: var(--lab-text-quaternary);
1766
+ font-size: 13px;
1767
+ text-align: center;
1768
+ }
1769
+
1770
+ .lab-card__body {
1771
+ display: flex;
1772
+ flex-direction: column;
1773
+ gap: var(--lab-space-sm);
1774
+ padding: var(--lab-space-lg) var(--lab-space-xl) var(--lab-space-xl);
1775
+ }
1776
+
1777
+ .lab-card__name {
1778
+ display: flex;
1779
+ align-items: baseline;
1780
+ gap: var(--lab-space-sm);
1781
+ min-width: 0;
1782
+ color: var(--lab-text-primary);
1783
+ font-weight: 600;
1784
+ }
1785
+
1786
+ .lab-card__name span:first-child {
1787
+ overflow: hidden;
1788
+ text-overflow: ellipsis;
1789
+ white-space: nowrap;
1790
+ }
1791
+
1792
+ .lab-card__version {
1793
+ flex: none;
1794
+ color: var(--lab-text-quaternary);
1795
+ font-size: 12px;
1796
+ font-weight: 500;
1797
+ }
1798
+
1799
+ .lab-card__sub {
1800
+ color: var(--lab-text-tertiary);
1801
+ overflow: hidden;
1802
+ text-overflow: ellipsis;
1803
+ white-space: nowrap;
1804
+ }
1805
+
1806
+ .lab-card__pills {
1807
+ display: flex;
1808
+ flex-wrap: wrap;
1809
+ gap: var(--lab-space-xs);
1810
+ margin-top: var(--lab-space-xs);
1811
+ }
1812
+
1813
+ /* VS Code and staging, over the thumbnail's corner, above the card's own link. */
1814
+ .lab-card__links {
1815
+ position: absolute;
1816
+ top: var(--lab-space-md);
1817
+ right: var(--lab-space-md);
1818
+ z-index: 2;
1819
+ display: flex;
1820
+ gap: var(--lab-space-xs);
1821
+ opacity: 0;
1822
+ transition: opacity var(--lab-ease);
1823
+ }
1824
+
1825
+ .lab-card:hover .lab-card__links,
1826
+ .lab-card:focus-within .lab-card__links {
1827
+ opacity: 1;
1828
+ }
1829
+
1830
+ .lab-card__links .lab-icon-btn {
1831
+ width: 28px;
1832
+ height: 28px;
1833
+ background-color: var(--lab-bg-primary);
1834
+ border: 1px solid var(--lab-border-secondary);
1835
+ box-shadow: var(--lab-shadow-xs);
1836
+ }
1837
+
1838
+ .lab-card__links .lab-icon-btn .lab-icon {
1839
+ width: 16px;
1840
+ height: 16px;
1841
+ }
1842
+
1843
+ .lab-empty {
1844
+ padding: 48px var(--lab-space-3xl);
1845
+ border: 1px dashed var(--lab-border-primary);
1846
+ border-radius: var(--lab-radius-xl);
1847
+ color: var(--lab-text-quaternary);
1848
+ text-align: center;
1849
+ }
1850
+
1851
+ .lab-empty strong {
1852
+ display: block;
1853
+ margin-bottom: var(--lab-space-xs);
1854
+ color: var(--lab-text-primary);
1855
+ font-size: 16px;
1856
+ line-height: 24px;
1857
+ }
1858
+
1859
+ /* ---------------------------------------------------------------------------------------------
1860
+ The viewport stage — the story or page in an <iframe> whose logical size you set. Zoom is a
1861
+ transform on the wrapper, so the frame's own width (and so its breakpoints) never changes.
1862
+ --------------------------------------------------------------------------------------------- */
1863
+ .lab-vp-size {
1864
+ display: inline-flex;
1865
+ align-items: center;
1866
+ gap: var(--lab-space-xs);
1867
+ padding: 0 var(--lab-space-xs) 0 var(--lab-space-md);
1868
+ min-height: 32px;
1869
+ border: 1px solid var(--lab-border-primary);
1870
+ border-radius: var(--lab-radius-md);
1871
+ background-color: var(--lab-bg-primary);
1872
+ box-shadow: var(--lab-shadow-xs);
1873
+ color: var(--lab-text-quaternary);
1874
+ font-size: 13px;
1875
+ font-variant-numeric: tabular-nums;
1876
+ }
1877
+
1878
+ .lab-vp-size input {
1879
+ width: 3.4em;
1880
+ padding: 0;
1881
+ border: 0;
1882
+ outline: none;
1883
+ background: transparent;
1884
+ color: var(--lab-text-primary);
1885
+ font: inherit;
1886
+ font-weight: 600;
1887
+ text-align: center;
1888
+ appearance: textfield;
1889
+ -moz-appearance: textfield;
1890
+ }
1891
+
1892
+ .lab-shell .lab-vp-size input:focus-visible {
1893
+ box-shadow: none;
1894
+ color: var(--lab-text-brand-secondary);
1895
+ }
1896
+
1897
+ .lab-vp-size:focus-within {
1898
+ border-color: var(--lab-fg-brand);
1899
+ }
1900
+
1901
+ .lab-vp-size input::-webkit-outer-spin-button,
1902
+ .lab-vp-size input::-webkit-inner-spin-button {
1903
+ -webkit-appearance: none;
1904
+ margin: 0;
1905
+ }
1906
+
1907
+ .lab-vp-size .lab-icon-btn {
1908
+ width: 26px;
1909
+ height: 26px;
1910
+ }
1911
+
1912
+ .lab-vp-size .lab-icon-btn .lab-icon {
1913
+ width: 14px;
1914
+ height: 14px;
1915
+ }
1916
+
1917
+ .lab-vp-band {
1918
+ font-variant-numeric: tabular-nums;
1919
+ }
1920
+
1921
+ .lab-vp {
1922
+ position: absolute;
1923
+ inset: 0;
1924
+ }
1925
+
1926
+ /* The preview's own light/dark, apart from the chrome's (decided 2026-09-24). The iframe's
1927
+ color-scheme is what `prefers-color-scheme` reads inside it, so it is pinned here — or a dark
1928
+ chrome would quietly turn every preview dark — and the preview theme switch sets it. */
1929
+ .lab-vp-wrapper iframe,
1930
+ .lab-card__thumb iframe {
1931
+ color-scheme: light;
1932
+ }
1933
+
1934
+ .lab-vp-wrapper iframe[data-theme="dark"] {
1935
+ color-scheme: dark;
1936
+ }
1937
+
1938
+ .lab-preview-theme-btn .lab-icon--moon,
1939
+ .lab-preview-theme-btn[data-theme="dark"] .lab-icon--sun {
1940
+ display: none;
1941
+ }
1942
+
1943
+ .lab-preview-theme-btn[data-theme="dark"] .lab-icon--moon {
1944
+ display: block;
1945
+ }
1946
+
1947
+ /* The live size while a handle is being dragged. */
1948
+ .lab-vp-live {
1949
+ position: absolute;
1950
+ right: 22px;
1951
+ bottom: 22px;
1952
+ z-index: 2;
1953
+ display: none;
1954
+ padding: 2px 8px;
1955
+ border-radius: var(--lab-radius-sm);
1956
+ background-color: var(--lab-text-primary);
1957
+ color: var(--lab-bg-primary);
1958
+ font-size: 12px;
1959
+ line-height: 18px;
1960
+ font-weight: 600;
1961
+ font-variant-numeric: tabular-nums;
1962
+ pointer-events: none;
1963
+ }
1964
+
1965
+ .lab-vp[data-dragging] .lab-vp-live {
1966
+ display: block;
1967
+ }
1968
+
1969
+ .lab-vp-stage {
1970
+ position: absolute;
1971
+ inset: 0;
1972
+ display: flex;
1973
+ overflow: auto;
1974
+ background-color: var(--lab-bg-secondary);
1975
+ background-image: radial-gradient(color-mix(in srgb, var(--lab-fg-quaternary) 45%, transparent) 1px, transparent 1px);
1976
+ background-size: 16px 16px;
1977
+ }
1978
+
1979
+ .lab-vp-box {
1980
+ position: relative;
1981
+ flex: none;
1982
+ margin: auto;
1983
+ padding: var(--lab-space-xl);
1984
+ box-sizing: content-box;
1985
+ overflow: hidden;
1986
+ }
1987
+
1988
+ .lab-vp-wrapper {
1989
+ position: relative;
1990
+ transform-origin: 0 0;
1991
+ border-radius: 2px;
1992
+ outline: 1px solid var(--lab-border-primary);
1993
+ background-color: #fff;
1994
+ box-shadow: var(--lab-shadow-lg);
1995
+ }
1996
+
1997
+ .lab-vp-wrapper iframe {
1998
+ display: block;
1999
+ width: 100%;
2000
+ height: 100%;
2001
+ border: 0;
2002
+ background-color: #fff;
2003
+ }
2004
+
2005
+ .lab-vp[data-dragging] .lab-vp-stage {
2006
+ user-select: none;
2007
+ }
2008
+
2009
+ .lab-vp-handle {
2010
+ position: absolute;
2011
+ touch-action: none;
2012
+ }
2013
+
2014
+ .lab-vp-handle::after {
2015
+ content: "";
2016
+ position: absolute;
2017
+ border-radius: var(--lab-radius-full);
2018
+ background-color: var(--lab-border-primary);
2019
+ transition: background-color var(--lab-ease);
2020
+ }
2021
+
2022
+ .lab-vp-handle:hover::after,
2023
+ .lab-vp[data-dragging="x"] .lab-vp-handle--e::after,
2024
+ .lab-vp[data-dragging="y"] .lab-vp-handle--s::after,
2025
+ .lab-vp[data-dragging="xy"] .lab-vp-handle--se::after {
2026
+ background-color: var(--lab-fg-brand);
2027
+ }
2028
+
2029
+ .lab-vp-handle--e {
2030
+ top: 16px;
2031
+ right: 0;
2032
+ bottom: 16px;
2033
+ width: 16px;
2034
+ cursor: ew-resize;
2035
+ }
2036
+
2037
+ .lab-vp-handle--e::after {
2038
+ top: 50%;
2039
+ left: 5px;
2040
+ width: 5px;
2041
+ height: 40px;
2042
+ transform: translateY(-50%);
2043
+ }
2044
+
2045
+ .lab-vp-handle--s {
2046
+ left: 16px;
2047
+ right: 16px;
2048
+ bottom: 0;
2049
+ height: 16px;
2050
+ cursor: ns-resize;
2051
+ }
2052
+
2053
+ .lab-vp-handle--s::after {
2054
+ left: 50%;
2055
+ top: 5px;
2056
+ width: 40px;
2057
+ height: 5px;
2058
+ transform: translateX(-50%);
2059
+ }
2060
+
2061
+ .lab-vp-handle--se {
2062
+ right: 0;
2063
+ bottom: 0;
2064
+ width: 20px;
2065
+ height: 20px;
2066
+ cursor: nwse-resize;
2067
+ }
2068
+
2069
+ .lab-vp-handle--se::after {
2070
+ top: 4px;
2071
+ left: 4px;
2072
+ width: 9px;
2073
+ height: 9px;
2074
+ }
2075
+
2076
+ /* The parameters panel: a drawer on the stage's right edge, present only when the story
2077
+ registered parameters through @orbytes/astrolab/params. */
2078
+ .lab-params {
2079
+ position: absolute;
2080
+ top: 0;
2081
+ right: 0;
2082
+ bottom: 0;
2083
+ z-index: 2;
2084
+ display: flex;
2085
+ flex-direction: column;
2086
+ gap: var(--lab-space-lg);
2087
+ width: 340px;
2088
+ padding: var(--lab-space-xl);
2089
+ overflow: auto;
2090
+ background-color: var(--lab-bg-primary);
2091
+ border-left: 1px solid var(--lab-border-secondary);
2092
+ box-shadow: var(--lab-shadow-lg);
2093
+ font-size: 13px;
2094
+ line-height: 18px;
2095
+ }
2096
+
2097
+ .lab-params[hidden] {
2098
+ display: none;
2099
+ }
2100
+
2101
+ .lab-params .vpp__group + .vpp__group {
2102
+ padding-top: var(--lab-space-lg);
2103
+ border-top: 1px solid var(--lab-border-secondary);
2104
+ }
2105
+
2106
+ .lab-params .vpp__head {
2107
+ display: flex;
2108
+ flex-wrap: wrap;
2109
+ align-items: baseline;
2110
+ gap: var(--lab-space-xs) var(--lab-space-lg);
2111
+ }
2112
+
2113
+ .lab-params .vpp__toggle {
2114
+ display: inline-flex;
2115
+ align-items: baseline;
2116
+ gap: var(--lab-space-sm);
2117
+ padding: 0;
2118
+ border: 0;
2119
+ background: transparent;
2120
+ color: var(--lab-text-primary);
2121
+ font: inherit;
2122
+ font-weight: 600;
2123
+ cursor: pointer;
2124
+ }
2125
+
2126
+ .lab-params .vpp__chev {
2127
+ display: inline-block;
2128
+ color: var(--lab-fg-quaternary);
2129
+ transition: transform var(--lab-ease);
2130
+ }
2131
+
2132
+ .lab-params .vpp__group[data-collapsed] .vpp__chev {
2133
+ transform: rotate(-90deg);
2134
+ }
2135
+
2136
+ .lab-params .vpp__group[data-collapsed] .vpp__body {
2137
+ display: none;
2138
+ }
2139
+
2140
+ .lab-params .vpp__source {
2141
+ color: var(--lab-text-quaternary);
2142
+ }
2143
+
2144
+ .lab-params .vpp__note {
2145
+ flex-basis: 100%;
2146
+ margin: 0;
2147
+ color: var(--lab-text-quaternary);
2148
+ }
2149
+
2150
+ .lab-params .vpp__body {
2151
+ display: flex;
2152
+ flex-direction: column;
2153
+ gap: var(--lab-space-lg);
2154
+ margin-top: var(--lab-space-sm);
2155
+ }
2156
+
2157
+ .lab-params .vpp__controls {
2158
+ display: flex;
2159
+ flex-direction: column;
2160
+ gap: var(--lab-space-lg);
2161
+ }
2162
+
2163
+ .lab-params .vpp__control {
2164
+ display: grid;
2165
+ grid-template-columns: 1fr auto;
2166
+ gap: var(--lab-space-xs) var(--lab-space-md);
2167
+ cursor: pointer;
2168
+ }
2169
+
2170
+ .lab-params .vpp__control input[type="range"] {
2171
+ grid-column: 1 / -1;
2172
+ width: 100%;
2173
+ margin: 0;
2174
+ accent-color: var(--lab-brand-solid);
2175
+ }
2176
+
2177
+ .lab-params .vpp__control--toggle {
2178
+ display: flex;
2179
+ align-items: center;
2180
+ gap: var(--lab-space-md);
2181
+ color: var(--lab-text-primary);
2182
+ }
2183
+
2184
+ .lab-params .vpp__control--toggle input {
2185
+ accent-color: var(--lab-brand-solid);
2186
+ }
2187
+
2188
+ .lab-params .vpp__select,
2189
+ .lab-params .vpp__swatch {
2190
+ grid-column: 1 / -1;
2191
+ width: 100%;
2192
+ border: 1px solid var(--lab-border-primary);
2193
+ border-radius: var(--lab-radius-sm);
2194
+ background-color: var(--lab-bg-primary);
2195
+ color: var(--lab-text-primary);
2196
+ font: inherit;
2197
+ }
2198
+
2199
+ .lab-params .vpp__select {
2200
+ padding: 4px 6px;
2201
+ }
2202
+
2203
+ .lab-params .vpp__swatch {
2204
+ height: 28px;
2205
+ padding: 0;
2206
+ cursor: pointer;
2207
+ }
2208
+
2209
+ .lab-params .vpp__label {
2210
+ color: var(--lab-text-tertiary);
2211
+ }
2212
+
2213
+ .lab-params .vpp__value {
2214
+ color: var(--lab-text-primary);
2215
+ font-variant-numeric: tabular-nums;
2216
+ font-weight: 600;
2217
+ }
2218
+
2219
+ .lab-params .vpp__actions {
2220
+ display: flex;
2221
+ gap: var(--lab-space-sm);
2222
+ }
2223
+
2224
+ .lab-params .vpp__button {
2225
+ padding: 4px 10px;
2226
+ border: 1px solid var(--lab-border-primary);
2227
+ border-radius: var(--lab-radius-sm);
2228
+ background-color: var(--lab-bg-primary);
2229
+ color: var(--lab-text-secondary);
2230
+ font: inherit;
2231
+ font-weight: 600;
2232
+ cursor: pointer;
2233
+ }
2234
+
2235
+ .lab-params .vpp__status {
2236
+ margin: 0;
2237
+ color: var(--lab-text-quaternary);
2238
+ white-space: pre-wrap;
2239
+ }
2240
+
2241
+ /* ---- the asset grid ------------------------------------------------------------------------ */
2242
+ .lab-asset {
2243
+ scroll-margin-top: var(--lab-space-3xl);
2244
+ }
2245
+
2246
+ .lab-asset:target {
2247
+ border-color: var(--lab-fg-brand);
2248
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--lab-fg-brand) 25%, transparent);
2249
+ }
2250
+
2251
+ .lab-asset .lab-card__thumb {
2252
+ aspect-ratio: 4 / 3;
2253
+ }
2254
+
2255
+ .lab-kv {
2256
+ display: grid;
2257
+ grid-template-columns: auto 1fr;
2258
+ gap: 2px var(--lab-space-md);
2259
+ margin: 0;
2260
+ color: var(--lab-text-quaternary);
2261
+ font-size: 12px;
2262
+ line-height: 18px;
2263
+ }
2264
+
2265
+ .lab-kv dt {
2266
+ font-weight: 500;
2267
+ }
2268
+
2269
+ .lab-kv dd {
2270
+ margin: 0;
2271
+ min-width: 0;
2272
+ color: var(--lab-text-secondary);
2273
+ overflow: hidden;
2274
+ text-overflow: ellipsis;
2275
+ white-space: nowrap;
2276
+ }
2277
+
2278
+ /* ---- placeholder pages (Support, Settings) ------------------------------------------------- */
2279
+ .lab-placeholder {
2280
+ display: grid;
2281
+ place-items: center;
2282
+ min-height: 100%;
2283
+ padding: 48px;
2284
+ }
2285
+
2286
+ .lab-placeholder__box {
2287
+ max-width: 440px;
2288
+ text-align: center;
2289
+ color: var(--lab-text-tertiary);
2290
+ }
2291
+
2292
+ .lab-placeholder__icon {
2293
+ display: inline-grid;
2294
+ place-items: center;
2295
+ width: 48px;
2296
+ height: 48px;
2297
+ margin-bottom: var(--lab-space-xl);
2298
+ border: 1px solid var(--lab-border-secondary);
2299
+ border-radius: var(--lab-radius-lg);
2300
+ background-color: var(--lab-bg-primary);
2301
+ box-shadow: var(--lab-shadow-xs);
2302
+ color: var(--lab-text-quaternary);
2303
+ }
2304
+
2305
+ .lab-placeholder h1 {
2306
+ margin: 0 0 var(--lab-space-md);
2307
+ color: var(--lab-text-primary);
2308
+ font-size: 18px;
2309
+ line-height: 28px;
2310
+ font-weight: 600;
2311
+ }
2312
+
2313
+ .lab-placeholder p {
2314
+ margin: 0;
2315
+ }
2316
+
2317
+ /* ---- small screens ------------------------------------------------------------------------- */
2318
+ @media (max-width: 1023px) {
2319
+ .lab-page {
2320
+ padding: var(--lab-space-2xl);
2321
+ }
2322
+ }
2323
+
2324
+ @media (prefers-reduced-motion: reduce) {
2325
+ .lab-shell *,
2326
+ .lab-shell *::before,
2327
+ .lab-shell *::after {
2328
+ transition-duration: 0ms !important;
2329
+ animation-duration: 0ms !important;
2330
+ }
549
2331
  }