reviewzy 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +2 -2
- package/src/dashboard/mount.ts +20 -3
- package/src/dashboard/static/components.css +1196 -451
- package/src/dashboard/static/cursor-rules.js +30 -34
- package/src/dashboard/static/cursor.css +118 -89
- package/src/dashboard/static/cursor.js +1636 -245
- package/src/dashboard/static/dashboard.css +275 -179
- package/src/dashboard/static/dashboard.js +330 -185
- package/src/dashboard/static/tokens.css +240 -54
- package/src/dashboard/static/ui.js +666 -171
- package/src/dashboard/views.tsx +1138 -841
- package/src/dashboard/word-diff.ts +18 -25
- package/src/db/migrations.ts +5 -6
- package/src/db/queries.ts +37 -0
- package/src/mcp/file-entries.ts +1 -1
- package/src/mcp/list-entries.ts +11 -2
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
One height ladder shared with inputs so horizontal toolbars stay aligned
|
|
8
8
|
regardless of label length, icon presence, or element type. The primary
|
|
9
9
|
is outlined: accent never fills area, so coverage stays under 10 percent
|
|
10
|
-
by construction. Press is
|
|
10
|
+
by construction. Press is the jelly squish, a scale settled by a spring,
|
|
11
|
+
no ripple. */
|
|
11
12
|
.btn {
|
|
12
13
|
display: inline-flex;
|
|
13
14
|
align-items: center;
|
|
@@ -25,13 +26,12 @@
|
|
|
25
26
|
background var(--dur-tint) var(--ease),
|
|
26
27
|
border-color var(--dur-tint) var(--ease),
|
|
27
28
|
color var(--dur-tint) var(--ease),
|
|
28
|
-
transform var(--dur
|
|
29
|
+
transform var(--press-dur) var(--press-spring);
|
|
29
30
|
white-space: nowrap;
|
|
30
31
|
text-decoration: none;
|
|
31
32
|
user-select: none;
|
|
32
33
|
}
|
|
33
34
|
input.btn { line-height: 34px; } /* native inputs need explicit line-height */
|
|
34
|
-
.btn:active { transform: translateY(1px); }
|
|
35
35
|
.btn svg { width: 14px; height: 14px; flex-shrink: 0; }
|
|
36
36
|
|
|
37
37
|
.btn-sm {
|
|
@@ -68,12 +68,14 @@ input.btn-lg { line-height: 42px; }
|
|
|
68
68
|
the floor in all three states (rest 6.0, hover 5.0, active 4.6). */
|
|
69
69
|
[data-theme="light"] .btn-primary { color: color-mix(in srgb, var(--accent) 50%, var(--text) 50%); }
|
|
70
70
|
.btn-primary:hover {
|
|
71
|
-
background: color-mix(in srgb, var(--accent) 24%,
|
|
72
|
-
border-color: color-mix(in srgb, var(--accent) 60%,
|
|
71
|
+
background: color-mix(in srgb, var(--accent) 24%, var(--tint-ground));
|
|
72
|
+
border-color: color-mix(in srgb, var(--accent) 60%, var(--tint-ground));
|
|
73
73
|
}
|
|
74
|
+
/* the press tint holds the dark label at 4.60:1 on surface-1 and 5.21 on --bg
|
|
75
|
+
(8-bit painted); 32 percent dropped it to 4.15 on the card, and 28 to 4.48 */
|
|
74
76
|
.btn-primary:active {
|
|
75
|
-
background: color-mix(in srgb, var(--accent)
|
|
76
|
-
border-color: color-mix(in srgb, var(--accent) 60%,
|
|
77
|
+
background: color-mix(in srgb, var(--accent) 27%, var(--tint-ground));
|
|
78
|
+
border-color: color-mix(in srgb, var(--accent) 60%, var(--tint-ground));
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
/* Filled: the ONE main action on a screen, and never a second one. Outlined
|
|
@@ -84,11 +86,12 @@ input.btn-lg { line-height: 42px; }
|
|
|
84
86
|
and --accent-press, which is what those two tokens are for. */
|
|
85
87
|
.btn-fill {
|
|
86
88
|
background: var(--accent);
|
|
89
|
+
--tint-ground: transparent;
|
|
87
90
|
color: var(--on-accent);
|
|
88
91
|
border-color: var(--accent);
|
|
89
92
|
}
|
|
90
|
-
.btn-fill:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
|
91
|
-
.btn-fill:active { background: var(--accent-press); border-color: var(--accent-press); }
|
|
93
|
+
.btn-fill:hover { background: var(--accent-hover); --tint-ground: transparent; border-color: var(--accent-hover); }
|
|
94
|
+
.btn-fill:active { background: var(--accent-press); --tint-ground: transparent; border-color: var(--accent-press); }
|
|
92
95
|
/* An accent fill swallows an accent focus ring, so the ring's own page keyline
|
|
93
96
|
is what separates the two. tokens.css ships it on :focus-visible. */
|
|
94
97
|
|
|
@@ -97,20 +100,24 @@ input.btn-lg { line-height: 42px; }
|
|
|
97
100
|
color: var(--text-2);
|
|
98
101
|
border-color: var(--surface-3);
|
|
99
102
|
}
|
|
100
|
-
.btn-secondary:hover { background: var(--surface-2); color: var(--text); border-color: var(--surface-3); }
|
|
101
|
-
.btn-secondary:active { background: var(--surface-3); }
|
|
103
|
+
.btn-secondary:hover { background: var(--surface-2); --tint-ground: transparent; color: var(--text); border-color: var(--surface-3); }
|
|
104
|
+
.btn-secondary:active { background: var(--surface-3); --tint-ground: transparent; }
|
|
102
105
|
|
|
103
106
|
.btn-danger {
|
|
104
107
|
background: transparent;
|
|
105
108
|
color: var(--danger);
|
|
106
|
-
border-color: color-mix(in srgb, var(--danger) 40%,
|
|
109
|
+
border-color: color-mix(in srgb, var(--danger) 40%, var(--tint-ground));
|
|
107
110
|
}
|
|
111
|
+
/* label and tint are one hue, so a deeper tint pulls the label down with it:
|
|
112
|
+
the hover and press label steps to --danger-text, measured in tokens.css */
|
|
108
113
|
.btn-danger:hover {
|
|
109
|
-
background: color-mix(in srgb, var(--danger) 16%,
|
|
110
|
-
|
|
114
|
+
background: color-mix(in srgb, var(--danger) 16%, var(--tint-ground));
|
|
115
|
+
color: var(--danger-text);
|
|
116
|
+
border-color: color-mix(in srgb, var(--danger) 60%, var(--tint-ground));
|
|
111
117
|
}
|
|
112
118
|
.btn-danger:active {
|
|
113
|
-
background: color-mix(in srgb, var(--danger) 24%,
|
|
119
|
+
background: color-mix(in srgb, var(--danger) 24%, var(--tint-ground));
|
|
120
|
+
color: var(--danger-text);
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
.btn-icon {
|
|
@@ -121,7 +128,7 @@ input.btn-lg { line-height: 42px; }
|
|
|
121
128
|
color: var(--text-3);
|
|
122
129
|
border-color: var(--line);
|
|
123
130
|
}
|
|
124
|
-
.btn-icon:hover { border-color: var(--surface-3); color: var(--text); background: var(--surface-2); }
|
|
131
|
+
.btn-icon:hover { border-color: var(--surface-3); color: var(--text); background: var(--surface-2); --tint-ground: transparent; }
|
|
125
132
|
.btn-icon svg { width: 15px; height: 15px; }
|
|
126
133
|
|
|
127
134
|
/* ---- Disabled ----
|
|
@@ -132,7 +139,7 @@ input.btn-lg { line-height: 42px; }
|
|
|
132
139
|
`.btn:disabled` already outranks. What no placement here can beat is a state
|
|
133
140
|
costume at (0,3,0), which is why the `!important` block near the end of the
|
|
134
141
|
file exists.
|
|
135
|
-
It replaces `opacity: 0.45
|
|
142
|
+
It replaces `opacity: 0.45`. An alpha dims the label with
|
|
136
143
|
the box, and a label dimmed that far stops clearing any floor; a colour dims
|
|
137
144
|
only what should read as off. --text-disabled measures 4.50:1 on --bg and 3.89
|
|
138
145
|
on --surface-1, both over the 3:1 floor for a control that cannot act.
|
|
@@ -153,12 +160,16 @@ input.btn-lg { line-height: 42px; }
|
|
|
153
160
|
block, the primary keeps a fill under a visible edge (--surface-3 on
|
|
154
161
|
--surface-2, 1.23:1 dark and 1.10 light, the same register the enabled edges
|
|
155
162
|
sit in), the two outlined ones keep an edge and no fill. */
|
|
156
|
-
.btn-fill:disabled { background: var(--surface-2); border-color: var(--surface-2); }
|
|
157
|
-
.btn-primary:disabled { background: var(--surface-2); border-color: var(--surface-3); }
|
|
163
|
+
.btn-fill:disabled { background: var(--surface-2); --tint-ground: transparent; border-color: var(--surface-2); }
|
|
164
|
+
.btn-primary:disabled { background: var(--surface-2); --tint-ground: transparent; border-color: var(--surface-3); }
|
|
158
165
|
.btn-secondary:disabled, .btn-danger:disabled { border-color: var(--line); }
|
|
159
166
|
|
|
160
167
|
/* Loading spinner */
|
|
161
168
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
169
|
+
/* the ring's loading breath: head anchored at 50, so the offset tracks L - 50 */
|
|
170
|
+
@keyframes ring-tail-breathe { to { stroke-dasharray: 46 100; stroke-dashoffset: -4; } }
|
|
171
|
+
/* its reduced-motion replacement, on the shape of the route bar's own hold */
|
|
172
|
+
@keyframes ring-hold { 50% { opacity: var(--ring-hold-dim); } }
|
|
162
173
|
.btn-loading { position: relative; color: transparent !important; }
|
|
163
174
|
.btn-loading::after {
|
|
164
175
|
content: '';
|
|
@@ -170,11 +181,20 @@ input.btn-lg { line-height: 42px; }
|
|
|
170
181
|
animation: spin 0.6s linear infinite;
|
|
171
182
|
color: var(--accent-text);
|
|
172
183
|
}
|
|
184
|
+
/* a filled button's ground is --accent, where that --accent-text ring is
|
|
185
|
+
invisible (1.22:1 dark rest, 1.00:1 hovered): the fill's spinner takes the
|
|
186
|
+
fill's own on-colour, 8.22:1 dark / 4.60:1 light */
|
|
187
|
+
.btn-fill.btn-loading::after { color: var(--on-accent); }
|
|
188
|
+
@media (prefers-reduced-motion: reduce) {
|
|
189
|
+
/* the busy cue replaces the motion rather than dying with it: the ring
|
|
190
|
+
rests, so the button still reads as busy without a spin */
|
|
191
|
+
.btn-loading::after { animation: none; }
|
|
192
|
+
}
|
|
173
193
|
|
|
174
194
|
/* ---- Page skeleton ----
|
|
175
195
|
Built independently by every dashboard project on the system: one
|
|
176
|
-
|
|
177
|
-
|
|
196
|
+
page column, section headers as a triplet, toolbars that compose.
|
|
197
|
+
Under a navbar, retune a build with --page-w, not per-page overrides. */
|
|
178
198
|
.page {
|
|
179
199
|
max-width: var(--page-w);
|
|
180
200
|
/* auto inline margins cancel a grid or flex item's stretch, so the column
|
|
@@ -185,22 +205,30 @@ input.btn-lg { line-height: 42px; }
|
|
|
185
205
|
padding-inline: var(--space-8);
|
|
186
206
|
}
|
|
187
207
|
@media (max-width: 768px) { .page { padding-inline: var(--space-4); } }
|
|
208
|
+
/* Beside a sidebar alone a cap here centres nothing; --page-w is past the
|
|
209
|
+
reading-column bound shells.md names. Measured 230px of dead page each side
|
|
210
|
+
on the dashboard example at 1912. A bleed banner's column follows the page's. */
|
|
211
|
+
.sidebar ~ .page,
|
|
212
|
+
.sidebar ~ * .page,
|
|
213
|
+
.sidebar ~ * .banner-inner { max-width: none; }
|
|
188
214
|
|
|
189
215
|
/* Eyebrow is .label; the triplet continues with title and lede.
|
|
190
|
-
The title is --t-
|
|
216
|
+
The title is --t-8, two steps above the document h2, because a section
|
|
191
217
|
heading and a paragraph heading are not the same object: h2 stays 26px for
|
|
192
|
-
prose.
|
|
218
|
+
prose. It steps to --t-7 under 768px as the page title steps to --t-8, so
|
|
219
|
+
the two stay one step apart at every width. The lede is --t-4, so it stops sitting at body size and the page
|
|
193
220
|
stops reading at one volume. The eyebrow is --text-4 (5.47:1 dark, 4.94:1
|
|
194
221
|
light on --bg): accent on every eyebrow down a page turns the signature
|
|
195
222
|
into wallpaper, which is the failure v1 shipped. */
|
|
196
223
|
.section-head { margin-bottom: var(--space-5); }
|
|
197
224
|
.section-head > .label { color: var(--text-4); }
|
|
198
225
|
.section-title {
|
|
199
|
-
font: 600 var(--t-
|
|
200
|
-
letter-spacing: -0.
|
|
226
|
+
font: 600 var(--t-8)/1.05 var(--font-sans);
|
|
227
|
+
letter-spacing: -0.035em;
|
|
201
228
|
color: var(--text);
|
|
202
229
|
margin-top: var(--space-2);
|
|
203
230
|
}
|
|
231
|
+
@media (max-width: 768px) { .section-title { font-size: var(--t-7); } }
|
|
204
232
|
.section-lede {
|
|
205
233
|
font-size: var(--t-4);
|
|
206
234
|
color: var(--text-3);
|
|
@@ -222,8 +250,16 @@ input.btn-lg { line-height: 42px; }
|
|
|
222
250
|
.toolbar-center { align-items: center; }
|
|
223
251
|
|
|
224
252
|
/* ---- Tabs (underline) ---- */
|
|
225
|
-
|
|
226
|
-
|
|
253
|
+
/* The hairline and the positioning context are the ROW's, not a wrapper's:
|
|
254
|
+
the ink is absolutely placed against the row's own bottom edge, so it lands
|
|
255
|
+
on the hairline it belongs to wherever the row sits. `.tabs-wrap` keeps no
|
|
256
|
+
rule of its own and survives as initTabs' query hook. */
|
|
257
|
+
.tabs {
|
|
258
|
+
display: flex;
|
|
259
|
+
gap: 0;
|
|
260
|
+
position: relative;
|
|
261
|
+
border-bottom: 1px solid var(--line-soft);
|
|
262
|
+
}
|
|
227
263
|
.tab {
|
|
228
264
|
position: relative;
|
|
229
265
|
padding: 9px 14px;
|
|
@@ -260,6 +296,7 @@ input.btn-lg { line-height: 42px; }
|
|
|
260
296
|
transform: scaleX(0);
|
|
261
297
|
transform-origin: left center;
|
|
262
298
|
background: var(--accent);
|
|
299
|
+
--tint-ground: transparent;
|
|
263
300
|
border-radius: var(--r-pill);
|
|
264
301
|
pointer-events: none;
|
|
265
302
|
opacity: 0;
|
|
@@ -280,8 +317,10 @@ input.btn-lg { line-height: 42px; }
|
|
|
280
317
|
padding: 2px 5px;
|
|
281
318
|
border-radius: var(--r-pill);
|
|
282
319
|
background: var(--surface-2);
|
|
320
|
+
--tint-ground: transparent;
|
|
283
321
|
color: var(--text-3);
|
|
284
322
|
}
|
|
323
|
+
/* light takes the shared remix: --accent-text alone reads 4.35:1 over --bg */
|
|
285
324
|
.tab.active .tab-badge,
|
|
286
325
|
.navbar-link.active .navbar-badge { background: var(--accent-quiet); color: var(--accent-text); }
|
|
287
326
|
|
|
@@ -296,6 +335,7 @@ input.btn-lg { line-height: 42px; }
|
|
|
296
335
|
so no-JS keeps a visible selection. */
|
|
297
336
|
.tabs-boxed-wrap {
|
|
298
337
|
background: var(--well);
|
|
338
|
+
--tint-ground: transparent;
|
|
299
339
|
border-radius: var(--r-2);
|
|
300
340
|
padding: 3px;
|
|
301
341
|
display: inline-flex;
|
|
@@ -312,10 +352,10 @@ input.btn-lg { line-height: 42px; }
|
|
|
312
352
|
/* concentric with the track: track radius minus its 3px padding */
|
|
313
353
|
border-radius: calc(var(--r-2) - 3px);
|
|
314
354
|
cursor: pointer;
|
|
315
|
-
transition: background var(--dur-tint) var(--ease), color var(--dur-tint) var(--ease);
|
|
355
|
+
transition: background var(--dur-tint) var(--ease), color var(--dur-tint) var(--ease), transform var(--press-dur) var(--press-spring);
|
|
316
356
|
user-select: none;
|
|
317
357
|
}
|
|
318
|
-
.tab-box:hover { background: var(--surface-2); color: var(--text-2); }
|
|
358
|
+
.tab-box:hover { background: var(--surface-2); --tint-ground: transparent; color: var(--text-2); }
|
|
319
359
|
.tab-box.active { color: var(--text); }
|
|
320
360
|
/* The segment and the pill that slides over it are ONE selection surface, so one
|
|
321
361
|
rule paints it. Without ink the segment carries it; with ink the pill does and
|
|
@@ -323,6 +363,7 @@ input.btn-lg { line-height: 42px; }
|
|
|
323
363
|
.tab-box.active,
|
|
324
364
|
.tabs-boxed-ink {
|
|
325
365
|
background: var(--surface-1);
|
|
366
|
+
--tint-ground: transparent;
|
|
326
367
|
box-shadow: var(--lit-1), 0 1px 3px oklch(0.14 0.02 280 / 0.45);
|
|
327
368
|
}
|
|
328
369
|
[data-theme="light"] .tab-box.active,
|
|
@@ -345,6 +386,30 @@ input.btn-lg { line-height: 42px; }
|
|
|
345
386
|
/* once the ink exists, the segment stops painting its own selection */
|
|
346
387
|
.tabs-boxed-wrap.has-ink .tab-box.active { background: transparent; box-shadow: none; }
|
|
347
388
|
|
|
389
|
+
/* ---- Press ----
|
|
390
|
+
The jelly squish: hover grows, press flattens wider and
|
|
391
|
+
shorter, and --press-spring settles the wobble over --press-dur. Only the
|
|
392
|
+
transform moves on the spring: .btn keeps its background, border-color and
|
|
393
|
+
color on --dur-tint, .tab-box its background and color, and neither
|
|
394
|
+
transitions box-shadow. The hover grow is fine-pointer only; the press squash
|
|
395
|
+
still fires on a tap. None of it under reduced motion, where the fades stay.
|
|
396
|
+
The non-uniform press scale momentarily distorts a control's corner radius,
|
|
397
|
+
which is the point of a squish; references/motion.md sanctions it. */
|
|
398
|
+
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
|
|
399
|
+
.btn:not(:disabled):hover,
|
|
400
|
+
.tab-box:not(:disabled):hover {
|
|
401
|
+
transform: scale(1.03);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/* after the hover grow: both rank (0,3,0), so source order hands a held
|
|
405
|
+
press the squash */
|
|
406
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
407
|
+
.btn:not(:disabled):active,
|
|
408
|
+
.tab-box:not(:disabled):active {
|
|
409
|
+
transform: scale(1.05, 0.90);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
348
413
|
/* ---- Inputs ----
|
|
349
414
|
Inputs sit below the page plane: --well, not raised. Most systems raise
|
|
350
415
|
inputs; this system sinks them. Field labels are 12px 500, sentence case:
|
|
@@ -374,19 +439,31 @@ input.btn-lg { line-height: 42px; }
|
|
|
374
439
|
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
|
|
375
440
|
column-gap: var(--space-5);
|
|
376
441
|
row-gap: var(--space-4);
|
|
442
|
+
/* A grid stretches its items, and a stretched label paints its one line at
|
|
443
|
+
the TOP of a 36px control's row: measured 11px above the control's centre,
|
|
444
|
+
against 0.2px for the same label inside a .field, which centres its own
|
|
445
|
+
subgrid. The bare pair is the shape the fieldset and half the sheet use. */
|
|
446
|
+
align-items: center;
|
|
377
447
|
}
|
|
378
448
|
.form-grid .field {
|
|
379
449
|
display: grid;
|
|
450
|
+
/* the span is restated here rather than taken from the child rule below:
|
|
451
|
+
subgrid needs it, and the scope is descendant so a field a wrapper div
|
|
452
|
+
sits between still levels with the grid's own columns */
|
|
380
453
|
grid-column: 1 / -1;
|
|
381
454
|
grid-template-columns: subgrid;
|
|
382
455
|
align-items: center;
|
|
383
456
|
}
|
|
384
457
|
.form-grid .field-label { grid-column: 1; }
|
|
385
458
|
.form-grid .field > :not(.field-label) { grid-column: 2; }
|
|
386
|
-
/*
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
459
|
+
/* Everything that is not one of the bare label/control pairs spans both
|
|
460
|
+
columns: a .field, a summary, a fieldset, a paragraph, a callout. Left to
|
|
461
|
+
`auto` the fieldset was placed in the 180px label column and its nested
|
|
462
|
+
grid crushed with it, the value clipping at "oncall@acm". The pair is
|
|
463
|
+
restated structurally one line down, so a child joins by being a label's
|
|
464
|
+
sibling rather than by being named. */
|
|
465
|
+
.form-grid > * { grid-column: 1 / -1; }
|
|
466
|
+
.form-grid > .field-label + * { grid-column: 2; }
|
|
390
467
|
|
|
391
468
|
/* ---- Fieldset ----
|
|
392
469
|
A grouped border, not a second card: line-soft, no shadow, the legend
|
|
@@ -434,9 +511,12 @@ input.btn-lg { line-height: 42px; }
|
|
|
434
511
|
}
|
|
435
512
|
.form-summary-links a { font-size: var(--t-2); color: var(--accent-text); } /* 7.25:1 dark, 5.17:1 light on the tint */
|
|
436
513
|
|
|
437
|
-
/* Stacked under 768px: labels above fields, the .field's own shape
|
|
514
|
+
/* Stacked under 768px: labels above fields, the .field's own shape. The pair
|
|
515
|
+
rule above names column 2, which a one-column grid does not have, so the
|
|
516
|
+
control returns to flow or it lands in an implicit second column. */
|
|
438
517
|
@media (max-width: 768px) {
|
|
439
518
|
.form-grid { grid-template-columns: minmax(0, 1fr); }
|
|
519
|
+
.form-grid > .field-label + * { grid-column: auto; }
|
|
440
520
|
}
|
|
441
521
|
|
|
442
522
|
/* ---- Setting rows ----
|
|
@@ -458,13 +538,22 @@ input.btn-lg { line-height: 42px; }
|
|
|
458
538
|
border-radius: 0;
|
|
459
539
|
transition: background var(--dur-tint) var(--ease);
|
|
460
540
|
}
|
|
461
|
-
/* the stack is flush
|
|
462
|
-
|
|
463
|
-
card edge
|
|
464
|
-
|
|
465
|
-
|
|
541
|
+
/* the stack is flush on both axes, because a card holding nothing but these
|
|
542
|
+
rows drops its own padding where `.card` is defined, so the edge rows take
|
|
543
|
+
the card's radius and the hover fill reaches its edge. Longhands, not the
|
|
544
|
+
shorthand: the two rules tie at (0,2,0) and a card with ONE row matches both,
|
|
545
|
+
so a shorthand pair hands it the later rule's square top corners, which used
|
|
546
|
+
to sit 20px inside the card and now sit on it. */
|
|
547
|
+
.setting-row:first-child {
|
|
548
|
+
border-start-start-radius: var(--r-2);
|
|
549
|
+
border-start-end-radius: var(--r-2);
|
|
550
|
+
}
|
|
551
|
+
.setting-row:last-child {
|
|
552
|
+
border-end-start-radius: var(--r-2);
|
|
553
|
+
border-end-end-radius: var(--r-2);
|
|
554
|
+
}
|
|
466
555
|
.setting-row + .setting-row { border-top: 1px solid var(--line-soft); }
|
|
467
|
-
.setting-row:hover { background: var(--surface-2); }
|
|
556
|
+
.setting-row:hover { background: var(--surface-2); --tint-ground: transparent; }
|
|
468
557
|
|
|
469
558
|
.setting-label {
|
|
470
559
|
grid-area: label;
|
|
@@ -519,12 +608,13 @@ input.btn-lg { line-height: 42px; }
|
|
|
519
608
|
row-gap: var(--space-3);
|
|
520
609
|
}
|
|
521
610
|
.setting-control { justify-content: flex-start; }
|
|
522
|
-
.setting-control .input,
|
|
611
|
+
.setting-control .input:not(.select-trigger),
|
|
523
612
|
.setting-control .select { width: 100%; min-width: 0; }
|
|
524
613
|
}
|
|
525
614
|
|
|
526
615
|
.input {
|
|
527
616
|
background: var(--well);
|
|
617
|
+
--tint-ground: transparent;
|
|
528
618
|
border: 1px solid var(--line);
|
|
529
619
|
border-radius: var(--r-1);
|
|
530
620
|
height: var(--ctl);
|
|
@@ -552,7 +642,7 @@ input.btn-lg { line-height: 42px; }
|
|
|
552
642
|
from what assistive tech reads. */
|
|
553
643
|
.input-invalid, .input[aria-invalid="true"] {
|
|
554
644
|
border-color: var(--danger);
|
|
555
|
-
--ring-color: color-mix(in srgb, var(--danger) 30%,
|
|
645
|
+
--ring-color: color-mix(in srgb, var(--danger) 30%, var(--glass-ground));
|
|
556
646
|
}
|
|
557
647
|
.input-invalid:focus, .input[aria-invalid="true"]:focus { border-color: var(--danger); }
|
|
558
648
|
/* every input-family control shares the disabled costume; before this the
|
|
@@ -590,6 +680,25 @@ select.input {
|
|
|
590
680
|
background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23676A74' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
591
681
|
}
|
|
592
682
|
|
|
683
|
+
/* Every number field is a numeric readout, so the mono face and tabular
|
|
684
|
+
figures are the type's own costume, not the stepper's: a bare
|
|
685
|
+
`.input[type=number]` in a form reads the same rule. The spinners go for
|
|
686
|
+
the reason `.select` exists: they are UA chrome, they ignore the theme, and
|
|
687
|
+
they put a second pair of arrows over the ring cursor. Alignment stays with
|
|
688
|
+
the field: the stepper centres its value because the controls flank it, and
|
|
689
|
+
a form field keeps the start edge. `inputmode=numeric` is markup, not CSS. */
|
|
690
|
+
.input[type="number"] {
|
|
691
|
+
font-family: var(--font-mono);
|
|
692
|
+
font-variant-numeric: tabular-nums;
|
|
693
|
+
font-feature-settings: "tnum";
|
|
694
|
+
appearance: textfield;
|
|
695
|
+
}
|
|
696
|
+
.input[type="number"]::-webkit-outer-spin-button,
|
|
697
|
+
.input[type="number"]::-webkit-inner-spin-button {
|
|
698
|
+
appearance: none;
|
|
699
|
+
margin: 0;
|
|
700
|
+
}
|
|
701
|
+
|
|
593
702
|
/* ---- Select (custom dropdown) ----
|
|
594
703
|
A native <select> styles its box and nothing else: the popup is OS chrome, so
|
|
595
704
|
it ignores the theme and puts the OS arrow back on screen over the ring
|
|
@@ -647,7 +756,7 @@ select.input {
|
|
|
647
756
|
transition: --fold var(--dur-out-near) cubic-bezier(0.55, 0, 1, 0.45);
|
|
648
757
|
}
|
|
649
758
|
/* --dur-near, not --dur-far: the ladder puts 122px of travel at 260ms, and at
|
|
650
|
-
260ms this panel reads slow.
|
|
759
|
+
260ms this panel reads slow. */
|
|
651
760
|
.select.open { --fold: 0; transition: --fold var(--dur-near) var(--ease-out); }
|
|
652
761
|
/* an invalid field stays marked while it is open to be corrected, body and
|
|
653
762
|
panel both. As a token swap it cannot be outranked by the open outline,
|
|
@@ -672,7 +781,7 @@ select.input {
|
|
|
672
781
|
.select-trigger:focus { --edge: var(--accent); }
|
|
673
782
|
.select-trigger:is(.input-invalid, [aria-invalid="true"]) { --edge: var(--danger); }
|
|
674
783
|
/* `.select-value` truncates on `tokens.css`'s `.truncate` rule, which names it. */
|
|
675
|
-
/* Square, so the sprite's 16-viewBox symbol drops in
|
|
784
|
+
/* Square, so the sprite's 16-viewBox symbol drops in, over
|
|
676
785
|
keeping a hand-drawn 10 x 6 chevron nothing could check. 14px is the ladder's
|
|
677
786
|
`.icon-sm` step, the one that sits beside 14px text. The glyph inside the box
|
|
678
787
|
is 8.75 x 4.4 against the old 8 x 4, so the caret reads the same size and the
|
|
@@ -699,6 +808,7 @@ select.input {
|
|
|
699
808
|
panel still on screen. Both were shipped, and both read as two objects. */
|
|
700
809
|
.select:is(.open, .closing) > .select-trigger {
|
|
701
810
|
background: color-mix(in oklab, var(--well), var(--surface-1) calc(var(--lift) * 100%));
|
|
811
|
+
--tint-ground: transparent;
|
|
702
812
|
border-color: color-mix(in oklab, var(--open-edge), var(--edge) calc(var(--seal) * 100%));
|
|
703
813
|
border-bottom-color: color-mix(in oklab, var(--line), var(--edge) calc(var(--seal) * 100%));
|
|
704
814
|
box-shadow: 0 0 0 calc(var(--seal) * var(--ring)) var(--ring-color);
|
|
@@ -732,11 +842,12 @@ select.input {
|
|
|
732
842
|
right: 0;
|
|
733
843
|
z-index: 60;
|
|
734
844
|
background: var(--surface-1);
|
|
845
|
+
--tint-ground: transparent;
|
|
735
846
|
border: 1px solid var(--line);
|
|
736
847
|
border-top: none; /* the trigger's own bottom border is the seam */
|
|
737
848
|
border-radius: 0 0 var(--r-1) var(--r-1);
|
|
738
849
|
box-shadow: var(--elev-float);
|
|
739
|
-
padding: var(--space-1)
|
|
850
|
+
padding: var(--space-1); /* the list panel's gutter */
|
|
740
851
|
max-height: 240px;
|
|
741
852
|
/* both axes named: a `visible` axis beside a scrolling one computes to `auto`,
|
|
742
853
|
which would hang a horizontal scrollbar off any option wider than the trigger */
|
|
@@ -776,15 +887,17 @@ select.input {
|
|
|
776
887
|
calc(-1 * var(--menu-bleed)));
|
|
777
888
|
}
|
|
778
889
|
|
|
779
|
-
|
|
890
|
+
/* 8 plus the panel's own 4px gutter: the text still lines up with the trigger's */
|
|
891
|
+
.select-menu .menu-item { padding-inline: 8px; }
|
|
780
892
|
/* The trigger keeps DOM focus while the panel is open, so the arrow-key row
|
|
781
893
|
carries its own mark. It is the row's HOVER costume, not a 2px accent edge:
|
|
782
894
|
`.focus` lands on the selected row at open time, so the edge stacked against
|
|
783
895
|
the panel's own accent border AND against that row's fill, which is the band
|
|
784
|
-
plus capsule
|
|
785
|
-
its one mark, so the two states never paint on one row. */
|
|
896
|
+
plus capsule the selected row no longer carries. The selected row keeps the
|
|
897
|
+
fill as its one mark, so the two states never paint on one row. */
|
|
786
898
|
.select-menu .menu-item.focus:not([aria-selected="true"]) {
|
|
787
899
|
background: var(--surface-2);
|
|
900
|
+
--tint-ground: transparent;
|
|
788
901
|
color: var(--text);
|
|
789
902
|
}
|
|
790
903
|
|
|
@@ -826,6 +939,16 @@ select.input {
|
|
|
826
939
|
user-select: none;
|
|
827
940
|
}
|
|
828
941
|
.checkbox-label:hover, .radio-label:hover { color: var(--text); }
|
|
942
|
+
/* a disabled box takes its row with it, as `.input-group` takes its addons: the
|
|
943
|
+
label kept the pointer, its hover and its live colour, so a dead row read as
|
|
944
|
+
live. --text-disabled: 4.50:1 on --bg and 3.89 on --surface-1 dark, 3.76 and
|
|
945
|
+
4.23 light, over the 3:1 floor for a control that cannot act. */
|
|
946
|
+
.checkbox-label:has(input:disabled),
|
|
947
|
+
.radio-label:has(input:disabled) {
|
|
948
|
+
color: var(--text-disabled);
|
|
949
|
+
cursor: not-allowed;
|
|
950
|
+
pointer-events: none;
|
|
951
|
+
}
|
|
829
952
|
|
|
830
953
|
.checkbox-label input[type="checkbox"],
|
|
831
954
|
.radio-label input[type="radio"] {
|
|
@@ -833,6 +956,7 @@ select.input {
|
|
|
833
956
|
height: 16px;
|
|
834
957
|
appearance: none;
|
|
835
958
|
background: var(--well);
|
|
959
|
+
--tint-ground: transparent;
|
|
836
960
|
border: 1px solid var(--line);
|
|
837
961
|
border-radius: calc(var(--r-1) * 0.5);
|
|
838
962
|
cursor: pointer;
|
|
@@ -848,6 +972,7 @@ select.input {
|
|
|
848
972
|
.radio-label input[type="radio"]:checked,
|
|
849
973
|
.checkbox-label input[type="checkbox"]:indeterminate {
|
|
850
974
|
background: var(--accent);
|
|
975
|
+
--tint-ground: transparent;
|
|
851
976
|
border-color: var(--accent);
|
|
852
977
|
}
|
|
853
978
|
/* the marks live on a pseudo that always exists: the check and the bar
|
|
@@ -860,6 +985,7 @@ select.input {
|
|
|
860
985
|
position: absolute;
|
|
861
986
|
inset: 3px;
|
|
862
987
|
background: var(--on-accent);
|
|
988
|
+
--tint-ground: transparent;
|
|
863
989
|
opacity: 0;
|
|
864
990
|
transform: scale(0.4);
|
|
865
991
|
transition: transform var(--dur-near) var(--ease-out), opacity var(--dur-near) var(--ease-out);
|
|
@@ -892,6 +1018,7 @@ select.input {
|
|
|
892
1018
|
width: 38px;
|
|
893
1019
|
height: 22px;
|
|
894
1020
|
background: var(--surface-3);
|
|
1021
|
+
--tint-ground: transparent;
|
|
895
1022
|
border-radius: var(--r-pill);
|
|
896
1023
|
position: relative;
|
|
897
1024
|
transition: background var(--dur-tint) var(--ease);
|
|
@@ -911,10 +1038,11 @@ select.input {
|
|
|
911
1038
|
width: 16px;
|
|
912
1039
|
height: 16px;
|
|
913
1040
|
background: var(--text);
|
|
1041
|
+
--tint-ground: transparent;
|
|
914
1042
|
border-radius: 50%;
|
|
915
1043
|
transition: transform var(--dur-near) var(--ease);
|
|
916
1044
|
}
|
|
917
|
-
.toggle.on { background: var(--accent); }
|
|
1045
|
+
.toggle.on { background: var(--accent); --tint-ground: transparent; }
|
|
918
1046
|
.toggle.on::after { transform: translateX(16px); }
|
|
919
1047
|
/* A switch is a graphic, so the disabled costume moves its FILLS rather than a
|
|
920
1048
|
text colour: the off track drops --surface-3 to --surface-2 and the on track
|
|
@@ -923,9 +1051,9 @@ select.input {
|
|
|
923
1051
|
carries the state and has to clear 3:1 on both tracks: 4.00 and 3.47 dark, 4.65
|
|
924
1052
|
and 4.75 light. --text-disabled measures 2.86 on the dark on-track and fails.
|
|
925
1053
|
.on stays legible because a disabled control still reports where it stands. */
|
|
926
|
-
.toggle:disabled { background: var(--surface-2); cursor: not-allowed; pointer-events: none; }
|
|
1054
|
+
.toggle:disabled { background: var(--surface-2); --tint-ground: transparent; cursor: not-allowed; pointer-events: none; }
|
|
927
1055
|
.toggle:disabled.on { background: var(--accent-quiet); }
|
|
928
|
-
.toggle:disabled::after { background: var(--text-4); }
|
|
1056
|
+
.toggle:disabled::after { background: var(--text-4); --tint-ground: transparent; }
|
|
929
1057
|
.toggle-label { font-size: var(--t-3); color: var(--text-2); }
|
|
930
1058
|
|
|
931
1059
|
/* ---- Disclosure (native details/summary) ----
|
|
@@ -975,25 +1103,33 @@ button.setting-row {
|
|
|
975
1103
|
}
|
|
976
1104
|
|
|
977
1105
|
/* ---- Card ----
|
|
978
|
-
Rises toward the light: surface-1 over the page, a
|
|
979
|
-
|
|
1106
|
+
Rises toward the light: surface-1 over the page, a crisp drawn line round
|
|
1107
|
+
its box, and no shadow at rest. The line is --card-line, a 1px box-shadow
|
|
1108
|
+
drawn outside the box so the content box never shrinks. */
|
|
980
1109
|
/* One raised surface. `.card-tight` and `.card-roomy` reach `.card` alone, so a
|
|
981
1110
|
stat card takes the default padding and nothing else changes. */
|
|
982
1111
|
.card, .stat-card {
|
|
1112
|
+
position: relative;
|
|
983
1113
|
background: var(--surface-1);
|
|
984
|
-
|
|
985
|
-
box-shadow: var(--
|
|
1114
|
+
--tint-ground: transparent;
|
|
1115
|
+
box-shadow: 0 0 0 1px var(--card-line);
|
|
986
1116
|
padding: var(--space-5);
|
|
987
1117
|
}
|
|
1118
|
+
/* the KPI tile takes the fourth, squircle step; every other card keeps --r-2 */
|
|
1119
|
+
.card { border-radius: var(--r-2); }
|
|
1120
|
+
.stat-card { border-radius: var(--r-4); }
|
|
988
1121
|
.card-interactive {
|
|
989
1122
|
cursor: pointer;
|
|
1123
|
+
text-decoration: none;
|
|
990
1124
|
transition:
|
|
991
1125
|
transform var(--dur-tint) var(--ease),
|
|
992
1126
|
box-shadow var(--dur-tint) var(--ease);
|
|
993
1127
|
}
|
|
1128
|
+
/* the lift deepens the contact shadow; while the card is raised, the line
|
|
1129
|
+
gives way to it */
|
|
994
1130
|
.card-interactive:hover {
|
|
995
1131
|
transform: translateY(-2px);
|
|
996
|
-
box-shadow: var(--
|
|
1132
|
+
box-shadow: var(--contact-2);
|
|
997
1133
|
}
|
|
998
1134
|
/* A card header and a modal header are one row: a space-between line under a
|
|
999
1135
|
hairline. Only the padding tells them apart, so only the padding is written
|
|
@@ -1012,7 +1148,7 @@ button.setting-row {
|
|
|
1012
1148
|
margin-bottom: var(--space-3);
|
|
1013
1149
|
}
|
|
1014
1150
|
/* A status tag anchors to the card's TOP right, never to the middle of the
|
|
1015
|
-
header row
|
|
1151
|
+
header row, and it settles `.stat-card` and the timeline
|
|
1016
1152
|
entry the same way for whenever either grows one. A header pairing a tag with
|
|
1017
1153
|
a title PLUS a subtitle centres the tag against both lines, which is what put
|
|
1018
1154
|
it mid-card.
|
|
@@ -1048,6 +1184,7 @@ button.setting-row {
|
|
|
1048
1184
|
.tag {
|
|
1049
1185
|
display: inline-flex;
|
|
1050
1186
|
align-items: center;
|
|
1187
|
+
text-decoration: none;
|
|
1051
1188
|
gap: 5px;
|
|
1052
1189
|
font: 500 var(--t-1)/1 var(--font-sans);
|
|
1053
1190
|
padding: 3px 8px;
|
|
@@ -1065,6 +1202,7 @@ button.setting-row {
|
|
|
1065
1202
|
.tag-default,
|
|
1066
1203
|
.tag-data {
|
|
1067
1204
|
background: var(--surface-2);
|
|
1205
|
+
--tint-ground: transparent;
|
|
1068
1206
|
color: var(--text-3);
|
|
1069
1207
|
border-color: var(--line);
|
|
1070
1208
|
}
|
|
@@ -1073,19 +1211,19 @@ button.setting-row {
|
|
|
1073
1211
|
color: var(--accent-text);
|
|
1074
1212
|
}
|
|
1075
1213
|
.tag-success {
|
|
1076
|
-
background: color-mix(in srgb, var(--success) 14%,
|
|
1214
|
+
background: color-mix(in srgb, var(--success) 14%, var(--tint-ground));
|
|
1077
1215
|
color: color-mix(in srgb, var(--success) 80%, var(--text) 20%);
|
|
1078
1216
|
}
|
|
1079
1217
|
.tag-warning {
|
|
1080
|
-
background: color-mix(in srgb, var(--warning) 14%,
|
|
1218
|
+
background: color-mix(in srgb, var(--warning) 14%, var(--tint-ground));
|
|
1081
1219
|
color: color-mix(in srgb, var(--warning) 80%, var(--text) 20%);
|
|
1082
1220
|
}
|
|
1083
1221
|
.tag-danger {
|
|
1084
|
-
background: color-mix(in srgb, var(--danger) 14%,
|
|
1222
|
+
background: color-mix(in srgb, var(--danger) 14%, var(--tint-ground));
|
|
1085
1223
|
color: color-mix(in srgb, var(--danger) 80%, var(--text) 20%);
|
|
1086
1224
|
}
|
|
1087
1225
|
.tag-info {
|
|
1088
|
-
background: color-mix(in srgb, var(--info) 14%,
|
|
1226
|
+
background: color-mix(in srgb, var(--info) 14%, var(--tint-ground));
|
|
1089
1227
|
color: color-mix(in srgb, var(--info) 80%, var(--text) 20%);
|
|
1090
1228
|
}
|
|
1091
1229
|
[data-theme="light"] .tag-success { color: color-mix(in srgb, var(--success) 70%, var(--text) 30%); }
|
|
@@ -1096,12 +1234,21 @@ button.setting-row {
|
|
|
1096
1234
|
/* ---- Table ---- */
|
|
1097
1235
|
.table-wrap {
|
|
1098
1236
|
background: var(--surface-1);
|
|
1237
|
+
--tint-ground: transparent;
|
|
1099
1238
|
border-radius: var(--r-2);
|
|
1100
1239
|
box-shadow: var(--elev-1);
|
|
1101
1240
|
/* A too-wide table scrolls in its own box; clipping it amputates columns
|
|
1102
1241
|
silently. Seven projects fixed this same defect independently. */
|
|
1103
1242
|
overflow-x: auto;
|
|
1104
1243
|
overflow-y: hidden;
|
|
1244
|
+
/* The global focus ring floats 3px outside the cell box, so a landing that
|
|
1245
|
+
stops a cell flush with the scroller edge amputates the first column's
|
|
1246
|
+
arc into the clip. scroll-padding gives scrollIntoView landings room to
|
|
1247
|
+
stop short of the edge (measured: 0px without, 8px with, chrome 152).
|
|
1248
|
+
Tab focus does not use it: chrome leaves a partially visible cell
|
|
1249
|
+
unscrolled, so a control flush with a clipped edge wants an inset ring
|
|
1250
|
+
instead. */
|
|
1251
|
+
scroll-padding-inline: 8px;
|
|
1105
1252
|
}
|
|
1106
1253
|
.table-wrap table {
|
|
1107
1254
|
width: 100%;
|
|
@@ -1111,8 +1258,9 @@ button.setting-row {
|
|
|
1111
1258
|
.table-wrap thead tr {
|
|
1112
1259
|
border-bottom: 1px solid var(--line-soft);
|
|
1113
1260
|
background: var(--well);
|
|
1261
|
+
--tint-ground: transparent;
|
|
1114
1262
|
}
|
|
1115
|
-
[data-theme="light"] .table-wrap thead tr { background: var(--surface-2); }
|
|
1263
|
+
[data-theme="light"] .table-wrap thead tr { background: var(--surface-2); --tint-ground: transparent; }
|
|
1116
1264
|
/* THE tracked-caps eyebrow, and the three places it is sanctioned: a table
|
|
1117
1265
|
header, a navigation-list group label, a weekday name. Not a merge into
|
|
1118
1266
|
`tokens.css`'s `.label`, which declares 11px/1.4 where all three declare /1 and
|
|
@@ -1146,19 +1294,21 @@ button.setting-row {
|
|
|
1146
1294
|
the 3px it had grown. What holds the sortable and plain labels together is
|
|
1147
1295
|
`vertical-align` on the button, one screen down. A DESCENDANT selector, so the
|
|
1148
1296
|
half pixel reaches the label in both places: moving only the plain one would
|
|
1149
|
-
split the row again.
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
.
|
|
1297
|
+
split the row again. With all five labels in the sortable row on one range top
|
|
1298
|
+
and one ink top, what is left is DEVICE-PIXEL PHASE and no stylesheet reaches
|
|
1299
|
+
it: a table's reading swings about a pixel with its fractional page position,
|
|
1300
|
+
measured -0.12 and +0.62 on one dashboard table 13.4px apart. So the nudge
|
|
1301
|
+
CENTRES that swing rather than fitting one table: at 0.5px the nine tables on
|
|
1302
|
+
disk read -0.25 to +0.62, at 0.25px -0.50 to +0.12, at the 0.5px budget's
|
|
1303
|
+
edge and never past it. */
|
|
1304
|
+
.table-wrap thead th .th-label { position: relative; top: 0.25px; }
|
|
1155
1305
|
.table-wrap tbody tr {
|
|
1156
1306
|
border-bottom: 1px solid var(--line-soft);
|
|
1157
1307
|
/* the transition lives with the selection rules in the table-patterns
|
|
1158
1308
|
block, which restate this selector at equal specificity later */
|
|
1159
1309
|
}
|
|
1160
1310
|
.table-wrap tbody tr:last-child { border-bottom: none; }
|
|
1161
|
-
.table-wrap tbody tr:hover { background: var(--surface-2); }
|
|
1311
|
+
.table-wrap tbody tr:hover { background: var(--surface-2); --tint-ground: transparent; }
|
|
1162
1312
|
.table-wrap tbody td {
|
|
1163
1313
|
padding: 10px var(--space-4);
|
|
1164
1314
|
color: var(--text-2);
|
|
@@ -1190,6 +1340,7 @@ button.setting-row {
|
|
|
1190
1340
|
code {
|
|
1191
1341
|
font: 400 var(--t-2)/1 var(--font-mono);
|
|
1192
1342
|
background: var(--well);
|
|
1343
|
+
--tint-ground: transparent;
|
|
1193
1344
|
border: 1px solid var(--line-soft);
|
|
1194
1345
|
padding: 2px 6px;
|
|
1195
1346
|
border-radius: var(--r-1);
|
|
@@ -1201,6 +1352,7 @@ code {
|
|
|
1201
1352
|
kbd {
|
|
1202
1353
|
font: 500 var(--t-1)/1 var(--font-mono);
|
|
1203
1354
|
background: var(--surface-1);
|
|
1355
|
+
--tint-ground: transparent;
|
|
1204
1356
|
border: 1px solid var(--line);
|
|
1205
1357
|
border-bottom-width: 2px;
|
|
1206
1358
|
border-radius: var(--r-1);
|
|
@@ -1212,6 +1364,7 @@ kbd {
|
|
|
1212
1364
|
pre {
|
|
1213
1365
|
font: 400 var(--t-2)/1.5 var(--font-mono);
|
|
1214
1366
|
background: var(--well);
|
|
1367
|
+
--tint-ground: transparent;
|
|
1215
1368
|
border: 1px solid var(--line-soft);
|
|
1216
1369
|
border-radius: var(--r-1);
|
|
1217
1370
|
padding: var(--space-3) var(--space-4);
|
|
@@ -1235,6 +1388,7 @@ pre code {
|
|
|
1235
1388
|
gap: var(--space-3);
|
|
1236
1389
|
padding: var(--space-2) var(--space-4);
|
|
1237
1390
|
background: var(--well);
|
|
1391
|
+
--tint-ground: transparent;
|
|
1238
1392
|
border: 1px solid var(--line-soft);
|
|
1239
1393
|
border-bottom: none;
|
|
1240
1394
|
border-radius: var(--r-1) var(--r-1) 0 0;
|
|
@@ -1271,7 +1425,7 @@ pre code {
|
|
|
1271
1425
|
:hover > .code-copy, .code-copy:focus-visible { opacity: 1; }
|
|
1272
1426
|
.code-copy:hover, .code-copy:focus-visible {
|
|
1273
1427
|
color: var(--text);
|
|
1274
|
-
background: color-mix(in srgb, var(--surface-2) 80%,
|
|
1428
|
+
background: color-mix(in srgb, var(--surface-2) 80%, var(--tint-ground));
|
|
1275
1429
|
}
|
|
1276
1430
|
.code-copy svg { width: 14px; height: 14px; }
|
|
1277
1431
|
.code-copy .icon-check { animation: check-pop var(--dur-near) var(--ease-out); }
|
|
@@ -1297,8 +1451,8 @@ pre code {
|
|
|
1297
1451
|
/* ---- Diff marks ----
|
|
1298
1452
|
The tint carries the semantics; the text keeps its own reading contrast. */
|
|
1299
1453
|
.diff-add, .diff-remove { border-radius: 2px; padding: 0 1px; }
|
|
1300
|
-
.diff-add { background: color-mix(in srgb, var(--success) 22%,
|
|
1301
|
-
.diff-remove { background: color-mix(in srgb, var(--danger) 22%,
|
|
1454
|
+
.diff-add { background: color-mix(in srgb, var(--success) 22%, var(--tint-ground)); }
|
|
1455
|
+
.diff-remove { background: color-mix(in srgb, var(--danger) 22%, var(--tint-ground)); }
|
|
1302
1456
|
|
|
1303
1457
|
/* ---- Stat / KPI ---- */
|
|
1304
1458
|
.stat-label {
|
|
@@ -1326,8 +1480,9 @@ pre code {
|
|
|
1326
1480
|
.stat-value,
|
|
1327
1481
|
.ring-value,
|
|
1328
1482
|
.claim-value { overflow-wrap: anywhere; }
|
|
1329
|
-
.stat-value
|
|
1483
|
+
.stat-value { font-size: var(--t-7); letter-spacing: -0.03em; }
|
|
1330
1484
|
.ring-value { font-size: var(--t-6); }
|
|
1485
|
+
@media (max-width: 768px) { .stat-value { font-size: var(--t-6); } }
|
|
1331
1486
|
.stat-delta {
|
|
1332
1487
|
font: 400 var(--t-2)/1 var(--font-sans);
|
|
1333
1488
|
margin-top: var(--space-2);
|
|
@@ -1355,22 +1510,28 @@ pre code {
|
|
|
1355
1510
|
height: 2px;
|
|
1356
1511
|
border-radius: var(--r-pill);
|
|
1357
1512
|
background: var(--accent-2);
|
|
1513
|
+
--tint-ground: transparent;
|
|
1358
1514
|
}
|
|
1359
|
-
/* Vertical form: marks a row in a list or a band in a chart.
|
|
1515
|
+
/* Vertical form: marks a row in a list or a band in a chart. The bar and its
|
|
1516
|
+
gap live in the container's own gutter, pulled back by exactly their width,
|
|
1517
|
+
so the marked line's first glyph lands where every plain line's does: with
|
|
1518
|
+
the padding alone the two measured 14px apart. */
|
|
1360
1519
|
.annot-mark {
|
|
1361
1520
|
border-inline-start: 2px solid var(--accent-2);
|
|
1362
1521
|
padding-inline-start: var(--space-3);
|
|
1522
|
+
margin-inline-start: calc(-2px - var(--space-3));
|
|
1363
1523
|
}
|
|
1364
1524
|
|
|
1365
1525
|
/* ---- Sparkline ----
|
|
1366
1526
|
preserveAspectRatio="none" stretches the viewBox to the card's width,
|
|
1367
1527
|
which would thin and fatten a declared stroke with the card (the
|
|
1368
|
-
scaled-stroke trap). non-scaling-stroke pins the drawn
|
|
1528
|
+
scaled-stroke trap, cursor.md). non-scaling-stroke pins the drawn
|
|
1369
1529
|
width at 1.5 css px whatever the stretch. Line contrast on the card,
|
|
1370
|
-
WCAG 1.4.11's 3:1 graphics floor: series-1 5.21:1 dark, 5.
|
|
1530
|
+
WCAG 1.4.11's 3:1 graphics floor: series-1 5.21:1 dark, 5.81:1 light
|
|
1371
1531
|
on --surface-1, measured. */
|
|
1372
1532
|
.stat-spark {
|
|
1373
1533
|
--spark: var(--chart-series-1);
|
|
1534
|
+
--series: var(--spark); /* the area and end point ui.js builds read --series */
|
|
1374
1535
|
height: 28px;
|
|
1375
1536
|
margin-top: var(--space-3);
|
|
1376
1537
|
}
|
|
@@ -1431,11 +1592,12 @@ pre code {
|
|
|
1431
1592
|
}
|
|
1432
1593
|
.toast {
|
|
1433
1594
|
background: var(--surface-1);
|
|
1595
|
+
--tint-ground: transparent;
|
|
1434
1596
|
border-radius: var(--r-2);
|
|
1435
1597
|
box-shadow: var(--elev-float);
|
|
1436
1598
|
padding: var(--space-4);
|
|
1437
1599
|
display: flex;
|
|
1438
|
-
align-items:
|
|
1600
|
+
align-items: center;
|
|
1439
1601
|
gap: var(--space-3);
|
|
1440
1602
|
pointer-events: all;
|
|
1441
1603
|
animation: toast-in var(--dur-near) var(--ease-out) both;
|
|
@@ -1453,16 +1615,15 @@ pre code {
|
|
|
1453
1615
|
.callout-icon,
|
|
1454
1616
|
.form-summary-icon,
|
|
1455
1617
|
.banner-icon { flex-shrink: 0; }
|
|
1456
|
-
/*
|
|
1457
|
-
|
|
1458
|
-
.
|
|
1459
|
-
.
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
.toast-icon svg,
|
|
1618
|
+
/* An icon dropped in without its own width lays out at 0, so the slot states
|
|
1619
|
+
it. `display: block` is what centres it: an inline svg sits on the text
|
|
1620
|
+
baseline, so the slot's line box runs 22.69px around a 16px glyph and the
|
|
1621
|
+
descender space rides the glyph 3.34px above the slot's own centre. Measured
|
|
1622
|
+
on every panel but the banner, which carried this one declaration alone. */
|
|
1623
|
+
.toast-icon > svg,
|
|
1463
1624
|
.callout-icon > svg,
|
|
1464
1625
|
.form-summary-icon > svg,
|
|
1465
|
-
.banner-icon svg { width: 16px; height: 16px; }
|
|
1626
|
+
.banner-icon > svg { display: block; width: 16px; height: 16px; }
|
|
1466
1627
|
.toast-body { flex: 1; min-width: 0; }
|
|
1467
1628
|
.toast-msg { font-size: var(--t-2); color: var(--text-3); margin-top: 3px; }
|
|
1468
1629
|
|
|
@@ -1477,6 +1638,9 @@ pre code {
|
|
|
1477
1638
|
.callout,
|
|
1478
1639
|
.banner {
|
|
1479
1640
|
display: flex;
|
|
1641
|
+
/* the icon centres on the BODY: a 1px nudge onto the
|
|
1642
|
+
first line put a two-line body's icon high by half the second line */
|
|
1643
|
+
align-items: center;
|
|
1480
1644
|
gap: var(--space-3);
|
|
1481
1645
|
border-radius: var(--r-2);
|
|
1482
1646
|
box-shadow: var(--lit-1);
|
|
@@ -1484,7 +1648,7 @@ pre code {
|
|
|
1484
1648
|
.form-summary,
|
|
1485
1649
|
.callout { padding: 12px var(--space-4); }
|
|
1486
1650
|
.callout,
|
|
1487
|
-
.banner { background: var(--surface-1); }
|
|
1651
|
+
.banner { background: var(--surface-1); --tint-ground: transparent; }
|
|
1488
1652
|
/* The icon carries the semantic, so the variant colours it. Leaving that to an
|
|
1489
1653
|
inline style is how a callout ships with a --text icon and reads as neutral. */
|
|
1490
1654
|
.callout-info > .callout-icon { color: var(--info); }
|
|
@@ -1510,6 +1674,7 @@ pre code {
|
|
|
1510
1674
|
script sets transform-origin per side, never the centre. */
|
|
1511
1675
|
.popover {
|
|
1512
1676
|
background: var(--surface-1);
|
|
1677
|
+
--tint-ground: transparent;
|
|
1513
1678
|
border-radius: var(--r-2);
|
|
1514
1679
|
box-shadow: var(--elev-float);
|
|
1515
1680
|
overflow: hidden;
|
|
@@ -1544,7 +1709,14 @@ pre code {
|
|
|
1544
1709
|
display: flex;
|
|
1545
1710
|
align-items: center;
|
|
1546
1711
|
gap: var(--space-3);
|
|
1547
|
-
|
|
1712
|
+
/* 10 and not 14: the panel's own 4px gutter supplies the rest, so the text
|
|
1713
|
+
column sits where it always did, 14px in from the panel edge. */
|
|
1714
|
+
padding: 8px 10px;
|
|
1715
|
+
/* THE row radius, in every state. A radius that arrives with a state is a
|
|
1716
|
+
corner that animates, and it left a hovered row's square band meeting a
|
|
1717
|
+
selected row's rounded one; a state rule declaring one is that defect
|
|
1718
|
+
restated. */
|
|
1719
|
+
border-radius: var(--r-1);
|
|
1548
1720
|
font-size: var(--t-3);
|
|
1549
1721
|
color: var(--text-2);
|
|
1550
1722
|
cursor: pointer;
|
|
@@ -1556,24 +1728,84 @@ pre code {
|
|
|
1556
1728
|
/* THE neutral hover, and every borderless control and list row in the system
|
|
1557
1729
|
takes it from here. --text on --surface-2 measures 8.56:1 dark, 11.28:1 light.
|
|
1558
1730
|
It has to sit at or before this point, not with the icon buttons further down:
|
|
1559
|
-
`.menu-item.active
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
.
|
|
1731
|
+
`.menu-item.active` and `.input-step:disabled` each tie it at (0,2,0) and have
|
|
1732
|
+
to win by coming after. `.menu-item.danger` ties it too, inertly: its own
|
|
1733
|
+
`:hover` at (0,3,0) sets the hovered label. `.disclosure[open] > summary` ties
|
|
1734
|
+
it too, at (0,2,1), inertly, since both spell --text. The remove control is
|
|
1735
|
+
not a member: removing is destructive, and its hover is its own, declared
|
|
1736
|
+
with `.close` in the one-remove-control section. */
|
|
1563
1737
|
.copy-chip-btn:hover,
|
|
1564
1738
|
.select-clear:hover,
|
|
1565
1739
|
.row-toggle:hover,
|
|
1566
1740
|
.disclosure summary:hover,
|
|
1567
1741
|
.input-step:hover,
|
|
1568
|
-
.menu-item:hover { background: var(--surface-2); color: var(--text); }
|
|
1742
|
+
.menu-item:hover { background: var(--surface-2); --tint-ground: transparent; color: var(--text); }
|
|
1569
1743
|
/* The chosen row, whether a menu marks it with `.active` or a select marks it
|
|
1570
|
-
with aria. It has to sit AFTER `:hover
|
|
1571
|
-
|
|
1744
|
+
with aria. It has to sit AFTER `:hover`, and the ORDER IS ONLY LOAD-BEARING
|
|
1745
|
+
FOR THE FIRST SELECTOR: `.menu-item.active` ties the hover at (0,2,0) and
|
|
1746
|
+
would repaint grey under the pointer from any earlier position, while the
|
|
1747
|
+
select's own selector measures (0,3,0) and wins from anywhere. */
|
|
1572
1748
|
.menu-item.active,
|
|
1573
1749
|
.select-menu .menu-item[aria-selected="true"] { color: var(--accent-text); background: var(--accent-quiet); }
|
|
1574
1750
|
.menu-item.danger { color: var(--danger); }
|
|
1575
|
-
.menu-item.danger:hover { background: color-mix(in srgb, var(--danger) 10%,
|
|
1576
|
-
|
|
1751
|
+
.menu-item.danger:hover { background: color-mix(in srgb, var(--danger) 10%, var(--tint-ground)); color: var(--danger-text); }
|
|
1752
|
+
/* The arrow-key row carries the hover costume, the same mark the select's
|
|
1753
|
+
listbox takes for its focused option. It has to sit after `.active`: a
|
|
1754
|
+
focused active row keeps its accent fill as its one mark. The danger row
|
|
1755
|
+
keeps its danger hover, never the neutral band. */
|
|
1756
|
+
.popover-menu .menu-item.focus:not(.active) {
|
|
1757
|
+
background: var(--surface-2);
|
|
1758
|
+
--tint-ground: transparent;
|
|
1759
|
+
color: var(--text);
|
|
1760
|
+
}
|
|
1761
|
+
.popover-menu .menu-item.danger.focus:not(.active) {
|
|
1762
|
+
background: color-mix(in srgb, var(--danger) 10%, var(--tint-ground));
|
|
1763
|
+
color: var(--danger-text);
|
|
1764
|
+
}
|
|
1765
|
+
/* 1px and not 3: the lane below contributes the other 2 on each side, so the
|
|
1766
|
+
painted gap around a divider stays the 3px it measured before the lane.
|
|
1767
|
+
`flex-shrink: 0` because the lane makes its panel a flex column and the
|
|
1768
|
+
select's is height-bounded: a row is floored by its own min-content, an empty
|
|
1769
|
+
1px rule is floored by nothing, and it measured 0px tall with no hairline. */
|
|
1770
|
+
.menu-divider { height: 1px; flex-shrink: 0; background: var(--line-soft); --tint-ground: transparent; margin: 1px 0; }
|
|
1771
|
+
|
|
1772
|
+
/* ---- The list panel's one gutter ----
|
|
1773
|
+
A row in a list panel sits in a 4px inset with --r-1 corners in every state
|
|
1774
|
+
and a 2px lane to the next row, so no two bands touch and no band reaches the
|
|
1775
|
+
panel's own corner.
|
|
1776
|
+
|
|
1777
|
+
4px is not a taste: on the --r-2 popover a row's corner is concentric with
|
|
1778
|
+
the panel's, since --r-2 - --space-1 is exactly --r-1. A clipped rounded
|
|
1779
|
+
panel bites r - sqrt(r^2 - (r - gap)^2) out of anything reaching its corner:
|
|
1780
|
+
the whole 14 at no gap, still 4.20 at 4px if the row's own corner were
|
|
1781
|
+
square. It is the CONCENTRICITY that takes the bite to nothing, not the
|
|
1782
|
+
inset alone. Both panels clip at their PADDING box, one glass border inside
|
|
1783
|
+
the radius they declare, so the identity lands a pixel off rather than dead
|
|
1784
|
+
on: 13 against the row's 10 at a 4px inset here, 9 against 10 in the select,
|
|
1785
|
+
whose panel rounds at --r-1. Either way the row's own corner leaves the clip
|
|
1786
|
+
nothing to take.
|
|
1787
|
+
|
|
1788
|
+
THE GUTTER IS KEYED ON WHAT THE PANEL HOLDS, not on `.popover-menu`: one page
|
|
1789
|
+
writes a bare `.popover` around its rows, and a contract a consumer can miss
|
|
1790
|
+
by one class is the contract that ships the defect back. `.select-menu` is
|
|
1791
|
+
not a `.popover`, and the palette's rows sit a level down, so those two state
|
|
1792
|
+
their own; the lane is shared, because the lane is one number.
|
|
1793
|
+
|
|
1794
|
+
The lane is a gap and not a margin because `.daterange-presets` turns into a
|
|
1795
|
+
wrapping row under 560px, where a block margin staggers the line instead of
|
|
1796
|
+
separating it. */
|
|
1797
|
+
.popover:has(> .menu-item) { padding: var(--space-1); }
|
|
1798
|
+
.popover:has(> .menu-item),
|
|
1799
|
+
.select-menu,
|
|
1800
|
+
.palette-group,
|
|
1801
|
+
.nav-group,
|
|
1802
|
+
.tree,
|
|
1803
|
+
.tree-group,
|
|
1804
|
+
.tree li {
|
|
1805
|
+
display: flex;
|
|
1806
|
+
flex-direction: column;
|
|
1807
|
+
gap: 2px;
|
|
1808
|
+
}
|
|
1577
1809
|
|
|
1578
1810
|
/* ---- Tooltip ----
|
|
1579
1811
|
The popover shell with a placement costume: surface-1 plus the float
|
|
@@ -1607,21 +1839,22 @@ pre code {
|
|
|
1607
1839
|
.popover.tooltip { animation: tooltip-in-top var(--dur-near) var(--ease-out) both; }
|
|
1608
1840
|
.popover.tooltip.below { animation-name: tooltip-in-below; }
|
|
1609
1841
|
.popover.tooltip.closing { animation: tooltip-out-top var(--dur-out-near) var(--ease) both; }
|
|
1610
|
-
|
|
1842
|
+
/* the below variant lifts as it leaves, the same move the palette exits on, so
|
|
1843
|
+
it takes that keyframe rather than a fourth copy of it */
|
|
1844
|
+
.popover.tooltip.below.closing { animation-name: lift-out; }
|
|
1611
1845
|
@keyframes tooltip-in-top { from { opacity: 0; transform: translateY(4px) scale(0.98); } }
|
|
1612
1846
|
@keyframes tooltip-in-below { from { opacity: 0; transform: translateY(-4px) scale(0.98); } }
|
|
1613
1847
|
@keyframes tooltip-out-top { to { opacity: 0; transform: translateY(4px) scale(0.99); } }
|
|
1614
|
-
@keyframes tooltip-out-below { to { opacity: 0; transform: translateY(-4px) scale(0.99); } }
|
|
1615
1848
|
|
|
1616
1849
|
/* Reduced motion: fades stay, transforms go. Restated at each rule's own
|
|
1617
1850
|
specificity; a shorter selector here is outranked and the transform
|
|
1618
1851
|
comes back. */
|
|
1619
1852
|
@media (prefers-reduced-motion: reduce) {
|
|
1620
|
-
.popover.tooltip, .popover.tooltip.below { animation:
|
|
1621
|
-
.popover.tooltip.closing, .popover.tooltip.below.closing { animation:
|
|
1853
|
+
.popover.tooltip, .popover.tooltip.below { animation: fade-in var(--dur-near) var(--ease) both; }
|
|
1854
|
+
.popover.tooltip.closing, .popover.tooltip.below.closing { animation: fade-out var(--dur-out-near) var(--ease) both; }
|
|
1622
1855
|
}
|
|
1623
|
-
@keyframes
|
|
1624
|
-
@keyframes
|
|
1856
|
+
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
1857
|
+
@keyframes fade-out { to { opacity: 0; } }
|
|
1625
1858
|
|
|
1626
1859
|
/* ---- Command palette ----
|
|
1627
1860
|
The popover shell dropped over the page from the top centre: a search head,
|
|
@@ -1667,12 +1900,13 @@ pre code {
|
|
|
1667
1900
|
}
|
|
1668
1901
|
/* its own exit, outranking the shell's at (0,3,0): the panel is centred, so it
|
|
1669
1902
|
leaves the way it arrived rather than toward a trigger's edge */
|
|
1670
|
-
.popover.palette.closing { animation:
|
|
1671
|
-
@keyframes
|
|
1903
|
+
.popover.palette.closing { animation: lift-out var(--dur-out-near) var(--ease) both; }
|
|
1904
|
+
@keyframes lift-out { to { opacity: 0; transform: translateY(-4px) scale(0.99); } }
|
|
1672
1905
|
|
|
1673
1906
|
/* ---- Search head ----
|
|
1674
|
-
|
|
1675
|
-
the
|
|
1907
|
+
Every column down the panel starts 14px in: the head states that gutter
|
|
1908
|
+
itself, and the group labels and the rows reach it as their own 10px inside
|
|
1909
|
+
the list panel's 4px. */
|
|
1676
1910
|
.palette-search {
|
|
1677
1911
|
display: flex;
|
|
1678
1912
|
align-items: center;
|
|
@@ -1691,8 +1925,12 @@ pre code {
|
|
|
1691
1925
|
font: 400 var(--t-4)/1.4 var(--font-sans);
|
|
1692
1926
|
color: var(--text); /* on the glass panel: 6.48:1 dark, 10.76:1 light */
|
|
1693
1927
|
}
|
|
1694
|
-
/*
|
|
1695
|
-
|
|
1928
|
+
/* the one placeholder painted straight on a panel rather than a --well, so it
|
|
1929
|
+
reads --text-4, the panel's dim text: on the glass panel --text-4 re-floors to
|
|
1930
|
+
--glass-dim (4.67:1 dark, 4.72:1 light), and on the opaque --surface-1 it holds
|
|
1931
|
+
4.73:1 dark / 5.55:1 light, where the --well-tuned --text-placeholder falls to
|
|
1932
|
+
3.89:1 dark. */
|
|
1933
|
+
.palette-input::placeholder { color: var(--text-4); }
|
|
1696
1934
|
/* The dialog holds focus here for its whole life and nothing else inside it
|
|
1697
1935
|
takes focus, so a ring around the only focusable element names nothing: the
|
|
1698
1936
|
caret marks the field, the accent edge marks the keyboard's row. The base
|
|
@@ -1711,10 +1949,10 @@ pre code {
|
|
|
1711
1949
|
}
|
|
1712
1950
|
/* each group carries its own breathing room, so a group the query filtered out
|
|
1713
1951
|
contributes none and the list needs no adjacency rule to stay even */
|
|
1714
|
-
.palette-group { padding: var(--space-2)
|
|
1952
|
+
.palette-group { padding: var(--space-2) var(--space-1); } /* block rhythm, then the list panel's gutter */
|
|
1715
1953
|
/* the sidebar group label's treatment: `.label` carries the caps, the tracking
|
|
1716
1954
|
and --text-3, which is --glass-dim inside this panel: 4.67:1 dark, 4.72 light */
|
|
1717
|
-
.palette-group-label { padding: var(--space-1)
|
|
1955
|
+
.palette-group-label { padding: var(--space-1) 10px; }
|
|
1718
1956
|
|
|
1719
1957
|
/* the sidebar's icon treatment: dimmed at rest, full on the active row.
|
|
1720
1958
|
--icon-dim is 0.7 on an opaque surface and 0.90 on glass, which this panel
|
|
@@ -1724,8 +1962,8 @@ pre code {
|
|
|
1724
1962
|
.palette-row.active > svg { opacity: 1; }
|
|
1725
1963
|
.palette-name { flex: 1 1 auto; min-width: 0; } /* a flex child needs this before .truncate can bite */
|
|
1726
1964
|
/* The active row is marked by the shared active-row band and nothing else, and
|
|
1727
|
-
`.menu-item.active` supplies --accent-text. On glass that fill is
|
|
1728
|
-
laid down
|
|
1965
|
+
`.menu-item.active` supplies --accent-text. On glass that fill is the panel's opaque
|
|
1966
|
+
quiet mix, laid down so it cannot composite twice: --accent-text measures 5.95:1
|
|
1729
1967
|
dark and 4.89 light on it.
|
|
1730
1968
|
The palette marks its arrow-key row `.active`, so it has one row state and no
|
|
1731
1969
|
`.focus`: `initSelect` is the only controller that writes that class, over
|
|
@@ -1738,7 +1976,7 @@ pre code {
|
|
|
1738
1976
|
--glass-dim 6.17:1 and 4.96:1 on the shortcut sheet's glass. */
|
|
1739
1977
|
.palette-hint kbd,
|
|
1740
1978
|
.palette-search kbd,
|
|
1741
|
-
.shortcut-keys kbd { background: var(--surface-2); }
|
|
1979
|
+
.shortcut-keys kbd { background: var(--surface-2); --tint-ground: transparent; }
|
|
1742
1980
|
|
|
1743
1981
|
/* No match: a row that names the fix, built from what was typed. */
|
|
1744
1982
|
.palette-empty {
|
|
@@ -1750,9 +1988,8 @@ pre code {
|
|
|
1750
1988
|
@media (prefers-reduced-motion: reduce) {
|
|
1751
1989
|
/* restated at the exit rule's own (0,3,0). A bare `.popover` here is
|
|
1752
1990
|
outranked and the panel keeps its move. */
|
|
1753
|
-
.popover.palette.closing { animation:
|
|
1991
|
+
.popover.palette.closing { animation: fade-out var(--dur-out-near) var(--ease) both; }
|
|
1754
1992
|
}
|
|
1755
|
-
@keyframes palette-out-fade { to { opacity: 0; } }
|
|
1756
1993
|
|
|
1757
1994
|
/* ---- Avatar ---- */
|
|
1758
1995
|
.avatar {
|
|
@@ -1778,12 +2015,14 @@ pre code {
|
|
|
1778
2015
|
.progress {
|
|
1779
2016
|
height: 3px;
|
|
1780
2017
|
background: var(--surface-2);
|
|
2018
|
+
--tint-ground: transparent;
|
|
1781
2019
|
border-radius: var(--r-pill);
|
|
1782
2020
|
overflow: hidden;
|
|
1783
2021
|
}
|
|
1784
2022
|
.progress-bar {
|
|
1785
2023
|
height: 100%;
|
|
1786
2024
|
background: var(--accent);
|
|
2025
|
+
--tint-ground: transparent;
|
|
1787
2026
|
border-radius: var(--r-pill);
|
|
1788
2027
|
transition: width var(--dur-far) var(--ease);
|
|
1789
2028
|
}
|
|
@@ -1791,6 +2030,7 @@ pre code {
|
|
|
1791
2030
|
/* ---- Skeleton (no shimmer, by spec) ---- */
|
|
1792
2031
|
.skeleton {
|
|
1793
2032
|
background: var(--surface-2);
|
|
2033
|
+
--tint-ground: transparent;
|
|
1794
2034
|
border-radius: var(--r-1);
|
|
1795
2035
|
}
|
|
1796
2036
|
|
|
@@ -1814,16 +2054,24 @@ pre code {
|
|
|
1814
2054
|
.empty-title { font: 500 var(--t-4)/1.3 var(--font-sans); color: var(--text); }
|
|
1815
2055
|
.empty-state .btn { margin-top: var(--space-3); }
|
|
1816
2056
|
|
|
1817
|
-
/* ---- Spinner (loading outside buttons) ----
|
|
2057
|
+
/* ---- Spinner (loading outside buttons) ----
|
|
2058
|
+
The ring is a graphic, so it holds the 3:1 floor: --accent is 7.49:1 on
|
|
2059
|
+
--bg and 6.48 on --surface-1 dark, 4.09 / 4.60 light. --accent-line was
|
|
2060
|
+
2.25 / 1.56 on --bg. */
|
|
1818
2061
|
.spinner {
|
|
1819
2062
|
display: inline-block;
|
|
1820
2063
|
width: 14px;
|
|
1821
2064
|
height: 14px;
|
|
1822
|
-
border: 2px solid var(--accent
|
|
2065
|
+
border: 2px solid var(--accent);
|
|
1823
2066
|
border-top-color: transparent;
|
|
1824
2067
|
border-radius: 50%;
|
|
1825
2068
|
animation: spin 0.6s linear infinite;
|
|
1826
2069
|
}
|
|
2070
|
+
/* rests under reduce like the button's ring; the consumers keep their own
|
|
2071
|
+
done-state cues beside it (a check glyph, a chevron, a dirty hairline) */
|
|
2072
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2073
|
+
.spinner { animation: none; }
|
|
2074
|
+
}
|
|
1827
2075
|
|
|
1828
2076
|
/* ---- Modal ----
|
|
1829
2077
|
Four projects hand-built this against "out of scope". The entrance
|
|
@@ -1841,6 +2089,7 @@ pre code {
|
|
|
1841
2089
|
}
|
|
1842
2090
|
.modal {
|
|
1843
2091
|
background: var(--surface-1);
|
|
2092
|
+
--tint-ground: transparent;
|
|
1844
2093
|
border-radius: var(--r-3);
|
|
1845
2094
|
box-shadow: var(--elev-float);
|
|
1846
2095
|
width: min(560px, 100%);
|
|
@@ -1908,30 +2157,47 @@ pre code {
|
|
|
1908
2157
|
white-space: nowrap;
|
|
1909
2158
|
}
|
|
1910
2159
|
|
|
1911
|
-
/* ---- Range slider ----
|
|
2160
|
+
/* ---- Range slider ----
|
|
2161
|
+
Real elements, no native range: a UA shadow thumb paints the platform pointer over
|
|
2162
|
+
the ring whatever cursor the page declares (the thumb
|
|
2163
|
+
pseudo computed `none` and the pointer still painted), the same family the native
|
|
2164
|
+
select and the file input retired for. The thumb rides the fill's right edge, so one
|
|
2165
|
+
width drives both. */
|
|
1912
2166
|
.slider {
|
|
1913
|
-
|
|
1914
|
-
|
|
2167
|
+
position: relative;
|
|
2168
|
+
display: flex;
|
|
2169
|
+
align-items: center;
|
|
2170
|
+
width: 100%;
|
|
2171
|
+
height: 14px;
|
|
2172
|
+
cursor: pointer;
|
|
2173
|
+
touch-action: none;
|
|
2174
|
+
}
|
|
2175
|
+
.slider-track {
|
|
2176
|
+
position: relative;
|
|
1915
2177
|
width: 100%;
|
|
1916
2178
|
height: 4px;
|
|
1917
2179
|
background: var(--surface-2);
|
|
2180
|
+
--tint-ground: transparent;
|
|
1918
2181
|
border-radius: var(--r-pill);
|
|
1919
|
-
cursor: pointer;
|
|
1920
2182
|
}
|
|
1921
|
-
.slider
|
|
1922
|
-
|
|
1923
|
-
|
|
2183
|
+
.slider-fill {
|
|
2184
|
+
position: relative; /* the thumb's containing block: without it the thumb anchors to the track and ignores the fill width */
|
|
2185
|
+
height: 100%;
|
|
2186
|
+
width: 0;
|
|
1924
2187
|
background: var(--accent);
|
|
1925
|
-
|
|
1926
|
-
border-radius:
|
|
1927
|
-
cursor: pointer;
|
|
2188
|
+
--tint-ground: transparent;
|
|
2189
|
+
border-radius: var(--r-pill);
|
|
1928
2190
|
}
|
|
1929
|
-
.slider
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2191
|
+
.slider-thumb {
|
|
2192
|
+
position: absolute;
|
|
2193
|
+
right: -7px;
|
|
2194
|
+
top: 50%;
|
|
2195
|
+
transform: translateY(-50%);
|
|
2196
|
+
width: 14px;
|
|
2197
|
+
height: 14px;
|
|
1933
2198
|
border-radius: 50%;
|
|
1934
|
-
|
|
2199
|
+
background: var(--accent);
|
|
2200
|
+
--tint-ground: transparent;
|
|
1935
2201
|
}
|
|
1936
2202
|
.slider:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
|
|
1937
2203
|
|
|
@@ -1958,13 +2224,19 @@ pre code {
|
|
|
1958
2224
|
see "Reveal JS" in the skill. */
|
|
1959
2225
|
.section-sticky {
|
|
1960
2226
|
position: sticky;
|
|
1961
|
-
/*
|
|
1962
|
-
|
|
1963
|
-
|
|
2227
|
+
/* --pin-top, never a bar's own height: it is the height of whatever bar this
|
|
2228
|
+
page actually holds, derived at the root beside the navbar and the topbar,
|
|
2229
|
+
and a sidebar shell holds none, so the header pins to the viewport top.
|
|
2230
|
+
It has to be the bar that is really there, or the header pins inside one
|
|
2231
|
+
that outranks it on z-index and loses its top edge behind it - or, on a
|
|
2232
|
+
page with no bar at all, hangs a bar's height below nothing. */
|
|
2233
|
+
top: var(--pin-top);
|
|
1964
2234
|
z-index: 5;
|
|
1965
2235
|
width: 100%;
|
|
1966
2236
|
padding: var(--space-2) 0;
|
|
1967
|
-
|
|
2237
|
+
/* no tone at rest: a header in flow paints over whatever the page shows
|
|
2238
|
+
there (the sky, a band), and the bar's tone exists only while stuck */
|
|
2239
|
+
background: transparent;
|
|
1968
2240
|
/* the hairline exists in both states so the stuck swap moves a colour,
|
|
1969
2241
|
never adds a border pixel */
|
|
1970
2242
|
border-bottom: 1px solid transparent;
|
|
@@ -1987,6 +2259,7 @@ pre code {
|
|
|
1987
2259
|
hairline is what says it is there. An engine with no backdrop-filter and
|
|
1988
2260
|
a viewer with reduced transparency then see one surface, not two. */
|
|
1989
2261
|
background: var(--bg);
|
|
2262
|
+
--tint-ground: transparent;
|
|
1990
2263
|
/* full-bleed: the bar spans the viewport while its toolbar stays on the
|
|
1991
2264
|
page column, via the matching inline padding. --vp-w is the
|
|
1992
2265
|
scrollbar-excluded viewport width the sticky controller writes;
|
|
@@ -2009,9 +2282,13 @@ pre code {
|
|
|
2009
2282
|
.section-sticky, .section-sticky.is-stuck { transition: none; }
|
|
2010
2283
|
}
|
|
2011
2284
|
/* A flush card header takes the card's top radius, the same way the setting
|
|
2012
|
-
row's edge rows do
|
|
2013
|
-
|
|
2014
|
-
.card > .section-sticky:first-child {
|
|
2285
|
+
row's edge rows do, and paints the card tone while it rides inside the
|
|
2286
|
+
card; the stuck state spans the viewport as a page bar and keeps --bg. */
|
|
2287
|
+
.card > .section-sticky:first-child {
|
|
2288
|
+
background: var(--surface-1);
|
|
2289
|
+
--tint-ground: transparent;
|
|
2290
|
+
border-radius: var(--r-2) var(--r-2) 0 0;
|
|
2291
|
+
}
|
|
2015
2292
|
.card > .section-sticky.is-stuck { border-radius: 0; }
|
|
2016
2293
|
/* the header's toolbar is single-line chrome, not the labeled-field row
|
|
2017
2294
|
.toolbar defaults to (flex-end aligns a stack of labelled fields at the
|
|
@@ -2038,13 +2315,14 @@ pre code {
|
|
|
2038
2315
|
z-index: 1100;
|
|
2039
2316
|
padding: var(--space-2) var(--space-4);
|
|
2040
2317
|
background: var(--surface-1);
|
|
2318
|
+
--tint-ground: transparent;
|
|
2041
2319
|
color: var(--text);
|
|
2042
2320
|
border-radius: var(--r-pill);
|
|
2043
2321
|
text-decoration: none;
|
|
2044
2322
|
transform: translateY(-160%);
|
|
2045
2323
|
/* the float shadow belongs to the revealed chip. The hidden one parks
|
|
2046
2324
|
60px above the viewport, but its shadow still painted ~4 levels deep
|
|
2047
|
-
into the top-left rows (measured 2026-08-31,
|
|
2325
|
+
into the top-left rows (measured 2026-08-31, the "shadow on top left
|
|
2048
2326
|
side" report); visibility:hidden would drop the link from the tab
|
|
2049
2327
|
order, so the shadow waits for focus instead. */
|
|
2050
2328
|
box-shadow: none;
|
|
@@ -2080,7 +2358,7 @@ html:has(.reveal-hover:focus-visible) :focus-within > .reveal-hover { opacity: 1
|
|
|
2080
2358
|
/* ---- Scrollbar ----
|
|
2081
2359
|
THE NATIVE ONE IS GONE, and the reason is the cursor. A scrollbar is a UA
|
|
2082
2360
|
shadow tree, so `cursor: none` does not reach it and the platform pointer
|
|
2083
|
-
paints over the ring for as long as you drag one
|
|
2361
|
+
paints over the ring for as long as you drag one, the
|
|
2084
2362
|
same family as the stretched file input and the native `<select>` popup,
|
|
2085
2363
|
both already retired for it. `initScrollbars` draws a real element instead,
|
|
2086
2364
|
which the ring paints over like any other surface.
|
|
@@ -2107,22 +2385,23 @@ html.has-drawn-scrollbars *::-webkit-scrollbar { width: 0; height: 0; }
|
|
|
2107
2385
|
position: fixed;
|
|
2108
2386
|
z-index: 999; /* over the modal, under the toast stack that can cover it */
|
|
2109
2387
|
background: var(--scrollbar-thumb);
|
|
2388
|
+
--tint-ground: transparent;
|
|
2110
2389
|
border-radius: var(--r-pill);
|
|
2111
2390
|
opacity: 0;
|
|
2112
2391
|
pointer-events: none;
|
|
2113
2392
|
touch-action: none;
|
|
2114
2393
|
/* THE RING READS ITS HOVER STATE OFF THIS DECLARATION. `cursor-rules.js`
|
|
2115
|
-
collects every selector declaring `cursor: pointer` and the ring
|
|
2116
|
-
over that list, so the drawn thumb is a target like any other control and
|
|
2394
|
+
collects every selector declaring `cursor: pointer` and the ring takes its
|
|
2395
|
+
hover state over that list, so the drawn thumb is a target like any other control and
|
|
2117
2396
|
the press state follows it through a drag. The value itself never paints
|
|
2118
|
-
while the ring is armed: `custom-cursor`
|
|
2119
|
-
everything, and the bar exists only on a page whose module ran. */
|
|
2397
|
+
while the ring is armed: `custom-cursor` (on v3, beside `cursor-v3` or `#cursor-paint`)
|
|
2398
|
+
forces `cursor: none` on everything, and the bar exists only on a page whose module ran. */
|
|
2120
2399
|
cursor: pointer;
|
|
2121
2400
|
transition: opacity var(--dur-tint) var(--ease), background var(--dur-tint) var(--ease);
|
|
2122
2401
|
}
|
|
2123
2402
|
.ui-scrollbar.is-on { opacity: 1; pointer-events: auto; }
|
|
2124
2403
|
.ui-scrollbar:hover,
|
|
2125
|
-
.ui-scrollbar.is-drag { background: var(--text-4); }
|
|
2404
|
+
.ui-scrollbar.is-drag { background: var(--text-4); --tint-ground: transparent; }
|
|
2126
2405
|
.ui-scrollbar-y { width: 6px; }
|
|
2127
2406
|
.ui-scrollbar-x { height: 6px; }
|
|
2128
2407
|
|
|
@@ -2130,6 +2409,7 @@ html.has-drawn-scrollbars *::-webkit-scrollbar { width: 0; height: 0; }
|
|
|
2130
2409
|
hr, .divider {
|
|
2131
2410
|
height: 1px;
|
|
2132
2411
|
background: var(--line);
|
|
2412
|
+
--tint-ground: transparent;
|
|
2133
2413
|
border: none;
|
|
2134
2414
|
margin: var(--space-6) 0;
|
|
2135
2415
|
}
|
|
@@ -2182,6 +2462,7 @@ hr, .divider {
|
|
|
2182
2462
|
--cbtn-slot: 0px;
|
|
2183
2463
|
--cbtn-rise: 0px;
|
|
2184
2464
|
background: var(--surface-1);
|
|
2465
|
+
--tint-ground: transparent;
|
|
2185
2466
|
border-right: 1px solid var(--line-soft);
|
|
2186
2467
|
width: 252px;
|
|
2187
2468
|
display: flex;
|
|
@@ -2211,18 +2492,50 @@ hr, .divider {
|
|
|
2211
2492
|
margin-bottom: var(--space-3);
|
|
2212
2493
|
flex-shrink: 0; /* pinned: the nav is the only item that gives */
|
|
2213
2494
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2495
|
+
/* ---- Brand mark ----
|
|
2496
|
+
The brand's image slot: a squircle at the bar's control height, a 2px ring
|
|
2497
|
+
in --brand-ring (rose, the identity accent), and the striped placeholder
|
|
2498
|
+
when empty. It holds an <img> that fills it; the ring radius is inherited.
|
|
2499
|
+
The empty slot carries no text: the wordmark beside it keeps the accessible
|
|
2500
|
+
name, so the slot is decorative (aria-hidden on an empty one, alt="" on its
|
|
2501
|
+
img). The stripe reuses .img-placeholder's pattern on --well, never a new
|
|
2502
|
+
literal. */
|
|
2503
|
+
.sona {
|
|
2220
2504
|
display: inline-flex;
|
|
2221
2505
|
align-items: center;
|
|
2222
2506
|
justify-content: center;
|
|
2223
|
-
|
|
2507
|
+
width: 32px;
|
|
2508
|
+
height: 32px;
|
|
2509
|
+
border-radius: var(--r-1);
|
|
2510
|
+
border: 2px solid var(--brand-ring);
|
|
2511
|
+
background-color: var(--well);
|
|
2512
|
+
--tint-ground: transparent;
|
|
2513
|
+
background-image: repeating-linear-gradient(
|
|
2514
|
+
-45deg,
|
|
2515
|
+
color-mix(in srgb, var(--text-4) 18%, var(--tint-ground)) 0 1px,
|
|
2516
|
+
transparent 1px 9px
|
|
2517
|
+
);
|
|
2224
2518
|
flex-shrink: 0;
|
|
2225
2519
|
}
|
|
2520
|
+
.sona img {
|
|
2521
|
+
width: 100%;
|
|
2522
|
+
height: 100%;
|
|
2523
|
+
object-fit: cover;
|
|
2524
|
+
border-radius: inherit;
|
|
2525
|
+
}
|
|
2526
|
+
/* the sidebar logo row has room for a larger mark */
|
|
2527
|
+
.sidebar-logo > .sona {
|
|
2528
|
+
width: 36px;
|
|
2529
|
+
height: 36px;
|
|
2530
|
+
}
|
|
2531
|
+
/* under 768 the navbar hides the wordmark; the sona matches the mark size it
|
|
2532
|
+
replaced so the nav link band keeps its width */
|
|
2533
|
+
@media (max-width: 768px) {
|
|
2534
|
+
.navbar-brand > .sona {
|
|
2535
|
+
width: 28px;
|
|
2536
|
+
height: 28px;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2226
2539
|
.logo-name {
|
|
2227
2540
|
font: 600 var(--t-4)/1.25 var(--font-sans);
|
|
2228
2541
|
letter-spacing: -0.02em;
|
|
@@ -2238,6 +2551,7 @@ hr, .divider {
|
|
|
2238
2551
|
/* inline padding lives with the rail geometry: `.sidebar .sidebar-search`
|
|
2239
2552
|
derives it from --rail and outranks anything stated here */
|
|
2240
2553
|
background: var(--well);
|
|
2554
|
+
--tint-ground: transparent;
|
|
2241
2555
|
border-radius: var(--r-1);
|
|
2242
2556
|
color: var(--text-3);
|
|
2243
2557
|
font-size: var(--t-2);
|
|
@@ -2246,6 +2560,7 @@ hr, .divider {
|
|
|
2246
2560
|
.sidebar-search kbd {
|
|
2247
2561
|
font: 500 10px/1 var(--font-mono);
|
|
2248
2562
|
background: var(--surface-1);
|
|
2563
|
+
--tint-ground: transparent;
|
|
2249
2564
|
border: none;
|
|
2250
2565
|
padding: 3px 6px;
|
|
2251
2566
|
border-radius: 5px;
|
|
@@ -2282,21 +2597,20 @@ hr, .divider {
|
|
|
2282
2597
|
user-select: none;
|
|
2283
2598
|
transition: color var(--dur-tint) var(--ease), background var(--dur-tint) var(--ease);
|
|
2284
2599
|
}
|
|
2285
|
-
.nav-item:hover { color: var(--text-2); background: var(--surface-2); }
|
|
2600
|
+
.nav-item:hover { color: var(--text-2); background: var(--surface-2); --tint-ground: transparent; }
|
|
2286
2601
|
.nav-item.active { color: var(--text); font-weight: 500; }
|
|
2287
|
-
/* The active row's band is one rule across the three rows that paint one
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
.
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
}
|
|
2602
|
+
/* The active row's band is one rule across the three rows that paint one. It
|
|
2603
|
+
is declared down in the tree block, the last of the three hover rules it has
|
|
2604
|
+
to outrank: `.nav-item:hover` above, `.menu-item:hover` for the palette and
|
|
2605
|
+
`.tree-row:hover` below all tie it at (0,2,0), and a hovered selected row
|
|
2606
|
+
keeps its accent rather than repainting grey, the treatment
|
|
2607
|
+
`.menu-item.active` already had. The shape is NOT here: each row carries
|
|
2608
|
+
--r-1 at rest, so the band arrives on a corner that is already rounded and
|
|
2609
|
+
nothing animates. ONE accent mark appears per row, and it is this band: the
|
|
2610
|
+
2px capsule the tree and the palette added on top of it is gone, for the same
|
|
2611
|
+
reason `.nav-indicator` was dropped. The sidebar's
|
|
2612
|
+
dot is not a second mark, since it is on every row at rest and only changes
|
|
2613
|
+
shape here. */
|
|
2300
2614
|
/* The dot IS the marker: the active row's own dot grows into a capsule, and the
|
|
2301
2615
|
leaving row's shrinks back, on the same frame. One element changing shape,
|
|
2302
2616
|
never two swapping. r-pill instead of 50% so the grown state stays a capsule;
|
|
@@ -2307,25 +2621,31 @@ hr, .divider {
|
|
|
2307
2621
|
border-radius: var(--r-pill);
|
|
2308
2622
|
flex-shrink: 0;
|
|
2309
2623
|
background: var(--text-4);
|
|
2624
|
+
--tint-ground: transparent;
|
|
2310
2625
|
transition:
|
|
2311
2626
|
height var(--dur-near) var(--ease),
|
|
2312
2627
|
background var(--dur-tint) var(--ease),
|
|
2313
2628
|
box-shadow var(--dur-tint) var(--ease);
|
|
2314
2629
|
}
|
|
2315
|
-
.nav-item:hover .nav-dot { background: var(--text-3); }
|
|
2316
|
-
.nav-item.active .nav-dot { height: var(--space-4); background: var(--accent); box-shadow: var(--accent-glow); }
|
|
2630
|
+
.nav-item:hover .nav-dot { background: var(--text-3); --tint-ground: transparent; }
|
|
2631
|
+
.nav-item.active .nav-dot { height: var(--space-4); background: var(--accent); --tint-ground: transparent; box-shadow: var(--accent-glow); }
|
|
2317
2632
|
.nav-item svg { width: 14px; height: 14px; flex-shrink: 0; opacity: var(--icon-dim); }
|
|
2318
2633
|
.nav-item.active svg { opacity: 1; }
|
|
2319
2634
|
.nav-badge {
|
|
2320
2635
|
font: 500 10px/1 var(--font-mono);
|
|
2321
2636
|
background: var(--surface-2);
|
|
2637
|
+
--tint-ground: transparent;
|
|
2322
2638
|
color: var(--text-3);
|
|
2323
2639
|
border-radius: var(--r-pill);
|
|
2324
2640
|
padding: 3px 7px;
|
|
2325
2641
|
margin-left: auto;
|
|
2326
2642
|
transition: background var(--dur-tint) var(--ease), color var(--dur-tint) var(--ease);
|
|
2327
2643
|
}
|
|
2328
|
-
|
|
2644
|
+
/* on the active row the badge drops its own fill and sits on the band: a
|
|
2645
|
+
quiet fill on the quiet band read 4.41:1 dark on the sidebar. On the band
|
|
2646
|
+
it reads 5.82:1 dark and, with the shared remix, 5.48
|
|
2647
|
+
light. */
|
|
2648
|
+
.nav-item.active .nav-badge { background: transparent; color: var(--accent-text); }
|
|
2329
2649
|
.sidebar-user {
|
|
2330
2650
|
margin-top: var(--space-2);
|
|
2331
2651
|
padding-block: var(--space-4) 0; /* inline side is a --rail function below */
|
|
@@ -2401,14 +2721,14 @@ hr, .divider {
|
|
|
2401
2721
|
padding-block: calc(var(--cbtn-rise) * var(--rail) + var(--cbtn-slot) * (1 - var(--rail))) var(--space-4);
|
|
2402
2722
|
padding-inline: calc(var(--space-4) + 3px * (1 - var(--rail)))
|
|
2403
2723
|
calc(var(--space-4) + var(--cbtn-slot) * var(--rail));
|
|
2404
|
-
border-bottom-color: color-mix(in srgb, var(--line-soft) calc(var(--rail) * 100%),
|
|
2724
|
+
border-bottom-color: color-mix(in srgb, var(--line-soft) calc(var(--rail) * 100%), var(--tint-ground));
|
|
2405
2725
|
}
|
|
2406
2726
|
|
|
2407
2727
|
/* the brand block clips at the slot's edge. The row clips at its PADDING box,
|
|
2408
2728
|
which is outside the slot, so a name longer than the column paints under
|
|
2409
2729
|
the chevron; and a nowrap flex item will not shrink past its text without
|
|
2410
2730
|
an explicit min-width. */
|
|
2411
|
-
.sidebar .sidebar-logo > :not(.
|
|
2731
|
+
.sidebar .sidebar-logo > :not(.sona):not(.sidebar-collapse) {
|
|
2412
2732
|
min-width: 0;
|
|
2413
2733
|
overflow: hidden;
|
|
2414
2734
|
}
|
|
@@ -2476,7 +2796,7 @@ hr, .divider {
|
|
|
2476
2796
|
transition: color var(--dur-tint) var(--ease), background var(--dur-tint) var(--ease);
|
|
2477
2797
|
}
|
|
2478
2798
|
/* 4.73:1 dark and 5.55:1 light at rest, 6.17 and 7.00 on the hover fill */
|
|
2479
|
-
.sidebar-collapse:hover { color: var(--text-2); background: var(--surface-2); }
|
|
2799
|
+
.sidebar-collapse:hover { color: var(--text-2); background: var(--surface-2); --tint-ground: transparent; }
|
|
2480
2800
|
.sidebar-collapse svg {
|
|
2481
2801
|
width: 14px;
|
|
2482
2802
|
height: 14px;
|
|
@@ -2567,11 +2887,16 @@ hr, .divider {
|
|
|
2567
2887
|
padding: 0 var(--space-8);
|
|
2568
2888
|
margin-inline: auto;
|
|
2569
2889
|
background: var(--surface-1);
|
|
2890
|
+
--tint-ground: transparent;
|
|
2570
2891
|
border-bottom: 1px solid var(--line-soft);
|
|
2571
2892
|
position: sticky;
|
|
2572
2893
|
top: 0;
|
|
2573
2894
|
z-index: 100;
|
|
2574
2895
|
}
|
|
2896
|
+
/* the pin derivation: a page holding this bar starts its
|
|
2897
|
+
pinned things under it, and a page holding none pins to the viewport top.
|
|
2898
|
+
--navbar-h itself stays the bar's own height and nothing else. */
|
|
2899
|
+
html:has(.navbar) { --pin-top: var(--navbar-h); }
|
|
2575
2900
|
/* The width change is a NAMED DEVIATION, same class as the rail collapse:
|
|
2576
2901
|
a bar resizing its own row; nothing below it reflows. Arrival at the
|
|
2577
2902
|
band is the entrance leg, the return to the card the 0.6x exit, each
|
|
@@ -2582,9 +2907,8 @@ hr, .divider {
|
|
|
2582
2907
|
|
|
2583
2908
|
Both legs run the slide curve, the one the inks run. --ease-out
|
|
2584
2909
|
front-loads 88 percent of its travel, which on a width change reads as
|
|
2585
|
-
a jump followed by a linear creep
|
|
2586
|
-
|
|
2587
|
-
the middle and lands soft. The card also floats --space-3 off the top
|
|
2910
|
+
a jump followed by a linear creep; the slide curve starts gentle, moves
|
|
2911
|
+
quick through the middle and lands soft. The card also floats --space-3 off the top
|
|
2588
2912
|
edge, on transform and never on margin: a margin reflows the page
|
|
2589
2913
|
below on every swap, a transform only moves the bar. Reduced motion
|
|
2590
2914
|
keeps the float and snaps the moves (transition: none below). */
|
|
@@ -2617,7 +2941,7 @@ hr, .divider {
|
|
|
2617
2941
|
/* the dock's cover strip: while the card is away, a fixed page-toned
|
|
2618
2942
|
band sits over the float's gap, so the transform can glide the bar
|
|
2619
2943
|
home instead of snapping and nothing paints in the strip at any
|
|
2620
|
-
scroll speed
|
|
2944
|
+
scroll speed. It lands the frame
|
|
2621
2945
|
scrolling starts, hides behind the docked band, and is gone at rest,
|
|
2622
2946
|
where the dot grid owns the strip.
|
|
2623
2947
|
|
|
@@ -2632,6 +2956,7 @@ hr, .divider {
|
|
|
2632
2956
|
inset: 0 0 auto 0;
|
|
2633
2957
|
height: calc(var(--space-3) + 1px);
|
|
2634
2958
|
background: var(--bg);
|
|
2959
|
+
--tint-ground: transparent;
|
|
2635
2960
|
z-index: 99;
|
|
2636
2961
|
}
|
|
2637
2962
|
}
|
|
@@ -2640,8 +2965,9 @@ hr, .divider {
|
|
|
2640
2965
|
}
|
|
2641
2966
|
.navbar-brand {
|
|
2642
2967
|
display: flex;
|
|
2643
|
-
flex-direction:
|
|
2644
|
-
|
|
2968
|
+
flex-direction: row;
|
|
2969
|
+
align-items: center;
|
|
2970
|
+
gap: var(--space-2);
|
|
2645
2971
|
padding-right: var(--space-5);
|
|
2646
2972
|
margin-right: var(--space-3);
|
|
2647
2973
|
border-right: 1px solid var(--line-soft);
|
|
@@ -2693,7 +3019,7 @@ hr, .divider {
|
|
|
2693
3019
|
user-select: none;
|
|
2694
3020
|
white-space: nowrap;
|
|
2695
3021
|
}
|
|
2696
|
-
.navbar-link:hover { color: var(--text); background: var(--surface-2); text-decoration: none; }
|
|
3022
|
+
.navbar-link:hover { color: var(--text); background: var(--surface-2); --tint-ground: transparent; text-decoration: none; }
|
|
2697
3023
|
.navbar-link.active { color: var(--accent-text); font-weight: 500; }
|
|
2698
3024
|
.navbar-link svg { width: 14px; height: 14px; flex-shrink: 0; opacity: var(--icon-dim); }
|
|
2699
3025
|
.navbar-link.active svg { opacity: 1; }
|
|
@@ -2725,6 +3051,7 @@ hr, .divider {
|
|
|
2725
3051
|
padding: 0 var(--space-4);
|
|
2726
3052
|
height: var(--topbar-h);
|
|
2727
3053
|
background: var(--surface-1);
|
|
3054
|
+
--tint-ground: transparent;
|
|
2728
3055
|
border-bottom: 1px solid var(--line-soft);
|
|
2729
3056
|
position: sticky;
|
|
2730
3057
|
top: 0;
|
|
@@ -2764,10 +3091,12 @@ hr, .divider {
|
|
|
2764
3091
|
|
|
2765
3092
|
@media (max-width: 768px) {
|
|
2766
3093
|
.mobile-topbar { display: flex; }
|
|
2767
|
-
/* the
|
|
2768
|
-
the
|
|
2769
|
-
|
|
2770
|
-
|
|
3094
|
+
/* the topbar's own display condition: under it, a page holding the topbar
|
|
3095
|
+
re-derives the pin at the mobile bar's height. Scoped to the bar's
|
|
3096
|
+
presence and not written bare, so a narrow page holding a navbar and no
|
|
3097
|
+
topbar keeps the navbar's 56; ties the navbar derivation at (0,1,1) and
|
|
3098
|
+
wins by coming after it. */
|
|
3099
|
+
html:has(.mobile-topbar) { --pin-top: var(--topbar-h); }
|
|
2771
3100
|
.sidebar {
|
|
2772
3101
|
position: fixed;
|
|
2773
3102
|
top: 0;
|
|
@@ -2828,9 +3157,6 @@ hr, .divider {
|
|
|
2828
3157
|
/* padding alone cannot promise the floor: the tab's own line-height and font
|
|
2829
3158
|
size decide the rest, and the pair measured 40.8px */
|
|
2830
3159
|
.tab { padding-top: 12px; padding-bottom: 12px; min-height: var(--ctl-lg); }
|
|
2831
|
-
/* the palette's rows are .palette-row, not .menu-item, so the row floor above
|
|
2832
|
-
never reached them: measured 21.7px */
|
|
2833
|
-
.palette-row { min-height: var(--ctl-lg); }
|
|
2834
3160
|
/* A segment carries the floor itself: a segmented control has no row to hand
|
|
2835
3161
|
it to, and the track grows with the segment, so the ink follows off its own
|
|
2836
3162
|
`calc(100% - 6px)` with no rule here. Measured 26.4px. */
|
|
@@ -2856,16 +3182,16 @@ hr, .divider {
|
|
|
2856
3182
|
Fades stay, transforms go. Nothing waits on transitionend, so no state
|
|
2857
3183
|
feedback is lost and no script is stranded. */
|
|
2858
3184
|
@media (prefers-reduced-motion: reduce) {
|
|
2859
|
-
.
|
|
2860
|
-
.toast { animation:
|
|
2861
|
-
.toast.removing { animation:
|
|
2862
|
-
.modal { animation:
|
|
2863
|
-
.modal-backdrop.closing .modal { animation:
|
|
2864
|
-
.popover { animation:
|
|
3185
|
+
.card-interactive:hover, .swatch:hover { transform: none; }
|
|
3186
|
+
.toast { animation: fade-in var(--dur-near) var(--ease) both; }
|
|
3187
|
+
.toast.removing { animation: fade-out var(--dur-out-near) var(--ease) both; }
|
|
3188
|
+
.modal { animation: fade-in var(--dur-near) var(--ease) both; }
|
|
3189
|
+
.modal-backdrop.closing .modal { animation: fade-out var(--dur-out-far) var(--ease) both; }
|
|
3190
|
+
.popover { animation: fade-in var(--dur-near) var(--ease) both; }
|
|
2865
3191
|
/* at (0,2,0), because the base exit is too and this block adds no specificity.
|
|
2866
3192
|
The daterange panel rides along for the same reason it rides the base. */
|
|
2867
3193
|
.popover.closing,
|
|
2868
|
-
.daterange.closing > .daterange-panel { animation-name:
|
|
3194
|
+
.daterange.closing > .daterange-panel { animation-name: fade-out; }
|
|
2869
3195
|
.reveal { transform: none; }
|
|
2870
3196
|
.zoom-img img, img.zoom-img { transform: none; }
|
|
2871
3197
|
.tab-ink, .navbar-ink, .tabs-boxed-ink { transition: opacity var(--dur-tint) var(--ease); }
|
|
@@ -2887,22 +3213,20 @@ hr, .divider {
|
|
|
2887
3213
|
base selector's own specificity so the (0,2,2) rules above cannot outrank */
|
|
2888
3214
|
.checkbox-label input[type="checkbox"]::after,
|
|
2889
3215
|
.radio-label input[type="radio"]::after { transition: opacity var(--dur-near) var(--ease-out); }
|
|
3216
|
+
/* the knob arrives; the track's own fill still fades */
|
|
3217
|
+
.toggle::after { transition: none; }
|
|
2890
3218
|
.tab-panel.active,
|
|
2891
3219
|
.form-summary { animation-name: panel-in-fade; }
|
|
2892
3220
|
.code-copy .icon-check { animation: none; }
|
|
2893
3221
|
}
|
|
2894
|
-
|
|
2895
|
-
|
|
3222
|
+
/* the reduce fallbacks are three bodies, not one per component: `panel-in-fade`
|
|
3223
|
+
holds the element's own end opacity, `fade-in` forces it to 1, and
|
|
3224
|
+
`fade-out` is the exit every one of them leaves on. */
|
|
2896
3225
|
@keyframes panel-in-fade { from { opacity: 0; } }
|
|
2897
3226
|
@media (prefers-reduced-motion: reduce) and (max-width: 768px) {
|
|
2898
3227
|
.sidebar { transform: none; opacity: 0; transition: opacity var(--dur-out-far) var(--ease); }
|
|
2899
3228
|
.sidebar.open { opacity: 1; }
|
|
2900
3229
|
}
|
|
2901
|
-
@keyframes toast-in-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
2902
|
-
@keyframes toast-out-fade { to { opacity: 0; } }
|
|
2903
|
-
@keyframes popover-in-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
2904
|
-
@keyframes modal-in-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
2905
|
-
@keyframes modal-out-fade { to { opacity: 0; } }
|
|
2906
3230
|
|
|
2907
3231
|
/* ---- Theme switch ----
|
|
2908
3232
|
The dataset.theme swap crossfades the whole page. document.startViewTransition
|
|
@@ -2990,15 +3314,15 @@ html[data-theme="light"] [data-theme-toggle] .icon-sun {
|
|
|
2990
3314
|
/* no control-height floor on a fine pointer: a header is not a control row, and
|
|
2991
3315
|
the 30px one made a sortable header 50.5px against a plain one's 31.5, so the
|
|
2992
3316
|
same table header rendered two heights. It is 36.5 now. The 44px tap floor
|
|
2993
|
-
lives in the coarse block and is untouched.
|
|
3317
|
+
lives in the coarse block and is untouched. */
|
|
2994
3318
|
transition: color var(--dur-tint) var(--ease);
|
|
2995
3319
|
}
|
|
2996
3320
|
.th-sort:hover { color: var(--text); }
|
|
2997
3321
|
|
|
2998
3322
|
/* One fixed box holding two sprite glyphs in one grid cell, the construction the
|
|
2999
3323
|
theme toggle's sun and moon already use: `#i-sort` while the column is unsorted,
|
|
3000
|
-
the direction chevron once it is, turned for descending.
|
|
3001
|
-
|
|
3324
|
+
the direction chevron once it is, turned for descending. The box is squared
|
|
3325
|
+
so a 16-viewBox symbol could drop in; the pair of hand-drawn
|
|
3002
3326
|
8 x 4 chevrons it replaces was the last glyph in the system no guard could read.
|
|
3003
3327
|
The box stays fixed at every state, so the glyph can never shift the column. */
|
|
3004
3328
|
.th-sort-icon {
|
|
@@ -3043,30 +3367,33 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3043
3367
|
}
|
|
3044
3368
|
.table-wrap tbody tr.row-selected {
|
|
3045
3369
|
background: color-mix(in srgb, var(--accent) 14%, var(--well));
|
|
3370
|
+
--tint-ground: transparent;
|
|
3046
3371
|
box-shadow: inset 2px 0 0 var(--accent);
|
|
3047
3372
|
}
|
|
3048
3373
|
[data-theme="light"] .table-wrap tbody tr.row-selected {
|
|
3049
3374
|
background: color-mix(in srgb, var(--accent) 12%, var(--surface-1));
|
|
3375
|
+
--tint-ground: transparent;
|
|
3050
3376
|
}
|
|
3051
3377
|
/* selection outranks hover: the row keeps its state under the pointer */
|
|
3052
3378
|
.table-wrap tbody tr.row-selected:hover {
|
|
3053
3379
|
background: color-mix(in srgb, var(--accent) 14%, var(--well));
|
|
3380
|
+
--tint-ground: transparent;
|
|
3054
3381
|
}
|
|
3055
3382
|
[data-theme="light"] .table-wrap tbody tr.row-selected:hover {
|
|
3056
3383
|
background: color-mix(in srgb, var(--accent) 12%, var(--surface-1));
|
|
3384
|
+
--tint-ground: transparent;
|
|
3057
3385
|
}
|
|
3058
3386
|
/* a fresh row set fades in staggered (sort and page changes); opacity only,
|
|
3059
|
-
a transform on a table-row is one engine's bug. The
|
|
3060
|
-
the class to restart it. */
|
|
3387
|
+
a transform on a table-row is one engine's bug. The page's render pass
|
|
3388
|
+
re-adds the class to restart it. */
|
|
3061
3389
|
.table-wrap tbody.rows-in tr {
|
|
3062
|
-
animation:
|
|
3390
|
+
animation: panel-in-fade var(--dur-near) var(--ease-out) both;
|
|
3063
3391
|
}
|
|
3064
3392
|
.table-wrap tbody.rows-in tr:nth-child(2) { animation-delay: calc(var(--stagger) * 1); }
|
|
3065
3393
|
.table-wrap tbody.rows-in tr:nth-child(3) { animation-delay: calc(var(--stagger) * 2); }
|
|
3066
3394
|
.table-wrap tbody.rows-in tr:nth-child(4) { animation-delay: calc(var(--stagger) * 3); }
|
|
3067
3395
|
.table-wrap tbody.rows-in tr:nth-child(5) { animation-delay: calc(var(--stagger) * 4); }
|
|
3068
3396
|
.table-wrap tbody.rows-in tr:nth-child(6) { animation-delay: calc(var(--stagger) * 5); }
|
|
3069
|
-
@keyframes row-in { from { opacity: 0; } }
|
|
3070
3397
|
|
|
3071
3398
|
/* a secondary text cell: the preview's inline-styled region column, as a
|
|
3072
3399
|
class. 5.47:1 on surface-1 dark, 5.89:1 light; on the selection tints
|
|
@@ -3079,8 +3406,10 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3079
3406
|
|
|
3080
3407
|
/* the row is the target: anywhere on a selection row toggles it, the JS
|
|
3081
3408
|
guard skips clicks that landed on another control. Text does not select,
|
|
3082
|
-
so a click cannot double as a drag.
|
|
3083
|
-
|
|
3409
|
+
so a click cannot double as a drag. A row whose box is disabled is not a
|
|
3410
|
+
target, and keeps no pointer. */
|
|
3411
|
+
.table-wrap tbody tr:has(.cell-select) { user-select: none; }
|
|
3412
|
+
.table-wrap tbody tr:has(.cell-select input:not(:disabled)) { cursor: pointer; }
|
|
3084
3413
|
|
|
3085
3414
|
/* ---- Pagination ----
|
|
3086
3415
|
The count line states the slice; the strip scrolls inside itself, so at
|
|
@@ -3132,20 +3461,25 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3132
3461
|
@keyframes chip-in { from { opacity: 0; transform: scale(0.85); } }
|
|
3133
3462
|
@keyframes chip-out { to { opacity: 0; transform: scale(0.85); } }
|
|
3134
3463
|
/* The chip's × is the one remove at chip scale: the skeleton, the glyph box and
|
|
3135
|
-
the
|
|
3464
|
+
the remove hover all come from `.close`, and these three override only the
|
|
3136
3465
|
parts that would break the pill. The scope is the chip, because the bare
|
|
3137
3466
|
`.close` rules further down this file would otherwise grow the × to the 30px
|
|
3138
3467
|
square; one (0,2,0) selector covers both shapes, since the svg form wears
|
|
3139
3468
|
`.close` beside `.icon` and outranks the shared `.icon` box either way. The
|
|
3140
|
-
pill row carries the 44px floor, so the glyph keeps its own box.
|
|
3469
|
+
pill row carries the 44px floor, so the glyph keeps its own box.
|
|
3470
|
+
The hover restates the remove costume only because the chip's own ground
|
|
3471
|
+
moves: the fill lifts with the select's --lift, and the well reads 1.28:1
|
|
3472
|
+
dark / 1.23 light on the resting chip, 1.51 dark on the lifted one and
|
|
3473
|
+
1.03 light there, one of the three light grounds no grey clears 1.2 on
|
|
3474
|
+
(the record lives with the remove skeleton rules). */
|
|
3141
3475
|
.filter-chip .close {
|
|
3142
3476
|
width: 16px;
|
|
3143
3477
|
height: 16px;
|
|
3144
3478
|
border-radius: var(--r-pill);
|
|
3145
3479
|
color: inherit;
|
|
3146
|
-
transition: background var(--dur-tint) var(--ease);
|
|
3480
|
+
transition: background var(--dur-tint) var(--ease), color var(--dur-tint) var(--ease);
|
|
3147
3481
|
}
|
|
3148
|
-
.filter-chip .close:hover {
|
|
3482
|
+
.filter-chip .close:hover { color: var(--danger); background: var(--well); --tint-ground: transparent; }
|
|
3149
3483
|
.filter-chip .close svg { width: 8px; height: 8px; }
|
|
3150
3484
|
|
|
3151
3485
|
/* The picker: a filter button plus a popover of checkbox rows anchored to
|
|
@@ -3153,21 +3487,21 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3153
3487
|
.menu-item.checkbox-label: one target, the system's own box. Closes on
|
|
3154
3488
|
outside click and Escape; the exit is the popover's missing one. */
|
|
3155
3489
|
.filter-menu-wrap { position: relative; }
|
|
3156
|
-
.filter-menu { padding: 0; }
|
|
3157
3490
|
/* A trigger on the bar's end edge opens its menu off the end of the wrapper,
|
|
3158
3491
|
since the panel is wider than the button it hangs from. Same edge
|
|
3159
3492
|
`.btn-split-menu` anchors to by default. */
|
|
3160
3493
|
.filter-menu.menu-end { left: auto; right: 0; }
|
|
3161
|
-
/* the
|
|
3162
|
-
|
|
3494
|
+
/* the checkbox already pads its row visually, so the row gives 2px back: above
|
|
3495
|
+
the first box the panel's gutter plus the row's own 8px measures a 15.84px
|
|
3496
|
+
strip of dead panel, against 13.84 at 6 */
|
|
3163
3497
|
.filter-menu .menu-item { padding-block: 6px; }
|
|
3164
3498
|
/* Late reduce block: the rules below this point sit after the main one, so
|
|
3165
3499
|
their restatements have to as well, or the later base rule wins by order. */
|
|
3166
3500
|
@media (prefers-reduced-motion: reduce) {
|
|
3167
3501
|
/* rows and chips appear and go instantly; the chip's removal timer still holds it */
|
|
3168
3502
|
.table-wrap tbody.rows-in tr { animation: none; }
|
|
3169
|
-
.filter-chip { animation-name:
|
|
3170
|
-
.filter-chip.closing { animation-name:
|
|
3503
|
+
.filter-chip { animation-name: panel-in-fade; }
|
|
3504
|
+
.filter-chip.closing { animation-name: fade-out; }
|
|
3171
3505
|
}
|
|
3172
3506
|
|
|
3173
3507
|
/* ---- Empty result inside the table ----
|
|
@@ -3192,13 +3526,11 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3192
3526
|
Viewport-driven, so it swaps in one frame: no animation, by design.
|
|
3193
3527
|
Under the navbar's own 768px boundary, where the search is already off
|
|
3194
3528
|
and the links scroll, links that carry an icon fold to it and the brand
|
|
3195
|
-
|
|
3196
|
-
.navbar .logo-mark { display: none; }
|
|
3529
|
+
keeps its mark while the wordmark hides. */
|
|
3197
3530
|
|
|
3198
3531
|
@media (max-width: 768px) {
|
|
3199
3532
|
.navbar { padding-inline: var(--space-4); }
|
|
3200
3533
|
|
|
3201
|
-
.navbar .logo-mark { display: inline-flex; }
|
|
3202
3534
|
.navbar .logo-name { display: none; }
|
|
3203
3535
|
.navbar-brand { padding-right: var(--space-3); margin-right: var(--space-2); }
|
|
3204
3536
|
|
|
@@ -3272,10 +3604,14 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3272
3604
|
|
|
3273
3605
|
/* The well sinks, so the tone step is its only edge: --lit-* are the raised
|
|
3274
3606
|
edges and there is no sunken one to reach for. */
|
|
3275
|
-
.band-well { background: var(--well); }
|
|
3607
|
+
.band-well { background: var(--well); --tint-ground: transparent; }
|
|
3276
3608
|
/* The raised band rises, so it takes the lit edge, which flips to the
|
|
3277
3609
|
underside in Latte because that is where a light page shows a raised plane. */
|
|
3278
|
-
.band-raised { background: var(--surface-1); box-shadow: var(--lit-1); }
|
|
3610
|
+
.band-raised { background: var(--surface-1); --tint-ground: transparent; box-shadow: var(--lit-1); }
|
|
3611
|
+
/* The accent band is the closing ask's ground, one per page: the sapphire turned
|
|
3612
|
+
up where a product page ends. It takes no edge, the tone is the step, and every
|
|
3613
|
+
text level and the filled action clear their floors on it (tokens.css). */
|
|
3614
|
+
.band-accent { background: var(--band-accent); --tint-ground: transparent; }
|
|
3279
3615
|
|
|
3280
3616
|
/* Two bands of the same tone are one band, so the seam keeps one section gap
|
|
3281
3617
|
instead of stacking two paddings into a dead strip. Two bands of DIFFERENT
|
|
@@ -3333,19 +3669,20 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3333
3669
|
what should decide, so let it shrink. */
|
|
3334
3670
|
.page-header > .toolbar { flex: 0 1 auto; }
|
|
3335
3671
|
|
|
3336
|
-
/* --t-
|
|
3337
|
-
it are never the same size.
|
|
3338
|
-
|
|
3672
|
+
/* --t-9, one step above .section-title's --t-8, so a page and a section inside
|
|
3673
|
+
it are never the same size. Tracking tightens with size, since Onest's
|
|
3674
|
+
default spacing opens up past 40px. overflow-wrap because a title is
|
|
3675
|
+
content: an unbroken machine string in one would otherwise widen the page. */
|
|
3339
3676
|
.page-title {
|
|
3340
|
-
font: 600 var(--t-
|
|
3341
|
-
letter-spacing: -0.
|
|
3677
|
+
font: 600 var(--t-9)/1 var(--font-sans);
|
|
3678
|
+
letter-spacing: -0.045em;
|
|
3342
3679
|
color: var(--text);
|
|
3343
3680
|
overflow-wrap: anywhere;
|
|
3344
3681
|
}
|
|
3345
|
-
@media (max-width: 768px) { .page-title { font-size: var(--t-
|
|
3682
|
+
@media (max-width: 768px) { .page-title { font-size: var(--t-8); } }
|
|
3346
3683
|
|
|
3347
3684
|
/* Metadata reads under the title, never beside it: it is the smallest type on
|
|
3348
|
-
the screen and a
|
|
3685
|
+
the screen and a 64px title beside 12px text has no shared baseline worth
|
|
3349
3686
|
aligning. --text-3 measures 6.35:1 dark and 5.27:1 light on --bg. The 20px
|
|
3350
3687
|
column gap is the separator; a middot pseudo lands at a line end or a line
|
|
3351
3688
|
start the moment the row wraps. */
|
|
@@ -3462,7 +3799,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3462
3799
|
card on the page, so the highlight reads as a stronger plane change than the
|
|
3463
3800
|
one the eye already tracks. */
|
|
3464
3801
|
.prose mark {
|
|
3465
|
-
background: color-mix(in srgb, var(--accent) 22%,
|
|
3802
|
+
background: color-mix(in srgb, var(--accent) 22%, var(--tint-ground));
|
|
3466
3803
|
color: var(--text);
|
|
3467
3804
|
border-radius: 2px;
|
|
3468
3805
|
padding: 0 1px;
|
|
@@ -3512,9 +3849,10 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3512
3849
|
place-items: center;
|
|
3513
3850
|
border-radius: var(--r-2);
|
|
3514
3851
|
background-color: var(--well);
|
|
3852
|
+
--tint-ground: transparent;
|
|
3515
3853
|
background-image: repeating-linear-gradient(
|
|
3516
3854
|
-45deg,
|
|
3517
|
-
color-mix(in srgb, var(--text-4) 18%,
|
|
3855
|
+
color-mix(in srgb, var(--text-4) 18%, var(--tint-ground)) 0 1px,
|
|
3518
3856
|
transparent 1px 9px
|
|
3519
3857
|
);
|
|
3520
3858
|
}
|
|
@@ -3524,6 +3862,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3524
3862
|
font: 400 var(--t-1)/1 var(--font-mono);
|
|
3525
3863
|
color: var(--text-3);
|
|
3526
3864
|
background: var(--surface-1);
|
|
3865
|
+
--tint-ground: transparent;
|
|
3527
3866
|
padding: var(--space-2) var(--space-3);
|
|
3528
3867
|
border-radius: var(--r-1);
|
|
3529
3868
|
}
|
|
@@ -3540,13 +3879,42 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3540
3879
|
carries rows, chips or numbers and there are eight of them. Roomy inside a
|
|
3541
3880
|
.band-well or on --bg where the card IS the section. Neither belongs on a
|
|
3542
3881
|
.band-raised: the card's fill and the band's are the same colour, so only
|
|
3543
|
-
|
|
3882
|
+
the drawn line separates them, and a raised band is for type,
|
|
3883
|
+
not for cards. A
|
|
3544
3884
|
card on a well band measures 1.283:1 dark and 1.230:1 light against its
|
|
3545
3885
|
ground, the strongest tone step the system has. */
|
|
3546
3886
|
.card.card-tight { padding: var(--space-3); }
|
|
3547
3887
|
.card.card-roomy { padding: var(--space-8); }
|
|
3548
3888
|
/* At 390px a roomy card spends 64 of 358 available pixels on air. */
|
|
3549
3889
|
@media (max-width: 768px) { .card.card-roomy { padding: var(--space-5); } }
|
|
3890
|
+
/* A card holding setting rows drops its INLINE padding for the rows, and every
|
|
3891
|
+
other child keeps the card's 20px gutter on its own: a header's title sits
|
|
3892
|
+
20px in from the card's left, a trailing save row 20px in from its right,
|
|
3893
|
+
and the card's block padding still holds above a title and under a save
|
|
3894
|
+
row: rows flush, the rest keeps the
|
|
3895
|
+
gutter. Only an edge row sits on the card's edge, so the hover fill
|
|
3896
|
+
reaches the edge the row touches and nothing else does.
|
|
3897
|
+
|
|
3898
|
+
A card holding NOTHING but rows is fully flush rather than inline-flush:
|
|
3899
|
+
the row's own 20px IS the gutter, its fill reaches both card edges and the
|
|
3900
|
+
edge rows' --r-2 corners are the card's own: the
|
|
3901
|
+
whole row should light up, without leaving empty space. At (0,3,0) that
|
|
3902
|
+
clause outranks `.card-tight` and `.card-roomy` on specificity rather than
|
|
3903
|
+
on where it sits.
|
|
3904
|
+
|
|
3905
|
+
Both clauses ask what PAINTS, so a `<template>` a page clones its rows
|
|
3906
|
+
from does not count as a child: it draws nothing, so nothing of it can meet
|
|
3907
|
+
the card's edge, and a rule that turned on an invisible element would be a
|
|
3908
|
+
defect nobody could see to diagnose. */
|
|
3909
|
+
.card:has(> .setting-row) { padding-inline: 0; }
|
|
3910
|
+
/* `.section-sticky` is excluded because it is full-bleed chrome rather than
|
|
3911
|
+
a gutter child: a stuck header spans the viewport and derives its own
|
|
3912
|
+
inline padding from the page column, so a 20px flat here would pull its
|
|
3913
|
+
toolbar off the column the first time the card scrolled. */
|
|
3914
|
+
.card:has(> .setting-row) > :not(.setting-row, .section-sticky, template, script, style) {
|
|
3915
|
+
padding-inline: var(--space-5);
|
|
3916
|
+
}
|
|
3917
|
+
.card:has(> .setting-row):not(:has(> :not(.setting-row, template, script, style))) { padding: 0; }
|
|
3550
3918
|
|
|
3551
3919
|
/* ---- Charts ----
|
|
3552
3920
|
Inline SVG, no library, no canvas. The shell is HTML and only the marks are
|
|
@@ -3576,9 +3944,9 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3576
3944
|
3. A rounded corner distorts with everything else, so bars are square.
|
|
3577
3945
|
|
|
3578
3946
|
The four series colours are the tokens, measured on --surface-1 for WCAG
|
|
3579
|
-
1.4.11's 3:1 graphics floor: 5.21 / 5.27 /
|
|
3580
|
-
5.
|
|
3581
|
-
and 5.
|
|
3947
|
+
1.4.11's 3:1 graphics floor: 5.21 / 5.27 / 6.62 / 6.45:1 dark and
|
|
3948
|
+
5.81 / 5.80 / 5.12 / 5.55:1 light; on --bg, 6.02 / 6.09 / 7.65 / 7.46 dark
|
|
3949
|
+
and 5.17 / 5.16 / 4.56 / 4.94 light. Four is the ceiling: a fifth series is
|
|
3582
3950
|
a legend problem, and yellow and red stay out because they read as status.
|
|
3583
3951
|
|
|
3584
3952
|
Gridlines are scaffolding, not graphical objects: the tick labels carry the
|
|
@@ -3601,9 +3969,6 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3601
3969
|
--series: var(--chart-series-1);
|
|
3602
3970
|
--chart-grid: var(--surface-3); /* 1.45:1 on --surface-1 dark, 1.31 light */
|
|
3603
3971
|
--chart-axis: var(--text-4); /* 4.73:1 dark, 5.55 light on --surface-1 */
|
|
3604
|
-
/* a tint that reads as a fill without competing with its own line: 1.30:1
|
|
3605
|
-
over --surface-1 dark, 1.26:1 light, and two stacked land 1.63 and 1.58 */
|
|
3606
|
-
--chart-area-alpha: 0.16;
|
|
3607
3972
|
--chart-aspect: 16 / 6;
|
|
3608
3973
|
|
|
3609
3974
|
display: grid;
|
|
@@ -3617,6 +3982,11 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3617
3982
|
container-type: inline-size;
|
|
3618
3983
|
}
|
|
3619
3984
|
|
|
3985
|
+
/* A tint that reads as a fill without competing with its own line: 1.30:1 over
|
|
3986
|
+
--surface-1 dark, 1.26:1 light, and two stacked land 1.63 and 1.58. A
|
|
3987
|
+
sparkline is the other ground an area lands on, so it carries the knob too. */
|
|
3988
|
+
.chart, .stat-spark { --chart-area-alpha: 0.16; }
|
|
3989
|
+
|
|
3620
3990
|
/* Each part carries its own gutter rather than the grid carrying a gap: an
|
|
3621
3991
|
absent axis title then costs nothing, where a gap would leave its lane. */
|
|
3622
3992
|
.chart-plot {
|
|
@@ -3653,6 +4023,8 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3653
4023
|
alias, so it can be snapped */
|
|
3654
4024
|
shape-rendering: crispEdges;
|
|
3655
4025
|
}
|
|
4026
|
+
/* dashed, so the fills under the lines carry the plot and the rules recede */
|
|
4027
|
+
.chart-grid line { stroke-dasharray: 2 5; }
|
|
3656
4028
|
|
|
3657
4029
|
/* ---- Ticks ----
|
|
3658
4030
|
Mono and tabular: digits line up down the y column and across a plate, and a
|
|
@@ -3774,7 +4146,8 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3774
4146
|
|
|
3775
4147
|
/* A flat tint, never a gradient, and never on its own: the fill sits far under
|
|
3776
4148
|
the 3:1 floor by design, and the 2px line on top is what carries the value.
|
|
3777
|
-
|
|
4149
|
+
ui.js builds it under every line and every sparkline, off the line's own
|
|
4150
|
+
points, so no page writes one. */
|
|
3778
4151
|
.chart-area {
|
|
3779
4152
|
fill: var(--series);
|
|
3780
4153
|
fill-opacity: var(--chart-area-alpha);
|
|
@@ -3795,6 +4168,9 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3795
4168
|
point is it, and a growing dot that lags the crosshair reads as a bug */
|
|
3796
4169
|
.chart-point.is-active,
|
|
3797
4170
|
.chart-point line.is-active { stroke-width: 11; }
|
|
4171
|
+
/* ui.js marks every line's last point with `.chart-end`; a sparkline's is
|
|
4172
|
+
scaled to its 1.5px line */
|
|
4173
|
+
.stat-spark .chart-point { stroke-width: 6; }
|
|
3798
4174
|
|
|
3799
4175
|
/* Bars are fills, so they scale with the plot the way the data does and need
|
|
3800
4176
|
no vector-effect. Square: a corner radius in a stretched viewBox turns into
|
|
@@ -3944,13 +4320,16 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3944
4320
|
.legend and .legend-item ship already; this adds the mark for a filled
|
|
3945
4321
|
series and the interactive form. Both read --series from the same slot class
|
|
3946
4322
|
the chart uses, so a legend cannot drift from the mark it names. */
|
|
3947
|
-
.legend .legend-line { background: var(--series, currentColor); }
|
|
4323
|
+
.legend .legend-line { background: var(--series, currentColor); --tint-ground: transparent; }
|
|
3948
4324
|
.legend-swatch {
|
|
3949
|
-
width
|
|
3950
|
-
|
|
4325
|
+
/* one width with the line mark, so the two sit level in a legend row; a
|
|
4326
|
+
filled block needs less height than a stroke reads long */
|
|
4327
|
+
width: 12px;
|
|
4328
|
+
height: 8px;
|
|
3951
4329
|
border-radius: calc(var(--r-1) * 0.3);
|
|
3952
4330
|
flex-shrink: 0;
|
|
3953
4331
|
background: var(--series, currentColor);
|
|
4332
|
+
--tint-ground: transparent;
|
|
3954
4333
|
}
|
|
3955
4334
|
|
|
3956
4335
|
/* The toggle: a real button, because a legend item that filters the chart is a
|
|
@@ -3960,13 +4339,19 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
3960
4339
|
border: none;
|
|
3961
4340
|
background: none;
|
|
3962
4341
|
padding: 0;
|
|
3963
|
-
font:
|
|
4342
|
+
/* not the `font` shorthand: it resets font-size and beats .legend-item's
|
|
4343
|
+
--t-2 by source order, painting a toggle 14px beside 12px static items */
|
|
4344
|
+
font-family: inherit;
|
|
4345
|
+
font-weight: inherit;
|
|
4346
|
+
line-height: inherit;
|
|
4347
|
+
letter-spacing: inherit;
|
|
3964
4348
|
color: var(--text-3); /* 5.49:1 dark, 5.92 light on --surface-1 */
|
|
3965
4349
|
cursor: pointer;
|
|
3966
4350
|
transition: color var(--dur-tint) var(--ease);
|
|
3967
4351
|
}
|
|
3968
4352
|
.legend-toggle:hover { color: var(--text); }
|
|
3969
4353
|
.legend-toggle[aria-pressed="false"] { color: var(--text-4); } /* 4.73:1 dark, 5.55 light */
|
|
4354
|
+
.legend-toggle[aria-pressed="false"]:hover { color: var(--text-3); } /* 5.49:1 dark, 5.92 light */
|
|
3970
4355
|
/* the mark hollows instead of greying: the series keeps its identity while it
|
|
3971
4356
|
is switched off, and an inset ring changes no box dimension */
|
|
3972
4357
|
.legend-toggle[aria-pressed="false"] .legend-swatch,
|
|
@@ -4061,7 +4446,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4061
4446
|
/* ---- Icon tile ----
|
|
4062
4447
|
The 32px soft-level square that heads a feature card. One colour drives the
|
|
4063
4448
|
fill, the hairline and the glyph, so a variant is one line. 32 is the size
|
|
4064
|
-
|
|
4449
|
+
SKILL.md names for it and the box .avatar already carries; no token holds it.
|
|
4065
4450
|
|
|
4066
4451
|
The fill and the hairline are laid down OPAQUE, mixed into --surface-1 rather
|
|
4067
4452
|
than tinted over whatever is behind, which is what keeps the tile off the
|
|
@@ -4091,8 +4476,9 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4091
4476
|
width: 32px;
|
|
4092
4477
|
height: 32px;
|
|
4093
4478
|
flex-shrink: 0;
|
|
4094
|
-
border-radius: var(--r-
|
|
4479
|
+
border-radius: var(--r-2);
|
|
4095
4480
|
background: color-mix(in srgb, var(--tile) var(--tile-fill), var(--surface-1));
|
|
4481
|
+
--tint-ground: transparent;
|
|
4096
4482
|
border: 1px solid color-mix(in srgb, var(--tile) var(--tile-line), var(--surface-1));
|
|
4097
4483
|
color: var(--tile);
|
|
4098
4484
|
}
|
|
@@ -4101,6 +4487,20 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4101
4487
|
does, so a `<use>` that carries no class cannot lay out at 300x150 */
|
|
4102
4488
|
.icon-tile > svg { width: 16px; height: 16px; }
|
|
4103
4489
|
|
|
4490
|
+
/* A feature grid's default mark fills deeper and edges harder: the page's
|
|
4491
|
+
sapphire turned up where a product page has room for it. The semantic
|
|
4492
|
+
variants keep the shipped mix, so their own hue still reads as the level.
|
|
4493
|
+
Glyph on the deeper fill, read off the painted pixel as above: 4.00 dark,
|
|
4494
|
+
3.47 light; the hairline 2.05 and 1.72 on it. */
|
|
4495
|
+
.feature > .icon-tile:not(.icon-tile-accent-2, .icon-tile-success, .icon-tile-warning, .icon-tile-danger) {
|
|
4496
|
+
--tile-fill: 24%;
|
|
4497
|
+
--tile-line: 60%;
|
|
4498
|
+
}
|
|
4499
|
+
[data-theme="light"] .feature > .icon-tile:not(.icon-tile-accent-2, .icon-tile-success, .icon-tile-warning, .icon-tile-danger) {
|
|
4500
|
+
--tile-fill: 20%;
|
|
4501
|
+
--tile-line: 55%;
|
|
4502
|
+
}
|
|
4503
|
+
|
|
4104
4504
|
.icon-tile-accent-2 { --tile: var(--accent-2); }
|
|
4105
4505
|
.icon-tile-success { --tile: var(--success); }
|
|
4106
4506
|
.icon-tile-warning { --tile: var(--warning); }
|
|
@@ -4164,7 +4564,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4164
4564
|
top: 0;
|
|
4165
4565
|
bottom: 0;
|
|
4166
4566
|
width: 2px;
|
|
4167
|
-
background: color-mix(in srgb, var(--text-4) 28%,
|
|
4567
|
+
background: color-mix(in srgb, var(--text-4) 28%, var(--tint-ground));
|
|
4168
4568
|
}
|
|
4169
4569
|
/* the thread begins at the first dot and ends at the last: no stub above the
|
|
4170
4570
|
first entry, none hanging below the last */
|
|
@@ -4180,11 +4580,12 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4180
4580
|
margin-top: var(--tl-lead);
|
|
4181
4581
|
border-radius: var(--r-pill);
|
|
4182
4582
|
background: var(--text-4); /* 5.47:1 dark and 4.94:1 light on --bg, 4.73 / 5.55 on --surface-1 */
|
|
4583
|
+
--tint-ground: transparent;
|
|
4183
4584
|
}
|
|
4184
|
-
.timeline-dot-accent { background: var(--accent); } /* 7.49 / 6.48 dark, 4.09 / 4.60 light */
|
|
4185
|
-
.timeline-dot-success { background: var(--success); } /* 8.89 / 7.69 dark, 4.85 / 5.46 light */
|
|
4186
|
-
.timeline-dot-warning { background: var(--warning); } /* 9.68 / 8.37 dark, 5.25 / 5.91 light */
|
|
4187
|
-
.timeline-dot-danger { background: var(--danger); } /* 5.98 / 5.18 dark, 5.36 / 6.02 light */
|
|
4585
|
+
.timeline-dot-accent { background: var(--accent); --tint-ground: transparent; } /* 7.49 / 6.48 dark, 4.09 / 4.60 light */
|
|
4586
|
+
.timeline-dot-success { background: var(--success); --tint-ground: transparent; } /* 8.89 / 7.69 dark, 4.85 / 5.46 light */
|
|
4587
|
+
.timeline-dot-warning { background: var(--warning); --tint-ground: transparent; } /* 9.68 / 8.37 dark, 5.25 / 5.91 light */
|
|
4588
|
+
.timeline-dot-danger { background: var(--danger); --tint-ground: transparent; } /* 5.98 / 5.18 dark, 5.36 / 6.02 light */
|
|
4188
4589
|
|
|
4189
4590
|
.timeline-head {
|
|
4190
4591
|
display: flex;
|
|
@@ -4220,7 +4621,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4220
4621
|
.timeline-more .timeline-dot {
|
|
4221
4622
|
margin-top: 0;
|
|
4222
4623
|
background: none;
|
|
4223
|
-
border: 2px solid color-mix(in srgb, var(--text-4) 28%,
|
|
4624
|
+
border: 2px solid color-mix(in srgb, var(--text-4) 28%, var(--tint-ground));
|
|
4224
4625
|
}
|
|
4225
4626
|
.timeline > .timeline-more:last-child::before { bottom: 50%; }
|
|
4226
4627
|
|
|
@@ -4245,6 +4646,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4245
4646
|
.accordion {
|
|
4246
4647
|
interpolate-size: allow-keywords;
|
|
4247
4648
|
background: var(--surface-1);
|
|
4649
|
+
--tint-ground: transparent;
|
|
4248
4650
|
border-radius: var(--r-2);
|
|
4249
4651
|
box-shadow: var(--elev-1);
|
|
4250
4652
|
/* the first and last summary's hover fill hugs the card's own corners */
|
|
@@ -4257,7 +4659,11 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4257
4659
|
overflow-wrap: anywhere; /* a long token cannot push the chevron off the row */
|
|
4258
4660
|
}
|
|
4259
4661
|
.accordion > .disclosure > .disclosure-body {
|
|
4260
|
-
|
|
4662
|
+
/* the body's ink sits centred between the label above it and the row's
|
|
4663
|
+
bottom edge: the summary's 12px under the label plus this 8px puts 20.8px
|
|
4664
|
+
of air above the text against the 21.6 below it, line-height slack
|
|
4665
|
+
included. Flush under the summary read top-heavy. */
|
|
4666
|
+
padding: var(--space-2) var(--space-5) var(--space-5);
|
|
4261
4667
|
font-size: var(--t-2);
|
|
4262
4668
|
color: var(--text-2); /* 7.30:1 dark, 8.36:1 light on --surface-1 */
|
|
4263
4669
|
}
|
|
@@ -4330,6 +4736,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4330
4736
|
flex-shrink: 0;
|
|
4331
4737
|
border-radius: var(--r-pill);
|
|
4332
4738
|
background: var(--well);
|
|
4739
|
+
--tint-ground: transparent;
|
|
4333
4740
|
border: 1px solid var(--line);
|
|
4334
4741
|
color: var(--text-4); /* 6.07:1 dark, 4.52:1 light on --well */
|
|
4335
4742
|
font: 500 var(--t-2)/1 var(--font-mono);
|
|
@@ -4366,11 +4773,12 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4366
4773
|
left: calc(50% + var(--step-mark) / 2 + var(--space-2));
|
|
4367
4774
|
right: calc(-50% + var(--step-mark) / 2 + var(--space-2));
|
|
4368
4775
|
height: 2px;
|
|
4369
|
-
background: color-mix(in srgb, var(--text-4) 28%,
|
|
4776
|
+
background: color-mix(in srgb, var(--text-4) 28%, var(--tint-ground));
|
|
4370
4777
|
}
|
|
4371
4778
|
|
|
4372
4779
|
.step.done .step-index {
|
|
4373
4780
|
background: var(--accent);
|
|
4781
|
+
--tint-ground: transparent;
|
|
4374
4782
|
border-color: var(--accent); /* 7.49 / 6.48 dark, 4.09 / 4.60 light */
|
|
4375
4783
|
color: var(--on-accent); /* 8.22:1 dark, 4.60:1 light on the fill */
|
|
4376
4784
|
}
|
|
@@ -4378,7 +4786,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4378
4786
|
/* the run got this far. Written with the child combinator, because the
|
|
4379
4787
|
connector's own rule carries `:not(:last-child)` and a bare `.step.done`
|
|
4380
4788
|
is outranked by it: measured, the done connector painted the neutral. */
|
|
4381
|
-
.stepper > .step.done::after { background: var(--accent); }
|
|
4789
|
+
.stepper > .step.done::after { background: var(--accent); --tint-ground: transparent; }
|
|
4382
4790
|
|
|
4383
4791
|
.step[aria-current="step"] .step-index {
|
|
4384
4792
|
background: var(--accent-quiet);
|
|
@@ -4389,6 +4797,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4389
4797
|
|
|
4390
4798
|
.step.error .step-index {
|
|
4391
4799
|
background: var(--danger);
|
|
4800
|
+
--tint-ground: transparent;
|
|
4392
4801
|
border-color: var(--danger);
|
|
4393
4802
|
color: var(--on-danger); /* 6.58:1 dark, 6.01:1 light on the fill */
|
|
4394
4803
|
}
|
|
@@ -4434,9 +4843,6 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4434
4843
|
|
|
4435
4844
|
/* ---- Meter ----
|
|
4436
4845
|
A level against a capacity: storage used, seats taken, rate budget spent.
|
|
4437
|
-
Reach for `.progress` when the number climbs to 100 and the job is then
|
|
4438
|
-
done; reach for `.meter` when the number can sit anywhere and 100 is a
|
|
4439
|
-
problem.
|
|
4440
4846
|
|
|
4441
4847
|
The fill rides scaleX, not width, so nothing here is a layout change: the
|
|
4442
4848
|
track clips at its own pill radius, which is why the fill needs no radius of
|
|
@@ -4476,12 +4882,14 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4476
4882
|
.meter-track {
|
|
4477
4883
|
height: var(--space-2);
|
|
4478
4884
|
background: var(--well);
|
|
4885
|
+
--tint-ground: transparent;
|
|
4479
4886
|
border-radius: var(--r-pill);
|
|
4480
4887
|
overflow: hidden;
|
|
4481
4888
|
}
|
|
4482
4889
|
.meter-fill {
|
|
4483
4890
|
height: 100%;
|
|
4484
4891
|
background: var(--accent); /* 8.31:1 dark, 3.74:1 light on the track */
|
|
4892
|
+
--tint-ground: transparent;
|
|
4485
4893
|
transform-origin: left center;
|
|
4486
4894
|
transform: scaleX(var(--meter-v));
|
|
4487
4895
|
transition: transform var(--dur-far) var(--ease), background var(--dur-tint) var(--ease);
|
|
@@ -4499,15 +4907,16 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4499
4907
|
height: 6px;
|
|
4500
4908
|
border-radius: var(--r-pill);
|
|
4501
4909
|
background: var(--text-3);
|
|
4910
|
+
--tint-ground: transparent;
|
|
4502
4911
|
}
|
|
4503
4912
|
.meter-hint {
|
|
4504
4913
|
font: 400 var(--t-1)/1.4 var(--font-sans);
|
|
4505
4914
|
color: var(--text-4); /* 5.47 / 4.73 dark, 4.94 / 5.55 light */
|
|
4506
4915
|
}
|
|
4507
4916
|
|
|
4508
|
-
.meter.warn .meter-fill { background: var(--warning); } /* 10.74:1 dark, 4.80:1 light on the track */
|
|
4917
|
+
.meter.warn .meter-fill { background: var(--warning); --tint-ground: transparent; } /* 10.74:1 dark, 4.80:1 light on the track */
|
|
4509
4918
|
.meter.warn .meter-value { color: var(--warning); } /* 9.68 / 8.37 dark, 5.25 / 5.91 light */
|
|
4510
|
-
.meter.over .meter-fill { background: var(--danger); } /* 6.64:1 dark, 4.89:1 light on the track */
|
|
4919
|
+
.meter.over .meter-fill { background: var(--danger); --tint-ground: transparent; } /* 6.64:1 dark, 4.89:1 light on the track */
|
|
4511
4920
|
.meter.over .meter-value { color: var(--danger); } /* 5.98 / 5.18 dark, 5.36 / 6.02 light */
|
|
4512
4921
|
/* no reading yet: the value says so and the empty track is the initial value */
|
|
4513
4922
|
.meter.unknown .meter-value { color: var(--text-4); }
|
|
@@ -4528,21 +4937,27 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4528
4937
|
because the live accent itself clears the track at only 3.74:1 in Latte. */
|
|
4529
4938
|
.meter[aria-disabled="true"] .meter-fill {
|
|
4530
4939
|
background: color-mix(in srgb, var(--accent) 50%, var(--well));
|
|
4940
|
+
--tint-ground: transparent;
|
|
4531
4941
|
}
|
|
4532
4942
|
.meter[aria-disabled="true"].warn .meter-fill {
|
|
4533
4943
|
background: color-mix(in srgb, var(--warning) 44%, var(--well));
|
|
4944
|
+
--tint-ground: transparent;
|
|
4534
4945
|
}
|
|
4535
4946
|
.meter[aria-disabled="true"].over .meter-fill {
|
|
4536
4947
|
background: color-mix(in srgb, var(--danger) 58%, var(--well));
|
|
4948
|
+
--tint-ground: transparent;
|
|
4537
4949
|
}
|
|
4538
4950
|
[data-theme="light"] .meter[aria-disabled="true"] .meter-fill {
|
|
4539
4951
|
background: color-mix(in srgb, var(--accent) 83%, var(--well));
|
|
4952
|
+
--tint-ground: transparent;
|
|
4540
4953
|
}
|
|
4541
4954
|
[data-theme="light"] .meter[aria-disabled="true"].warn .meter-fill {
|
|
4542
4955
|
background: color-mix(in srgb, var(--warning) 74%, var(--well));
|
|
4956
|
+
--tint-ground: transparent;
|
|
4543
4957
|
}
|
|
4544
4958
|
[data-theme="light"] .meter[aria-disabled="true"].over .meter-fill {
|
|
4545
4959
|
background: color-mix(in srgb, var(--danger) 66%, var(--well));
|
|
4960
|
+
--tint-ground: transparent;
|
|
4546
4961
|
}
|
|
4547
4962
|
|
|
4548
4963
|
/* ---- Ring progress ----
|
|
@@ -4592,14 +5007,29 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4592
5007
|
that: `r: 13.5px` measured a 33.6px ring inside its 96px box on viewBox 100 and
|
|
4593
5008
|
a 21.33px stroke on viewBox 36, against a .ring-center measuring 46.8 x 41.2
|
|
4594
5009
|
(62.4 diagonal). On the contract the hole measures 78.7px at --ring-size 96 and
|
|
4595
|
-
52.5px at 64, so the label clears the arc at both.
|
|
4596
|
-
|
|
5010
|
+
52.5px at 64, so the label clears the arc at both. The markup is what holds it,
|
|
5011
|
+
since the CSS can no longer see it. */
|
|
4597
5012
|
}
|
|
4598
5013
|
.ring-track { stroke: var(--well); }
|
|
4599
5014
|
.ring-fill {
|
|
4600
5015
|
stroke: var(--accent); /* 8.31:1 dark, 3.74:1 light on the track */
|
|
4601
5016
|
stroke-linecap: butt; /* a round cap paints a dot at zero */
|
|
5017
|
+
/* the gap collapses AT full, never below it: sign(1 - v) is 1 under, 0 at 1
|
|
5018
|
+
and negative over, so a complete ring paints closed and no partial ring
|
|
5019
|
+
moves. A literal `v*100 100` at v = 1 draws a dash exactly as long as the
|
|
5020
|
+
path, and Chrome antialiases the joint where it meets itself into a dark
|
|
5021
|
+
notch at twelve o'clock (measured 2 device px at 4x); `100 0` closes it
|
|
5022
|
+
and a negative gap paints the whole stroke the same way. Every RESTING
|
|
5023
|
+
pixel under 100 percent is unchanged, since the gap is literally 100 there.
|
|
5024
|
+
A down-sweep from a full ring is the one thing that moves: the gap
|
|
5025
|
+
interpolates from a negative value and clamps at 0, so the ring reads
|
|
5026
|
+
closed until the dash reaches 84 rather than 100, about 130ms of the 260ms
|
|
5027
|
+
transition. Up-sweeps are identical to the old form, both crossing at 100.
|
|
5028
|
+
THE FIRST DECLARATION IS THE FALLBACK, not a leftover: an engine with no
|
|
5029
|
+
`sign()` drops the second line and keeps the old notch, where dropping the
|
|
5030
|
+
pair would paint every determinate ring closed at every value. */
|
|
4602
5031
|
stroke-dasharray: calc(var(--ring-v) * 100) 100;
|
|
5032
|
+
stroke-dasharray: calc(var(--ring-v) * 100) calc(100 * sign(1 - var(--ring-v)));
|
|
4603
5033
|
transition: stroke-dasharray var(--dur-far) var(--ease), stroke var(--dur-tint) var(--ease);
|
|
4604
5034
|
}
|
|
4605
5035
|
.ring-center {
|
|
@@ -4623,15 +5053,42 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4623
5053
|
.ring.over .ring-fill { stroke: var(--danger); }
|
|
4624
5054
|
.ring.over .ring-value { color: var(--danger); }
|
|
4625
5055
|
.ring.unknown .ring-value { color: var(--text-4); }
|
|
4626
|
-
/* indeterminate:
|
|
4627
|
-
|
|
5056
|
+
/* indeterminate: the tail breathes. The head is pinned to
|
|
5057
|
+
the rim at the turn speed and never varies; behind it the tail swings as the
|
|
5058
|
+
arc opens and closes, so there is always a steady point to follow. The bound
|
|
5059
|
+
that picks the numbers: dasharray L 100 with dashoffset d draws the arc at
|
|
5060
|
+
[-d, -d+L], and under the spin's theta' (71.429 u/s at 1.4s) both ends stay
|
|
5061
|
+
forward iff the moving end's share of |L'| stays under theta'. An ease-in-out
|
|
5062
|
+
alternate breath peaks at 1.7241 * A / half-period, which at these numbers
|
|
5063
|
+
leaves 51.7 percent of the rim to spare. The pattern must not WRAP either:
|
|
5064
|
+
L 100 has period L+100, longer than the path, so the dash is laid once from
|
|
5065
|
+
-offset and whatever falls outside [0,100] paints nothing. Anchoring the head
|
|
5066
|
+
at the path's midpoint keeps every amplitude here inside the path. */
|
|
5067
|
+
/* the trough of the reduced-motion pulse, per theme, set where the arc lands on
|
|
5068
|
+
the 3:1 graphics floor over --well: 0.50 measures 3.03:1 dark, 0.84 measures
|
|
5069
|
+
3.02:1 light. Light needs the higher trough because --accent on --well starts
|
|
5070
|
+
at 3.74:1 there against 8.31:1 dark, so it has 0.74 of headroom to spend. */
|
|
5071
|
+
.ring { --ring-hold-dim: 0.50; }
|
|
5072
|
+
[data-theme="light"] .ring { --ring-hold-dim: 0.84; }
|
|
4628
5073
|
.ring.loading .ring-fill {
|
|
4629
|
-
stroke-dasharray:
|
|
5074
|
+
stroke-dasharray: 16 100;
|
|
5075
|
+
stroke-dashoffset: -34;
|
|
4630
5076
|
transform-box: view-box;
|
|
4631
5077
|
transform-origin: center;
|
|
4632
|
-
animation: spin
|
|
5078
|
+
animation: spin 1.4s linear infinite,
|
|
5079
|
+
ring-tail-breathe 1.5s ease-in-out infinite alternate;
|
|
4633
5080
|
transition: none;
|
|
4634
5081
|
}
|
|
5082
|
+
/* Restated at the loading rule's own (0,3,0) and after it: a media query adds
|
|
5083
|
+
no specificity, so the shipped reduce blocks at (0,1,0) cannot reach this.
|
|
5084
|
+
The arc holds the arithmetic mid of its own breath, at its own anchor,
|
|
5085
|
+
pulsing to the floor rather than turning. */
|
|
5086
|
+
@media (prefers-reduced-motion: reduce) {
|
|
5087
|
+
.ring.loading .ring-fill {
|
|
5088
|
+
animation: ring-hold 1.4s ease-in-out infinite;
|
|
5089
|
+
stroke-dasharray: 31 100; stroke-dashoffset: -19;
|
|
5090
|
+
}
|
|
5091
|
+
}
|
|
4635
5092
|
|
|
4636
5093
|
/* ---- Avatar, completed ----
|
|
4637
5094
|
The shipped `.avatar` is the initials disc. This adds the photo, the size
|
|
@@ -4675,6 +5132,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4675
5132
|
.avatar-group > .avatar { box-shadow: 0 0 0 2px var(--dot-ring, var(--bg)); }
|
|
4676
5133
|
.avatar-more {
|
|
4677
5134
|
background: var(--surface-2);
|
|
5135
|
+
--tint-ground: transparent;
|
|
4678
5136
|
border-color: var(--line);
|
|
4679
5137
|
color: var(--text-3); /* 4.65:1 dark, 4.96:1 light on --surface-2 */
|
|
4680
5138
|
font-family: var(--font-mono);
|
|
@@ -4693,12 +5151,13 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4693
5151
|
height: 30%;
|
|
4694
5152
|
border-radius: var(--r-pill);
|
|
4695
5153
|
background: var(--text-4); /* 5.47:1 dark, 4.94:1 light against a --bg ring */
|
|
5154
|
+
--tint-ground: transparent;
|
|
4696
5155
|
box-shadow: 0 0 0 2px var(--dot-ring, var(--bg));
|
|
4697
5156
|
}
|
|
4698
|
-
.avatar-presence.online { background: var(--success); } /* 8.89 dark, 4.85 light */
|
|
4699
|
-
.avatar-presence.away { background: var(--warning); } /* 9.68 dark, 5.25 light */
|
|
4700
|
-
.avatar-presence.busy { background: var(--danger); } /* 5.98 dark, 5.36 light */
|
|
4701
|
-
.avatar-presence.offline { background: var(--text-4); }
|
|
5157
|
+
.avatar-presence.online { background: var(--success); --tint-ground: transparent; } /* 8.89 dark, 4.85 light */
|
|
5158
|
+
.avatar-presence.away { background: var(--warning); --tint-ground: transparent; } /* 9.68 dark, 5.25 light */
|
|
5159
|
+
.avatar-presence.busy { background: var(--danger); --tint-ground: transparent; } /* 5.98 dark, 5.36 light */
|
|
5160
|
+
.avatar-presence.offline { background: var(--text-4); --tint-ground: transparent; }
|
|
4702
5161
|
|
|
4703
5162
|
/* The unread mark for an icon button, which the navbar wanted and had no way
|
|
4704
5163
|
to say. It sits outside the button's box, so it can never land on the glyph,
|
|
@@ -4715,14 +5174,12 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4715
5174
|
height: 8px;
|
|
4716
5175
|
border-radius: var(--r-pill);
|
|
4717
5176
|
background: var(--accent); /* 7.49:1 dark, 4.09:1 light against a --bg ring */
|
|
5177
|
+
--tint-ground: transparent;
|
|
4718
5178
|
box-shadow: 0 0 0 2px var(--dot-ring, var(--bg));
|
|
4719
5179
|
}
|
|
4720
5180
|
|
|
4721
5181
|
/* ---- Banner ----
|
|
4722
|
-
One per page, at the top: a condition the whole page is under.
|
|
4723
|
-
explains something where it stands and stays; `.toast` announces something
|
|
4724
|
-
that just happened and leaves; a banner states a condition and stays until
|
|
4725
|
-
it is dismissed or resolved.
|
|
5182
|
+
One per page, at the top: a condition the whole page is under.
|
|
4726
5183
|
|
|
4727
5184
|
The callout's material, because it is the same material: a 6 percent tint
|
|
4728
5185
|
over surface-1 and the lit edge. What differs is the placement and the
|
|
@@ -4735,7 +5192,6 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4735
5192
|
at --text-3 4.81 / 5.38. --text-4 was the first choice and lands at 4.14 on
|
|
4736
5193
|
the dark warning tint. */
|
|
4737
5194
|
.banner {
|
|
4738
|
-
align-items: center; /* her call: the icon, the actions and the close centre on the body */
|
|
4739
5195
|
flex-wrap: wrap;
|
|
4740
5196
|
padding: var(--space-3) var(--space-4);
|
|
4741
5197
|
}
|
|
@@ -4744,11 +5200,11 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4744
5200
|
a tint declared earlier in the file would lose to it and every banner would
|
|
4745
5201
|
paint plain surface-1. The validation summary is the danger one, and its tint
|
|
4746
5202
|
resolves to #37313F dark, #F9F0F4 light. */
|
|
4747
|
-
.callout-info, .banner-info { background: color-mix(in srgb, var(--info) 6%, var(--surface-1)); }
|
|
4748
|
-
.callout-success, .banner-success { background: color-mix(in srgb, var(--success) 6%, var(--surface-1)); }
|
|
4749
|
-
.callout-warning, .banner-warning { background: color-mix(in srgb, var(--warning) 6%, var(--surface-1)); }
|
|
5203
|
+
.callout-info, .banner-info { background: color-mix(in srgb, var(--info) 6%, var(--surface-1)); --tint-ground: transparent; }
|
|
5204
|
+
.callout-success, .banner-success { background: color-mix(in srgb, var(--success) 6%, var(--surface-1)); --tint-ground: transparent; }
|
|
5205
|
+
.callout-warning, .banner-warning { background: color-mix(in srgb, var(--warning) 6%, var(--surface-1)); --tint-ground: transparent; }
|
|
4750
5206
|
.callout-danger, .banner-danger,
|
|
4751
|
-
.form-summary { background: color-mix(in srgb, var(--danger) 6%, var(--surface-1)); }
|
|
5207
|
+
.form-summary { background: color-mix(in srgb, var(--danger) 6%, var(--surface-1)); --tint-ground: transparent; }
|
|
4752
5208
|
|
|
4753
5209
|
/* Only in the bleed form. The column itself is shared with .navbar-inner. */
|
|
4754
5210
|
.banner-inner { align-items: center; flex-wrap: wrap; }
|
|
@@ -4758,7 +5214,6 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4758
5214
|
padding-inline: var(--space-8); /* the page column's own gutter */
|
|
4759
5215
|
}
|
|
4760
5216
|
|
|
4761
|
-
.banner-icon svg { display: block; }
|
|
4762
5217
|
.banner-info .banner-icon { color: var(--info); }
|
|
4763
5218
|
.banner-success .banner-icon { color: var(--success); }
|
|
4764
5219
|
.banner-warning .banner-icon { color: var(--warning); }
|
|
@@ -4825,6 +5280,25 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4825
5280
|
color: var(--text-3); /* 5.49:1 dark / 5.92:1 light on --surface-1, 7.05 / 4.82 on --well */
|
|
4826
5281
|
}
|
|
4827
5282
|
.close svg { width: 14px; height: 14px; display: block; }
|
|
5283
|
+
/* THE remove hover, one glyph and one grey well wherever the control sits.
|
|
5284
|
+
--danger on the --well fill: 6.62:1 dark, 4.89:1 light. The well reads
|
|
5285
|
+
1.28:1 dark and 1.23:1 light on a card, 1.39-1.46 / 1.12-1.13 on the four
|
|
5286
|
+
banner tints, 1.28-1.51 on the chips. Where the ground is not a card the
|
|
5287
|
+
fill is named per ground below, because no single grey reads on every
|
|
5288
|
+
ground: --surface-2 measures 1.18:1 on a dark card and 1.08-1.10 on the
|
|
5289
|
+
light tints, and --well itself is 1.00 on the light well row.
|
|
5290
|
+
|
|
5291
|
+
LIGHT HOLDS THREE GROUNDS NO GREY CLEARS 1.2 ON: the four banner tints
|
|
5292
|
+
(well 1.12-1.13; the nearest fill, --surface-3, reads 1.19-1.21 and drops
|
|
5293
|
+
the glyph to 4.60), the select chip lifted by an open panel (1.03; the
|
|
5294
|
+
nearest, --surface-1, reads 1.19), and the glass panel's worst plate (see
|
|
5295
|
+
the glass rule below). --well stays because it is the one fill that holds
|
|
5296
|
+
the glyph's 4.5 floor everywhere in light, and every one of these grounds
|
|
5297
|
+
already sat under an invisible well before this rule: the neutral hover's
|
|
5298
|
+
--surface-2 measured 1.08-1.10 on the tints and 1.03 on the lifted chip.
|
|
5299
|
+
A visible well on any of the three needs a token this ramp does not have,
|
|
5300
|
+
which the system owns, not a fill to invent here. The hover itself lives down
|
|
5301
|
+
in the select-chip block, placed after the one rest rule that ties it. */
|
|
4828
5302
|
|
|
4829
5303
|
/* the exit is the chip's shape at the chip's speed; the page removes the node
|
|
4830
5304
|
on animationend, and that removal is the one frame the page reflows */
|
|
@@ -4856,9 +5330,11 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4856
5330
|
@media (prefers-reduced-motion: reduce) {
|
|
4857
5331
|
.accordion > .disclosure::details-content,
|
|
4858
5332
|
.accordion > .disclosure[open]::details-content { transition: none; }
|
|
5333
|
+
/* the chevron still turns, it just arrives there */
|
|
5334
|
+
.disclosure summary svg { transition: none; }
|
|
4859
5335
|
.meter-fill { transition: background var(--dur-tint) var(--ease); }
|
|
4860
5336
|
.ring-fill { transition: stroke var(--dur-tint) var(--ease); }
|
|
4861
|
-
.banner.closing { animation-name:
|
|
5337
|
+
.banner.closing { animation-name: fade-out; }
|
|
4862
5338
|
}
|
|
4863
5339
|
|
|
4864
5340
|
/* ---- Market surfaces ----
|
|
@@ -4867,9 +5343,9 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4867
5343
|
here paints a second tone: `.band` is the tone device and every one of these
|
|
4868
5344
|
sits on it or on the page.
|
|
4869
5345
|
|
|
4870
|
-
REGISTER. The demo strings are all lowercase
|
|
4871
|
-
|
|
4872
|
-
|
|
5346
|
+
REGISTER. The demo strings are all lowercase; a sentence-case product page
|
|
5347
|
+
swaps the copy and keeps the classes, and nothing in this block encodes a
|
|
5348
|
+
case.
|
|
4873
5349
|
|
|
4874
5350
|
ONE GRID, SIX USERS. Every row here is N equal cells that collapse, and the
|
|
4875
5351
|
collapse is keyed on the CELL's own minimum rather than on a viewport
|
|
@@ -4927,10 +5403,11 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4927
5403
|
and not a 3.4k headline. A build that wants more raises --page-w once.
|
|
4928
5404
|
|
|
4929
5405
|
The headline is `.page-title` and the sentence is `.section-lede`, the two
|
|
4930
|
-
the system already ships: --t-
|
|
5406
|
+
the system already ships: --t-9 falling to --t-8 under 768px, overflow-wrap
|
|
4931
5407
|
on the title because a headline is content, and the measure cap on the lede.
|
|
4932
|
-
|
|
4933
|
-
|
|
5408
|
+
The hero's lede alone steps up to --t-5, so the one sentence under a 64px
|
|
5409
|
+
headline does not read as a caption. A headline twice as long wraps inside
|
|
5410
|
+
the cell and moves the media down; no rule here reads its length. */
|
|
4934
5411
|
.hero {
|
|
4935
5412
|
--cell-min: 400px;
|
|
4936
5413
|
--cell-cols: 2;
|
|
@@ -4947,13 +5424,17 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4947
5424
|
}
|
|
4948
5425
|
.hero-stack > .hero-copy > .toolbar { justify-content: center; }
|
|
4949
5426
|
|
|
4950
|
-
/* Flow, not flex:
|
|
4951
|
-
|
|
4952
|
-
|
|
5427
|
+
/* Flow, not flex: 16 under the eyebrow and under the headline, 24 over the
|
|
5428
|
+
actions, and one gap value for all three reads as a list of four things
|
|
5429
|
+
rather than as one block with an action under it. */
|
|
4953
5430
|
.hero-copy > :first-child { margin-block-start: 0; }
|
|
4954
5431
|
.hero-copy > .label { color: var(--text-4); } /* 5.47:1 dark, 4.94:1 light on --bg */
|
|
4955
|
-
.hero-copy > .page-title { margin-block-start: var(--space-
|
|
4956
|
-
.hero-copy > .section-lede {
|
|
5432
|
+
.hero-copy > .page-title { margin-block-start: var(--space-4); }
|
|
5433
|
+
.hero-copy > .section-lede {
|
|
5434
|
+
margin-block-start: var(--space-4);
|
|
5435
|
+
font-size: var(--t-5);
|
|
5436
|
+
line-height: 1.5;
|
|
5437
|
+
}
|
|
4957
5438
|
.hero-copy > .toolbar { margin-block-start: var(--space-6); }
|
|
4958
5439
|
|
|
4959
5440
|
.hero-media { min-width: 0; }
|
|
@@ -4998,11 +5479,21 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
4998
5479
|
}
|
|
4999
5480
|
/* A pull quote. Not `.prose blockquote`: that one is a rule in the margin of a
|
|
5000
5481
|
document, this is a cell that stands alone. --text-2 measures 8.43 / 9.36 /
|
|
5001
|
-
7.30 dark and 7.44 / 6.79 / 8.36 light. */
|
|
5482
|
+
7.30 dark and 7.44 / 6.79 / 8.36 light on --bg / --well / --surface-1. */
|
|
5002
5483
|
.quote {
|
|
5003
5484
|
font-size: var(--t-3);
|
|
5004
5485
|
color: var(--text-2);
|
|
5005
5486
|
}
|
|
5487
|
+
/* The opening mark sits above the line in a 22px slot, and it is a glyph, so it
|
|
5488
|
+
takes the accent's text token: 9.15 / 10.15 / 7.91 dark, 5.06 / 4.62 / 5.69
|
|
5489
|
+
light on the same three grounds. */
|
|
5490
|
+
.quote::before {
|
|
5491
|
+
content: "\201C";
|
|
5492
|
+
display: block;
|
|
5493
|
+
height: 22px;
|
|
5494
|
+
font: 600 var(--t-8)/1 var(--font-sans);
|
|
5495
|
+
color: var(--accent-text);
|
|
5496
|
+
}
|
|
5006
5497
|
.quote footer {
|
|
5007
5498
|
margin-block-start: var(--space-3);
|
|
5008
5499
|
font-size: var(--t-2);
|
|
@@ -5062,7 +5553,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5062
5553
|
`.stat-card` is the dashboard tile: a labelled surface with a delta and a
|
|
5063
5554
|
sparkline, eight to a screen. This is type on a band, and there is one of it.
|
|
5064
5555
|
|
|
5065
|
-
--t-
|
|
5556
|
+
--t-9 in mono, tabular, stepping to --t-8 under 768px exactly as
|
|
5066
5557
|
`.page-title` does, so the biggest number on the page and the biggest word
|
|
5067
5558
|
on it never disagree about how big that is.
|
|
5068
5559
|
|
|
@@ -5088,9 +5579,19 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5088
5579
|
display: flex;
|
|
5089
5580
|
flex-direction: column;
|
|
5090
5581
|
gap: var(--space-2);
|
|
5582
|
+
container-type: inline-size;
|
|
5091
5583
|
}
|
|
5092
|
-
|
|
5093
|
-
|
|
5584
|
+
/* the claim is the page's number, so it takes the accent's text token:
|
|
5585
|
+
9.15 / 10.15 / 7.91 dark, 5.06 / 4.62 / 5.69 light on --bg / --well /
|
|
5586
|
+
--surface-1 */
|
|
5587
|
+
.claim-value { font-size: var(--t-9); letter-spacing: -0.04em; color: var(--accent-text); }
|
|
5588
|
+
@media (max-width: 768px) { .claim-value { font-size: var(--t-8); } }
|
|
5589
|
+
/* The step down is a CELL width, not only a viewport one. A mono character at
|
|
5590
|
+
--t-9 advances 0.56em after the tracking, so a five-character claim ("4.5:1")
|
|
5591
|
+
is 179.2px, measured, and four cells in a tablet column run 158.25 to 179px
|
|
5592
|
+
from 769 to 852px of viewport, where the claim broke after "4.5:". A sidebar
|
|
5593
|
+
shell narrows the same cells at any viewport. */
|
|
5594
|
+
@container (width < 180px) { .claim-value { font-size: var(--t-8); } }
|
|
5094
5595
|
.claim-label {
|
|
5095
5596
|
font-size: var(--t-3);
|
|
5096
5597
|
color: var(--text-3);
|
|
@@ -5105,13 +5606,12 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5105
5606
|
viewport (measured), which left a 128px band of exactly the shape the
|
|
5106
5607
|
media query exists to prevent.
|
|
5107
5608
|
|
|
5108
|
-
THE MARK IS NOT ELEVATION. --
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
pointing at. --accent on --surface-1 measures 6.48:1 dark and 4.60:1 light,
|
|
5609
|
+
THE MARK IS NOT ELEVATION. --contact-2 buys a wider spread than --contact-1,
|
|
5610
|
+
not a deeper one: tokens.css carries both depths and is the one place they are
|
|
5611
|
+
written. Swapping them marks nothing the eye can read, so the mark is three
|
|
5612
|
+
things that do: the badge, the filled button, and a 2px accent edge inset
|
|
5613
|
+
along the top, which is the same device `.row-selected` uses for the row the
|
|
5614
|
+
system is pointing at. --accent on --surface-1 measures 6.48:1 dark and 4.60:1 light,
|
|
5115
5615
|
over the 3:1 graphics floor. The card itself is never tinted: a coloured
|
|
5116
5616
|
tier and a filled button in the same cell are two marks competing. */
|
|
5117
5617
|
.pricing {
|
|
@@ -5130,7 +5630,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5130
5630
|
}
|
|
5131
5631
|
/* both classes on purpose: written one class longer than `.card`, so the pair
|
|
5132
5632
|
cannot be decided by wherever this block lands in the file */
|
|
5133
|
-
.tier.tier-marked { box-shadow: var(--
|
|
5633
|
+
.tier.tier-marked { box-shadow: var(--contact-1), inset 0 2px 0 0 var(--accent); }
|
|
5134
5634
|
|
|
5135
5635
|
.tier-head {
|
|
5136
5636
|
display: flex;
|
|
@@ -5242,7 +5742,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5242
5742
|
gap: var(--space-4);
|
|
5243
5743
|
}
|
|
5244
5744
|
@media (max-width: 768px) { .footer-brand { grid-column: 1 / -1; } }
|
|
5245
|
-
/* the sidebar's own brand pair, on a link: `.
|
|
5745
|
+
/* the sidebar's own brand pair, on a link: `.sona` and `.logo-name` are
|
|
5246
5746
|
already the system's mark and wordmark */
|
|
5247
5747
|
.footer-logo {
|
|
5248
5748
|
display: inline-flex;
|
|
@@ -5345,7 +5845,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5345
5845
|
dragged out is the END, not the band: a committed end is filled, a tentative
|
|
5346
5846
|
end is outlined. A second, weaker tint would have to be translucent, and a
|
|
5347
5847
|
translucent tint over the glass panel composites twice - the trap
|
|
5348
|
-
|
|
5848
|
+
the glass block's opaque quiet mix exists to answer.
|
|
5349
5849
|
|
|
5350
5850
|
The day number is mono: it is a numeric readout, and a proportional face puts
|
|
5351
5851
|
the 1s and the 8s on different centres down a column. */
|
|
@@ -5373,6 +5873,13 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5373
5873
|
z-index: 60;
|
|
5374
5874
|
display: flex;
|
|
5375
5875
|
flex-direction: column;
|
|
5876
|
+
/* The width clamp, for the coarse overhang bands: 44px cells put 334px of
|
|
5877
|
+
panel against a 320px viewport, and the document grows a scrollbar for a
|
|
5878
|
+
popover. The bound keeps a 40px gutter beside the trigger's own offset and
|
|
5879
|
+
never bites a fine pointer: the fine panel's narrowest is 276 at a 320
|
|
5880
|
+
viewport against a 280 bound, and the 560 cap keeps the clamp off the
|
|
5881
|
+
beside-form (494 at its widest) at every width that can hold it. */
|
|
5882
|
+
max-width: min(560px, calc(100vw - 2 * var(--space-5)));
|
|
5376
5883
|
/* no background and no border here: `.popover` carries both, and the glass
|
|
5377
5884
|
block restates them at (0,1,0) after this one */
|
|
5378
5885
|
}
|
|
@@ -5406,19 +5913,40 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5406
5913
|
.daterange-presets {
|
|
5407
5914
|
display: flex;
|
|
5408
5915
|
flex-direction: column;
|
|
5409
|
-
|
|
5916
|
+
gap: 2px; /* the list panel's lane */
|
|
5917
|
+
padding: var(--space-1); /* and its gutter */
|
|
5410
5918
|
border-inline-end: 1px solid var(--line-soft);
|
|
5411
|
-
/* "last 30 days" at --t-3
|
|
5412
|
-
160 leaves the longest shipped preset a whole line and no
|
|
5413
|
-
|
|
5414
|
-
|
|
5919
|
+
/* "last 30 days" at --t-3 measures 132px from column edge to column edge,
|
|
5920
|
+
gutters included; 160 leaves the longest shipped preset a whole line and no
|
|
5921
|
+
more. It stays out of the shared lane rule: this column turns into a
|
|
5922
|
+
wrapping row under 560px and would take that rule's column back.
|
|
5923
|
+
|
|
5924
|
+
A WIDTH THAT MAY SHRINK, not a floor that may not. The panel is clamped,
|
|
5925
|
+
and `.popover` clips, so whatever the clamp takes has to come out of this
|
|
5926
|
+
column or out of the content: at `min-width: 160px; flex-shrink: 0` it came
|
|
5927
|
+
out of the content, and a preset label past the ceiling was cut inside the
|
|
5928
|
+
popover with no scroll and no ellipsis - measured 48px lost at a 35
|
|
5929
|
+
character label, identically at coarse 830, coarse 900 and fine 1440, which
|
|
5930
|
+
is what says it is the clamp and not any one viewport. Resting width is
|
|
5931
|
+
unchanged at 160. */
|
|
5932
|
+
width: 160px;
|
|
5933
|
+
min-width: 0;
|
|
5934
|
+
flex-shrink: 1;
|
|
5415
5935
|
}
|
|
5416
5936
|
/* the rows are `.menu-item` on `role="option"`, the shape `.select-menu` already
|
|
5417
5937
|
uses, so the hover, the active fill and the 44px coarse floor all arrive with
|
|
5418
5938
|
the class. Roving tabindex moves real focus, so the row needs no second
|
|
5419
5939
|
keyboard marker. --text-2 on the panel 4.62:1 dark and 6.67:1 light; active,
|
|
5420
|
-
--accent-text on the opaque
|
|
5421
|
-
|
|
5940
|
+
--accent-text on the panel's opaque quiet mix, 5.95:1 and 4.89:1.
|
|
5941
|
+
|
|
5942
|
+
The one thing it does NOT take from `.menu-item` is nowrap: this row is in a
|
|
5943
|
+
column that shrinks, so it takes two lines rather than losing its end. The
|
|
5944
|
+
two rules tie at (0,1,0) and this one is later, which `props white-space`
|
|
5945
|
+
shows and `ties` cannot, since the two are different subjects. Without it a
|
|
5946
|
+
label past the shrunken column runs under the panel's clip and is cut with no
|
|
5947
|
+
ellipsis to say so - measured, "quarter to date" wanted 110px of a 97px
|
|
5948
|
+
button and lost 13 silently. */
|
|
5949
|
+
.daterange-preset { padding-inline: 10px; white-space: normal; } /* plus the column's own 4px */
|
|
5422
5950
|
|
|
5423
5951
|
.daterange-cal { padding: var(--space-3); }
|
|
5424
5952
|
.daterange-head {
|
|
@@ -5513,22 +6041,23 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5513
6041
|
background: currentColor;
|
|
5514
6042
|
}
|
|
5515
6043
|
|
|
5516
|
-
/* --accent-text on the opaque
|
|
6044
|
+
/* --accent-text on the panel's opaque quiet mix: 5.95:1 dark, 4.89:1 light */
|
|
5517
6045
|
.daterange-day.is-in-range { background: var(--accent-quiet); color: var(--accent-text); }
|
|
5518
6046
|
/* --on-accent on the fill: 8.22:1 dark, 4.62:1 light */
|
|
5519
6047
|
.daterange-day:is(.is-start, .is-end) {
|
|
5520
6048
|
background: var(--accent);
|
|
6049
|
+
--tint-ground: transparent;
|
|
5521
6050
|
color: var(--on-accent);
|
|
5522
6051
|
font-weight: 500;
|
|
5523
6052
|
}
|
|
5524
6053
|
.daterange-day.is-start { border-start-start-radius: var(--r-1); border-end-start-radius: var(--r-1); }
|
|
5525
6054
|
.daterange-day.is-end { border-start-end-radius: var(--r-1); border-end-end-radius: var(--r-1); }
|
|
5526
6055
|
/* THE inset focus ring, and every row in the system that needs one takes it from
|
|
5527
|
-
here. Each of these
|
|
6056
|
+
here. Each of these clips at its own edge, so the page's 3px outward ring
|
|
5528
6057
|
is cut on a start or end edge, lands on a neighbouring cell, or rules across
|
|
5529
6058
|
the body under an open summary. The base ring pairs its outline with a 1px
|
|
5530
6059
|
--bg separator shadow, which at a negative offset is a stray keyline drawn
|
|
5531
|
-
around the box, so it goes with it.
|
|
6060
|
+
around the box, so it goes with it. All the rows here take it.
|
|
5532
6061
|
Nothing here restates the selected row's edge: a selected tree row's band is
|
|
5533
6062
|
the whole active mark, and an inset edge would trace its radius as a crescent.
|
|
5534
6063
|
|
|
@@ -5540,6 +6069,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5540
6069
|
.daterange-day:focus-visible,
|
|
5541
6070
|
.tree-row:focus-visible,
|
|
5542
6071
|
.ctxmenu > .menu-item:focus-visible,
|
|
6072
|
+
.popover-menu .menu-item:focus-visible,
|
|
5543
6073
|
.daterange-preset:focus-visible { outline-offset: -2px; box-shadow: none; }
|
|
5544
6074
|
/* the end being dragged out is outlined, not filled. The band already shows the
|
|
5545
6075
|
range; a second fill would say this end is committed when it is not. The ring
|
|
@@ -5549,15 +6079,27 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5549
6079
|
border-start-end-radius: var(--r-1);
|
|
5550
6080
|
border-end-end-radius: var(--r-1);
|
|
5551
6081
|
}
|
|
6082
|
+
/* and it rounds that end only where the band STOPS. Rounded against a cell that
|
|
6083
|
+
carries the band, the ring reads as a second object cut into the run: the
|
|
6084
|
+
corner has to answer the neighbour, which is why the test is on the cell and
|
|
6085
|
+
not on the day. */
|
|
6086
|
+
.daterange-grid td:has(+ td > .daterange-day:is(.is-in-range, .is-start, .is-end)) > .daterange-day.is-preview-end {
|
|
6087
|
+
border-start-end-radius: 0;
|
|
6088
|
+
border-end-end-radius: 0;
|
|
6089
|
+
}
|
|
5552
6090
|
|
|
5553
6091
|
/* hover paints the neutral cell and leaves every range state alone: the state is
|
|
5554
|
-
already the feedback, and a hover fill over the band breaks the run.
|
|
5555
|
-
|
|
6092
|
+
already the feedback, and a hover fill over the band breaks the run.
|
|
6093
|
+
ORDER IS THE ONLY THING DECIDING THESE THREE. `:not()` carries its argument's
|
|
6094
|
+
specificity, so the base measures (0,3,0) and both restatements tie it there
|
|
6095
|
+
rather than leading it by a class: each has to stay BELOW the base, and a
|
|
6096
|
+
resting screenshot cannot see them swap. The state-merge check drives
|
|
6097
|
+
the hover and reads the paint, which is what holds it.
|
|
5556
6098
|
--text on --surface-2 measures 8.56:1 dark and 11.28:1 light; --on-accent on
|
|
5557
|
-
--accent-hover 10.
|
|
5558
|
-
.daterange-day:hover:not(:disabled) { background: var(--surface-2); }
|
|
6099
|
+
--accent-hover 10.03:1 and 5.90:1. */
|
|
6100
|
+
.daterange-day:hover:not(:disabled) { background: var(--surface-2); --tint-ground: transparent; }
|
|
5559
6101
|
.daterange-day.is-in-range:hover { background: var(--accent-quiet); color: var(--accent-text); }
|
|
5560
|
-
.daterange-day:is(.is-start, .is-end):hover { background: var(--accent-hover); color: var(--on-accent); }
|
|
6102
|
+
.daterange-day:is(.is-start, .is-end):hover { background: var(--accent-hover); --tint-ground: transparent; color: var(--on-accent); }
|
|
5561
6103
|
|
|
5562
6104
|
/* on the two filled ends the inset ring takes the fill's own on-colour, which an
|
|
5563
6105
|
accent ring would vanish into */
|
|
@@ -5589,11 +6131,37 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5589
6131
|
.daterange-presets {
|
|
5590
6132
|
flex-direction: row;
|
|
5591
6133
|
flex-wrap: wrap;
|
|
6134
|
+
/* the side column's own 160px is a COLUMN width; stacked, the row is as
|
|
6135
|
+
wide as the panel */
|
|
6136
|
+
width: auto;
|
|
5592
6137
|
min-width: 0;
|
|
5593
6138
|
border-inline-end: none;
|
|
5594
6139
|
border-block-end: 1px solid var(--line-soft);
|
|
5595
6140
|
}
|
|
5596
6141
|
}
|
|
6142
|
+
/* The preset column gives up its whole-line width before the panel gives up
|
|
6143
|
+
its shape. A 44px grid makes the beside-form 494 wide, and a shell whose
|
|
6144
|
+
content column starts 308 in leaves less than that, so the panel overhung
|
|
6145
|
+
the document from 769 to 801 (measured 33 and 2).
|
|
6146
|
+
|
|
6147
|
+
THE CLAMP IS A LENGTH, NOT A CONTENT WIDTH, and that is the whole point:
|
|
6148
|
+
letting the column shrink without capping the panel leaves the panel exactly
|
|
6149
|
+
as wide as its longest preset string, so the overhang came back at a label
|
|
6150
|
+
two characters longer than the sheet's - measured at coarse 769, uniform
|
|
6151
|
+
labels, 12 characters clears and 14 overhangs by 3, 17 by 26, 20 by 46. 440
|
|
6152
|
+
is the bound the shell case allows (461 of column at 769, less the page's own
|
|
6153
|
+
gutters), so the panel is 440 whatever it holds, and the preset column's own
|
|
6154
|
+
`flex-shrink` and wrap pay for it.
|
|
6155
|
+
|
|
6156
|
+
TRADING WIDTH FOR HEIGHT IS WHAT THIS AVOIDS: stacking the column takes the
|
|
6157
|
+
panel from 383 tall to 528, and a popover that deep hanging off a control is
|
|
6158
|
+
the case `.daterange-up` exists to flip, in a band where the viewport is wide
|
|
6159
|
+
enough that nothing needed to move. Measured in a 431px frame, the stacked
|
|
6160
|
+
form stood 145px past it. Under 560 the panel stacks anyway and that block's
|
|
6161
|
+
own `min-width: 0` owns it. */
|
|
6162
|
+
@media (pointer: coarse) and (min-width: 561px) and (max-width: 820px) {
|
|
6163
|
+
.daterange-panel { max-width: min(440px, calc(100vw - 2 * var(--space-5))); }
|
|
6164
|
+
}
|
|
5597
6165
|
|
|
5598
6166
|
/* ---- Tree navigation ----
|
|
5599
6167
|
Nested rows that expand: a file tree, a nested resource list, a scope picker.
|
|
@@ -5605,12 +6173,10 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5605
6173
|
knows how far down it is. The indent is the group's padding, which is what
|
|
5606
6174
|
lets the line sit on the parent twist's centre at every level with one value.
|
|
5607
6175
|
|
|
5608
|
-
The selected row's band is the shared active-row band
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
exception: flush with a rounded card they take the card's radius, or the band
|
|
5613
|
-
cuts it.
|
|
6176
|
+
The selected row's band is the shared active-row band, and the whole mark:
|
|
6177
|
+
the quiet fill on the --r-1 corners every row already carries, starting at
|
|
6178
|
+
the row's own edge. The 2px accent capsule that used to sit beside it went
|
|
6179
|
+
with `.nav-indicator`.
|
|
5614
6180
|
|
|
5615
6181
|
The band starts at the row's own edge rather than the tree's, which is what
|
|
5616
6182
|
nesting the padding buys. A band that spans the full width at every depth
|
|
@@ -5637,7 +6203,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5637
6203
|
inset-block: 0;
|
|
5638
6204
|
inset-inline-start: calc(var(--space-2) + var(--tree-twist) / 2 - 1px);
|
|
5639
6205
|
width: 2px;
|
|
5640
|
-
background: color-mix(in srgb, var(--text-4) 28%,
|
|
6206
|
+
background: color-mix(in srgb, var(--text-4) 28%, var(--tint-ground));
|
|
5641
6207
|
}
|
|
5642
6208
|
|
|
5643
6209
|
.tree-row {
|
|
@@ -5648,7 +6214,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5648
6214
|
min-height: var(--ctl-sm);
|
|
5649
6215
|
padding: 0 var(--space-2);
|
|
5650
6216
|
border: none;
|
|
5651
|
-
border-radius:
|
|
6217
|
+
border-radius: var(--r-1); /* the list panel's row radius, in every state */
|
|
5652
6218
|
background: none;
|
|
5653
6219
|
font: 400 var(--t-3)/1.3 var(--font-sans);
|
|
5654
6220
|
color: var(--text-3); /* 5.48:1 on --surface-1 and 6.34:1 on --bg dark, 5.92 / 5.28 light */
|
|
@@ -5658,13 +6224,18 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5658
6224
|
user-select: none;
|
|
5659
6225
|
transition: background var(--dur-tint) var(--ease), color var(--dur-tint) var(--ease);
|
|
5660
6226
|
}
|
|
5661
|
-
.tree-row:hover { background: var(--surface-2); color: var(--text-2); } /* 6.17:1 dark, 7.00:1 light */
|
|
5662
|
-
/* The
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
6227
|
+
.tree-row:hover { background: var(--surface-2); --tint-ground: transparent; color: var(--text-2); } /* 6.17:1 dark, 7.00:1 light */
|
|
6228
|
+
/* The active row's band, one rule across the three rows that paint one: the
|
|
6229
|
+
quiet accent fill spans the row and starts at the row's own indent. Derived
|
|
6230
|
+
from .nav-item.active. It sits HERE, after
|
|
6231
|
+
`.tree-row:hover`, because the two tie at (0,2,0) and the last of the three
|
|
6232
|
+
member hover rules is the tree's: placed in the sidebar block as it shipped,
|
|
6233
|
+
a hovered selected tree row repainted its band grey (measured
|
|
6234
|
+
oklch(0.345 0.026 280) under the pointer). The sidebar's and the palette's
|
|
6235
|
+
hover rules sit above it in the file, so the one move covers all three. */
|
|
6236
|
+
.nav-item.active,
|
|
6237
|
+
.tree-row[aria-selected="true"],
|
|
6238
|
+
.palette-row.active { background: var(--accent-quiet); }
|
|
5668
6239
|
/* The band is the shared active-row rule; this is the accent text on it.
|
|
5669
6240
|
--accent-text on the quiet fill measures 5.78:1 over --surface-1 and 6.66:1
|
|
5670
6241
|
over --bg, dark. */
|
|
@@ -5761,6 +6332,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5761
6332
|
flex-shrink: 0;
|
|
5762
6333
|
border-radius: var(--r-pill);
|
|
5763
6334
|
background: var(--status);
|
|
6335
|
+
--tint-ground: transparent;
|
|
5764
6336
|
}
|
|
5765
6337
|
.status-key-success { --status: var(--success); }
|
|
5766
6338
|
.status-key-warning { --status: var(--warning); }
|
|
@@ -5801,7 +6373,7 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5801
6373
|
place-items: center;
|
|
5802
6374
|
padding: var(--space-10) var(--space-6);
|
|
5803
6375
|
}
|
|
5804
|
-
/*
|
|
6376
|
+
/* these screens make the dot grid the subject, and the copy
|
|
5805
6377
|
sits in a gap torn in it. The grid has to be visible for that to be true, and
|
|
5806
6378
|
at the shipped resting alpha it is not: measured, a resting dot lands 5 sRGB
|
|
5807
6379
|
levels off the page at 0.24 and 13 dark / 12 light at 0.60. The lift is on the
|
|
@@ -5814,10 +6386,14 @@ th.num-col .th-sort { flex-direction: row-reverse; }
|
|
|
5814
6386
|
grid because of a 404 six levels deep inside a panel. Measured, the direct-child
|
|
5815
6387
|
form matches the route and misses the specimen. */
|
|
5816
6388
|
html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
6389
|
+
/* The same route keeps the flat page under the grid. The gap below is a
|
|
6390
|
+
page-toned shape, so over the sky it cut a dark cap out of the light above
|
|
6391
|
+
the copy; the grid is this screen's subject, and the sky is not. */
|
|
6392
|
+
html:has(> body > .page-state) > body::after { content: none; }
|
|
5817
6393
|
/* The gap IS the design, and it is TORN rather than cut. A page-toned box with a
|
|
5818
6394
|
radius paints a hard-edged rectangle across the canvas, which reads as a card
|
|
5819
|
-
laid on the grid:
|
|
5820
|
-
|
|
6395
|
+
laid on the grid: a gray square that blocks the view of the background dots.
|
|
6396
|
+
The radial ramp holds the copy's own area at page tone and hands the
|
|
5821
6397
|
grid back on the way out, so there is no edge to read. A stepped, grid-aligned
|
|
5822
6398
|
edge was tried at the grid's own 40px pitch (--dot-gap) and reads no
|
|
5823
6399
|
differently from the plain box. */
|
|
@@ -5838,8 +6414,8 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
5838
6414
|
max-width: var(--page-narrow);
|
|
5839
6415
|
text-align: center;
|
|
5840
6416
|
}
|
|
5841
|
-
/* --t-7, the
|
|
5842
|
-
names a screen inside a product; this screen has no product around it.
|
|
6417
|
+
/* --t-7, a step under the two display sizes. --t-9 belongs to `.page-title`,
|
|
6418
|
+
which names a screen inside a product; this screen has no product around it.
|
|
5843
6419
|
--text measures 11.67:1 on --bg and 10.09 on --surface-1 dark, 12.00 / 13.45
|
|
5844
6420
|
light. */
|
|
5845
6421
|
.page-state-title {
|
|
@@ -5900,6 +6476,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
5900
6476
|
flex: 1 1 auto;
|
|
5901
6477
|
height: 1px;
|
|
5902
6478
|
background: var(--surface-3);
|
|
6479
|
+
--tint-ground: transparent;
|
|
5903
6480
|
}
|
|
5904
6481
|
.page-state-foot { font-size: var(--t-2); color: var(--text-3); text-align: center; } /* 5.48 / 5.92 */
|
|
5905
6482
|
|
|
@@ -5931,7 +6508,6 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
5931
6508
|
z-index: 550;
|
|
5932
6509
|
min-width: 180px;
|
|
5933
6510
|
max-width: min(320px, calc(100vw - 2 * var(--space-4)));
|
|
5934
|
-
padding-block: var(--space-1);
|
|
5935
6511
|
translate: 0 0;
|
|
5936
6512
|
transform-origin: top left;
|
|
5937
6513
|
}
|
|
@@ -5997,6 +6573,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
5997
6573
|
width: 100%;
|
|
5998
6574
|
height: 100%;
|
|
5999
6575
|
background: var(--accent); /* 7.48:1 dark, 4.10:1 light on --bg, over the 3:1 floor */
|
|
6576
|
+
--tint-ground: transparent;
|
|
6000
6577
|
transform-origin: left center;
|
|
6001
6578
|
}
|
|
6002
6579
|
.route-progress.is-loading { visibility: visible; }
|
|
@@ -6039,8 +6616,11 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6039
6616
|
gap: var(--space-2);
|
|
6040
6617
|
max-width: 100%;
|
|
6041
6618
|
min-width: 0;
|
|
6042
|
-
padding
|
|
6619
|
+
/* no end padding: the button's hover well fills the chip's end cap, and a
|
|
6620
|
+
2px sliver of chip around a hovered well read as a gap in the fill */
|
|
6621
|
+
padding-inline: var(--space-2) 0;
|
|
6043
6622
|
background: var(--well);
|
|
6623
|
+
--tint-ground: transparent;
|
|
6044
6624
|
border: 1px solid var(--line-soft);
|
|
6045
6625
|
border-radius: var(--r-1);
|
|
6046
6626
|
vertical-align: middle;
|
|
@@ -6074,10 +6654,19 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6074
6654
|
}
|
|
6075
6655
|
/* 24px and bare, the shape `.code-copy` already is: no border, no fill at rest,
|
|
6076
6656
|
below every control size. --text-3 on --well measures 7.02:1 dark and 4.82:1
|
|
6077
|
-
light, and --text on the hover fill 8.56:1 and 11.28:1.
|
|
6657
|
+
light, and --text on the hover fill 8.56:1 and 11.28:1.
|
|
6658
|
+
The button takes the chip's full height and its end cap, so the hover well
|
|
6659
|
+
meets the chip's inner edges on all three sides: a fixed 24px height left a
|
|
6660
|
+
1px sliver above and below whenever the chip's row ran taller. Stretch, not
|
|
6661
|
+
a height, so a wrapping value grows the well with the chip; the floor holds
|
|
6662
|
+
the 24 for a chip whose text is shorter than that. The radius is the
|
|
6663
|
+
chip's own --r-1, which sits a hair outside the 1px-border arc
|
|
6664
|
+
an exact concentric fit would take (9px); the chip does not clip, and the
|
|
6665
|
+
difference is sub-pixel at 1x. */
|
|
6078
6666
|
.copy-chip-btn {
|
|
6079
6667
|
width: 24px;
|
|
6080
|
-
height: 24px;
|
|
6668
|
+
min-height: 24px;
|
|
6669
|
+
align-self: stretch;
|
|
6081
6670
|
border-radius: var(--r-1);
|
|
6082
6671
|
color: var(--text-3);
|
|
6083
6672
|
}
|
|
@@ -6143,9 +6732,10 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6143
6732
|
The cue that a scrolling box holds more than it shows. The scrollers carry it
|
|
6144
6733
|
themselves: `initScrollFade` writes the cue class onto every `pre`,
|
|
6145
6734
|
`.table-wrap`, `.chart-scroll`, `.modal-body`, `.log-view`,
|
|
6146
|
-
`.pagination-pages`, `.sidebar-nav
|
|
6147
|
-
|
|
6148
|
-
the one
|
|
6735
|
+
`.pagination-pages`, `.sidebar-nav` and `.navbar-nav`; a consumer scroller
|
|
6736
|
+
opts in with `data-scroll-fade`, and floating panels (`.select-menu`,
|
|
6737
|
+
`.palette-list`) scroll bare. `no-scroll-fade` on any scroller is the one
|
|
6738
|
+
opt-out.
|
|
6149
6739
|
|
|
6150
6740
|
IT IS A MASK, NOT A PAINTED RAMP. A painted ramp has to know the surface
|
|
6151
6741
|
behind it, and the same scroller sits on a card, on the page and inside a
|
|
@@ -6153,7 +6743,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6153
6743
|
|
|
6154
6744
|
THE BLEED IS LOAD-BEARING. A mask clips to its own painting area, the painting
|
|
6155
6745
|
area is the border box, and a masked box therefore loses its shadow outright:
|
|
6156
|
-
measured on a 300x140 card, --elev-1 falls from
|
|
6746
|
+
measured on a 300x140 card, --elev-1 falls from 23 levels under and 11 beside
|
|
6157
6747
|
to 0 and 0, and it falls there for a mask that fades nothing at all.
|
|
6158
6748
|
`mask-clip: no-clip` lifts the clip, but the image then has to REACH past the
|
|
6159
6749
|
box or the region outside it masks to transparent just the same (measured:
|
|
@@ -6161,8 +6751,8 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6161
6751
|
sized 48px past the box on every side and carries an opaque plateau in the
|
|
6162
6752
|
bleed with a hard stop at the box's own edge. 48 is `.select-menu`'s own bleed
|
|
6163
6753
|
and for the same reason: --elev-float's widest coat is what has to clear it.
|
|
6164
|
-
On the shipped `.table-wrap` with the fade on, the shadow measures
|
|
6165
|
-
under and
|
|
6754
|
+
On the shipped `.table-wrap` with the fade on, the shadow measures 24 levels
|
|
6755
|
+
under and 12 beside against 23 and 11 on the same page's unmasked one.
|
|
6166
6756
|
|
|
6167
6757
|
THE FADE IS OFF AT REST. Both windows are 0 until a controller says the box
|
|
6168
6758
|
can scroll that way, so a page with no controller paints no fade at all, and
|
|
@@ -6182,9 +6772,10 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6182
6772
|
--fade-start: 0px;
|
|
6183
6773
|
--fade-end: 0px;
|
|
6184
6774
|
--fade-bleed: 48px;
|
|
6775
|
+
--fade-dir: to right;
|
|
6185
6776
|
/* a mask reads alpha and nothing else. #000 and #0000 are the two ends of
|
|
6186
6777
|
that alpha ramp, not a colour choice, which is why no token fits here. */
|
|
6187
|
-
--fade-mask: linear-gradient(
|
|
6778
|
+
--fade-mask: linear-gradient(var(--fade-dir),
|
|
6188
6779
|
#000 0 var(--fade-bleed),
|
|
6189
6780
|
#0000 var(--fade-bleed),
|
|
6190
6781
|
#000 calc(var(--fade-bleed) + var(--fade-start)),
|
|
@@ -6199,16 +6790,9 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6199
6790
|
}
|
|
6200
6791
|
/* the block axis, written by the controller when the element scrolls down:
|
|
6201
6792
|
`.modal-body`, `.log-view`, the sheet's own column. It rides on `.scroll-fade`,
|
|
6202
|
-
which owns the mask
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
#000 0 var(--fade-bleed),
|
|
6206
|
-
#0000 var(--fade-bleed),
|
|
6207
|
-
#000 calc(var(--fade-bleed) + var(--fade-start)),
|
|
6208
|
-
#000 calc(100% - var(--fade-bleed) - var(--fade-end)),
|
|
6209
|
-
#0000 calc(100% - var(--fade-bleed)),
|
|
6210
|
-
#000 calc(100% - var(--fade-bleed)) 100%);
|
|
6211
|
-
}
|
|
6793
|
+
which owns the mask, and turns it: the axis is the only thing that differs,
|
|
6794
|
+
so it is the only thing this rule says. */
|
|
6795
|
+
.scroll-fade-y { --fade-dir: to bottom; }
|
|
6212
6796
|
.scroll-fade.has-more-start { --fade-start: var(--fade); }
|
|
6213
6797
|
.scroll-fade.has-more-end { --fade-end: var(--fade); }
|
|
6214
6798
|
}
|
|
@@ -6237,6 +6821,34 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6237
6821
|
.scroll-fade-y.has-more-start.has-more-end {
|
|
6238
6822
|
box-shadow: inset 0 1px 0 var(--line), inset 0 -1px 0 var(--line);
|
|
6239
6823
|
}
|
|
6824
|
+
/* a scroller that carries elevation restates it beside the hairline: the
|
|
6825
|
+
generic rules above replace `box-shadow` whole, so a scrolling `.table-wrap`
|
|
6826
|
+
spent its three --elev-1 coats on one inset line. At (0,3,0) these win on
|
|
6827
|
+
specificity whatever the order; the scrollers with no shadow of their own
|
|
6828
|
+
keep the generic form. */
|
|
6829
|
+
.table-wrap.scroll-fade.has-more-start {
|
|
6830
|
+
box-shadow: var(--elev-1), inset 1px 0 0 var(--line);
|
|
6831
|
+
}
|
|
6832
|
+
.table-wrap.scroll-fade.has-more-end {
|
|
6833
|
+
box-shadow: var(--elev-1), inset -1px 0 0 var(--line);
|
|
6834
|
+
}
|
|
6835
|
+
.table-wrap.scroll-fade.has-more-start.has-more-end {
|
|
6836
|
+
box-shadow: var(--elev-1), inset 1px 0 0 var(--line), inset -1px 0 0 var(--line);
|
|
6837
|
+
}
|
|
6838
|
+
/* the second scroller in the controller's list that carries its own
|
|
6839
|
+
elevation. It scrolls on the block axis past its own max-height, so it
|
|
6840
|
+
takes the -y forms, and it floats, so the coat it restates is --elev-float:
|
|
6841
|
+
without this a menu long enough to scroll paints no elevation at all over
|
|
6842
|
+
the card behind it. */
|
|
6843
|
+
.select-menu.scroll-fade-y.has-more-start {
|
|
6844
|
+
box-shadow: var(--elev-float), inset 0 1px 0 var(--line);
|
|
6845
|
+
}
|
|
6846
|
+
.select-menu.scroll-fade-y.has-more-end {
|
|
6847
|
+
box-shadow: var(--elev-float), inset 0 -1px 0 var(--line);
|
|
6848
|
+
}
|
|
6849
|
+
.select-menu.scroll-fade-y.has-more-start.has-more-end {
|
|
6850
|
+
box-shadow: var(--elev-float), inset 0 1px 0 var(--line), inset 0 -1px 0 var(--line);
|
|
6851
|
+
}
|
|
6240
6852
|
}
|
|
6241
6853
|
|
|
6242
6854
|
/* ---- Coarse pointers ----
|
|
@@ -6245,7 +6857,20 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6245
6857
|
by default, and the copy button is a 24px glyph, the same shape `.code-copy`
|
|
6246
6858
|
grows from. */
|
|
6247
6859
|
@media (pointer: coarse) {
|
|
6248
|
-
|
|
6860
|
+
/* The cell is what the panel's width IS: seven of them plus the calendar's
|
|
6861
|
+
own 2 * --space-3 gutter. So the panel's clamp is expressed here, on the
|
|
6862
|
+
one number every part of the grid derives from, rather than on a box that
|
|
6863
|
+
`.popover`'s `overflow: hidden` would then cut a date column out of.
|
|
6864
|
+
(100vw - 64px) / 7 is that clamp read back: 64 is the panel's own
|
|
6865
|
+
2 * --space-5 bound plus the calendar gutter. It bites under 372px, where
|
|
6866
|
+
seven 44px cells plus the gutters fit no viewport - measured 44.0 at 373
|
|
6867
|
+
and 374, 42.3 at 360, 36.6 at 320 - so every phone at 373 and up keeps the
|
|
6868
|
+
floor whole, and under it the cell keeps its 44px HEIGHT and gives up
|
|
6869
|
+
width, which is the only thing left to give. */
|
|
6870
|
+
.daterange { --dr-cell: min(var(--ctl-lg), calc((100vw - 64px) / 7)); }
|
|
6871
|
+
/* the token carries both axes, and only the width may give: restated at the
|
|
6872
|
+
base rule's own (0,1,0), later in the file, so the row keeps the floor */
|
|
6873
|
+
.daterange-day { height: var(--ctl-lg); }
|
|
6249
6874
|
.tree-row { min-height: var(--ctl-lg); }
|
|
6250
6875
|
}
|
|
6251
6876
|
|
|
@@ -6257,6 +6882,9 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6257
6882
|
the ladder's rule is that reduced motion replaces. */
|
|
6258
6883
|
@media (prefers-reduced-motion: reduce) {
|
|
6259
6884
|
.tree-twist > svg { transition: none; }
|
|
6885
|
+
/* the flipped menu's own (0,2,0) and (0,3,0) outrank the `.popover` fades */
|
|
6886
|
+
.ctxmenu.ctxmenu-flip-y { animation-name: fade-in; }
|
|
6887
|
+
.ctxmenu.ctxmenu-flip-y.closing { animation-name: fade-out; }
|
|
6260
6888
|
.copy-chip.is-copied .copy-chip-btn > svg { animation: none; }
|
|
6261
6889
|
/* keyed on the running state like the sweep it replaces, and at that rule's own
|
|
6262
6890
|
(0,2,0), or reduce would start the hold on a bar that is at rest */
|
|
@@ -6295,7 +6923,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6295
6923
|
1.23:1 light, the same step a card takes over a well band. Every "its own
|
|
6296
6924
|
surface" in this block is that step.
|
|
6297
6925
|
|
|
6298
|
-
ONE NEUTRAL HAIRLINE, `color-mix(in srgb, var(--text-4) 40%,
|
|
6926
|
+
ONE NEUTRAL HAIRLINE, `color-mix(in srgb, var(--text-4) 40%, var(--tint-ground))`,
|
|
6299
6927
|
for the dropzone's dash, the sticky column's divider and the inline field's
|
|
6300
6928
|
hover edge. The opaque neutrals cannot do this job: --line measures 1.11:1 on
|
|
6301
6929
|
the light well and --surface-3 1.06:1, so both vanish in Latte. The mix tracks
|
|
@@ -6373,14 +7001,14 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6373
7001
|
|
|
6374
7002
|
/* ---- The panel's non-row states ----
|
|
6375
7003
|
No match, loading and "type to search" are all `.palette-empty`, the system's
|
|
6376
|
-
row that names the fix. Only the gutter changes, from the palette's 14px
|
|
6377
|
-
the
|
|
6378
|
-
with the options above it. */
|
|
7004
|
+
row that names the fix. Only the gutter changes, from the palette's own 14px
|
|
7005
|
+
to the 8 `.select-menu .menu-item` already uses inside the panel's 4px, so
|
|
7006
|
+
the text column lines up with the options above it. */
|
|
6379
7007
|
.select-menu .palette-empty {
|
|
6380
7008
|
display: flex;
|
|
6381
7009
|
align-items: center;
|
|
6382
7010
|
gap: var(--space-2);
|
|
6383
|
-
padding-inline:
|
|
7011
|
+
padding-inline: 8px;
|
|
6384
7012
|
}
|
|
6385
7013
|
|
|
6386
7014
|
/* The matched substring. `<mark>` carries a UA yellow that nothing in this
|
|
@@ -6471,10 +7099,38 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6471
7099
|
whole color-mix would fall back to transparent. */
|
|
6472
7100
|
.select-chip {
|
|
6473
7101
|
background: color-mix(in oklab, var(--surface-1), var(--surface-2) calc(var(--lift, 0) * 100%));
|
|
7102
|
+
--tint-ground: transparent;
|
|
6474
7103
|
color: var(--text-2); /* 7.30:1 dark and 8.36:1 light at rest, 6.17 / 7.00 open */
|
|
6475
7104
|
max-width: 100%;
|
|
6476
7105
|
}
|
|
6477
7106
|
.select-chip > .close { color: var(--text-3); } /* 5.49 / 5.92 at rest, 4.65 / 4.96 open */
|
|
7107
|
+
/* THE remove hover, declared here rather than with `.close` in the
|
|
7108
|
+
one-remove-control section because this rest rule ties it at (0,2,0) and a
|
|
7109
|
+
state must not lose to a rest: the order is the same treatment
|
|
7110
|
+
`.menu-item.active` got against `.menu-item:hover`. The chip's own
|
|
7111
|
+
(0,3,0) hover carries the same costume at chip scale either way, so this
|
|
7112
|
+
is the belt to that braces, and the braces reach the select chip because
|
|
7113
|
+
every select chip is spelled `tag filter-chip select-chip`: derive that set
|
|
7114
|
+
from the markup rather than carrying its count, which differs per tree.
|
|
7115
|
+
Measured 2026-09-07: with this rest colour planted BELOW the hover, winning
|
|
7116
|
+
the (0,2,0) tie, a hovered select chip still paints --danger through the
|
|
7117
|
+
filter-chip rule. A select chip written without `.filter-chip` puts it back
|
|
7118
|
+
on order alone. Numbers and the light-theme record live with the skeleton
|
|
7119
|
+
rules in the one-remove-control section. */
|
|
7120
|
+
.close:hover {
|
|
7121
|
+
color: var(--danger);
|
|
7122
|
+
background: var(--well);
|
|
7123
|
+
--tint-ground: transparent;
|
|
7124
|
+
}
|
|
7125
|
+
/* a glass panel is the one ground where --well loses and --surface-2 reads:
|
|
7126
|
+
1.28:1 dark over the black plate and 1.34 over the white, against --well's
|
|
7127
|
+
1.18. --danger is --glass-danger inside these panels, and holds 6.11:1 on
|
|
7128
|
+
the fill. Light is one of the three grounds no grey clears 1.2 on:
|
|
7129
|
+
--surface-2 reads 1.05 / 1.19, the same fill the neutral hover already
|
|
7130
|
+
painted there, and --surface-1 clears the black plate at 1.25 only to read
|
|
7131
|
+
1.00 on the white one. */
|
|
7132
|
+
.toast .close:hover,
|
|
7133
|
+
.modal .close:hover { background: var(--surface-2); --tint-ground: transparent; }
|
|
6478
7134
|
|
|
6479
7135
|
/* One line, the remainder folded into a count the controller writes. Same
|
|
6480
7136
|
contract `.tabs-boxed-wrap.has-ink` runs under: the class is added at runtime,
|
|
@@ -6513,7 +7169,8 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6513
7169
|
text-align: center;
|
|
6514
7170
|
border-radius: var(--r-2);
|
|
6515
7171
|
background: var(--well);
|
|
6516
|
-
|
|
7172
|
+
--tint-ground: transparent;
|
|
7173
|
+
border: 1px dashed color-mix(in srgb, var(--text-4) 40%, var(--tint-ground));
|
|
6517
7174
|
cursor: pointer;
|
|
6518
7175
|
transition: background var(--dur-tint) var(--ease), border-color var(--dur-tint) var(--ease);
|
|
6519
7176
|
}
|
|
@@ -6572,6 +7229,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6572
7229
|
lifts a step for this state only. */
|
|
6573
7230
|
.dropzone.is-over > .dropzone-target {
|
|
6574
7231
|
background: color-mix(in srgb, var(--accent) 10%, var(--well));
|
|
7232
|
+
--tint-ground: transparent;
|
|
6575
7233
|
border-color: var(--accent);
|
|
6576
7234
|
border-style: dashed;
|
|
6577
7235
|
}
|
|
@@ -6588,6 +7246,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6588
7246
|
of them shipped the lift. */
|
|
6589
7247
|
.dropzone.is-rejected > .dropzone-target {
|
|
6590
7248
|
background: color-mix(in srgb, var(--danger) 8%, var(--well));
|
|
7249
|
+
--tint-ground: transparent;
|
|
6591
7250
|
border-color: var(--danger);
|
|
6592
7251
|
}
|
|
6593
7252
|
.dropzone.is-rejected > .dropzone-target > svg { color: var(--danger); }
|
|
@@ -6599,7 +7258,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6599
7258
|
color: var(--text-disabled);
|
|
6600
7259
|
/* the dashed edge is DERIVED from the same token as the copy, so quieting the
|
|
6601
7260
|
copy quiets the edge with it and the zone keeps one construction */
|
|
6602
|
-
border-color: color-mix(in srgb, var(--text-disabled) 40%,
|
|
7261
|
+
border-color: color-mix(in srgb, var(--text-disabled) 40%, var(--tint-ground));
|
|
6603
7262
|
cursor: not-allowed;
|
|
6604
7263
|
}
|
|
6605
7264
|
|
|
@@ -6618,6 +7277,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6618
7277
|
gap: var(--space-3);
|
|
6619
7278
|
padding: var(--space-2) var(--space-3);
|
|
6620
7279
|
background: var(--well);
|
|
7280
|
+
--tint-ground: transparent;
|
|
6621
7281
|
border-radius: var(--r-1);
|
|
6622
7282
|
/* the entrance carries no fill mode, so a page with no controller paints the
|
|
6623
7283
|
settled row and the row is never hidden behind a class */
|
|
@@ -6649,14 +7309,18 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6649
7309
|
dark and 1.23:1 light, with --accent on it at 6.48 / 4.60 over the 3:1 floor. */
|
|
6650
7310
|
.dropzone-file .progress {
|
|
6651
7311
|
background: var(--surface-1);
|
|
7312
|
+
--tint-ground: transparent;
|
|
6652
7313
|
margin-top: var(--space-2);
|
|
6653
7314
|
}
|
|
6654
7315
|
/* The remove is `.close`, the one remove control with the coarse floor already
|
|
6655
|
-
on it.
|
|
6656
|
-
|
|
6657
|
-
|
|
7316
|
+
on it. The row's ground is --well, the one ground where the well itself is
|
|
7317
|
+
invisible: --well on --well measures 1.00:1 in both themes. The fill rises
|
|
7318
|
+
instead, the move this block already makes for the file's progress track:
|
|
7319
|
+
--surface-1 reads 1.28:1 dark and 1.23:1 light on the row, and --danger on
|
|
7320
|
+
it 5.17:1 dark and 6.00:1 light. */
|
|
6658
7321
|
.dropzone-file > .close:hover {
|
|
6659
|
-
background:
|
|
7322
|
+
background: var(--surface-1);
|
|
7323
|
+
--tint-ground: transparent;
|
|
6660
7324
|
color: var(--danger);
|
|
6661
7325
|
}
|
|
6662
7326
|
|
|
@@ -6679,27 +7343,20 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6679
7343
|
border-radius: var(--r-1);
|
|
6680
7344
|
--ring-color: var(--accent-line);
|
|
6681
7345
|
}
|
|
6682
|
-
/* the corners are the ladder's: --r-1 outside, square inside.
|
|
6683
|
-
|
|
7346
|
+
/* the corners are the ladder's: --r-1 outside, square inside. The two end radii
|
|
7347
|
+
are the same pair every joined group takes, so they are declared once for all
|
|
7348
|
+
three in the button-group block. */
|
|
6684
7349
|
.input-group > * {
|
|
6685
7350
|
position: relative;
|
|
6686
7351
|
border-radius: 0;
|
|
6687
7352
|
}
|
|
6688
|
-
.input-group > :first-child {
|
|
6689
|
-
border-start-start-radius: var(--r-1);
|
|
6690
|
-
border-end-start-radius: var(--r-1);
|
|
6691
|
-
}
|
|
6692
|
-
.input-group > :last-child {
|
|
6693
|
-
border-start-end-radius: var(--r-1);
|
|
6694
|
-
border-end-end-radius: var(--r-1);
|
|
6695
|
-
}
|
|
6696
7353
|
.input-group > * + * { margin-inline-start: -1px; }
|
|
6697
7354
|
.input-group > .input { flex: 1 1 auto; min-width: 0; }
|
|
6698
7355
|
/* the field keeps its accent border and gives up its own halo to the group */
|
|
6699
7356
|
.input-group > .input:focus { --ring: 0px; z-index: 2; }
|
|
6700
7357
|
.input-group:has(> .input:focus) { box-shadow: 0 0 0 3px var(--ring-color); }
|
|
6701
7358
|
.input-group:has(> .input:is(.input-invalid, [aria-invalid="true"])) {
|
|
6702
|
-
--ring-color: color-mix(in srgb, var(--danger) 30%,
|
|
7359
|
+
--ring-color: color-mix(in srgb, var(--danger) 30%, var(--glass-ground));
|
|
6703
7360
|
}
|
|
6704
7361
|
/* a disabled field takes its addons with it: `.input:disabled` dims only itself.
|
|
6705
7362
|
The addon's own icon names --text-4, so it is restated or it stays lit inside a
|
|
@@ -6717,6 +7374,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6717
7374
|
flex-shrink: 0;
|
|
6718
7375
|
padding: 0 12px; /* `.input`'s own inline padding, so the two texts line up */
|
|
6719
7376
|
background: var(--surface-1);
|
|
7377
|
+
--tint-ground: transparent;
|
|
6720
7378
|
border: 1px solid var(--line);
|
|
6721
7379
|
color: var(--text-3); /* 5.49:1 dark, 5.92:1 light on --surface-1 */
|
|
6722
7380
|
font: 400 var(--t-3)/1.4 var(--font-sans);
|
|
@@ -6739,6 +7397,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6739
7397
|
flex-shrink: 0;
|
|
6740
7398
|
padding: 0;
|
|
6741
7399
|
background: var(--surface-1);
|
|
7400
|
+
--tint-ground: transparent;
|
|
6742
7401
|
border: 1px solid var(--line);
|
|
6743
7402
|
color: var(--text-3); /* 5.49:1 dark, 5.92:1 light on --surface-1, over the 3:1 floor */
|
|
6744
7403
|
cursor: pointer;
|
|
@@ -6750,32 +7409,16 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6750
7409
|
page's, and the raised z-index keeps it over both neighbours */
|
|
6751
7410
|
.input-step:focus-visible { z-index: 2; }
|
|
6752
7411
|
|
|
6753
|
-
/*
|
|
6754
|
-
|
|
6755
|
-
12.98:1 dark and 10.95:1 light on --well. */
|
|
6756
|
-
.input-group-num > .input {
|
|
6757
|
-
text-align: center;
|
|
6758
|
-
font-family: var(--font-mono);
|
|
6759
|
-
font-variant-numeric: tabular-nums;
|
|
6760
|
-
font-feature-settings: "tnum";
|
|
6761
|
-
}
|
|
6762
|
-
.input-group-num > .input[type="number"] { appearance: textfield; }
|
|
6763
|
-
.input-group-num > .input::-webkit-outer-spin-button,
|
|
6764
|
-
.input-group-num > .input::-webkit-inner-spin-button {
|
|
6765
|
-
appearance: none;
|
|
6766
|
-
margin: 0;
|
|
6767
|
-
}
|
|
7412
|
+
/* The value centres because the two controls flank it. The mono face and the
|
|
7413
|
+
spinners-off are the number field's own, declared with `.input` above.
|
|
7414
|
+
--text measures 12.98:1 dark and 10.95:1 light on --well. */
|
|
7415
|
+
.input-group-num > .input { text-align: center; }
|
|
6768
7416
|
|
|
6769
7417
|
/* ============================================================
|
|
6770
7418
|
Button group and split button
|
|
6771
7419
|
============================================================ */
|
|
6772
7420
|
|
|
6773
|
-
/*
|
|
6774
|
-
it selects a panel. `.btn-group` is actions or a pressed-state toggle set
|
|
6775
|
-
sharing one outline, and it selects nothing. Reaching for the wrong one is
|
|
6776
|
-
the mistake this pair exists to prevent.
|
|
6777
|
-
|
|
6778
|
-
The corners are the ladder's --r-1 outside and square inside; no new number
|
|
7421
|
+
/* The corners are the ladder's --r-1 outside and square inside; no new number
|
|
6779
7422
|
appears anywhere in this block. Members shrink rather than scroll, so a group
|
|
6780
7423
|
can never widen the page: put a `.truncate` span inside a label that has to
|
|
6781
7424
|
give way. Past four segments the control is a `.select`. */
|
|
@@ -6791,11 +7434,18 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6791
7434
|
flex: 0 1 auto;
|
|
6792
7435
|
min-width: 0;
|
|
6793
7436
|
}
|
|
7437
|
+
/* the run's two end radii, for all three joined groups. Logical, so a
|
|
7438
|
+
right-to-left page rounds the end the reader's eye actually starts at. It has
|
|
7439
|
+
to sit AFTER the `border-radius: 0` resets above: `.btn-split > .btn` ties
|
|
7440
|
+
`.btn-split > .btn-split-toggle` at (0,2,0), so an earlier placement squares
|
|
7441
|
+
the split button's own outer end. */
|
|
7442
|
+
.input-group > :first-child,
|
|
6794
7443
|
.btn-group > .btn:first-child,
|
|
6795
7444
|
.btn-split > .btn:first-child {
|
|
6796
7445
|
border-start-start-radius: var(--r-1);
|
|
6797
7446
|
border-end-start-radius: var(--r-1);
|
|
6798
7447
|
}
|
|
7448
|
+
.input-group > :last-child,
|
|
6799
7449
|
.btn-group > .btn:last-child,
|
|
6800
7450
|
.btn-split > .btn-split-toggle {
|
|
6801
7451
|
border-start-end-radius: var(--r-1);
|
|
@@ -6842,9 +7492,21 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6842
7492
|
to the tag family, where each variant remixes its OWN semantic token, and only
|
|
6843
7493
|
--info happens to resolve to the accent.
|
|
6844
7494
|
|
|
6845
|
-
|
|
6846
|
-
|
|
7495
|
+
ITS PLACEMENT IS FREE WHILE BOTH ITS `.btn-group` SELECTORS TRAVEL TOGETHER,
|
|
7496
|
+
and the tie this comment used to claim is not the one that exists. Every
|
|
7497
|
+
selector here carries `[data-theme="light"]`, one step above the dark rule it
|
|
7498
|
+
remixes: the pressed pair measures (0,4,0) and (0,5,0) against the dark (0,3,0)
|
|
7499
|
+
and (0,4,0), and every other member gains the same step. The one real tie is
|
|
7500
|
+
this rule's (0,4,0) base selector against the dark rule's (0,4,0) `:hover`
|
|
7501
|
+
selector on a hovered pressed segment, and it is inert only because the (0,5,0)
|
|
7502
|
+
selector beside it wins that element outright. Split the two and the base is
|
|
7503
|
+
order-decided. Measured 2026-09-07 with the dark pair appended last, winning
|
|
7504
|
+
every tie: the light label did not move, hovered or at rest. */
|
|
6847
7505
|
[data-theme="light"] .pagination .btn[aria-current="page"],
|
|
7506
|
+
[data-theme="light"] .tag-accent,
|
|
7507
|
+
[data-theme="light"] .tab.active .tab-badge,
|
|
7508
|
+
[data-theme="light"] .navbar-link.active .navbar-badge,
|
|
7509
|
+
[data-theme="light"] .nav-item.active .nav-badge,
|
|
6848
7510
|
[data-theme="light"] .step[aria-current="step"] .step-index,
|
|
6849
7511
|
[data-theme="light"] .avatar,
|
|
6850
7512
|
[data-theme="light"] .tree-row[aria-selected="true"],
|
|
@@ -6872,13 +7534,14 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6872
7534
|
says the second one opens something. currentColor rather than a token, so it
|
|
6873
7535
|
tracks the hover and press fills without a rule for each. */
|
|
6874
7536
|
.btn-split > .btn-fill + .btn-fill {
|
|
6875
|
-
border-inline-start-color: color-mix(in srgb, currentColor 25%,
|
|
7537
|
+
border-inline-start-color: color-mix(in srgb, currentColor 25%, var(--tint-ground));
|
|
6876
7538
|
}
|
|
6877
7539
|
/* One control: hovering either filled half lifts the pair together. Letting
|
|
6878
7540
|
`.btn-fill:hover` fire on the half under the pointer alone is what made the
|
|
6879
7541
|
caret segment read as a separate, brighter button. */
|
|
6880
7542
|
.btn-split:hover > .btn-fill:not(:disabled) {
|
|
6881
7543
|
background: var(--accent-hover);
|
|
7544
|
+
--tint-ground: transparent;
|
|
6882
7545
|
border-color: var(--accent-hover);
|
|
6883
7546
|
}
|
|
6884
7547
|
/* The outward focus ring wraps one half and reads as a second button on the
|
|
@@ -6901,7 +7564,6 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6901
7564
|
.btn-split-menu {
|
|
6902
7565
|
inset-inline-start: auto;
|
|
6903
7566
|
inset-inline-end: 0;
|
|
6904
|
-
padding: var(--space-1) 0;
|
|
6905
7567
|
}
|
|
6906
7568
|
|
|
6907
7569
|
/* ============================================================
|
|
@@ -6955,6 +7617,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6955
7617
|
overflow: hidden;
|
|
6956
7618
|
content-visibility: hidden;
|
|
6957
7619
|
background: var(--well); /* 1.28:1 dark, 1.23:1 light against the table's surface-1 */
|
|
7620
|
+
--tint-ground: transparent;
|
|
6958
7621
|
/* a detail belonging to its row, not a hole punched across the table: full
|
|
6959
7622
|
bleed with square corners inside an --r-3 container read as the second */
|
|
6960
7623
|
border-radius: var(--r-1);
|
|
@@ -6997,28 +7660,31 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
6997
7660
|
stays behind while the cell travels. The neutral hairline reads on all four
|
|
6998
7661
|
grounds - 1.92 / 1.81 / 2.03 / 1.90 dark and 1.77 / 1.69 / 1.68 / 1.70 light
|
|
6999
7662
|
on --surface-1, the hover fill, the header well and the selection tint. */
|
|
7000
|
-
.table-sticky-1 tbody tr { background: var(--surface-1); }
|
|
7663
|
+
.table-sticky-1 tbody tr { background: var(--surface-1); --tint-ground: transparent; }
|
|
7001
7664
|
.table-sticky-1 :is(th, td):first-child {
|
|
7002
7665
|
position: sticky;
|
|
7003
7666
|
inset-inline-start: 0;
|
|
7004
7667
|
z-index: 1;
|
|
7005
7668
|
background: inherit;
|
|
7006
|
-
box-shadow: inset -1px 0 0 0 color-mix(in srgb, var(--text-4) 40%,
|
|
7669
|
+
box-shadow: inset -1px 0 0 0 color-mix(in srgb, var(--text-4) 40%, var(--tint-ground));
|
|
7007
7670
|
}
|
|
7008
7671
|
/* the row's own selection edge is painted by the row and covered by the cell's
|
|
7009
7672
|
ground, so the frozen cell carries both marks itself */
|
|
7010
7673
|
.table-sticky-1 tbody tr.row-selected :is(th, td):first-child {
|
|
7011
7674
|
box-shadow:
|
|
7012
7675
|
inset 2px 0 0 var(--accent),
|
|
7013
|
-
inset -1px 0 0 0 color-mix(in srgb, var(--text-4) 40%,
|
|
7676
|
+
inset -1px 0 0 0 color-mix(in srgb, var(--text-4) 40%, var(--tint-ground));
|
|
7014
7677
|
}
|
|
7015
7678
|
|
|
7016
7679
|
/* ---- Density ----
|
|
7017
7680
|
Three steps, both ends off the spacing scale, around the 10px the table
|
|
7018
7681
|
already ships. The control that sets them is a `.btn-group` with
|
|
7019
|
-
`aria-pressed`, which is the other half of this block.
|
|
7020
|
-
|
|
7021
|
-
|
|
7682
|
+
`aria-pressed`, which is the other half of this block. BODY rows only: the
|
|
7683
|
+
header keeps the base height at every density, so a dense table's header is
|
|
7684
|
+
not the shortest thing on the page and a roomy one's does not outgrow the
|
|
7685
|
+
card. */
|
|
7686
|
+
.table-wrap.table-dense tbody td { padding: var(--space-1) var(--space-3); }
|
|
7687
|
+
.table-wrap.table-roomy tbody td { padding: var(--space-4); }
|
|
7022
7688
|
|
|
7023
7689
|
/* ---- Inline edit ----
|
|
7024
7690
|
The cell holds a real field for its whole life rather than swapping one in on
|
|
@@ -7055,11 +7721,42 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7055
7721
|
border-color: transparent;
|
|
7056
7722
|
color: var(--text); /* 10.12:1 dark, 13.47:1 light on --surface-1 */
|
|
7057
7723
|
}
|
|
7724
|
+
/* The transparent edge above is (0,1,0) and sits thousands of lines below
|
|
7725
|
+
`.input-invalid`, which is (0,1,0) too, so it wins on source order and an
|
|
7726
|
+
invalid inline cell painted NO edge at all - measured `rgba(0, 0, 0, 0)` in
|
|
7727
|
+
both themes, with or without the unsaved state. The attribute arm of that
|
|
7728
|
+
rule is (0,2,0) and always did paint; restating the pair here at (0,2,0)
|
|
7729
|
+
makes the class form agree with it, which is what its own comment promises.
|
|
7730
|
+
Scoped to this field rather than reordered upstream: the invalid rule dresses
|
|
7731
|
+
every control in the system and this is the one whose base rule outranks it. */
|
|
7732
|
+
.input-inline.input-invalid,
|
|
7733
|
+
.input-inline[aria-invalid="true"] {
|
|
7734
|
+
border-color: var(--danger); /* 5.18:1 dark, 6.02:1 light on --surface-1 */
|
|
7735
|
+
}
|
|
7736
|
+
/* unsaved. The accent hairline rides the field itself, beside the value it
|
|
7737
|
+
marks: on the cell's edge it sat about 290px away from a numeric column's
|
|
7738
|
+
field and read as a stray line. A BORDER and not a shadow, so it cannot
|
|
7739
|
+
composite with the field's own; the hover companion sits at (0,7,1) and beats
|
|
7740
|
+
the (0,5,0) neutral hover edge below on specificity, so source order does not
|
|
7741
|
+
decide it.
|
|
7742
|
+
|
|
7743
|
+
THE INVALID CARVE-OUT IS LOAD-BEARING, and it is what a shadow did not need:
|
|
7744
|
+
unsaved and failed-to-save are two states of one cell, and this selector is
|
|
7745
|
+
longer than `.input[aria-invalid="true"]` at (0,2,0) and its `:focus` at
|
|
7746
|
+
(0,3,0), so without the `:not()` pair a failed save paints the accent and
|
|
7747
|
+
--danger never reaches the field. The old cell shadow composited beside the
|
|
7748
|
+
border and could not collide with it. Same pair `.input`'s own hover rule
|
|
7749
|
+
carries, for the same reason. A dirty field that is also [readonly] loses to
|
|
7750
|
+
that rule's (0,6,0) form, which is a state pair the component cannot be in. */
|
|
7751
|
+
.table-wrap td.cell-edit.is-dirty .input-inline:not(.input-invalid):not([aria-invalid="true"]),
|
|
7752
|
+
.table-wrap td.cell-edit.is-dirty .input-inline:hover:not(.input-invalid):not([aria-invalid="true"]) {
|
|
7753
|
+
border-color: var(--accent); /* 6.48:1 dark, 4.60:1 light on --surface-1 */
|
|
7754
|
+
}
|
|
7058
7755
|
/* `.input:hover:not(:focus):not(.input-invalid):not([aria-invalid="true"])` is
|
|
7059
7756
|
(0,5,0) and would paint --surface-3, which measures 1.06:1 on the light well.
|
|
7060
7757
|
Matched here at the same (0,5,0), later in the file. */
|
|
7061
7758
|
.input-inline:hover:not(:focus):not(.input-invalid):not([aria-invalid="true"]) {
|
|
7062
|
-
border-color: color-mix(in srgb, var(--text-4) 40%,
|
|
7759
|
+
border-color: color-mix(in srgb, var(--text-4) 40%, var(--tint-ground));
|
|
7063
7760
|
}
|
|
7064
7761
|
/* A cell nobody may edit offers no affordance, and carving `[readonly]` out of
|
|
7065
7762
|
the rule above is not enough: the shipped (0,5,0) rule is still there behind
|
|
@@ -7068,7 +7765,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7068
7765
|
.input-inline[readonly]:hover:not(:focus):not(.input-invalid):not([aria-invalid="true"]) {
|
|
7069
7766
|
border-color: transparent;
|
|
7070
7767
|
}
|
|
7071
|
-
.input-inline:focus { background: var(--well); }
|
|
7768
|
+
.input-inline:focus { background: var(--well); --tint-ground: transparent; }
|
|
7072
7769
|
.input-inline[readonly] {
|
|
7073
7770
|
color: var(--text-2); /* 7.30:1 dark, 8.36:1 light on --surface-1 */
|
|
7074
7771
|
cursor: default;
|
|
@@ -7088,9 +7785,6 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7088
7785
|
min-width: 3ch;
|
|
7089
7786
|
margin-inline-start: auto;
|
|
7090
7787
|
}
|
|
7091
|
-
/* unsaved. The system's own active-row edge, on the cell rather than on the
|
|
7092
|
-
field, so it cannot be outranked by the (0,6,0) hover rule above. */
|
|
7093
|
-
.table-wrap td.cell-edit.is-dirty { box-shadow: inset 2px 0 0 var(--accent); } /* 6.48:1 dark, 4.60:1 light */
|
|
7094
7788
|
/* the field shares its cell with a spinner or a retry: it gives up the 100%
|
|
7095
7789
|
width and takes what the row leaves, keeping its own pulled-back padding */
|
|
7096
7790
|
.cell-edit-row {
|
|
@@ -7132,8 +7826,11 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7132
7826
|
takes its size from the cell's own line box above the floor. */
|
|
7133
7827
|
.input-inline { min-height: var(--ctl-lg); }
|
|
7134
7828
|
/* a dense table on a phone is not a density choice, it is a miss: the variant
|
|
7135
|
-
collapses onto the base row, the way .btn-sm collapses onto the floor
|
|
7136
|
-
|
|
7829
|
+
collapses onto the base row, the way .btn-sm collapses onto the floor. BODY
|
|
7830
|
+
rows only, like the density rules themselves: the header takes the base
|
|
7831
|
+
height at every density and this block must not be the one place it does
|
|
7832
|
+
not, however equal the two values happen to be today. */
|
|
7833
|
+
.table-wrap.table-dense tbody td { padding: 10px var(--space-4); }
|
|
7137
7834
|
/* the coarse floor for every bare icon button, and it has to be THIS block:
|
|
7138
7835
|
each of these has a (0,1,0) box rule further down the file that an earlier
|
|
7139
7836
|
coarse declaration would lose to on source order */
|
|
@@ -7166,7 +7863,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7166
7863
|
overlay on the field types one real example, holds it, deletes it, and moves to
|
|
7167
7864
|
the next. `data-typed` on the field carries the examples; `ui.js` builds the
|
|
7168
7865
|
wrapper and the overlay and owns the loop. Ported from the contact form it was
|
|
7169
|
-
built for
|
|
7866
|
+
built for.
|
|
7170
7867
|
|
|
7171
7868
|
IT RENDERS REAL VALUES, so it is content and not a decorative hint.
|
|
7172
7869
|
`--text-placeholder` is what the native placeholder takes; this sits one step
|
|
@@ -7208,13 +7905,48 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7208
7905
|
.typed-overlay { display: none; }
|
|
7209
7906
|
}
|
|
7210
7907
|
|
|
7908
|
+
/* ---- Draw-on underline ----
|
|
7909
|
+
A swash that draws itself under a highlighted word once on load. The host is
|
|
7910
|
+
the highlighted text, made inline-block relative; the consumer pastes this
|
|
7911
|
+
svg inside it with the path normalized (pathLength="1"), so the css dash
|
|
7912
|
+
sweep draws any shape with no js and no measured length. The stroke is the
|
|
7913
|
+
one shape a transform cannot draw, which is why the sweep rides
|
|
7914
|
+
stroke-dashoffset (motion.md's exception table). Reduced motion keeps the
|
|
7915
|
+
full underline and drops the sweep. */
|
|
7916
|
+
.draw-underline {
|
|
7917
|
+
position: absolute;
|
|
7918
|
+
left: 0;
|
|
7919
|
+
bottom: -0.16em;
|
|
7920
|
+
width: 100%;
|
|
7921
|
+
height: 0.38em;
|
|
7922
|
+
overflow: visible;
|
|
7923
|
+
pointer-events: none;
|
|
7924
|
+
}
|
|
7925
|
+
.draw-underline path {
|
|
7926
|
+
fill: none;
|
|
7927
|
+
stroke: var(--accent-2);
|
|
7928
|
+
stroke-width: 3;
|
|
7929
|
+
stroke-linecap: round;
|
|
7930
|
+
vector-effect: non-scaling-stroke;
|
|
7931
|
+
stroke-dasharray: 1;
|
|
7932
|
+
stroke-dashoffset: 1;
|
|
7933
|
+
animation: draw-underline var(--dur-far) var(--ease-out) forwards;
|
|
7934
|
+
}
|
|
7935
|
+
@keyframes draw-underline {
|
|
7936
|
+
to { stroke-dashoffset: 0; }
|
|
7937
|
+
}
|
|
7938
|
+
@media (prefers-reduced-motion: reduce) {
|
|
7939
|
+
.draw-underline path {
|
|
7940
|
+
animation: none;
|
|
7941
|
+
stroke-dashoffset: 0;
|
|
7942
|
+
}
|
|
7943
|
+
}
|
|
7944
|
+
|
|
7211
7945
|
/* ---- Glass ----
|
|
7212
|
-
Real background blur on the surfaces something actually passes behind
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
passes behind it, and a full-width blurred band is the slab the strength
|
|
7217
|
-
round rejected. The scrim itself stays a flat tint.
|
|
7946
|
+
Real background blur, on the surfaces something actually passes behind.
|
|
7947
|
+
Which six they are is in references/glass.md; the navbar is not one of them,
|
|
7948
|
+
because at the top nothing passes behind it and a full-width blurred band is
|
|
7949
|
+
the slab the strength round rejected. The scrim itself stays a flat tint.
|
|
7218
7950
|
|
|
7219
7951
|
Two materials, one filter. Bars tint with --bg: a bar is the page held still,
|
|
7220
7952
|
and on a dark page a surface-tone bar reads as an opaque slab whatever the
|
|
@@ -7250,9 +7982,9 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7250
7982
|
The fill list is explicit rather than blanket, or a bare disabled `.btn` would
|
|
7251
7983
|
grow a fill it does not have at rest.
|
|
7252
7984
|
|
|
7253
|
-
A STATIC GUARD CANNOT SEE THIS. The check that keeps it honest
|
|
7254
|
-
|
|
7255
|
-
|
|
7985
|
+
A STATIC GUARD CANNOT SEE THIS. The check that keeps it honest mounts each
|
|
7986
|
+
state and reads the painted colour; that gate is only as good as the
|
|
7987
|
+
specimens the pages carry.
|
|
7256
7988
|
|
|
7257
7989
|
THIS IS THE ONLY DECLARATION of the disabled label colour for the four
|
|
7258
7990
|
components in the list. Each of them used to restate it as well, six dead
|
|
@@ -7265,6 +7997,7 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7265
7997
|
}
|
|
7266
7998
|
:is(.btn, .daterange-day, .menu-item):is(:disabled, [aria-disabled="true"]):is([aria-pressed="true"], [aria-current="page"], .is-in-range, .is-start, .is-end, .active) {
|
|
7267
7999
|
background: var(--surface-2) !important;
|
|
8000
|
+
--tint-ground: transparent;
|
|
7268
8001
|
border-color: var(--line) !important;
|
|
7269
8002
|
}
|
|
7270
8003
|
|
|
@@ -7280,7 +8013,8 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7280
8013
|
/* the tokens with no headroom left on a translucent panel. --danger is
|
|
7281
8014
|
3.31:1 unlifted, and a translucent accent fill laid over a translucent
|
|
7282
8015
|
surface composites twice: 4.40:1 dark, 4.04 light, against 5.95 and 4.89
|
|
7283
|
-
for the same mix laid down opaque. Numbers and method in
|
|
8016
|
+
for the same mix laid down opaque on a panel. Numbers and method in
|
|
8017
|
+
tokens.css. */
|
|
7284
8018
|
--text-3: var(--glass-dim);
|
|
7285
8019
|
--text-4: var(--glass-dim);
|
|
7286
8020
|
--text-placeholder: var(--glass-dim);
|
|
@@ -7289,21 +8023,31 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7289
8023
|
otherwise. Its own lift is the lowest value still clearing 3:1 there. */
|
|
7290
8024
|
--text-disabled: var(--glass-disabled);
|
|
7291
8025
|
--danger: var(--glass-danger);
|
|
7292
|
-
--
|
|
8026
|
+
--danger-text: var(--glass-danger-text);
|
|
8027
|
+
/* mixed HERE, on the surface, because a custom property resolves its var()
|
|
8028
|
+
where it is declared: a mix on :root would read the root's transparent
|
|
8029
|
+
--glass-ground. On a bar both land on --bg, on a panel on --surface-1. */
|
|
8030
|
+
--accent-quiet: color-mix(in oklab, var(--accent) var(--glass-quiet-share), var(--glass-ground));
|
|
8031
|
+
--accent-line: color-mix(in srgb, var(--accent) var(--glass-line-share), var(--glass-ground));
|
|
7293
8032
|
/* the dimmed icon is a graphic against the same moving surface: on the dark
|
|
7294
8033
|
panel 0.85 leaves a text-3 icon at 2.99:1, under the 3:1 floor, so the
|
|
7295
8034
|
lift is 0.90: 3.15 dark and 3.89 light on the panel worst cases */
|
|
7296
8035
|
--icon-dim: 0.90;
|
|
7297
8036
|
}
|
|
7298
8037
|
/* the bar is the page held still: page colour, hairline, and whatever passes
|
|
7299
|
-
under it, ghosting through the blur. It
|
|
7300
|
-
|
|
8038
|
+
under it, ghosting through the blur. It re-floors the same tokens as a
|
|
8039
|
+
panel, because the block above names it: a bar eyebrow or placeholder reads
|
|
8040
|
+
--glass-dim, --glass-danger and --glass-disabled there too (the sheet's
|
|
8041
|
+
stuck-header specimen paints a .label on it). */
|
|
8042
|
+
/* --tint-ground is each surface's own tone, so a hover, press or tag tint
|
|
8043
|
+
inside it is an opaque mix the backdrop cannot move; any opaque ground
|
|
8044
|
+
nested inside resets it to transparent (the --tint-ground token's note). */
|
|
7301
8045
|
.mobile-topbar,
|
|
7302
|
-
.section-sticky.is-stuck { background: var(--glass-bar-tint); }
|
|
8046
|
+
.section-sticky.is-stuck { background: var(--glass-bar-tint); --tint-ground: var(--bg); --glass-ground: var(--bg); }
|
|
7303
8047
|
.popover,
|
|
7304
8048
|
.select-menu,
|
|
7305
8049
|
.toast,
|
|
7306
|
-
.modal { background: var(--glass-tint); }
|
|
8050
|
+
.modal { background: var(--glass-tint); --tint-ground: var(--surface-1); --glass-ground: var(--surface-1); }
|
|
7307
8051
|
/* A full-bleed bar has one edge that meets the page, so it keeps the hairline
|
|
7308
8052
|
it already had and only changes its colour. A floating panel gets all four:
|
|
7309
8053
|
over a photo or a card its own tone is no longer the thing that separates
|
|
@@ -7324,8 +8068,8 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7324
8068
|
.popover,
|
|
7325
8069
|
.select-menu,
|
|
7326
8070
|
.toast,
|
|
7327
|
-
.modal
|
|
7328
|
-
|
|
8071
|
+
.modal,
|
|
8072
|
+
.section-sticky.is-stuck {
|
|
7329
8073
|
-webkit-backdrop-filter: none;
|
|
7330
8074
|
backdrop-filter: none;
|
|
7331
8075
|
/* `unset` on a custom property is `inherit`, so each surface takes the root
|
|
@@ -7337,22 +8081,23 @@ html:has(> body > .page-state) { --dot-rest: 0.60; }
|
|
|
7337
8081
|
--text-placeholder: unset;
|
|
7338
8082
|
--text-disabled: unset;
|
|
7339
8083
|
--danger: unset;
|
|
8084
|
+
--danger-text: unset;
|
|
7340
8085
|
--accent-quiet: unset;
|
|
8086
|
+
--accent-line: unset;
|
|
7341
8087
|
--icon-dim: unset;
|
|
8088
|
+
--tint-ground: unset;
|
|
8089
|
+
--glass-ground: unset;
|
|
7342
8090
|
}
|
|
8091
|
+
/* the surface is the only thing that differs across the six */
|
|
8092
|
+
.mobile-topbar,
|
|
8093
|
+
.popover,
|
|
8094
|
+
.select-menu,
|
|
8095
|
+
.toast,
|
|
8096
|
+
.modal { background: var(--surface-1); }
|
|
7343
8097
|
/* the sticky header's own pre-glass surface is the page, so that is what
|
|
7344
8098
|
reduce restores, with the plain hairline in place of the glass edge */
|
|
7345
8099
|
.section-sticky.is-stuck {
|
|
7346
8100
|
background: var(--bg); /* same tone the @supports-less fallback paints */
|
|
7347
|
-
-webkit-backdrop-filter: none;
|
|
7348
|
-
backdrop-filter: none;
|
|
7349
|
-
--text-3: unset;
|
|
7350
|
-
--text-4: unset;
|
|
7351
|
-
--text-placeholder: unset;
|
|
7352
|
-
--text-disabled: unset;
|
|
7353
|
-
--danger: unset;
|
|
7354
|
-
--accent-quiet: unset;
|
|
7355
|
-
--icon-dim: unset;
|
|
7356
8101
|
border-bottom: 1px solid var(--line-soft);
|
|
7357
8102
|
}
|
|
7358
8103
|
.mobile-topbar { border-bottom-color: var(--line-soft); }
|