Repository navigation
Expand file tree
/
Copy pathjoiner.css
More file actions
601 lines (567 loc) · 29.3 KB
/
Copy pathjoiner.css
File metadata and controls
601 lines (567 loc) · 29.3 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
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
/* The three Binnacle faces, served from here rather than fetched. A page that
is meant to be static should not need a third party to render its own type,
and an offline shop should not get fallback metrics. */
@font-face{
font-family:'Inter'; font-style:normal; font-weight:400 700; font-display:swap;
src:url('vendor/fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype');
}
@font-face{
font-family:'Space Grotesk'; font-style:normal; font-weight:400 700; font-display:swap;
src:url('vendor/fonts/SpaceGrotesk-VariableFont_wght.ttf') format('truetype');
}
@font-face{
font-family:'IBM Plex Mono'; font-style:normal; font-weight:400; font-display:swap;
src:url('vendor/fonts/IBMPlexMono-Regular.ttf') format('truetype');
}
@font-face{
font-family:'IBM Plex Mono'; font-style:normal; font-weight:500; font-display:swap;
src:url('vendor/fonts/IBMPlexMono-Medium.ttf') format('truetype');
}
/* Joiner · browser front end — the Binnacle port that needs no porting.
The desktop app translates these tokens into QSS by hand, which is where the
one contrast defect in this project's history came from. Here they are the
tokens themselves. */
:root{
/* What is genuinely Joiner's, and no house token covers.
Everything else — the surfaces, the accent contract, the text ramp, the
type roles — comes from binnacle-tokens.css, which is generated from the
standard's own file. This page used to carry its own copy of all of it,
and the copy had drifted: --text read #E6EDF3 against the house #E7EEF4,
and --muted #9FB0BE against #93A4B2. Nobody could have seen that by eye,
which is the argument for not keeping a second copy. */
--hair:rgba(143,160,174,.18); /* a translucent rule; the house line is solid */
}
*{box-sizing:border-box}
/* An explicit display beats the hidden attribute, which is how the boot screen
once stayed on top of a perfectly good window. */
[hidden]{display:none !important}
html,body{height:100%}
body{
margin:0; background:var(--ink); color:var(--text);
font-family:var(--font-ui); font-size:13.5px; line-height:1.5;
-webkit-font-smoothing:antialiased;
}
.mono{font-family:var(--font-mono)}
/* ---- boot ---------------------------------------------------------------- */
#boot{position:fixed; inset:0; display:grid; place-items:center; background:var(--ink); z-index:50}
.boot-inner{text-align:center; width:min(360px,80vw)}
.boot-mark{font-family:var(--font-disp); font-size:44px; font-weight:600; letter-spacing:-.02em}
.boot-mark span{color:var(--amber)}
.boot-note{font-family:var(--font-mono); font-size:11px; letter-spacing:.18em;
text-transform:uppercase; color:var(--muted-2); margin:14px 0 18px}
.boot-bar{height:2px; background:var(--hair); border-radius:2px; overflow:hidden}
.boot-bar span{display:block; height:100%; width:0; background:var(--amber);
transition:width var(--dur) var(--ease)}
/* ---- frame --------------------------------------------------------------- */
#app{height:100%; display:flex; flex-direction:column}
/* the menu bar: a classic bar, in the house language rather than in 1995 */
.menubar{
display:flex; align-items:stretch; gap:2px; padding:0 12px; flex:0 0 auto;
background:var(--ink); border-bottom:1px solid var(--hair);
}
/* The wordmark, set by the house rule rather than by eye: the name in caps at
19px, the display face's heavy weight, tracked *in* by a hundredth rather
than out, and the full stop in amber. */
.menubar .brand{
display:flex; align-items:center; margin-right:14px;
font-family:var(--font-disp); font-weight:700; font-size:19px;
text-transform:uppercase; letter-spacing:-.01em;
}
/* the version, at the far end of the bar: there to answer one question —
"am I running the thing we just changed?" — so it is quiet and stays out
of the way of the menus it shares the bar with */
.menubar .version{
margin-left:auto; display:flex; align-items:center;
font-size:11.5px; color:var(--muted); user-select:text;
}
/* what a command will do beyond what its label says — a warning, not a
refusal, so it sits quietly after the label rather than greying it */
.menu .row .note, .drop .row .note{
margin-left:auto; padding-left:14px; font-size:11px; color:var(--muted);
font-style:italic;
}
.menus{display:flex; align-items:stretch}
.menus > .top{
position:relative; display:flex; align-items:center; padding:7px 11px;
font-size:12.5px; color:var(--muted); cursor:default; border-radius:6px 6px 0 0;
user-select:none;
}
.menus > .top:hover{color:var(--text); background:var(--panel)}
.menus > .top[data-open="1"]{color:var(--text); background:var(--panel)}
.menus > .top > .drop{
position:absolute; top:100%; left:0; z-index:30; min-width:236px; padding:5px;
background:var(--panel); border:1px solid var(--hair); border-radius:0 9px 9px 9px;
box-shadow:0 14px 36px rgba(0,0,0,.55);
}
.brand{font-family:var(--font-disp); font-weight:600; font-size:15px; margin-right:8px}
.brand .dot{color:var(--amber)}
.bar-gap{flex:1}
/* ---- the bottom rail ------------------------------------------------------ */
.rail{
display:flex; align-items:center; gap:14px; padding:4px 14px; flex:0 0 auto;
background:var(--panel); border-top:1px solid var(--hair);
font-size:11.5px; color:var(--muted-2);
}
.rail-cell{white-space:nowrap}
/* What the pointer is over. It takes what room is going and gives it back the
moment the pointer leaves, so the rail does not jump about as a hand crosses
a menu. Quiet, because it is there to be ignored as often as it is read. */
.rail-about{
min-width:0; overflow:hidden; text-overflow:ellipsis; color:var(--muted);
border-left:1px solid var(--hair); padding-left:14px;
}
.rail-about:empty{border-left:0; padding-left:0}
#rail-selection{overflow:hidden; text-overflow:ellipsis}
/* ---- panes --------------------------------------------------------------- */
/* Three columns of panes with a draggable gutter between each, and each column
splitting its own two panes: the structure tree and the preview want the
room at different moments, and one shared row made every drag move both.
Every size is a variable, so a drag moves the layout without rewriting it. */
.grid{
--col-left:250px; --col-right:290px; --row-left:1.35fr; --row-mid:1.35fr; --gut:5px;
flex:1; min-height:0; display:grid; gap:0; padding:1px;
grid-template-columns:var(--col-left) var(--gut) minmax(0,1fr) var(--gut) var(--col-right);
grid-template-rows:minmax(0,1fr);
background:var(--hair);
}
.stack{
min-height:0; min-width:0; display:grid;
grid-template-rows:minmax(0,var(--row-left)) var(--gut) minmax(0,1fr);
}
.stack-mid{grid-template-rows:minmax(0,var(--row-mid)) var(--gut) minmax(0,1fr)}
.pane{background:var(--panel); min-height:0; display:flex; flex-direction:column; overflow:hidden}
.pane-view{background:var(--well)}
/* A gutter is mostly empty space around a hairline: the line is the join the
panes already showed, and the space is what makes it possible to grab. */
.gutter{background:var(--panel); position:relative; z-index:2}
.gutter::after{content:''; position:absolute; background:var(--hair)}
.gutter:hover::after,.gutter[data-dragging="1"]::after{background:var(--amber)}
.gutter-v{cursor:col-resize}
.gutter-v::after{top:0; bottom:0; left:50%; width:1px; margin-left:-.5px}
.gutter-h{cursor:row-resize}
.gutter-h::after{left:0; right:0; top:50%; height:1px; margin-top:-.5px}
.pane-head{
font-family:var(--font-mono); font-size:10px; letter-spacing:.2em; text-transform:uppercase;
color:var(--muted-2); padding:9px 12px 7px; border-bottom:1px solid var(--hair);
flex:0 0 auto;
}
.pane-head .idx{color:var(--amber); margin-right:7px}
/* The panel's head (UI spec §21). At the top, where a heading belongs: ours
was at the foot under a caption, saying "—" when nothing was selected, which
is a heading in the place a summary goes. The kind is a caption, the name is
the biggest thing in the pane because it is the answer to "what am I looking
at", and the note is the way back in words. */
.head{
padding:12px 12px 10px; border-bottom:1px solid var(--hair);
}
.head[hidden]{display:none}
.head-kind{
font-family:var(--font-mono); font-size:10px; letter-spacing:.2em;
text-transform:uppercase; color:var(--amber);
}
.head-name{
font-family:var(--font-disp); font-size:16px; font-weight:600;
color:var(--text); margin-top:3px; line-height:1.25;
}
/* A name with somewhere to go is a control; one without is text. The chevron
is the difference, and it is the same one the tree folds with. */
button.head-name{
display:flex; align-items:center; gap:6px; width:100%;
background:none; border:0; padding:0; cursor:pointer; text-align:left;
}
button.head-name:hover{color:var(--amber)}
/* A click leaves the focus ring behind, which reads as a text box around the
name. Kept for the keyboard, where it is the only thing saying where you
are, and dropped for the mouse, which does not need telling. */
button.head-name:focus:not(:focus-visible){outline:none}
button.head-name:focus-visible{outline:1px solid var(--amber); outline-offset:2px}
.head-mark{width:12px; height:12px; flex:none; color:var(--muted)}
button.head-name:hover .head-mark{color:var(--amber)}
.head-note{
font-size:11.5px; color:var(--muted); margin-top:3px; line-height:1.4;
}
.pane-sub{
font-family:var(--font-mono); font-size:10px; letter-spacing:.2em; text-transform:uppercase;
color:var(--muted-2); padding:12px 12px 4px;
}
/* A position typed straight at the preview, where the drag was. */
input.typed{
position:fixed; z-index:40; width:120px; font-family:var(--font-mono); font-size:12.5px;
color:var(--text); background:var(--panel); border:1px solid var(--amber);
border-radius:7px; padding:5px 8px; box-shadow:0 10px 26px rgba(0,0,0,.5);
}
input.typed:focus{outline:none}
/* ---- tree ---------------------------------------------------------------- */
.tree,.parts{list-style:none; margin:0; padding:5px 0; overflow:auto; flex:1; min-height:0}
.tree li,.parts li{
padding:4px 12px; cursor:default; display:flex; gap:8px; align-items:baseline;
border-left:2px solid transparent; white-space:nowrap;
}
.parts li{display:grid; grid-template-columns:minmax(0,1fr) auto; row-gap:0}
.parts li > span{overflow:hidden; text-overflow:ellipsis}
.tree li:hover,.parts li:hover{background:var(--panel-2)}
.tree li[data-selected="1"],.parts li[data-selected="1"]{
background:var(--panel-2); border-left-color:var(--amber);
}
/* selected as well, but not the one in hand: about to be edited, not inspected */
.tree li[data-also="1"]{background:var(--panel-2); border-left-color:var(--cyan)}
.tree .det{margin-left:auto; font-family:var(--font-mono); font-size:11.5px; color:var(--muted-2)}
.parts .size{font-family:var(--font-mono); font-size:11.5px; color:var(--muted-2); text-align:right}
.parts .role{grid-column:1; font-size:10.5px}
.tree .kind-division{color:var(--muted)}
/* The disclosure arrow. Always the same width, present or not, so labels line
up down the column rather than shifting as zones gain and lose children. */
.tree .twist{
flex:0 0 11px; width:11px; font-family:var(--font-mono); font-size:9px;
color:var(--muted-2); cursor:pointer; user-select:none; text-align:center;
}
.tree .twist:hover{color:var(--amber)}
/* Renaming happens on the row, so the row must not move while it does. */
.tree input.rename{
font:inherit; font-size:12.5px; color:var(--text); background:var(--panel-2);
border:1px solid var(--amber); border-radius:5px; padding:0 5px; margin:-1px 0;
min-width:0; flex:1 1 auto;
}
.tree input.rename:focus{outline:none}
.tree li[data-collapsed="1"] > span:not(.twist){color:var(--muted)}
.tree .front{
font-family:var(--font-mono); font-size:10px; letter-spacing:.12em; text-transform:uppercase;
color:var(--cyan);
}
/* a row that names a front nothing will cut says so, in the colour that means
stop — and in words too, never colour alone */
.tree li[data-hidden="1"] .name{color:var(--muted-2)}
.tree li[data-tone="bad"] .front{color:var(--danger)}
.tree li[data-tone="bad"]{border-left-color:var(--danger)}
.parts .role{font-family:var(--font-mono); color:var(--muted-2)}
/* ---- preview ------------------------------------------------------------- */
#scene{flex:1; min-height:0; width:100%; display:block}
/* ---- properties ---------------------------------------------------------- */
/* The properties column fills its height, and the history takes whatever is
left rather than a fixed 180px strip with empty space under it. The panel
above scrolls on its own when the selection is a long one, so the list of
steps never gets pushed off the bottom. */
.pane-props{display:flex; flex-direction:column; min-height:0; overflow:hidden}
.props-top{flex:1 1 auto; min-height:0; overflow:auto}
.fields{padding:12px 12px 4px; display:grid; gap:9px}
/* One vertical split, in the same place every time (UI spec §21). Labels left
of it, values right of it, and it does not move when the selection changes —
that one line is doing most of the work of making the panel scannable. 82px
rather than 74: a nested row spends part of the column on its indent, and at
74 the deeper labels wrapped. */
.field{display:grid; grid-template-columns:82px 1fr; align-items:center; gap:10px}
.field label{color:var(--muted); font-size:12.5px}
.field input[type=text],.field select{
font-family:var(--font-mono); font-size:12.5px; color:var(--text);
background:var(--panel-2); border:1px solid var(--hair); border-radius:7px;
padding:5px 9px; width:100%;
}
.field input[data-invalid="1"]{border-color:var(--danger)}
.field input:focus,.field select:focus{outline:none; border-color:var(--amber)}
.field.toggle-row{grid-template-columns:1fr}
/* A value that has parts. The row keeps its value — the word it used to
flatten to — and the chevron opens it where it stands. A nest is a row with
a value; a group is a band without one, which is the whole difference. */
.field.nest > .fold{
display:flex; align-items:center; gap:6px; padding:0;
background:none; border:0; cursor:pointer; color:var(--muted);
font-family:var(--font-ui); font-size:12.5px; text-align:left;
}
.field.nest > .fold:hover{color:var(--text)}
.field.nest > .fold svg{width:11px; height:11px; flex:none}
.field.nest > .fold:focus:not(:focus-visible){outline:none}
/* A fact is a value with no control, drawn as what it is: text beside its
label, in the figure face, with no box promising an edit nobody can make. */
.field .said{
font-size:12.5px; color:var(--text); padding:5px 0; min-width:0;
overflow-wrap:anywhere;
}
/* How far in a row sits. One step per level, off the label column, so the
nesting reads without the value column moving — the split between label and
value stays in the same place, which is most of what makes a panel
scannable. */
.field.nested{grid-template-columns:calc(82px - var(--step)) 1fr}
.field.nested{--step:calc(var(--depth, 1) * 11px); padding-left:var(--step)}
.field.toggle-row label{display:flex; gap:7px; align-items:center; cursor:pointer}
/* ---- the timeline -------------------------------------------------------- */
/* Along the bottom of the *viewport*, not of the window: it describes the
thing above it, and stretched across the whole program it read as a
property of the program. The canvas is a flex child beside it, so the two
share the pane and the drawing gives up exactly the strip's height. */
.timeline{
display:flex; align-items:center; gap:12px; padding:3px 10px; flex:0 0 auto;
background:var(--well); border-top:1px solid var(--hair);
}
.history{
list-style:none; margin:0; padding:0; display:flex; align-items:center; gap:3px;
overflow-x:auto; overflow-y:hidden; min-width:0; flex:1 1 auto;
}
.history .step{
flex:0 0 auto; width:22px; height:22px; display:grid; place-items:center;
font-family:var(--font-mono); font-size:13px; line-height:1; cursor:pointer;
color:var(--muted-2); border:1px solid transparent; border-radius:6px;
background:var(--panel-2); user-select:none;
}
.history .step:hover{color:var(--text); border-color:var(--muted-2)}
.history .step[data-current="1"]{color:var(--amber); border-color:var(--amber)}
.history .step[data-undone="1"]{opacity:.4}
.timeline-now{
font-size:11.5px; color:var(--muted-2); white-space:nowrap;
overflow:hidden; text-overflow:ellipsis; max-width:38%;
}
.detail{padding:2px 12px 8px; font-size:12px; color:var(--muted); white-space:pre-wrap}
#inspector{padding:0 12px 10px}
.detail b{color:var(--text); font-weight:500}
.status{
margin:4px 12px 14px; padding:8px 10px; border-radius:7px; font-size:12px;
border-left:2px solid var(--neutral); background:var(--well); color:var(--muted);
min-height:0;
}
.status[hidden]{display:none}
.status[data-tone="bad"]{border-left-color:var(--danger); color:var(--text)}
.status[data-tone="now"]{border-left-color:var(--amber)}
.status[data-tone="good"]{border-left-color:var(--cyan)}
.status .glyph{font-family:var(--font-mono); margin-right:7px; color:inherit}
.status .fix{display:block; color:var(--muted-2); margin-top:3px}
/* ---- cut list ------------------------------------------------------------ */
.table-wrap{overflow:auto; flex:1; min-height:0}
table{border-collapse:collapse; width:100%; font-size:12.5px}
th{
position:sticky; top:0; background:var(--panel); text-align:left;
font-family:var(--font-mono); font-size:10px; letter-spacing:.16em; text-transform:uppercase;
color:var(--muted-2); font-weight:500; padding:8px 10px; border-bottom:1px solid var(--hair);
}
td{padding:5px 10px; border-bottom:1px solid var(--hair); color:var(--muted)}
td:first-child{font-family:var(--font-mono); color:var(--text)}
td:nth-child(n+4):nth-child(-n+6){font-family:var(--font-mono); color:var(--text)}
tbody tr:hover td{background:var(--panel-2)}
/* ---- menus: the same rows at the cursor and along the top ---------------- */
.menu{
position:fixed; z-index:40; min-width:230px; padding:5px;
background:var(--panel); border:1px solid var(--hair); border-radius:9px;
box-shadow:0 12px 34px rgba(0,0,0,.5);
}
.menu .row,.drop .row{
padding:6px 10px; border-radius:6px; cursor:default; display:flex;
align-items:center; gap:9px; font-size:12.5px; white-space:nowrap;
}
.menu .row.item:hover,.drop .row.item:hover{background:var(--panel-2)}
.menu .row.heading,.drop .row.heading{
font-family:var(--font-mono); font-size:10px; letter-spacing:.16em; text-transform:uppercase;
color:var(--muted-2); cursor:default;
}
.menu .row.disabled,.drop .row.disabled{color:var(--muted-2); opacity:.6}
.menu .sep,.drop .sep{height:1px; background:var(--hair); margin:5px 8px}
.menu .tick,.drop .tick{font-family:var(--font-mono); width:1em; color:var(--cyan)}
.menu .arrow,.drop .arrow{margin-left:auto; color:var(--muted-2); padding-left:14px}
.menu .row,.drop .row{position:relative}
.menu .sub,.drop .sub{
display:none; position:absolute; left:calc(100% - 6px); top:-5px; z-index:35;
min-width:170px; padding:5px; background:var(--panel);
border:1px solid var(--hair); border-radius:9px; box-shadow:0 14px 36px rgba(0,0,0,.55);
}
.menu .row:hover > .sub,.drop .row:hover > .sub{display:block}
/* a submenu that would run off the right edge opens to the left instead */
.menu .sub.flip,.drop .sub.flip{left:auto; right:calc(100% - 6px)}
/* ---- a notice: About, the licence — read once, then dismissed ------------- */
.notice{
position:fixed; inset:0; z-index:60; display:flex; align-items:center;
justify-content:center; background:rgba(5,8,11,.62); padding:24px;
}
.notice-card{
max-width:min(560px,92vw); background:var(--panel); color:var(--text);
border:1px solid var(--hair); border-radius:12px; padding:22px 24px;
box-shadow:0 24px 60px rgba(0,0,0,.6);
}
.notice-card h2{
font-family:var(--font-disp); font-size:20px; font-weight:600; margin:0 0 10px;
}
.notice-card p{margin:0 0 10px; color:var(--muted); font-size:13px}
.notice-card .notice-lead{color:var(--text); margin-bottom:14px}
.notice-facts{
display:grid; grid-template-columns:auto 1fr; gap:5px 16px;
margin:0 0 14px; font-size:12.5px; align-items:baseline;
}
.notice-facts dt{font-family:var(--font-mono); color:var(--muted-2); white-space:nowrap}
.notice-facts dd{margin:0; color:var(--text)}
.notice-card button{margin-top:8px}
/* The padlock on a field that can be held against a drag. Quiet until it is
shut: a control that shouts while doing nothing teaches people to stop
reading it. */
/* the row grows a third column only when there is a padlock to put in it */
.field:has(.lock){grid-template-columns:82px 1fr auto}
.field .lock{background:none; border:1px solid transparent; border-radius:6px;
color:var(--muted); cursor:pointer; font-size:13px; line-height:1;
padding:3px 5px}
.field .lock:hover{border-color:var(--line-2); color:var(--text)}
.field .lock[data-on="1"]{color:var(--amber); border-color:var(--line-2)}
/* ---- the house glyphs ------------------------------------------------------
Sized in ems so a mark scales with the words beside it, and coloured by
currentColor so a theme or a vision profile reaches it without a redraw. */
svg.gl{width:1em; height:1em; display:inline-block; vertical-align:-0.14em;
fill:none; color:inherit}
svg.gl.twist-mark{width:.72em; height:.72em}
svg.gl.front-mark{width:.9em; height:.9em; cursor:pointer; color:var(--muted);
margin-left:auto; flex:0 0 auto; padding:0 1px}
svg.gl.front-mark:hover{color:var(--amber)}
/* two shapes and two colours: the struck-through eye is the channel that does
not depend on seeing colour, and the amber says *you did this* */
.tree li[data-hidden="1"] svg.gl.front-mark{color:var(--amber)}
.field .lock svg.gl{width:.95em; height:.95em}
svg.gl.step-mark{width:11px; height:11px}
svg.gl.tick-mark{width:.8em; height:.8em}
/* ---- scrollbars ------------------------------------------------------------
The page was leaving these to the browser, which draws the platform's own —
a white bar with arrows on it, down the side of a dark instrument panel. The
same rule the desktop follows: a thin handle in the line colour, no arrows,
no trough. `scrollbar-*` covers Firefox; the `::-webkit-` block covers the
rest, and neither is a fallback for the other. */
*{scrollbar-width:thin; scrollbar-color:var(--line-2) transparent}
::-webkit-scrollbar{width:11px; height:11px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-corner{background:transparent}
::-webkit-scrollbar-thumb{
background:var(--line-2); border-radius:5px; border:2px solid transparent;
background-clip:padding-box;
}
::-webkit-scrollbar-thumb:hover{background:var(--muted-2); background-clip:padding-box}
::-webkit-scrollbar-button{display:none; width:0; height:0}
/* a locked field: still readable, plainly not typeable */
.field input[data-held="1"]{color:var(--muted); background:var(--well); border-color:var(--line)}
/* What presses a menu row, at the far end of it. Dim and monospaced, because
it is a reminder rather than the name of the thing. */
.menu .row .chord,.drop .row .chord{
margin-left:auto; padding-left:24px; color:var(--muted-2);
font-family:var(--font-mono); font-size:11.5px;
}
/* A label you can drag to nudge the value beside it. */
.field label.scrub{cursor:ew-resize; user-select:none}
/* A group heading: the same fold the tree uses, at the width of the panel. */
.field.group{padding:10px 0 2px}
.field.group .fold{
display:flex; align-items:center; gap:6px; width:100%; padding:2px 0;
background:none; border:0; cursor:pointer; color:var(--muted);
font-family:var(--font-ui); font-size:10.5px; letter-spacing:.09em;
}
.field.group .fold:hover{color:var(--text)}
.field.group .fold svg{width:12px; height:12px}
/* The palette. Over the window rather than in it: it is a way of reaching
things, not a pane, and it goes away the moment it has been used. */
.palette{
position:fixed; inset:0; z-index:60; background:rgba(0,0,0,.38);
display:flex; justify-content:center; align-items:flex-start; padding-top:12vh;
}
.palette-card{
width:min(560px, 92vw); background:var(--panel); border:1px solid var(--line);
border-radius:10px; box-shadow:0 18px 50px rgba(0,0,0,.45); padding:10px;
}
.palette-card input{
width:100%; padding:9px 10px; border-radius:7px; border:1px solid var(--line-2);
background:var(--panel-2); color:var(--text); font-family:var(--font-ui);
font-size:13.5px;
}
.found-list{max-height:46vh; overflow-y:auto; margin-top:8px}
.found{
display:flex; align-items:center; gap:10px; padding:7px 9px; border-radius:6px;
font-size:12.5px; cursor:default;
}
.found[data-on="1"]{background:var(--panel-2)}
.found-label{flex:0 1 auto}
.found-hint{color:var(--muted-2); font-size:11px; margin-left:auto}
.found .chord{margin-left:10px}
.found.disabled{color:var(--muted-2); opacity:.6}
.found.more{color:var(--muted-2); font-style:italic}
/* The settings window: the palette's card, taller, holding rows.
It is a *document* more than a panel — a person reads it once, decides, and
leaves — so it is set for reading rather than for density. The properties
panel's 74px label column is right for `Width` beside a viewport and wrong
for `Carcass thickness` in a card twice as wide: at 74px every name in the
sheet wrapped onto two lines and the column of controls went ragged. */
.settings-card{width:min(680px, 94vw); padding:14px}
.settings-list{max-height:66vh; overflow-y:auto; margin:8px 0 12px; padding-right:4px}
.settings-list .field{grid-template-columns:150px 1fr; gap:12px; padding:3px 0}
.settings-list .field label{color:var(--text); font-size:13px; line-height:1.35}
.settings-list .field input[type=text],
.settings-list .field select{font-size:13px; padding:6px 10px}
.settings-note{
margin:0 0 8px; color:var(--muted-2); font-size:12px; line-height:1.55;
}
/* A section heading, in the accent that means *this is the structure*, with a
hairline above it so the eye finds the joins without reading the words. */
.settings-list .field.group{
padding:16px 0 4px; margin-top:6px; border-top:1px solid var(--hair);
}
.settings-list .field.group:first-child{border-top:0; margin-top:0; padding-top:2px}
.settings-list .field.group .fold{
color:var(--amber); font-size:11px; letter-spacing:.14em;
}
.settings-list .field.group .fold:hover{color:var(--text)}
/* `.palette-card input{width:100%}` was written for the one line the palette
holds and then reached every control in this card: a checkbox stretched to
the width of the list, pushing its own label off the edge. A tick is the
size a tick is. */
.palette-card input[type=checkbox],
.field input[type=checkbox]{width:auto; flex:none; padding:0; margin:0}
.field input[type=checkbox]{accent-color:var(--amber)}
/* A line to narrow a list by, in the pane's own head. Quiet: it is a way of
looking at the list rather than part of it. */
.pane-head .filter{
margin-left:auto; width:110px; padding:2px 7px; border-radius:5px;
border:1px solid var(--line); background:var(--panel-2); color:var(--text);
font-family:var(--font-ui); font-size:11px; letter-spacing:0;
text-transform:none;
}
.pane-head .filter:focus{outline:none; border-color:var(--line-2)}
.pane-head .hidden-count{
margin-left:8px; color:var(--muted-2); font-size:10.5px; letter-spacing:.04em;
}
/* ── The command bar ────────────────────────────────────────────────────────
Assembled in the view layer out of the menus' own marked rows, so nothing
here decides what is on it. Every mark carries its word: a row of pictures
nobody can name is a row nobody finds twice. */
/* The bar row: what kind of work this is, then what can be done within it.
The mode does not scroll away with the marks, because it is the one thing on
the row that is not a command. */
.bar-row{
display:flex; align-items:stretch; min-width:0;
background:var(--well); border-bottom:1px solid var(--hair);
}
.mode-now{
display:flex; align-items:center; flex:none; padding:0 12px;
border-right:1px solid var(--hair);
font-family:var(--font-ui); font-size:10.5px; font-weight:600;
letter-spacing:.16em; text-transform:uppercase; color:var(--amber);
cursor:default;
}
.bar{
display:flex; align-items:stretch; gap:1px; padding:2px 10px;
min-width:0; flex:1 1 auto;
overflow-x:auto; scrollbar-width:none;
}
.bar::-webkit-scrollbar{display:none}
.bar-sep{width:1px; margin:4px 7px; background:var(--hair); flex:none}
/* The mark sits BESIDE its word, not over it. Stacked, every button stood
twice the height of the menu bar it hangs under — a lot of window spent on a
strip that is only glanced at. The word stays; it just stops taking a line of
its own. */
.bar-item{
display:flex; align-items:center; gap:5px;
padding:2px 7px 2px 5px; flex:none;
background:transparent; border:1px solid transparent; border-radius:5px;
color:var(--muted); font:inherit; font-size:11px; line-height:1.5;
cursor:pointer;
}
.bar-item:hover:not(.disabled){color:var(--text); border-color:var(--line-2)}
.bar-item:focus-visible{outline:2px solid var(--amber); outline-offset:1px}
/* A refusal keeps its place and its reason, the way the menus do. */
.bar-item.disabled{color:var(--muted-2); opacity:.55; cursor:default}
/* The bar carries state as well as commands: a command that is currently ON
is drawn pressed, because after ten minutes away the question is *where am
I* rather than *what may I do*. */
.bar-item[data-on="1"]{
color:var(--text); border-color:var(--amber); background:var(--panel-2);
}
.bar-mark{width:13px; height:13px; flex:none}
.bar-word{letter-spacing:.01em}
/* The same mark, on the menu row that names it. */
.row-mark{width:13px; height:13px; margin-right:6px; opacity:.8}