返回 CodeWhale
primitives.css
根目录 / web / app / styles / primitives.css
1 /* ------------------------------------------------------------------ */
2 /* Primitives — the components every page composes */
3 /* ------------------------------------------------------------------ */
4 /*
5 * Class names are the API. Pages never define a colour, radius, shadow or
6 * duration; they compose these. Recipes: buttons, page header, sections,
7 * group cards, definition rows, directory rows, status (a mark and a
8 * word), segmented choice, tabs, code with copy, plate, figure, callout,
9 * disclosure rows, empty / loading / error states, data tables, fields,
10 * prose, steps, the whale's poses, and the horizon.
11 */
12
13 /* ---------- icons ---------- */
14 /* components/icon.tsx: 24-unit, 1.7 round stroke, currentColor. */
15 .icon { display: inline-block; width: 1rem; height: 1rem; flex: none; vertical-align: -0.125em; }
16
17 /* ---------- buttons ---------- */
18 /* One primary per view: the logo gradient with white text. Everything else
19 is secondary (raised), outline, ghost or danger (never filled). */
20 .btn,
21 .portal-button {
22 display: inline-flex;
23 align-items: center;
24 justify-content: center;
25 gap: 0.375rem;
26 min-height: 2.25rem;
27 padding-inline: 0.875rem;
28 font-family: var(--font-body);
29 font-size: var(--text-ui);
30 font-weight: 500;
31 line-height: 1.25;
32 color: var(--text);
33 background: transparent;
34 border: 1px solid transparent;
35 border-radius: var(--radius-control);
36 white-space: nowrap;
37 text-decoration: none;
38 cursor: pointer;
39 transition-property: background-color, border-color, color, box-shadow, opacity;
40 transition-duration: var(--dur-state);
41 transition-timing-function: var(--ease-out);
42 }
43 .btn svg,
44 .portal-button svg { width: 1rem; height: 1rem; flex: none; }
45 .btn:active,
46 .portal-button:active { opacity: 0.8; }
47 .btn:disabled,
48 .btn[aria-disabled="true"],
49 .portal-button:disabled { opacity: 0.5; cursor: not-allowed; }
50 .btn[aria-busy="true"],
51 .portal-button[aria-busy="true"] { cursor: progress; }
52
53 .btn-primary,
54 .portal-button-primary {
55 background: var(--brand-fill);
56 color: var(--on-brand);
57 font-weight: 600;
58 }
59 .btn-primary:hover,
60 .portal-button-primary:hover {
61 background: var(--brand-fill-hover);
62 color: var(--on-brand);
63 box-shadow: var(--glow-focus);
64 }
65
66 .btn-secondary,
67 .portal-button-secondary {
68 background: var(--panel);
69 border-color: var(--line);
70 color: var(--text);
71 }
72 .btn-secondary:hover,
73 .portal-button-secondary:hover { background: var(--hover); }
74
75 .btn-ghost { color: var(--text); }
76 .btn-ghost:hover { background: var(--hover); }
77
78 .btn-danger { border-color: var(--line-strong); color: var(--danger); }
79 .btn-danger:hover { background: var(--hover); }
80
81 .btn-sm { min-height: 1.75rem; padding-inline: 0.5rem; font-size: 0.8125rem; }
82 .btn-lg { min-height: 2.75rem; padding-inline: 1.125rem; font-size: 0.9375rem; }
83
84 @media (pointer: coarse) {
85 .btn,
86 .portal-button { min-height: 2.75rem; }
87 }
88
89 .actions,
90 .portal-actions {
91 display: flex;
92 flex-wrap: wrap;
93 align-items: center;
94 gap: var(--space-3);
95 }
96
97 /* ---------- the whale's poses ---------- */
98 /* One character everywhere: the v2 whale's still poses (public/whale). A
99 pose carries meaning — rest, listen, think, read, search, write, run,
100 browse, talk, computer, connect, needs you, done, hmm, sleep, busy, and
101 the pod for parallel agents. Never decorative filler. */
102 .whale-pose {
103 display: block;
104 flex: none;
105 width: var(--pose-size, 4rem);
106 height: var(--pose-size, 4rem);
107 user-select: none;
108 }
109
110 /* ---------- page header ---------- */
111 /* What the reader knows (the title), why they are here (one lede), what to
112 do next (the actions). The whale sits beside it, doing the page's job. */
113 .page-head {
114 width: var(--container);
115 margin-inline: auto;
116 padding-block: clamp(2.5rem, 6vw, 4.5rem) clamp(1.5rem, 3vw, 2.5rem);
117 }
118
119 .page-head-inner {
120 display: grid;
121 grid-template-columns: minmax(0, 1fr) auto;
122 align-items: end;
123 gap: var(--space-5) clamp(1.5rem, 5vw, 4rem);
124 }
125
126 .page-head-text { display: grid; gap: var(--space-4); min-width: 0; }
127
128 .page-kicker {
129 display: inline-flex;
130 align-items: center;
131 gap: var(--space-2);
132 color: var(--muted);
133 font-size: var(--text-ui);
134 font-weight: 500;
135 }
136 .page-kicker svg { width: 1rem; height: 1rem; color: var(--accent); }
137
138 .page-title { max-width: 22ch; font-size: var(--text-display); line-height: 1.08; }
139
140 .page-lede {
141 max-width: 44rem;
142 color: var(--muted);
143 font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
144 line-height: 1.6;
145 }
146
147 .page-meta { color: var(--muted); font-size: var(--text-ui); }
148
149 .page-head .actions { margin-top: var(--space-2); }
150
151 .page-head-pose { --pose-size: clamp(4.5rem, 10vw, 7.5rem); align-self: center; }
152
153 @media (max-width: 640px) {
154 .page-head-inner { grid-template-columns: minmax(0, 1fr); }
155 .page-head-pose { --pose-size: 3.5rem; grid-row: 1; align-self: start; }
156 }
157
158 /* ---------- page body and sections ---------- */
159 .page-body {
160 width: var(--container);
161 margin-inline: auto;
162 padding-bottom: var(--section-gap);
163 }
164 .page-body-narrow { max-width: var(--measure-prose); }
165
166 .page-section { padding-top: var(--section-gap); }
167 .page-section:first-child { padding-top: var(--space-5); }
168
169 .section-head {
170 display: flex;
171 flex-wrap: wrap;
172 align-items: end;
173 justify-content: space-between;
174 gap: var(--space-2) var(--space-5);
175 margin-bottom: var(--space-5);
176 }
177 .section-head-text { display: grid; gap: var(--space-3); min-width: 0; max-width: 46rem; }
178 /* The editorial scale: a section title reads as a heading from across the
179 room (28-40px), not as a label. */
180 .section-title {
181 max-width: 22ch;
182 font-size: var(--text-section);
183 font-weight: 600;
184 line-height: 1.12;
185 letter-spacing: -0.025em;
186 text-wrap: balance;
187 }
188 .section-scope { max-width: 40rem; color: var(--muted); font-size: var(--text-body); line-height: 1.6; }
189
190 /* A running head above a section title: "01 / Your terminal", or a seal and
191 the subject. */
192 .section-label {
193 display: inline-flex;
194 align-items: center;
195 gap: var(--space-3);
196 color: var(--accent);
197 font-size: var(--text-ui);
198 font-weight: 500;
199 font-variant-numeric: tabular-nums;
200 }
201
202 /* The two-column section form: the title, scope and link on the left; the
203 content (usually a ruled list) on the right. */
204 .section-split {
205 display: grid;
206 gap: var(--space-5) clamp(2rem, 6vw, 5rem);
207 grid-template-columns: minmax(0, 1fr);
208 }
209 .section-split-head { min-width: 0; }
210 .section-split-body { min-width: 0; }
211 @media (min-width: 900px) {
212 .section-split { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); align-items: start; }
213 .section-split-head { position: sticky; top: calc(var(--nav-height, 4rem) + var(--space-5)); }
214 }
215 .section-split .section-link { justify-self: start; margin-top: var(--space-2); }
216
217 .section-link {
218 display: inline-flex;
219 align-items: center;
220 gap: var(--space-1);
221 color: var(--accent);
222 font-size: var(--text-ui);
223 font-weight: 500;
224 border-radius: var(--radius-control);
225 }
226 .section-link:hover { text-decoration: underline; text-underline-offset: 0.2em; }
227 .section-link svg { width: 1rem; height: 1rem; }
228
229 /* Two- and three-up grids for group cards and tiles. */
230 .grid-2,
231 .grid-3 {
232 display: grid;
233 gap: var(--space-4);
234 grid-template-columns: minmax(0, 1fr);
235 }
236 @media (min-width: 720px) {
237 .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
238 .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
239 }
240 @media (min-width: 1024px) {
241 .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
242 }
243
244 /* A split: a short explanation beside the list it explains. */
245 .split {
246 display: grid;
247 gap: var(--space-5) clamp(2rem, 6vw, 5rem);
248 grid-template-columns: minmax(0, 1fr);
249 }
250 @media (min-width: 900px) {
251 .split { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); }
252 }
253
254 /* ---------- group cards and tiles ---------- */
255 /* A raised plate with a hairline edge. A card never sits inside a card. */
256 .group-card {
257 border: 1px solid var(--line);
258 border-radius: var(--radius-surface);
259 background: color-mix(in srgb, var(--panel) 72%, transparent);
260 overflow: hidden;
261 }
262 .group-row { padding: var(--space-3) var(--space-4); }
263 .group-row + .group-row { border-top: 1px solid var(--line); }
264
265 .tile {
266 display: grid;
267 align-content: start;
268 gap: var(--space-2);
269 padding: var(--space-5);
270 border: 1px solid var(--line);
271 border-radius: var(--radius-surface);
272 background: color-mix(in srgb, var(--panel) 72%, transparent);
273 }
274 .tile-icon {
275 display: inline-grid;
276 place-items: center;
277 width: 2.25rem;
278 height: 2.25rem;
279 margin-bottom: var(--space-2);
280 color: var(--accent);
281 background: color-mix(in srgb, var(--accent) 12%, transparent);
282 border-radius: var(--radius-control);
283 }
284 .tile-icon svg { width: 1.25rem; height: 1.25rem; }
285 .tile h3 { font-size: var(--text-heading); }
286 .tile p { color: var(--muted); font-size: var(--text-ui); line-height: 1.6; }
287 .tile .section-link { margin-top: var(--space-2); }
288 a.tile { transition: background-color var(--dur-state) var(--ease-out), border-color var(--dur-state) var(--ease-out); }
289 a.tile:hover { background: var(--hover); border-color: var(--line-strong); }
290
291 /* ---------- ruled lists ---------- */
292 /* The editorial list: a hairline between rows, a mono key on the left and
293 the explanation beside it. No box; the rules carry the structure. Used
294 wherever a list does not need icons. */
295 .ruled-list { border-top: 1px solid var(--line-strong); }
296 .ruled-list > div {
297 display: grid;
298 gap: var(--space-1) var(--space-5);
299 padding-block: var(--space-4);
300 border-bottom: 1px solid var(--line);
301 }
302 .ruled-list dt {
303 color: var(--text);
304 font-family: var(--font-mono);
305 font-size: var(--text-mono);
306 line-height: 1.5;
307 overflow-wrap: anywhere;
308 }
309 .ruled-list dd { min-width: 0; color: var(--muted); font-size: var(--text-ui); line-height: 1.6; }
310 .ruled-list dd strong { color: var(--text); font-weight: 500; }
311 .ruled-list dd > .status:first-child { display: flex; width: max-content; margin-bottom: var(--space-1); }
312 .ruled-list dd code { color: var(--text); font-family: var(--font-mono); font-size: var(--text-mono); overflow-wrap: anywhere; }
313 @media (min-width: 640px) {
314 .ruled-list > div { grid-template-columns: minmax(8rem, 13rem) minmax(0, 1fr); }
315 }
316
317 /* Stacked: a heading-like key above its explanation, for statements rather
318 than facts. */
319 .ruled-list.ruled-list-stacked > div { grid-template-columns: minmax(0, 1fr); }
320 .ruled-list-stacked dt { font-family: var(--font-body); font-size: var(--text-body); font-weight: 600; }
321
322 /* Ruled columns: three short statements divided by hairlines. */
323 .ruled-cols {
324 display: grid;
325 grid-template-columns: minmax(0, 1fr);
326 border-top: 1px solid var(--line-strong);
327 }
328 .ruled-cols > * {
329 display: grid;
330 align-content: start;
331 gap: var(--space-2);
332 padding: var(--space-5) 0;
333 border-bottom: 1px solid var(--line);
334 }
335 .ruled-cols h3 { font-size: var(--text-heading); }
336 .ruled-cols p { color: var(--muted); font-size: var(--text-ui); line-height: 1.6; }
337 .ruled-cols .ruled-icon { color: var(--accent); }
338 .ruled-cols .ruled-icon svg { width: 1.25rem; height: 1.25rem; }
339 @media (min-width: 900px) {
340 .ruled-cols { grid-template-columns: repeat(3, minmax(0, 1fr)); border-bottom: 1px solid var(--line); }
341 .ruled-cols > * { padding: var(--space-5) var(--space-5) var(--space-6); border-bottom: 0; }
342 .ruled-cols > :first-child { padding-inline-start: 0; }
343 .ruled-cols > * + * { border-inline-start: 1px solid var(--line); }
344 }
345
346 /* ---------- seals ---------- */
347 /* One Han glyph stamped in the logo's ombre (components/seal.tsx). */
348 .seal {
349 display: inline-grid;
350 place-items: center;
351 flex: none;
352 width: 2.5rem;
353 height: 2.5rem;
354 color: var(--on-brand);
355 background: var(--brand-fill);
356 border-radius: var(--radius-control);
357 box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.18);
358 font-family: var(--font-cjk);
359 font-size: 1.25rem;
360 font-weight: 700;
361 line-height: 1;
362 letter-spacing: 0;
363 }
364 .seal-sm { width: 2rem; height: 2rem; font-size: 1rem; }
365 .seal-lg { width: 3rem; height: 3rem; font-size: 1.5rem; }
366 @media (forced-colors: active) {
367 .seal { border: 1px solid CanvasText; }
368 }
369
370 /* The bilingual Han title: the page's name in the other script, set beside
371 it in the ombre ink. */
372 .page-title-aside {
373 font-family: var(--font-cjk);
374 font-size: 0.72em;
375 font-weight: 600;
376 letter-spacing: 0;
377 color: var(--accent);
378 white-space: nowrap;
379 }
380 @supports ((-webkit-background-clip: text) or (background-clip: text)) {
381 .page-title-aside {
382 background: var(--ombre-ink);
383 -webkit-background-clip: text;
384 background-clip: text;
385 -webkit-text-fill-color: transparent;
386 }
387 }
388 @media (forced-colors: active) {
389 .page-title-aside { background: none; -webkit-text-fill-color: currentColor; }
390 }
391
392 /* ---------- definition rows ---------- */
393 .def-rows {
394 border: 1px solid var(--line);
395 border-radius: var(--radius-surface);
396 background: color-mix(in srgb, var(--panel) 72%, transparent);
397 overflow: hidden;
398 }
399 .def-row {
400 display: grid;
401 gap: var(--space-1) var(--space-4);
402 padding: var(--space-3) var(--space-4);
403 }
404 .def-row + .def-row { border-top: 1px solid var(--line); }
405 .def-row dt { color: var(--text); font-size: var(--text-ui); font-weight: 500; }
406 .def-row dd { color: var(--muted); font-size: var(--text-ui); line-height: 1.6; min-width: 0; }
407 .def-row dd strong { color: var(--text); font-weight: 500; }
408 .def-row dd > .status:first-child { display: flex; width: max-content; margin-bottom: var(--space-1); }
409 @media (min-width: 640px) {
410 .def-row { grid-template-columns: minmax(8rem, 15rem) minmax(0, 1fr); }
411 }
412
413 /* ---------- directory rows ---------- */
414 /* A link that behaves like a row: a mark, a title and purpose, a status and
415 an action. Docs topics, integrations, channels, feeds, roadmap items. */
416 .dir-list { display: grid; gap: 2px; }
417 .dir-list-card {
418 gap: 0;
419 border: 1px solid var(--line);
420 border-radius: var(--radius-surface);
421 background: color-mix(in srgb, var(--panel) 72%, transparent);
422 overflow: hidden;
423 }
424 .dir-list-card > li + li { border-top: 1px solid var(--line); }
425
426 .dir-row {
427 display: flex;
428 align-items: center;
429 gap: var(--space-3);
430 padding: var(--space-3) var(--space-4);
431 color: var(--text);
432 text-decoration: none;
433 border-radius: var(--radius-control);
434 transition: background-color var(--dur-state) var(--ease-out);
435 }
436 .dir-list-card .dir-row { border-radius: 0; }
437 a.dir-row:hover { background: var(--hover); }
438 .dir-row[aria-current="page"] { background: var(--selected); }
439
440 .dir-mark {
441 display: inline-grid;
442 place-items: center;
443 flex: none;
444 width: 2.25rem;
445 height: 2.25rem;
446 color: var(--accent);
447 background: color-mix(in srgb, var(--accent) 12%, transparent);
448 border-radius: var(--radius-control);
449 }
450 .dir-mark svg { width: 1.125rem; height: 1.125rem; }
451
452 .dir-text { display: grid; gap: 0.125rem; flex: 1; min-width: 0; }
453 .dir-title { font-size: var(--text-ui); font-weight: 500; }
454 .dir-purpose { color: var(--muted); font-size: var(--text-ui); line-height: 1.5; }
455 .dir-meta { color: var(--muted); font-family: var(--font-mono); font-size: var(--text-caption); overflow-wrap: anywhere; }
456 .dir-action { flex: none; color: var(--muted); }
457 .dir-action svg { width: 1rem; height: 1rem; }
458 a.dir-row:hover .dir-action { color: var(--text); }
459
460 /* ---------- status: a mark and a word ---------- */
461 .status {
462 display: inline-flex;
463 align-items: center;
464 gap: 0.4rem;
465 color: var(--text);
466 font-size: var(--text-caption);
467 font-weight: 500;
468 white-space: nowrap;
469 }
470 .status-dot {
471 width: 0.5rem;
472 height: 0.5rem;
473 flex: none;
474 border-radius: var(--radius-pill);
475 background: currentColor;
476 color: var(--muted);
477 }
478 .status-ready .status-dot,
479 .status-live .status-dot { color: var(--live); }
480 .status-live .status-dot { box-shadow: var(--glow); }
481 .status-attention .status-dot { color: var(--attention); box-shadow: var(--glow); }
482 .status-accent .status-dot { color: var(--accent); }
483 .status-idle { color: var(--muted); }
484 .status-danger,
485 .status-danger .status-dot { color: var(--danger); }
486
487 /* A one-line health or release statement. */
488 .status-line {
489 display: flex;
490 flex-wrap: wrap;
491 align-items: center;
492 gap: var(--space-2) var(--space-4);
493 color: var(--muted);
494 font-size: var(--text-ui);
495 }
496
497 /* The legacy <StatusBadge> markup, as a mark and a word. */
498 .status-badge {
499 display: inline-flex;
500 align-items: center;
501 gap: 0.4rem;
502 color: var(--text);
503 font-size: var(--text-caption);
504 font-weight: 500;
505 }
506 .status-badge-dot { width: 0.5rem; height: 0.5rem; border-radius: var(--radius-pill); background: var(--muted); }
507 .status-badge-experimental .status-badge-dot,
508 .status-badge-preview .status-badge-dot { background: var(--attention); }
509 .status-badge-pending .status-badge-dot,
510 .status-badge-unavailable .status-badge-dot { background: var(--muted); }
511
512 /* ---------- segmented choice ---------- */
513 .segmented {
514 display: inline-flex;
515 gap: 2px;
516 padding: 2px;
517 background: var(--surface);
518 border: 1px solid var(--line);
519 border-radius: var(--radius-control);
520 }
521 .segmented button {
522 min-height: 1.75rem;
523 padding-inline: 0.625rem;
524 color: var(--muted);
525 font-size: 0.8125rem;
526 font-weight: 500;
527 border-radius: var(--radius-control);
528 cursor: pointer;
529 transition: background-color var(--dur-state) var(--ease-out), color var(--dur-state) var(--ease-out);
530 }
531 .segmented button:hover { background: var(--hover); color: var(--text); }
532 .segmented button[aria-pressed="true"] {
533 background: var(--selected);
534 color: var(--text);
535 /* A non-colour mark: the fill alone is under 3:1 against the track. */
536 box-shadow: inset 0 0 0 1px var(--line-strong);
537 }
538 @media (forced-colors: active) {
539 .segmented button[aria-pressed="true"] { outline-style: solid; outline-width: 2px; outline-color: Highlight; outline-offset: -2px; }
540 }
541 @media (pointer: coarse) {
542 .segmented button { min-height: 2.5rem; }
543 }
544
545 /* ---------- nav tabs ---------- */
546 .tabs {
547 display: flex;
548 flex-wrap: wrap;
549 gap: var(--space-1);
550 border-bottom: 1px solid var(--line);
551 }
552 .tab {
553 position: relative;
554 padding: var(--space-2) var(--space-3);
555 color: var(--muted);
556 font-size: var(--text-ui);
557 font-weight: 500;
558 border-radius: var(--radius-control) var(--radius-control) 0 0;
559 transition: background-color var(--dur-state) var(--ease-out), color var(--dur-state) var(--ease-out);
560 }
561 .tab:hover { background: var(--hover); color: var(--text); }
562 .tab[aria-current="page"] { color: var(--text); }
563 .tab[aria-current="page"]::after {
564 content: "";
565 position: absolute;
566 inset-inline: var(--space-3);
567 bottom: -1px;
568 height: 2px;
569 background: var(--accent);
570 }
571
572 /* ---------- code with copy ---------- */
573 .code { position: relative; min-width: 0; }
574 .code pre.code-block { padding-right: 6.5rem; }
575 /* A long command scrolls under the button, so the button sits flush with the
576 block's edge and a short fade on its leading side hides the text beneath
577 it instead of letting a sliver show on either side. Commands are always
578 left to right (see base.css), so the button is on the physical right. */
579 .code .code-copy {
580 position: absolute;
581 background: var(--surface);
582 top: 50%;
583 right: 0.25rem;
584 transform: translateY(-50%);
585 color: var(--muted);
586 box-shadow: -0.75rem 0 0.5rem -0.25rem var(--surface);
587 }
588 .code .code-copy:focus-visible { box-shadow: var(--glow-focus); }
589 .code .code-copy:hover { background: var(--hover); color: var(--text); }
590 .code-copy[data-copied="true"] { color: var(--live); }
591
592 /* ---------- plate ---------- */
593 /* The install plate: a raised sheet that holds a choice, a command and its
594 copy button. */
595 .plate {
596 display: grid;
597 gap: var(--space-3);
598 padding: var(--space-3);
599 background: color-mix(in srgb, var(--panel) 88%, transparent);
600 border: 1px solid var(--line);
601 border-radius: var(--radius-sheet);
602 transition: border-color var(--dur-state) var(--ease-out);
603 }
604 .plate:focus-within { border-color: var(--line-strong); }
605 .plate pre.code-block { background: var(--surface); }
606
607 /* ---------- a live terminal capture ---------- */
608 /* components/terminal-capture.tsx: real PTY cells as text. 100 columns stay
609 100 columns; a narrow screen scrolls the frame, never shrinks it past
610 legibility. */
611 /* The capture scales to its frame: the whole terminal is always visible,
612 sized from the frame's width and the grid's column count (--cols, set by
613 components/terminal-capture.tsx). 0.61em is a monospace advance with a
614 little room for the fallback fonts. #0a1e33 and #f6f2e8 are the captured
615 terminal's own defaults, not site colours. */
616 .term-capture-scroll {
617 container-type: inline-size;
618 overflow: hidden;
619 background: #0a1e33;
620 }
621 .term-capture {
622 display: block;
623 width: 100%;
624 margin: 0;
625 padding: 1.6em 1.8em;
626 color: #f6f2e8;
627 font-family: var(--font-mono);
628 font-size: calc(100cqi / (var(--cols, 100) * 0.61 + 3.6));
629 line-height: 1.25;
630 white-space: pre;
631 font-variant-ligatures: none;
632 tab-size: 8;
633 }
634 /* A row is one terminal line; each run fills the whole line box, so row
635 backgrounds meet without seams. */
636 .term-capture-row { display: block; height: 1.25em; }
637 .term-capture-row > span { display: inline-block; height: 100%; vertical-align: top; }
638 .term-capture-cell { display: inline-block; width: 1ch; overflow: hidden; vertical-align: top; }
639
640 /* ---------- figure ---------- */
641 .figure { display: grid; gap: var(--space-3); min-width: 0; }
642 .figure-frame {
643 background: var(--surface);
644 border: 1px solid var(--line);
645 border-radius: var(--radius-surface);
646 overflow: hidden;
647 }
648 .figure-frame img { display: block; width: 100%; height: auto; }
649 .figure-caption { color: var(--muted); font-size: var(--text-caption); line-height: 1.5; }
650
651 /* ---------- callout ---------- */
652 .callout {
653 display: grid;
654 gap: var(--space-2);
655 padding: var(--space-4) var(--space-5);
656 background: color-mix(in srgb, var(--panel) 72%, transparent);
657 border: 1px solid var(--line);
658 border-radius: var(--radius-surface);
659 }
660 .callout-title { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-weight: 500; }
661 .callout p:not(.callout-title) { color: var(--muted); font-size: var(--text-ui); line-height: 1.6; }
662
663 /* ---------- disclosure rows (FAQ) ---------- */
664 .disclosure-row > summary {
665 display: flex;
666 align-items: center;
667 gap: var(--space-3);
668 min-height: 3rem;
669 padding: var(--space-3) var(--space-4);
670 color: var(--text);
671 font-size: var(--text-body);
672 font-weight: 500;
673 list-style: none;
674 cursor: pointer;
675 transition: background-color var(--dur-state) var(--ease-out);
676 }
677 .disclosure-row > summary::-webkit-details-marker { display: none; }
678 .disclosure-row > summary:hover { background: var(--hover); }
679 .disclosure-row > summary .disclosure-chevron {
680 width: 1rem;
681 height: 1rem;
682 margin-inline-start: auto;
683 flex: none;
684 color: var(--muted);
685 transition: transform var(--dur-state) var(--ease-out);
686 }
687 .disclosure-row[open] > summary .disclosure-chevron { transform: rotate(180deg); }
688 .disclosure-row + .disclosure-row { border-top: 1px solid var(--line); }
689 .disclosure-body { padding: 0 var(--space-4) var(--space-4); }
690
691 /* ---------- empty / loading / error ---------- */
692 /* A statement, never a placeholder: the whale's pose says what is going on,
693 the title says it in words, one action recovers. */
694 .empty-state,
695 .state-block {
696 display: grid;
697 justify-items: center;
698 gap: var(--space-3);
699 max-width: 36rem;
700 margin-inline: auto;
701 padding-block: clamp(3rem, 8vw, 4.5rem);
702 text-align: center;
703 }
704 .empty-state .whale-pose { --pose-size: 6rem; margin-bottom: var(--space-2); }
705 .empty-state-title,
706 .state-title { font-size: var(--text-title); font-weight: 600; line-height: 1.25; }
707 h1.empty-state-title,
708 h1.state-title { font-size: var(--text-display); }
709 .empty-state-reason,
710 .state-body { max-width: 30rem; color: var(--muted); font-size: var(--text-body); line-height: 1.6; }
711 .empty-state-actions,
712 .state-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-3); margin-top: var(--space-2); }
713
714 .state-block-compact,
715 .empty-state-compact {
716 justify-items: start;
717 max-width: none;
718 margin: 0;
719 padding: var(--space-4) var(--space-5);
720 text-align: start;
721 border: 1px solid var(--line);
722 border-radius: var(--radius-surface);
723 background: color-mix(in srgb, var(--panel) 72%, transparent);
724 }
725 .state-block-compact .state-title,
726 .empty-state-compact .empty-state-title { font-size: var(--text-ui); font-weight: 500; }
727 .state-block-compact .whale-pose,
728 .empty-state-compact .whale-pose { --pose-size: 2.5rem; margin: 0; }
729 .state-block-compact .state-actions,
730 .empty-state-compact .empty-state-actions { justify-content: flex-start; }
731
732 .state-block-error .state-title { color: var(--danger); }
733
734 .state-skeleton,
735 .skeleton { display: grid; gap: var(--space-2); width: min(100%, 24rem); }
736 .state-skeleton span {
737 display: block;
738 height: 0.75rem;
739 background: var(--surface);
740 border-radius: var(--radius-control);
741 }
742
743 /* The working spinner: a sonar sweep. Motion that shows work; reduced motion
744 shows the still ring. */
745 .spinner {
746 display: inline-block;
747 width: 1.25rem;
748 height: 1.25rem;
749 border: 2px solid color-mix(in srgb, var(--accent) 25%, transparent);
750 border-top-color: var(--accent);
751 border-radius: var(--radius-pill);
752 }
753 @media (prefers-reduced-motion: no-preference) {
754 .spinner { animation: spin var(--dur-spin) linear infinite; }
755 }
756 @keyframes spin { to { transform: rotate(360deg); } }
757
758 /* ---------- data table ---------- */
759 .data-table-wrap {
760 overflow-x: auto;
761 border: 1px solid var(--line);
762 border-radius: var(--radius-surface);
763 background: color-mix(in srgb, var(--panel) 72%, transparent);
764 }
765 .data-table { width: 100%; border-collapse: collapse; font-size: var(--text-ui); }
766 .data-table th,
767 .data-table td { padding: var(--space-3) var(--space-4); text-align: start; vertical-align: top; }
768 .data-table thead th {
769 color: var(--muted);
770 font-size: var(--text-caption);
771 font-weight: 500;
772 border-bottom: 1px solid var(--line);
773 white-space: nowrap;
774 }
775 .data-table tbody tr + tr > * { border-top: 1px solid var(--line); }
776 .data-table tbody th { font-weight: 500; }
777 .data-table td { color: var(--muted); }
778 .data-table code { font-family: var(--font-mono); font-size: var(--text-mono); color: var(--text); overflow-wrap: anywhere; }
779 .data-table-sort {
780 display: inline-flex;
781 align-items: center;
782 gap: var(--space-1);
783 margin: -0.25rem -0.375rem;
784 padding: 0.25rem 0.375rem;
785 color: inherit;
786 font: inherit;
787 border-radius: var(--radius-control);
788 cursor: pointer;
789 }
790 .data-table-sort:hover { background: var(--hover); color: var(--text); }
791 .data-table-sort svg { width: 0.875rem; height: 0.875rem; transition: transform var(--dur-state) var(--ease-out); }
792 .data-table-sort[data-dir="asc"] svg { transform: rotate(180deg); }
793 .data-table-sort[data-dir="none"] svg { opacity: 0.45; }
794 th[aria-sort="ascending"] .data-table-sort,
795 th[aria-sort="descending"] .data-table-sort { color: var(--text); }
796
797 /* ---------- fields ---------- */
798 .field { display: grid; gap: var(--space-2); }
799 .field-label { font-size: var(--text-ui); font-weight: 500; }
800 .field-input {
801 width: 100%;
802 min-height: 2.75rem;
803 padding-inline: var(--space-3);
804 font: inherit;
805 font-size: var(--text-body);
806 color: var(--text);
807 background: var(--surface);
808 border: 1px solid var(--line-strong);
809 border-radius: var(--radius-control);
810 transition: border-color var(--dur-state) var(--ease-out);
811 }
812 .field-input:focus-visible { border-color: var(--ring); }
813 .field-hint { color: var(--muted); font-size: var(--text-caption); }
814 .field-hint-error { color: var(--danger); }
815
816 /* ---------- prose ---------- */
817 .prose {
818 max-width: var(--measure-prose);
819 color: var(--text);
820 font-size: var(--text-prose);
821 line-height: var(--leading-prose);
822 }
823 .prose p,
824 .prose li { max-width: var(--measure-text); }
825 .prose > * + * { margin-top: var(--space-4); }
826 .prose h2 { margin-top: var(--space-7); font-size: var(--text-title); line-height: 1.25; }
827 .prose h3 { margin-top: var(--space-6); font-size: var(--text-heading); }
828 .prose h2 + *,
829 .prose h3 + * { margin-top: var(--space-2); }
830 .prose ul,
831 .prose ol { padding-inline-start: 1.25rem; }
832 .prose ul { list-style: disc; }
833 .prose ol { list-style: decimal; }
834 .prose li + li { margin-top: var(--space-2); }
835 .prose ::marker { color: var(--muted); }
836 .prose strong { font-weight: 600; }
837 .prose blockquote { border-inline-start: 2px solid var(--line-strong); padding-inline-start: var(--space-4); color: var(--muted); }
838 .prose hr { border: 0; border-top: 1px solid var(--line); }
839 .prose table { width: 100%; border-collapse: collapse; font-size: var(--text-ui); }
840 .prose th,
841 .prose td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--line); text-align: start; vertical-align: top; }
842 .prose th { color: var(--muted); font-weight: 500; }
843 html[lang="zh"] .prose,
844 html[lang="ja"] .prose,
845 html[lang="ko"] .prose { line-height: 1.8; letter-spacing: 0; }
846
847 /* ---------- steps (the shared getting-started path) ---------- */
848 .gs-steps {
849 display: grid;
850 grid-template-columns: repeat(4, minmax(0, 1fr));
851 gap: 0 var(--space-6);
852 margin: 0;
853 padding: 0;
854 list-style: none;
855 }
856 /* A sequence on paper: a rule above each step and its numeral, no box. */
857 .gs-steps li {
858 display: grid;
859 align-content: start;
860 gap: var(--space-2);
861 min-width: 0;
862 padding-top: var(--space-4);
863 border-top: 1px solid var(--line-strong);
864 }
865 .gs-step-index {
866 color: var(--accent);
867 font-size: var(--text-title);
868 font-weight: 600;
869 line-height: 1;
870 font-variant-numeric: tabular-nums;
871 }
872 .gs-steps .gs-step-title { margin-top: var(--space-2); font-size: var(--text-heading); }
873 .gs-steps p { color: var(--muted); font-size: var(--text-ui); line-height: 1.6; }
874 pre.gs-step-commands { margin-top: var(--space-1); font-size: var(--text-caption); white-space: pre-wrap; overflow-wrap: anywhere; }
875 .gs-step-link {
876 display: inline-flex;
877 align-items: center;
878 gap: var(--space-1);
879 margin-top: var(--space-1);
880 color: var(--accent);
881 font-size: var(--text-ui);
882 font-weight: 500;
883 }
884 .gs-step-link:hover { text-decoration: underline; text-underline-offset: 0.2em; }
885 .docs-content .gs-steps { grid-template-columns: minmax(0, 1fr); }
886 .docs-content .gs-steps { gap: var(--space-5); }
887
888 @media (max-width: 760px) {
889 .gs-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-6); }
890 }
891 @media (max-width: 520px) {
892 .gs-steps { grid-template-columns: 1fr; gap: var(--space-5); }
893 }
894
895 /* Any other numbered list of steps: as many columns as fit, divided by
896 1px of the line colour showing through the gaps. */
897 .steps {
898 display: grid;
899 grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
900 gap: 1px;
901 margin: 0;
902 padding: 0;
903 list-style: none;
904 background: var(--line);
905 border: 1px solid var(--line);
906 border-radius: var(--radius-surface);
907 overflow: hidden;
908 }
909 .steps > li {
910 display: grid;
911 align-content: start;
912 gap: var(--space-2);
913 min-width: 0;
914 padding: var(--space-5);
915 background: var(--panel);
916 }
917 .steps h3 { margin-top: var(--space-2); font-size: var(--text-heading); }
918 .steps p { color: var(--muted); font-size: var(--text-ui); line-height: 1.6; }
919
920 .stack { display: grid; align-content: start; gap: var(--space-4); min-width: 0; }
921
922 /* ---------- session media (docs guide) ---------- */
923 .session-media { border: 1px solid var(--line); border-radius: var(--radius-surface); overflow: hidden; }
924 .session-media video { display: block; width: 100%; height: auto; background: var(--ocean-abyss); }
925 .session-media-stage {
926 display: flex;
927 flex-direction: column;
928 align-items: flex-start;
929 gap: var(--space-3);
930 padding: clamp(1.5rem, 4vw, 2.5rem);
931 border-bottom: 1px solid var(--line);
932 background: var(--surface);
933 }
934 .session-media-pending-note { max-width: 44rem; margin: 0; color: var(--muted); font-size: var(--text-ui); line-height: 1.7; }
935 .session-media-caption { display: grid; gap: var(--space-2); padding: var(--space-4) var(--space-5); color: var(--muted); font-size: var(--text-ui); line-height: 1.6; }
936 .session-media-caption strong { color: var(--text); font-size: var(--text-body); }
937 .session-media-caption a { color: var(--accent); font-size: var(--text-caption); }
938
939 /* ---------- the horizon ---------- */
940 /* One calm, empty horizon per page: a straight line where the sky meets the
941 sea. Nothing stands on it but the whale. The sea below is the stage. */
942 .horizon {
943 position: relative;
944 height: 1px;
945 background: var(--horizon-light);
946 }
947
948 .sea { background: var(--sea), var(--ocean-abyss); }
949
950 @media (forced-colors: active) {
951 .horizon { background: CanvasText; }
952 }
953
954 /* ---------- search field ---------- */
955 .search-field { position: relative; }
956 .search-field .search-input { padding-inline: 2.5rem 3rem; }
957 .search-field-icon {
958 position: absolute;
959 inset-inline-start: 0.875rem;
960 top: 50%;
961 width: 1rem;
962 height: 1rem;
963 color: var(--muted);
964 transform: translateY(-50%);
965 pointer-events: none;
966 }
967 .search-field .search-field-clear,
968 .search-field .search-field-kbd {
969 position: absolute;
970 inset-inline-end: 0.375rem;
971 top: 50%;
972 transform: translateY(-50%);
973 }
974 .search-field .search-field-kbd { inset-inline-end: 0.75rem; }
975
975 lines CSS