Repository navigation
Expand file tree
/
Copy pathpage.css
More file actions
492 lines (468 loc) · 32 KB
/
Copy pathpage.css
File metadata and controls
492 lines (468 loc) · 32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
/* @robertblust/design — page.css, assembled from the shared blocks: reset v3 · link v1 ·
header v18 · title v2 · lines v3 · footer v4 · principles v1 · processes v1 · surfaces v2 ·
index v2 · home v1.
Editing this file in a site does nothing: the next npm run design overwrites it. */
*{box-sizing:border-box; margin:0; padding:0}
body{
font-family:"Instrument Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;
background:var(--ground); color:var(--ink); line-height:1.5; min-height:100vh;
background-image:var(--sky); background-repeat:no-repeat;
-webkit-font-smoothing:antialiased;
display:flex; flex-direction:column;
}
.mono{font-family:"Plex Mono", ui-monospace, "SF Mono", Menlo, monospace}
/* The gutter is proportional until it has enough, and then it stops. `box-sizing` is
border-box here, so the padding comes out of the max-width rather than sitting outside it:
with a bare `7vw` the column got narrower the wider the screen got — 1001px at 1280,
906 at 1960, 822 at 2560 — and never reached 1180 at any width at all. It peaked at
1015px on a 1180px viewport and shrank in both directions from there.
A page that narrows as its window widens is wrong on its own, and it broke the header a
second time on a large screen: measured, the German nav row needs 1000px, and above 2000
the column no longer had it. The cap is 80px because that is where `7vw` lands at 1143px,
so every width below the widest laptop is unchanged to the pixel and only the screens
where this was broken move. */
.shell{width:100%; max-width:1180px; margin:0 auto; padding:0 min(7vw, 80px)}
:where(a){color:inherit}
:where(a:not([class])){color:var(--c-mid); text-decoration:none; border-bottom:1px solid transparent}
:where(a:not([class]):hover, a:not([class]):focus-visible){border-bottom-color:var(--c-mid)}
/* The header stays on screen. A reader deep in a long page reaches the nav, the language and
the theme without scrolling back, on every page of the three sites. It is drawn as the one
modal's head and the chat's title bar are drawn — the terminal's `--t-top`, edge to edge,
the row close to its edges — so every bar the family puts at the top of something is the
same bar. The band is a `border-image` outset: it paints past the shell sideways and
nowhere else, adds no sideways scroll, and clips nothing, so the menu panel that hangs
below the row on a phone still shows; a `box-shadow` band would need a `clip-path` that
cuts it off.
The sticky box is the `.shell` around the header, not the header: every page wraps its
header in a shell of its own that ends with it, and a sticky element only sticks inside its
parent, which for the header is a box one row tall. `:has(> header > .bar)` picks that
shell out, and leaves alone the chat panel's title bar, a `header` too, and a deck's page,
which carries none of this row. The header's own padding gives way to the bar's, and the
air the page had above its title comes back as a margin under the bar.
`scroll-padding-top` keeps an anchor honest: a `#section` link lands under the bar, not
behind it. */
.shell:has(> header > .bar){position:sticky; top:0; z-index:40; margin-bottom:1.5rem;
background:var(--t-top); border-image:linear-gradient(var(--t-top) 0 0) fill 0 / / 0 100vmax}
.shell > header:has(> .bar){padding-top:.6rem; padding-bottom:.6rem}
/* One height, whether the row holds the links or the menu button: the button's 40px is the
row's floor, so the bar does not grow as a narrowing window collapses the nav. */
header > .bar{min-height:40px}
html:has(header > .bar){scroll-padding-top:5rem}
/* `align-items:center`, not the default `stretch`: a stretched link is a box the height of
the whole row with its text at the top, which put the words above the language control
beside them. Centered, each box is its own height and one line runs through every text. */
nav{display:flex; align-items:center; gap:1.9rem; font-size:.76rem; font-weight:500;
letter-spacing:.14em; text-transform:uppercase; position:relative}
.navlinks{display:flex; align-items:center; gap:1.9rem}
/* Hidden until the row runs out of width; the media query at the end of this sheet
is the only place it appears. The two icons swap on aria-expanded, the way the
deck's play button swaps its own. */
.burger{display:none; align-items:center; justify-content:center; width:40px; height:40px;
background:none; border:1px solid var(--rule); border-radius:8px;
color:var(--dim); cursor:pointer; padding:0;
transition:color .15s ease, border-color .15s ease}
.burger:hover{color:var(--ink); border-color:var(--c-mid)}
.burger svg{width:18px; height:18px; fill:none; stroke:currentColor;
stroke-width:1.7; stroke-linecap:round}
.burger .ix{display:none}
.burger[aria-expanded="true"]{color:var(--c-mid); border-color:var(--c-mid)}
.burger[aria-expanded="true"] .ibars{display:none}
.burger[aria-expanded="true"] .ix{display:block}
nav a{color:var(--dim); text-decoration:none; padding:.2rem 0;
border-top:1px solid transparent; border-bottom:1px solid transparent}
/* Hover is an underline and nothing else: `border-bottom-color`, not `border-color`,
because the box now has a transparent border on both edges to keep its text centered,
and `border-color` painted a line above the word as well as under it.
The current page is not underlined — it is brighter. A page marks where you are with
a different weight of ink; the underline is what follows the pointer. When both used
the same line, the selected item read as permanently hovered. */
nav a:hover, nav a:focus-visible{border-bottom-color:var(--c-mid)}
/* the bar is the same component on both pages, so this page says which one you are on
rather than dropping the item and leaving the header subtly different */
nav a[aria-current="page"]{color:var(--c-firm)}
/* The segmented control the decks put in their transport, at nav scale. Two real buttons
in one bordered box, the pressed one filled — someone who has used the deck already
knows what it is. It replaces one button with two clickable spans inside it, which said
one thing to a pointer and another to a screen reader. */
.seg{display:inline-flex; border:1px solid var(--rule); border-radius:8px; overflow:hidden;
flex:0 0 auto}
.seg button{font:inherit; letter-spacing:inherit; text-transform:inherit; background:none;
border:0; cursor:pointer; padding:.26rem .5rem; color:var(--dim);
transition:color .15s ease, background-color .15s ease}
.seg button:hover{color:var(--ink)}
.seg button[aria-pressed="true"]{color:var(--c-mid); background:var(--press); font-weight:600}
.seg button:focus-visible{outline:2px solid var(--c-mid); outline-offset:-2px}
/* The theme control is a .seg like the language one, so it inherits the border, the radius
and the pressed treatment. Only the icons differ: 14px square, drawn with currentColor so
the pressed state colors them without a second rule. */
/* The language control's buttons are as tall as the text line box inside them; these hold a
14px icon and came out 4px shorter, so the two controls sat at different heights in a row
whose whole point is one baseline. One line box is the floor here too, which keeps the
language control the base rather than copying a number off it: `em` first for anything
without `lh`, both resolving to the same 18.24px today. The padding is added back because
`box-sizing` is `border-box` here — a bare line box is smaller than the button already is
and would bind on nothing, which is exactly how the first attempt at this failed. */
.seg.theme button{display:grid; place-items:center; padding:.26rem .48rem;
min-height:calc(1.5em + .52rem); min-height:calc(1lh + .52rem)}
.seg.theme svg{width:14px; height:14px; display:block; fill:none; stroke:currentColor;
stroke-width:2; stroke-linecap:round; stroke-linejoin:round}
/* The row narrows before it collapses, and this is the band where that happens. It used to
sit at 900px — below the collapse, where `nav` is `display:contents` and a gap on it does
nothing at all. The rule was dead for as long as it has existed. */
@media (max-width:1200px){
nav{gap:1.4rem}
.navlinks{gap:1.4rem}
}
/* The tight row: what `nav fit` tries before the button, when the wide row does not fit.
The same links, closer and with less letter-spacing, so a long row in German reads as a
row on a desktop instead of collapsing. Nothing else changes: a row that fits wide never
carries this attribute. */
:root[data-nav="tight"] nav{gap:1.2rem; letter-spacing:.11em}
:root[data-nav="tight"] .navlinks{gap:1.2rem}
/* One mobile behavior for the whole family. Below this width the links do not fit beside
the wordmark, and every page in the three sites used to answer that differently — some
wrapped the bar, some wrapped the nav, and the two whose `.bar` had no `flex-wrap` at
all let the wordmark itself break in half. They collapse behind a button instead.
1000px, measured on blust.ch rather than chosen. This said 640 until 2026-09-16, and
between 641 and 1035 the bar wrapped in English and to 1099 in German: the brand on one
row and the nav orphaned under it, no rule, no alignment. It was not a German problem and
it did not arrive with a sixth nav item — five items already wrapped from 641 to 850 in
English. It went unseen because the checks that watch this row read page-level sideways
scroll and the wordmark's height, and a wrapped bar causes neither.
The number is the longest language's, not the shortest: German needs 1000 with the row
narrowed, English 920. One number for the family, set by whichever language needs most,
because a header that collapses at a different width per language is a header a visitor
cannot predict. Every width this newly collapses was already broken, so nothing that
worked has been taken away.
The language control stays on the bar. It is two characters, it is used constantly by a
bilingual audience, and a language a visitor cannot find is worse than one menu tap
saved. It keeps the right-hand end of the row, with the theme control beside it; the menu
button holds the left-hand corner, which is where a thumb goes looking for it. */
/* The row collapses when it does not fit, and `data-nav` is how the page says so. It used to
be a width — 640 until 2026-09-16, then 1000 — and a width cannot be right for three sites
whose navs hold three, four and six items. Measured, German needed 720 on guestgraph.io,
900 on companygraph.io and 1000 on blust.ch, so any single number gave two of them a menu
button where a row would have read perfectly well.
The number is gone. The nav-fit block measures what the row needs against what it has and
sets the attribute, so this is right for every site, every language, and the next nav item
nobody has added yet. See blocks/nav-fit.js for how it measures without a flash.
Without JavaScript the attribute is never set and the row stays wide, wrapping on a narrow
screen. That is not a regression: with JavaScript off the button here has never opened
anything, so a visitor went from six unreachable links to six wrapped but reachable ones. */
/* The menu button takes the left-hand corner. It cannot be moved in the DOM — the bar's
markup is written per page, sixteen of them across three repositories, while this fence
is CSS alone — so `nav` stops generating a box and its children become items of `.bar`
itself. The button then reaches the corner with `order`, in flow, aligned by the same
`align-items:center` as everything else in the row.
Absolute positioning was the first attempt and it fought the bar's own wrapping: `.bar`
has `flex-wrap` and already went to two rows at 360px before this button moved, so a
vertically centered button hung in the gap between them. Padding the wordmark clear of it
then inflated `.brand` enough that `mobileNav` reported a broken wordmark — correctly,
since that check reads the brand's height to find a wrapped one. `.bar` takes the
positioning `nav` gives up, so the panel still has something to hang from. */
:root[data-nav="compact"] .bar{position:relative; gap:12px}
:root[data-nav="compact"] nav{display:contents}
:root[data-nav="compact"] .burger{display:inline-flex; order:-1}
:root[data-nav="compact"] .brand{margin-right:auto}
/* The panel spans the bar rather than hugging its longest word, so the row of controls
above it and the panel below share one pair of edges. Its geometry is three custom
properties because the theme control has to land in its top-right corner while living
outside it in the DOM — see below — and a corner described twice is a corner that
drifts the first time either number is touched. */
:root[data-nav="compact"] .bar{--drawer-gap:.85rem; --drawer-pad-y:.55rem; --drawer-pad-x:1.1rem;
/* Half the difference between a link's box and the theme control's, so the control
and the first link read as one row instead of two things that nearly line up.
Measured, not derived — a link's height comes from its own padding and line box,
which CSS cannot subtract here. Changing `.navlinks a`'s padding or the control's
height moves it; the centers are 117 and 118 as it stands. */
--drawer-first-row:.5rem}
:root[data-nav="compact"] .navlinks{display:none; position:absolute; top:calc(100% + var(--drawer-gap));
left:0; right:0; z-index:30;
flex-direction:column; align-items:flex-start; gap:0;
padding:var(--drawer-pad-y) var(--drawer-pad-x);
background:var(--raise); border:1px solid var(--rule); border-radius:10px}
:root[data-nav="compact"] .navlinks[data-open="true"]{display:flex}
/* The theme control leaves the bar. Measured at 393px: the bar has 338px to spend and the
brand's 136 plus the button's 40 plus the switchers' 147 need 347 even at a 12px gap, so
the row wrapped on every phone width tested — 320 through 412 — and the header stood
101px tall before a word of content. The wordmark is the other lever and the contract
closes it: it is nowrap by rule, and `mobileNav` reads its height to catch one that
broke. Dropping the theme control is what is left, and it buys one row of 40px
everywhere down to 320.
It is not lost. Opening the menu drops it below the language control it belongs with —
positioned, not in flow, so the bar stays 40px and nothing above it moves. In flow it
pushed the language control onto a second row as the menu opened, which is a control
jumping out from under a thumb that was reaching for it.
`.bar .theme` and not `.theme`: the control is a `.seg` too, and `.seg{display:inline-flex}`
is the same specificity and sits later in the cascade, so the bare class lost and the
control stayed on the bar with the file looking correct. */
:root[data-nav="compact"] .bar .theme{display:none}
/* Positioned into the panel's top-right corner, not placed in it: `.switchers` is a
sibling of `.navlinks`, and the bar's markup is written per page — sixteen of them
across three repositories — while this fence is CSS alone. Anchoring it to the same
three properties the panel uses is what makes the two agree.
Above the panel's own z-index, or the panel paints over it. A tap on it does not close
the menu: the page's handler closes on a click outside `nav`, and `.switchers` is
inside `nav`. */
:root[data-nav="compact"] .navlinks[data-open="true"] ~ .switchers .theme{
display:inline-flex; position:absolute; z-index:31;
top:calc(100% + var(--drawer-gap) + var(--drawer-pad-y) + var(--drawer-first-row));
right:var(--drawer-pad-x)}
/* Tall enough to be a comfortable target; the underline still hugs the word, because
each item is only as wide as its own text. */
:root[data-nav="compact"] .navlinks a{padding:.8rem 0}
.title h1{font-family:"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
letter-spacing:-.035em; line-height:1.0;
font-size:clamp(2.2rem,6vw,4.6rem)}
.title h1 .r70{display:block; font-weight:300; color:var(--dim)}
.title h1 .rcl{display:block; font-weight:800}
.title h1 em{font-style:normal; color:var(--c-firm)}
.tagline{margin-top:1.6rem; font-size:clamp(1.1rem,1.9vw,1.4rem); color:var(--dim);
max-width:46ch; line-height:1.4; text-wrap:balance}
@media (max-width:900px){ .tagline{max-width:100%} }
.title .note{margin-top:2rem; max-width:54ch; padding-left:1rem;
border-left:2px solid var(--c-flag); color:var(--ink); font-size:1rem}
.conclusion{margin-block:1rem; max-width:62ch; padding-left:1rem;
border-left:2px solid var(--c-sum); color:var(--ink)}
.keyline{margin:.6rem 0 1.4rem; max-width:54ch; font-size:1.2rem; font-weight:500;
line-height:1.45; color:var(--c-firm); text-wrap:balance}
footer{padding:0 0 calc(2rem + env(safe-area-inset-bottom)); display:flex; flex-wrap:wrap; gap:.9rem 2.6rem; align-items:center;
font-family:"Plex Mono",monospace; font-size:.76rem; letter-spacing:.08em; color:var(--dim)}
footer a[aria-current="page"]{color:var(--c-firm)}
.value{border-top:2px solid var(--rule); padding-top:1.1rem; margin-top:1.9rem}
.value h3{font-family:"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
font-weight:700; letter-spacing:-.02em; font-size:clamp(1.15rem,2.1vw,1.5rem)}
.value .tagline{margin-top:.5rem; color:var(--c-firm); max-width:62ch}
.value p{margin-top:.9rem; color:var(--dim); max-width:70ch; line-height:1.6}
/* The board. --cols (the seat column) and --phase (the width of each phase column) are
declared once and used by the head row and every summary, which is what lines the columns
up across rows without a table. Only the seat column is a template track: each cell after
it opens a column of its own at --phase, so the board has as many phase columns as the
process has phases and never an empty one or a wrapped one. */
/* blocks/surfaces.css declares this same base rule for its own .mk. */
.mk{width:1.05em; height:1.05em; flex:none; vertical-align:-.16em}
.mk.human{color:var(--c-firm)}
.mk.agent{color:var(--c-mid)}
.hw .mk{width:2.3rem; height:2.3rem}
/* A seat several profiles hold: two marks, one behind the other, the one behind at half
strength and lifted, the one in front cut out of whatever it sits on. --halo is that ground:
the page's on the rail, a row's own where a row is shaded, so the cut-out never shows as a
box. */
.stack{display:inline-flex; flex:none; align-items:flex-end}
.stack .mk:first-child{opacity:.5; transform:translateY(-.22em)}
.stack .mk + .mk{margin-left:-.42em; background:var(--halo, var(--ground)); border-radius:.3em;
box-shadow:0 0 0 .1em var(--halo, var(--ground))}
.hw .stack .mk + .mk{margin-left:-1rem}
.hdrail{display:flex; gap:2rem; align-items:center; justify-content:space-between;
padding-bottom:1.1rem; border-bottom:1px solid var(--rule); margin-top:.9rem}
.whos{display:flex; gap:2.6rem}
.hw{display:flex; gap:.7rem; align-items:center}
.hw .nm{font-family:"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
font-weight:700; font-size:1.05rem}
.openall{background:none; border:1px solid var(--rule); border-radius:5px; color:var(--dim);
font:inherit; font-size:.76rem; padding:.28rem .7rem; cursor:pointer}
.openall:hover, .openall:focus-visible{color:var(--c-mid); border-color:var(--c-mid)}
/* Three cell marks, one hue. tokens.css allows one hue at four brightnesses and refuses a
second, so what a color would have carried is carried by shape. */
.g{display:inline-block; width:10px; height:10px; margin:0 1px}
.g.ex{background:var(--c-mid); border-radius:50%}
.g.su{border:1.4px solid var(--c-weak); border-radius:50%}
.g.ga{width:0; height:0; border-left:6px solid transparent; border-right:6px solid transparent;
border-bottom:10px solid var(--c-firm); vertical-align:-1px}
.legend{display:flex; gap:1.5rem; flex-wrap:wrap; margin-top:1.1rem; color:var(--dim);
font-size:.76rem}
.legend span{display:flex; align-items:center; gap:.45rem}
.grid{--cols:30%; --phase:1fr; margin-top:0}
.ghead{display:grid; grid-template-columns:var(--cols); grid-auto-flow:column;
grid-auto-columns:var(--phase); border-bottom:1px solid var(--rule);
padding:.9rem 0 .8rem}
.ghead > span{text-align:center}
.ghead > span:first-child{text-align:left}
.phnum{color:var(--c-weak); font-size:.66rem; letter-spacing:.12em; display:block;
font-family:"Plex Mono", ui-monospace, Menlo, monospace}
.phname{font-family:"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
font-weight:700; font-size:.9rem; color:var(--ink)}
.grid details{border-bottom:1px solid var(--rule)}
.grid summary{display:grid; grid-template-columns:var(--cols); grid-auto-flow:column;
grid-auto-columns:var(--phase); align-items:center;
list-style:none; cursor:pointer; padding:.62rem 0}
.grid summary::-webkit-details-marker{display:none}
.grid summary:hover{background:var(--press); --halo:var(--press)}
.grid summary:focus-visible{outline:2px solid var(--c-mid); outline-offset:-2px}
.grid summary > span{text-align:center}
.grid .sname{display:flex; align-items:center; gap:.6rem; text-align:left}
.grid .sname .tw{font-weight:600; font-size:.9rem}
/* The disclosure triangle is this page's only affordance that a row opens, since the page
carries no tooltip: the card is the seat's detail, and a hover showing a shorter version
of what a click shows in full is a second answer to one question. */
.grid .sname::before{content:""; width:0; height:0; border-style:solid; border-width:4px 0 4px 6px;
border-color:transparent transparent transparent var(--c-weak);
transition:transform .12s ease; flex:none}
.grid details[open] .sname::before{transform:rotate(90deg); border-left-color:var(--c-mid)}
.grid details[open] .sname .tw{color:var(--c-firm)}
.grid details.human summary{background:var(--press); --halo:var(--press)}
/* Full width, not an indent: the References table under a seat wrapped its URLs at anything
narrower. The card's own prose keeps a measure inside it. */
.grid .drawer{padding:.3rem 0 1.5rem}
.grid .drawer .card{width:100%}
@media (prefers-reduced-motion: reduce){ .grid .sname::before{transition:none} }
/* The phases in words, under the board that only names them. A grid rather than a
table: two columns, the phase and its one line, and the phase column sized to the longest
name so the sentences start on one edge. */
.phases{margin-top:1.9rem; padding-top:1.5rem; border-top:1px solid var(--rule);
display:grid; grid-template-columns:max-content 1fr; gap:.55rem 1.4rem}
.phases dt{display:flex; align-items:baseline; gap:.5rem; white-space:nowrap}
.phases dd{margin:0; color:var(--dim); font-size:.93rem; line-height:1.5; max-width:72ch}
@media (max-width:640px){
.phases{grid-template-columns:1fr; gap:.2rem 0}
.phases dd{margin-bottom:.9rem}
}
@media (max-width:900px){
/* The board stays a board. Its argument is a column — which seats carry a phase's gate,
say, is read off the shape of the column rather than off any one row — so a form with
no columns has no argument left to make. The phase names give way to their numbers
and the columns narrow; the block under the board is what says which number is which
phase, and it earns its place twice over here. */
.grid{--cols:minmax(0,1fr); --phase:2.1rem}
.ghead .phname{display:none}
.ghead .phnum{font-size:.74rem; color:var(--dim); letter-spacing:.08em}
.grid summary{padding:.72rem 0}
.grid .sname .tw{font-size:.86rem}
.g{width:9px; height:9px; margin:0}
.g.ga{border-left-width:5px; border-right-width:5px; border-bottom-width:9px}
.hdrail{flex-wrap:wrap; gap:1rem}
.whos{gap:1.4rem}
}
@media (max-width:640px){
/* Side by side, the head rail's profiles wrap their "holds … of …" line onto a second line
and put the last one hard against the edge. One under the other, each reads on one line. */
.whos{flex-direction:column; gap:.9rem}
.hdrail{align-items:flex-start}
.grid{--cols:minmax(0,1fr); --phase:1.8rem}
.grid .sname{gap:.42rem}
.grid .sname .tw{font-size:.8rem}
}
/* One board per process when a model holds more than one: the process's name and tagline
head its board, one step below the section's h2 and in the title block's own terms: the
name in the display face at the weight the phase names use, the tagline in --dim as the
title block's is. `section.proc` is a <section>, so the page's own `section` rule would
put a section's full gap between the label and the first board; the first sits where
the head rail would, and each one after takes the figure section's gap. */
.proc{margin-top:.9rem}
.proc + .proc{margin-top:clamp(2rem,5vh,3.5rem)}
.procname{font-family:"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
font-weight:700; letter-spacing:-.02em; font-size:clamp(1.15rem,2.1vw,1.5rem);
color:var(--ink)}
.proctag{margin-top:.4rem; color:var(--dim); max-width:62ch}
/* blocks/processes.css declares this same base rule for its own .mk. */
.mk{width:1.05em; height:1.05em; flex:none; vertical-align:-.16em}
.mk.hand{color:var(--c-firm)}
.mk.build{color:var(--c-mid)}
/* The lineage. Three columns on a wide screen — the model, the makers, the surfaces — laid out
by nesting, so the markup is the tree the drawing shows and a reader with no CSS gets the
same order. The wires sit behind it and are drawn by the page from where the nodes landed. */
.lineage{position:relative; display:grid; grid-template-columns:minmax(0,13rem) minmax(0,1fr);
column-gap:clamp(3rem,7vw,6rem); align-items:center; margin-top:1.4rem}
.wires{position:absolute; inset:0; width:100%; height:100%; overflow:visible; pointer-events:none}
/* A wire at rest is the candidate color, as the stage's resting edge is. --rule draws the
boxes, which their text holds in place; a wire in it was a hairline with nothing beside it,
and on the dark ground it was lost. */
.wires path{fill:none; stroke:var(--c-weak); stroke-width:1.5; transition:stroke .2s ease}
.wires path.hand{stroke-dasharray:5 5}
/* --c-path is the token for exactly this: the way back to the root from what is focused. */
.wires path.on{stroke:var(--c-path); stroke-width:2}
.ln-model{position:relative; padding:1.1rem 1.2rem; border:1.5px solid var(--c-firm); border-radius:10px;
background:var(--raise)}
.ln-model .nm{font-family:"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif; font-weight:800;
font-size:clamp(1.2rem,2vw,1.5rem); letter-spacing:-.02em; line-height:1.05; margin-top:.35rem}
.ln-model .at{font-size:.78rem; color:var(--c-mid); margin-top:.3rem}
.ln-groups, .ln-surfaces{list-style:none}
.ln-groups{display:grid; gap:2.2rem}
.ln-group{display:grid; grid-template-columns:minmax(0,12rem) minmax(0,1fr);
column-gap:clamp(2.5rem,6vw,5rem); align-items:center}
.ln-maker{position:relative; display:flex; gap:.6rem; align-items:center; padding:.5rem 0}
.ln-maker .mk{width:1.5rem; height:1.5rem}
.ln-maker .who{font-weight:600; font-size:.92rem; line-height:1.25}
.ln-maker .how{display:block; font-size:.76rem; color:var(--dim); font-weight:400}
.ln-surfaces{display:grid; grid-template-columns:minmax(0,1fr); gap:.6rem}
.ln-surfaces li{min-width:0}
.ln-s{position:relative; width:100%; text-align:left; font:inherit; color:var(--ink); cursor:pointer;
display:flex; flex-direction:column; gap:.1rem; padding:.7rem .95rem;
background:var(--ground); border:1px solid var(--rule); border-radius:8px;
transition:border-color .15s ease, background-color .15s ease}
.ln-s:hover{border-color:var(--c-mid)}
.ln-s:focus-visible{outline:2px solid var(--c-mid); outline-offset:2px}
.ln-s .nm{font-family:"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif; font-weight:700; font-size:1.02rem}
/* An address, so mono: it is a record value, not prose. */
.ln-s .host{font-family:"Plex Mono", ui-monospace, Menlo, monospace; font-size:.72rem; color:var(--dim);
overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.ln-s[aria-pressed="true"]{border-color:var(--c-path); background:var(--press)}
.ln-s[aria-pressed="true"] .nm{color:var(--c-firm)}
@media (prefers-reduced-motion: reduce){ .wires path, .ln-s{transition:none} }
/* No room for three columns: the drawing becomes the tree it already is, its lines drawn as
rules — dashed under the hand, as the wire is. */
@media (max-width:760px){
.wires{display:none}
.lineage{grid-template-columns:minmax(0,1fr); row-gap:0}
.ln-model{justify-self:start}
.ln-groups{gap:0; margin-left:1rem; padding:1.2rem 0 0 1.1rem; border-left:1.5px solid var(--c-weak)}
.ln-group{grid-template-columns:minmax(0,1fr); padding-bottom:1.2rem}
.ln-surfaces{margin-left:.7rem; padding-left:1rem; border-left:1.5px solid var(--c-weak)}
.ln-group.hand .ln-surfaces{border-left-style:dashed}
}
.index{margin-top:clamp(3rem,6vh,5rem)}
.index .row{border-top:2px solid var(--rule)}
.index .row:last-child{border-bottom:2px solid var(--rule)}
.index .entry{
display:grid; grid-template-columns:minmax(0,1fr) auto; gap:.5rem 2.5rem; align-items:baseline;
padding:clamp(1.6rem,3vh,2.4rem) 0;
text-decoration:none; color:inherit; transition:border-color .2s ease;
}
.index .entry:hover .t, .index .entry:focus-visible .t{color:var(--c-mid)}
.index .t{font-family:"Bricolage Grotesque",sans-serif; font-size:clamp(1.32rem,2.65vw,2rem); font-weight:700; line-height:1.2;
letter-spacing:-.015em; transition:color .2s ease}
.index .d{grid-column:1; margin-top:.7rem; color:var(--dim);
font-size:clamp(1rem,1.35vw,1.15rem); line-height:1.5; max-width:62ch}
.index .meta{font-family:"Plex Mono",monospace; font-size:.78rem;
color:var(--dim); letter-spacing:.1em; white-space:nowrap; text-align:right}
.index .meta b{color:var(--ink); font-weight:600}
.index .dl{margin:0 0 clamp(1.6rem,3vh,2.4rem); font-size:.78rem; letter-spacing:.06em}
.index .sep{color:var(--dim); margin:0 .7em}
@media (max-width:900px){
.index .entry{grid-template-columns:minmax(0,1fr); gap:.5rem}
.index .meta{grid-column:1; text-align:left; margin-top:.4rem}
.index .d{grid-column:1; max-width:100%}
}
@media (prefers-reduced-motion:reduce){
.index .entry, .index .t{transition:none}
}
.sec{margin-top:clamp(4rem,9vh,7rem)}
.sec h2{font-weight:600; letter-spacing:-.025em; line-height:1.06; font-size:clamp(1.9rem,3.7vw,3rem); margin-top:.9rem}
.sec h2 em{font-style:normal; color:var(--c-mid)}
.sec .lede{margin-top:1.5rem; font-size:clamp(1rem,1.35vw,1.18rem); color:var(--dim); max-width:52ch}
.sec .kicker{font-size:.82rem; font-weight:500; letter-spacing:.14em; text-transform:uppercase; color:var(--dim)}
.sec .values{margin-top:2rem; border-top:2px solid var(--rule)}
.sec .values a{display:grid; grid-template-columns:minmax(0,30ch) minmax(0,1fr); gap:.4rem 2.5rem; align-items:baseline;
padding:1.25rem 0; border-bottom:1px solid var(--rule); text-decoration:none; color:inherit}
.sec .values a:last-child{border-bottom:2px solid var(--rule)}
.sec .values b{font-family:"Bricolage Grotesque",sans-serif; font-weight:700; font-size:1.2rem; letter-spacing:-.01em; line-height:1.2; transition:color .2s}
.sec .values span{color:var(--dim); line-height:1.5}
.sec .values a:hover b{color:var(--c-mid)}
.sec .tiles{margin-top:2rem; display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:1rem}
.sec .tile{display:flex; flex-direction:column; gap:.7rem; padding:1.5rem; border:1.5px solid var(--rule); border-radius:10px;
background:var(--raise); text-decoration:none; color:inherit; text-align:left; font:inherit; cursor:pointer;
transition:border-color .18s}
.sec .tile:hover, .sec .tile:focus-visible{border-color:var(--c-mid)}
.sec .tile b{font-family:"Bricolage Grotesque",sans-serif; font-weight:700; font-size:1.3rem; letter-spacing:-.01em}
.sec .tile span{color:var(--dim); line-height:1.5; font-size:.98rem}
.sec .tile i{font-style:normal; margin-top:auto; padding-top:.4rem; color:var(--c-mid); font-size:.82rem; letter-spacing:.07em}
@media (max-width:760px){
.sec .values a{grid-template-columns:1fr}
.sec .tiles{grid-template-columns:1fr}
}
@media (max-width:900px){
.sec .lede{max-width:100%}
}
@media (prefers-reduced-motion:reduce){
.sec .tile, .sec .values b{transition:none}
}