@wtfalch/design 0.9.0 → 0.10.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.
@@ -17,12 +17,9 @@
17
17
  --font-sans: var(--font-sans);
18
18
  --font-mono: var(--font-mono);
19
19
  --text-xs: var(--text-xs);
20
- --text-xs--line-height: inherit;
21
20
  --text-sm: var(--text-sm);
22
- --text-sm--line-height: inherit;
23
21
  --text-base: var(--text-base);
24
22
  --text-lg: var(--text-lg);
25
- --text-lg--line-height: inherit;
26
23
  --text-xl: var(--text-xl);
27
24
  --font-weight-normal: var(--weight);
28
25
  --tracking-normal: var(--tracking);
@@ -37,19 +34,20 @@
37
34
  --spacing-5: var(--space-5);
38
35
  --spacing-6: var(--space-6);
39
36
  --spacing-10: var(--space-10);
40
- --text-2xs--line-height: inherit;
41
37
  --text-2xs: var(--text-2xs);
42
38
  --text-md: var(--text-md);
43
39
  --font-weight-strong: var(--weight-strong);
44
40
  --radius-pill: var(--radius-pill);
45
41
  --color-text: var(--text);
46
42
  --color-muted: var(--muted);
47
- --color-accent: var(--accent);
48
43
  --color-border: var(--border);
49
44
  --color-border-strong: var(--border-strong);
50
45
  }
51
46
  }
52
47
  @layer utilities {
48
+ .pointer-events-none {
49
+ pointer-events: none;
50
+ }
53
51
  .collapse {
54
52
  visibility: collapse;
55
53
  }
@@ -82,6 +80,9 @@
82
80
  .sticky {
83
81
  position: sticky;
84
82
  }
83
+ .inset-0 {
84
+ inset: 0px;
85
+ }
85
86
  .z-\[60\] {
86
87
  z-index: 60;
87
88
  }
@@ -115,12 +116,21 @@
115
116
  .mt-1 {
116
117
  margin-top: var(--spacing-1);
117
118
  }
119
+ .mt-2 {
120
+ margin-top: var(--spacing-2);
121
+ }
122
+ .mt-px {
123
+ margin-top: 1px;
124
+ }
118
125
  .mb-3 {
119
126
  margin-bottom: var(--spacing-3);
120
127
  }
121
128
  .ml-2 {
122
129
  margin-left: var(--spacing-2);
123
130
  }
131
+ .ml-auto {
132
+ margin-left: auto;
133
+ }
124
134
  .block {
125
135
  display: block;
126
136
  }
@@ -145,6 +155,9 @@
145
155
  .table {
146
156
  display: table;
147
157
  }
158
+ .h-4 {
159
+ height: var(--spacing-4);
160
+ }
148
161
  .h-\[14px\] {
149
162
  height: 14px;
150
163
  }
@@ -163,6 +176,9 @@
163
176
  .min-h-screen {
164
177
  min-height: 100vh;
165
178
  }
179
+ .w-4 {
180
+ width: var(--spacing-4);
181
+ }
166
182
  .w-\[18px\] {
167
183
  width: 18px;
168
184
  }
@@ -190,6 +206,18 @@
190
206
  .flex-1 {
191
207
  flex: 1;
192
208
  }
209
+ .flex-\[0_0_var\(--split\)\] {
210
+ flex: 0 0 var(--split);
211
+ }
212
+ .flex-\[1_1_0\] {
213
+ flex: 1 1 0;
214
+ }
215
+ .flex-\[1_1_260px\] {
216
+ flex: 1 1 260px;
217
+ }
218
+ .flex-\[1_1_auto\] {
219
+ flex: 1 1 auto;
220
+ }
193
221
  .flex-none {
194
222
  flex: none;
195
223
  }
@@ -220,6 +248,9 @@
220
248
  .flex-wrap {
221
249
  flex-wrap: wrap;
222
250
  }
251
+ .place-items-center {
252
+ place-items: center;
253
+ }
223
254
  .items-baseline {
224
255
  align-items: baseline;
225
256
  }
@@ -253,6 +284,9 @@
253
284
  .gap-\[var\(--border-width\)\] {
254
285
  gap: var(--border-width);
255
286
  }
287
+ .gap-px {
288
+ gap: 1px;
289
+ }
256
290
  .self-center {
257
291
  align-self: center;
258
292
  }
@@ -300,9 +334,6 @@
300
334
  --tw-border-style: dashed;
301
335
  border-style: dashed;
302
336
  }
303
- .border-accent {
304
- border-color: var(--color-accent);
305
- }
306
337
  .border-border {
307
338
  border-color: var(--color-border);
308
339
  }
@@ -318,9 +349,15 @@
318
349
  .p-2 {
319
350
  padding: var(--spacing-2);
320
351
  }
352
+ .p-3 {
353
+ padding: var(--spacing-3);
354
+ }
321
355
  .p-4 {
322
356
  padding: var(--spacing-4);
323
357
  }
358
+ .px-2 {
359
+ padding-inline: var(--spacing-2);
360
+ }
324
361
  .px-4 {
325
362
  padding-inline: var(--spacing-4);
326
363
  }
@@ -339,6 +376,12 @@
339
376
  .py-10 {
340
377
  padding-block: var(--spacing-10);
341
378
  }
379
+ .pt-2 {
380
+ padding-top: var(--spacing-2);
381
+ }
382
+ .pb-1 {
383
+ padding-bottom: var(--spacing-1);
384
+ }
342
385
  .pb-2 {
343
386
  padding-bottom: var(--spacing-2);
344
387
  }
@@ -353,19 +396,18 @@
353
396
  }
354
397
  .text-2xs {
355
398
  font-size: var(--text-2xs);
356
- line-height: var(--tw-leading, var(--text-2xs--line-height));
399
+ }
400
+ .text-base {
401
+ font-size: var(--text-base);
357
402
  }
358
403
  .text-lg {
359
404
  font-size: var(--text-lg);
360
- line-height: var(--tw-leading, var(--text-lg--line-height));
361
405
  }
362
406
  .text-sm {
363
407
  font-size: var(--text-sm);
364
- line-height: var(--tw-leading, var(--text-sm--line-height));
365
408
  }
366
409
  .text-xs {
367
410
  font-size: var(--text-xs);
368
- line-height: var(--tw-leading, var(--text-xs--line-height));
369
411
  }
370
412
  .leading-\[0\] {
371
413
  --tw-leading: 0;
@@ -379,6 +421,10 @@
379
421
  --tw-leading: 1.25;
380
422
  line-height: 1.25;
381
423
  }
424
+ .leading-\[1\.35\] {
425
+ --tw-leading: 1.35;
426
+ line-height: 1.35;
427
+ }
382
428
  .leading-\[16px\] {
383
429
  --tw-leading: 16px;
384
430
  line-height: 16px;
@@ -420,12 +466,12 @@
420
466
  --tw-numeric-spacing: tabular-nums;
421
467
  font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
422
468
  }
423
- .opacity-0 {
424
- opacity: 0%;
425
- }
426
469
  .opacity-60 {
427
470
  opacity: 60%;
428
471
  }
472
+ .opacity-90 {
473
+ opacity: 90%;
474
+ }
429
475
  .opacity-\[0\.55\] {
430
476
  opacity: 0.55;
431
477
  }
@@ -465,9 +511,6 @@
465
511
  .surface-panel {
466
512
  background: var(--panel);
467
513
  }
468
- .surface-panel-2 {
469
- background: var(--panel-2);
470
- }
471
514
  .disabled\:cursor-default:disabled {
472
515
  cursor: default;
473
516
  }
@@ -707,7 +750,19 @@
707
750
  }
708
751
  }
709
752
 
710
- /* ---- ./base.css ---- */
753
+ /* ---- ./_system-light.css ---- */
754
+ /* The `system` theme's light half.
755
+ *
756
+ * Its own file, and outside the components layer, because it is a *theme*
757
+ * rather than a component rule. `tokens.css` states the dark base on `:root`
758
+ * and is unlayered; the moment this sat inside `@layer components` it lost to
759
+ * that, and every `system` specimen rendered dark on a light-mode machine.
760
+ * Unlayered beats layered whatever the selector says, which is exactly the
761
+ * trap this repository keeps finding in different costumes.
762
+ *
763
+ * The underscore marks it the same way `_tailwind.built.css` is marked:
764
+ * `copy-css.mjs` passes both through without wrapping them.
765
+ */
711
766
  /* Light, only under the `system` theme. This used to override `:root`
712
767
  unconditionally, so choosing a dark theme on a light-mode laptop got
713
768
  silently repainted. Every other theme states its own palette. */
@@ -736,6 +791,8 @@
736
791
  }
737
792
  }
738
793
 
794
+ /* ---- ./base.css ---- */
795
+ @layer components {
739
796
  /* Derived from the two surfaces every theme already defines, so a theme gets a
740
797
  control colour without knowing this exists. */
741
798
  /* Pseudo-elements too. `*` does not match them, and `box-sizing` is not
@@ -1453,23 +1510,6 @@ input:active, textarea:active, select:active { transform: none; }
1453
1510
  template are allowed to diverge, and this is only saying that they have. */
1454
1511
  .set-hint.drifted { color: var(--warn); }
1455
1512
 
1456
- /* What an applet drew, read back. A tree rather than prose, so it is left as
1457
- preformatted text and given a ceiling -- a busy applet is a hundred lines and
1458
- this sits inside a settings pane. */
1459
- .drawn {
1460
- margin: var(--space-2) 0 0;
1461
- padding: var(--space-2) var(--space-2);
1462
- max-height: 220px;
1463
- overflow: auto;
1464
- font-size: var(--text-xs);
1465
- line-height: 1.45;
1466
- white-space: pre-wrap;
1467
- overflow-wrap: anywhere;
1468
- border: 1px solid var(--border);
1469
- border-radius: var(--radius);
1470
- background: var(--panel-2);
1471
- }
1472
-
1473
1513
  /* One applet, alone on the page, at `/applet/<id>`. Fills the window: there is
1474
1514
  no grid here to decide a height, and a screenshot of an applet letterboxed
1475
1515
  inside a page is mostly not the applet. */
@@ -1516,11 +1556,6 @@ input:active, textarea:active, select:active { transform: none; }
1516
1556
  that belongs to a conversation lives here any more, so it can breathe. */
1517
1557
  .topbar .ctl-grow { flex: 1; min-width: 0; }
1518
1558
 
1519
- /* Back on the left, Next on the right, with the gap between them doing the
1520
- separating -- so the button that goes forward is in the same place on both
1521
- pages instead of sliding when Back appears. */
1522
- .first-run-actions { align-items: center; }
1523
-
1524
1559
  @keyframes listening-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .35 } }
1525
1560
 
1526
1561
  /* table ---------------------------------------------------------------- */
@@ -1596,8 +1631,10 @@ input:active, textarea:active, select:active { transform: none; }
1596
1631
  color: var(--muted);
1597
1632
  opacity: 1;
1598
1633
  }
1634
+ }
1599
1635
 
1600
1636
  /* ---- ./select.css ---- */
1637
+ @layer components {
1601
1638
  /* How big a control is, asked for rather than inherited.
1602
1639
  Four sizes existed and every one of them came from a descendant selector:
1603
1640
  `.set-row button` was 11px in 3px of padding, `.approval-actions button` was
@@ -1692,16 +1729,19 @@ input:active, textarea:active, select:active { transform: none; }
1692
1729
  /* State is read off React Aria's attributes: `data-open` on the root,
1693
1730
  `data-focused` / `data-selected` / `data-disabled` on an item, where
1694
1731
  `.open`, `[data-active]`, `.on` and `.off` used to be. */
1695
- .sel-list { scroll-padding-block: var(--space-1); box-shadow: var(--shadow-2); })` brings up ended flush against the
1696
- border and read as clipped rather than as one of a set -- and this list
1697
- opens scrolled to whatever is chosen, so that was the ordinary state.
1698
- `scroll-padding` insets what counts as "in view", so it stops short by
1699
- the same four the sides have. */
1732
+ /* The list keeps two things the utilities on `Select` cannot say.
1733
+ *
1734
+ * `scroll-padding-block`: the same room while the list is scrolled as it has
1735
+ * at rest. Padding at the top of a scroll container scrolls away with the
1736
+ * content, so the row `scrollIntoView({block: 'nearest'})` brings up ended
1737
+ * flush against the border and read as clipped rather than as one of a set --
1738
+ * and this list opens scrolled to whatever is chosen, so that was the
1739
+ * ordinary state.
1740
+ *
1741
+ * `box-shadow`: `--shadow-2` is a token, and a utility would have to inline
1742
+ * the value, which is the one thing the vocabulary exists to stop. */
1743
+ .sel-list {
1700
1744
  scroll-padding-block: var(--space-1);
1701
- list-style: none;
1702
- border: var(--border-width) solid var(--border-strong);
1703
- border-radius: var(--radius-md);
1704
- background: var(--panel);
1705
1745
  box-shadow: var(--shadow-2);
1706
1746
  }
1707
1747
 
@@ -1716,6 +1756,19 @@ input:active, textarea:active, select:active { transform: none; }
1716
1756
 
1717
1757
  /* The tick marks the chosen row. Colour alone would not: a list where the only
1718
1758
  difference is a hue is a list you cannot read past. */
1759
+ /* The tick's resting state lives here, beside the state that overrides it.
1760
+ *
1761
+ * It was `opacity-0` on the element and `opacity: 1` here, which worked only
1762
+ * while this sheet was unlayered. A utility is in `@layer utilities` and
1763
+ * beats `@layer components` whatever the selector, so the moment the sheets
1764
+ * moved into a layer the tick stopped appearing on the selected row -- the
1765
+ * resting state won permanently.
1766
+ *
1767
+ * The rule generalises: a utility for a resting state whose active state is a
1768
+ * relationship between elements will always beat that active state. The pair
1769
+ * has to sit in the same layer. */
1770
+ .sel-tick { opacity: 0; }
1771
+
1719
1772
  .sel-item[data-selected] .sel-tick { opacity: 1; }
1720
1773
 
1721
1774
  /* The icon on a speaker's row in the open list -- `Select`'s `aside`. Last in
@@ -1750,8 +1803,10 @@ input:active, textarea:active, select:active { transform: none; }
1750
1803
  .field > input,
1751
1804
  .field > textarea,
1752
1805
  .field > .sel { width: 100%; }
1806
+ }
1753
1807
 
1754
1808
  /* ---- ./dangerzone.css ---- */
1809
+ @layer components {
1755
1810
  /* Destructive, and legible as such standing still. */
1756
1811
  .btn.danger {
1757
1812
  color: var(--bad);
@@ -1841,8 +1896,6 @@ input:active, textarea:active, select:active { transform: none; }
1841
1896
  border-radius: 4px;
1842
1897
  }
1843
1898
 
1844
- .danger-unavailable { margin-top: var(--space-2); opacity: 0.85; }
1845
-
1846
1899
  /* Label beside the control, for a settings pane: a column of names on the left
1847
1900
  and their controls on the right reads as a list of what is set, where the
1848
1901
  same rows stacked read as a form waiting to be filled in. The description
@@ -1863,8 +1916,10 @@ input:active, textarea:active, select:active { transform: none; }
1863
1916
  controls in the same column at the same indent that do not line up read as
1864
1917
  an accident rather than as a pair. */
1865
1918
  .field-row > button { min-width: 9ch; }
1919
+ }
1866
1920
 
1867
1921
  /* ---- ./callout.css ---- */
1922
+ @layer components {
1868
1923
  /* One shape for everything that tells you something.
1869
1924
  `.banner` and `.callout` were the same box drawn twice: a bordered, rounded,
1870
1925
  12px panel with a tint. They differed in padding by a pixel in each direction
@@ -1918,8 +1973,6 @@ input:active, textarea:active, select:active { transform: none; }
1918
1973
  /* A callout that leaves on its own.
1919
1974
  Room at the bottom for the bar, and `position: relative` so the bar can sit
1920
1975
  on the border rather than inside the text. */
1921
- .callout-timed { position: relative; padding-bottom: var(--space-5); overflow: hidden; }
1922
-
1923
1976
  /* The countdown. Drawn as a shrinking bar rather than a number: nobody wants to
1924
1977
  read a clock, they want to know whether they have time. Animated width rather
1925
1978
  than a transition, so it starts the moment it is painted. */
@@ -1980,14 +2033,6 @@ input:active, textarea:active, select:active { transform: none; }
1980
2033
  is the operating system's font drawing a glyph beside an app that has an icon
1981
2034
  set for exactly this. The ring went with it: a circle around a warning
1982
2035
  triangle is two shapes saying one thing. */
1983
- .callout-mark {
1984
- flex: none;
1985
- display: grid;
1986
- place-items: center;
1987
- margin-top: 1px;
1988
- opacity: 0.9;
1989
- }
1990
-
1991
2036
  /* Held-back history when a local conversation would cross to a hosted model.
1992
2037
  Deliberately loud: the cost of missing it is uploading work that was kept
1993
2038
  local on purpose. */
@@ -2003,8 +2048,23 @@ input:active, textarea:active, select:active { transform: none; }
2003
2048
  }
2004
2049
 
2005
2050
  .callout button { flex: none; }
2051
+ /* A state, so it stays a class.
2052
+ *
2053
+ * `Callout` applies this as `running ? ' callout-timed' : ''` inside a
2054
+ * template literal -- which is not a class list a rewrite can read, and the
2055
+ * first pass at converting it deleted the rule while leaving the usage. The
2056
+ * box lost five spacing steps of padding and the countdown bar had nothing
2057
+ * to sit in. A class that appears only inside a conditional is a state; the
2058
+ * sheet is where those belong. */
2059
+ .callout-timed {
2060
+ position: relative;
2061
+ padding-bottom: var(--space-5);
2062
+ overflow: hidden;
2063
+ }
2064
+ }
2006
2065
 
2007
2066
  /* ---- ./card.css ---- */
2067
+ @layer components {
2008
2068
  /* `.card` is public API, not this component's private styling.
2009
2069
  *
2010
2070
  * Consumers apply it to their own markup -- the gallery's skeleton specimen
@@ -2059,16 +2119,6 @@ input:active, textarea:active, select:active { transform: none; }
2059
2119
  which reads as a different column of content. */
2060
2120
  .card:has(.card-icon) .card-inline { padding-left: calc(var(--card-icon) + var(--space-3)); }
2061
2121
 
2062
- /* Its own line, above the sentence it introduces. */
2063
- .card-inline-label {
2064
- display: block;
2065
- font-size: var(--text-xs);
2066
- text-transform: uppercase;
2067
- letter-spacing: 0.04em;
2068
- color: var(--muted);
2069
- margin-bottom: 2px;
2070
- }
2071
-
2072
2122
  .card-title { display: block; font-size: var(--text-base); }
2073
2123
 
2074
2124
  .card-desc { margin-top: var(--space-1); color: var(--muted); font-size: var(--text-sm); line-height: 1.5; }
@@ -2146,8 +2196,10 @@ input:active, textarea:active, select:active { transform: none; }
2146
2196
  }
2147
2197
 
2148
2198
  .card[aria-pressed='true']:hover { border-color: var(--accent); }
2199
+ }
2149
2200
 
2150
2201
  /* ---- ./toggle.css ---- */
2202
+ @layer components {
2151
2203
  .card-action .switch-row { padding: 0; gap: var(--space-2); }
2152
2204
 
2153
2205
  .card-action .switch-row .switch-body { flex: none; }
@@ -2204,12 +2256,8 @@ input:active, textarea:active, select:active { transform: none; }
2204
2256
  tooltip ended up orphaned under it. */
2205
2257
  .set-row > .switch-row { flex: 1; min-width: 0; padding-block: 0; }
2206
2258
 
2207
- .switch-label { color: var(--text); }
2208
-
2209
2259
  .switch-hint { font-size: var(--text-sm); color: var(--muted); }
2210
2260
 
2211
- .switch-said { flex: none; font-size: var(--text-xs); color: var(--muted); }
2212
-
2213
2261
  /* Three sizes, the same names every other control uses. The switch scales with
2214
2262
  the row: a 30x18 track beside 12px text is a different control from the same
2215
2263
  track beside 16px text. */
@@ -2378,19 +2426,10 @@ input[type='radio']:active:not(:disabled) {
2378
2426
  @media (prefers-reduced-motion: reduce) {
2379
2427
  .toggle-sweep::before { animation: none; background: color-mix(in oklab, var(--accent) 45%, transparent); }
2380
2428
  }
2381
-
2382
- /* ---- ./skeleton.css ---- */
2383
- .muted { color: var(--muted); }
2384
-
2385
- .pulse-block {
2386
- display: inline-block;
2387
- height: 8px;
2388
- border-radius: var(--radius-sm);
2389
- background: var(--muted);
2390
- animation: ds-pulse 1.4s ease-in-out infinite;
2391
- vertical-align: middle;
2392
2429
  }
2393
2430
 
2431
+ /* ---- ./skeleton.css ---- */
2432
+ @layer components {
2394
2433
  /* skeletons ------------------------------------------------------------ */
2395
2434
 
2396
2435
  /* Ink at low alpha, not a named surface.
@@ -2434,8 +2473,10 @@ input[type='radio']:active:not(:disabled) {
2434
2473
  /* Still distinguishable from a real block of colour, and still still. */
2435
2474
  .skel-pulse { animation: none; opacity: 0.8; }
2436
2475
  }
2476
+ }
2437
2477
 
2438
2478
  /* ---- ./rows.css ---- */
2479
+ @layer components {
2439
2480
  .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2440
2481
 
2441
2482
  /* A name and the pills that qualify it, on one line, where the name is the only
@@ -2647,8 +2688,6 @@ input[type='radio']:active:not(:disabled) {
2647
2688
 
2648
2689
  .rows-split:first-of-type { margin-top: var(--space-2); }
2649
2690
 
2650
- .rows-empty { padding: var(--space-3) 0; }
2651
-
2652
2691
  /* The name, the pills and the line underneath. `min-width: 0` is the whole
2653
2692
  reason this is a class: a flex item will not shrink below its content, so
2654
2693
  without it the name pushes the pills off the end -- and the pills are the
@@ -2665,8 +2704,6 @@ input[type='radio']:active:not(:disabled) {
2665
2704
 
2666
2705
  /* The aligned column. `margin-left: auto` rather than a grid, because the rows
2667
2706
  are flex and a size that varies by two characters does not need a track. */
2668
- .row-trail { flex: none; margin-left: auto; white-space: nowrap; }
2669
-
2670
2707
  .rows-row.rows-top { align-items: flex-start; }
2671
2708
 
2672
2709
  .rows-row.rows-bad .truncate { color: var(--bad); }
@@ -2696,8 +2733,10 @@ input[type='radio']:active:not(:disabled) {
2696
2733
  .row-hit:hover .truncate { color: var(--accent); }
2697
2734
 
2698
2735
  .row-hit:focus-visible { outline: none; box-shadow: var(--focus-ring); }
2736
+ }
2699
2737
 
2700
2738
  /* ---- ./progress.css ---- */
2739
+ @layer components {
2701
2740
  /* progress ----------------------------------------------------------- */
2702
2741
 
2703
2742
  /* The bar itself: a track with a child it fills, an animation and a
@@ -2739,8 +2778,10 @@ input[type='radio']:active:not(:disabled) {
2739
2778
  /* Still distinguishable from empty, and from full. */
2740
2779
  .bar-indeterminate > i { animation: none; width: 100%; opacity: 0.35; }
2741
2780
  }
2781
+ }
2742
2782
 
2743
2783
  /* ---- ./tabs.css ---- */
2784
+ @layer components {
2744
2785
  /* ---------------------------------------------------------------- tabs */
2745
2786
  .tabs { position: relative; display: flex; gap: var(--space-1); }
2746
2787
 
@@ -2808,14 +2849,6 @@ input[type='radio']:active:not(:disabled) {
2808
2849
 
2809
2850
  .tabs-vertical .tab.on .tab-label { color: var(--accent); }
2810
2851
 
2811
- .tab-hint {
2812
- display: block;
2813
- margin-top: 1px;
2814
- font-size: var(--text-xs);
2815
- color: var(--muted);
2816
- line-height: 1.35;
2817
- }
2818
-
2819
2852
  /* A heading above a run of tabs. Set apart from the run above by space, not
2820
2853
  a rule -- the rail already has one edge -- and the tabs under it step in
2821
2854
  by the label's own padding so the label reads as theirs. */
@@ -2826,8 +2859,6 @@ input[type='radio']:active:not(:disabled) {
2826
2859
 
2827
2860
  .tabs-vertical .tab-group:first-child { padding-top: var(--space-2); }
2828
2861
 
2829
- .tab-group-label { display: block; font-size: var(--text-sm); font-weight: 600; color: var(--text); }
2830
-
2831
2862
  .tabs-vertical .tab.in-group { padding-left: var(--space-5); }
2832
2863
 
2833
2864
  /* No line under a vertical strip -- its edge is the border on the right. */
@@ -2841,8 +2872,10 @@ input[type='radio']:active:not(:disabled) {
2841
2872
  }
2842
2873
 
2843
2874
  .tabs-vertical .tab:active:not(:disabled) { border-color: transparent; }
2875
+ }
2844
2876
 
2845
2877
  /* ---- ./toast.css ---- */
2878
+ @layer components {
2846
2879
  /* ---------------------------------------------------------------- toasts */
2847
2880
  .toasts {
2848
2881
  position: fixed;
@@ -2940,8 +2973,10 @@ input[type='radio']:active:not(:disabled) {
2940
2973
  outline: 2px solid var(--accent);
2941
2974
  outline-offset: 2px;
2942
2975
  }
2976
+ }
2943
2977
 
2944
2978
  /* ---- ./modal.css ---- */
2979
+ @layer components {
2945
2980
  /* modal --------------------------------------------------------------- */
2946
2981
 
2947
2982
  /* `hidden` needs saying out loud here. The browser's own `[hidden]` rule is a
@@ -3033,8 +3068,6 @@ input[type='radio']:active:not(:disabled) {
3033
3068
  `<strong>` rule above is the one that sets the size. */
3034
3069
  /* Under the title, on the header's padding rather than the body's, so it reads
3035
3070
  as part of the heading rather than as the first line of content. */
3036
- .modal-desc { line-height: 1.5; }
3037
-
3038
3071
  .modal-body { padding: var(--space-2) var(--space-4) var(--space-3); overflow-y: auto; }
3039
3072
 
3040
3073
  /* The close cross. Its rule stayed behind in tf's stylesheet when the split
@@ -3172,8 +3205,10 @@ input[type='radio']:active:not(:disabled) {
3172
3205
  .modal.sheet[data-entering],
3173
3206
  .modal.sheet[data-exiting] { animation: none; }
3174
3207
  }
3208
+ }
3175
3209
 
3176
3210
  /* ---- ./slider.css ---- */
3211
+ @layer components {
3177
3212
  /* slider ----------------------------------------------------------------- */
3178
3213
  /* A number from a range -- `Slider.tsx` says when one is the right control.
3179
3214
  The row is laid out like a switch row: words on the left, the control on the
@@ -3200,12 +3235,8 @@ input[type='radio']:active:not(:disabled) {
3200
3235
 
3201
3236
  .slider-row.is-disabled { opacity: 0.55; }
3202
3237
 
3203
- .slider-body { flex: 1 1 260px; min-width: 0; display: grid; gap: 1px; }
3204
-
3205
3238
  .set-row > .slider-row { flex: 1; min-width: 0; padding-block: 0; }
3206
3239
 
3207
- .slider-label { color: var(--text); }
3208
-
3209
3240
  .slider-hint { font-size: var(--text-sm); color: var(--muted); }
3210
3241
 
3211
3242
  /* The box, with the value printed inside it at the right where a select
@@ -3395,8 +3426,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3395
3426
  the scale, and the two line up; an explicit `size-*` still wins. */
3396
3427
  :where(.set-row) button,
3397
3428
  :where(.set-row) .slider-box { font-size: var(--text-xs); }
3429
+ }
3398
3430
 
3399
3431
  /* ---- ./checkbox.css ---- */
3432
+ @layer components {
3400
3433
  /* A row you choose. There is no checkbox in it.
3401
3434
  Every one of the fourteen in this app sits beside a name and a line of
3402
3435
  explanation, so the row was always the thing being designed and the 17px
@@ -3429,8 +3462,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3429
3462
  /* A run of checkboxes is one choice, not several settings that happen to be
3430
3463
  adjacent. Tight enough that the group reads as a block -- at the spacing a
3431
3464
  form field wants, four tools look like four unrelated questions. */
3432
- .choice-group { display: grid; gap: var(--space-1); }
3433
-
3434
3465
  /* The same choice, laid out as tiles instead of a column.
3435
3466
 
3436
3467
  Four short names down the full width of a card is four wide bands for four
@@ -3554,8 +3585,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3554
3585
  second line is what the first line is actually asking for. */
3555
3586
  .choice-why { display: block; color: var(--muted); font-size: var(--text-sm); margin-top: 1px; }
3556
3587
 
3557
- .choice-meta { color: var(--muted); font-size: var(--text-xs); font-family: var(--font-mono); }
3558
-
3559
3588
  .choice[data-disabled] {
3560
3589
  cursor: not-allowed;
3561
3590
  border-color: var(--border);
@@ -3566,8 +3595,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3566
3595
  .choice[data-disabled] .choice-why { opacity: 0.5; }
3567
3596
 
3568
3597
  .choice[data-disabled]::after { opacity: 0.4; }
3598
+ }
3569
3599
 
3570
3600
  /* ---- ./table.css ---- */
3601
+ @layer components {
3571
3602
  /* A second line under a row's name, inside the `<th scope="row">` that makes it
3572
3603
  one. The cell is bold because it is a heading; what hangs under it is not,
3573
3604
  and inherited the weight -- so a pinned version read as loud as the thing it
@@ -3615,27 +3646,48 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3615
3646
  .data-table .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
3616
3647
 
3617
3648
  .data-table thead th[style*='right'] { text-align: right; }
3649
+ }
3618
3650
 
3619
3651
  /* ---- ./sizegrid.css ---- */
3620
- /* What is left of the size grid once it draws itself.
3652
+ @layer components {
3653
+ /* The cell's colour, all of it, in one place.
3654
+ *
3655
+ * `SizeGrid` draws the cell's geometry with utilities and leaves every colour
3656
+ * here, because `on` and `chosen` override the resting state -- and a utility
3657
+ * is in `@layer utilities`, which beats `@layer components` whatever the
3658
+ * selector. Stated as `surface-panel-2` and `border-border` on the element,
3659
+ * the resting state won permanently and no cell ever lit up.
3621
3660
  *
3622
- * `on` follows the pointer; `chosen` is what is actually saved, shown only
3623
- * when nothing is hovered so the two can never contradict each other.
3624
- * `chosen` is a border colour and says so at the call site; `on` is a fill
3625
- * mixed from the accent, and `color-mix` in an arbitrary value is less
3626
- * readable than the rule it replaces. */
3661
+ * That is the third time this pattern has bitten: `Select`'s tick and
3662
+ * `Empty`'s figure were the other two. The rule: a resting state whose
3663
+ * active state is a CSS rule has to be a CSS rule too. */
3664
+ .size-cell {
3665
+ border: var(--border-width) solid var(--border);
3666
+ background: var(--panel-2);
3667
+ }
3668
+
3669
+ /* `on` follows the pointer; `chosen` is what is actually saved, shown only
3670
+ when nothing is hovered so the two can never contradict each other. */
3627
3671
  .size-cell.on {
3628
3672
  border-color: var(--accent);
3629
3673
  background: color-mix(in srgb, var(--accent) 45%, transparent);
3630
3674
  }
3631
3675
 
3676
+ .size-cell.chosen { border-color: var(--accent); }
3677
+ }
3678
+
3632
3679
  /* ---- ./tooltip.css ---- */
3680
+ @layer components {
3633
3681
  /* A callout: something worth knowing, with nothing to answer. Distinct from a
3634
3682
  `?`, which is an offer to read more, and from a banner, which is a problem.
3635
3683
  The mark is a round `i` rather than an emoji so it takes the theme's colours
3636
3684
  and stays the same size as the text beside it. */
3637
- /* An info-toned `.callout`, spelled the old way. Identical to it now rather than
3638
- nearly identical to it, which is the whole point of the consolidation: the
3685
+ /* An info-toned `.callout`, spelled the old way. Identical to it now rather
3686
+ than nearly identical to it, which is the whole point of the consolidation.
3687
+ (This comment has been unterminated since before the Tailwind branch -- it
3688
+ stops mid-sentence and never closes, so the CSS parser merged it with the
3689
+ one below and recovered. Nothing was lost from the stylesheet; the rest of
3690
+ the sentence was. Closed here rather than guessed at.) */
3639
3691
 
3640
3692
  /* What is left once `Tooltip` draws its own mark: the hover and focus that
3641
3693
  reach the mark from its parent, and the tip -- whose closed and open
@@ -3718,8 +3770,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3718
3770
  }
3719
3771
 
3720
3772
  .explain-tip strong { color: var(--text); font-weight: 600; }
3773
+ }
3721
3774
 
3722
3775
  /* ---- ./illustration.css ---- */
3776
+ @layer components {
3723
3777
  /* illustrations -------------------------------------------------------- */
3724
3778
 
3725
3779
  /* Open Peeps, two colours, wearing whatever the theme is.
@@ -3727,8 +3781,9 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3727
3781
  reason they can live in an app with four palettes: `ink` becomes the current
3728
3782
  text colour and `paper` becomes the surface behind it, so one file covers
3729
3783
  Night, Paper and both halves of System. Give the wrapper a `color` and
3730
- the figure follows it -- `.muted` for a quiet empty state, `--accent` for the
3731
- one thing on screen worth looking at. */
3784
+ the figure follows it: `--muted` for a quiet empty state, `--accent` for the
3785
+ one thing on screen worth looking at. Set that colour on the wrapper --
3786
+ `Empty` reaches for `text-muted`, and any token works. */
3732
3787
  .illo {
3733
3788
  display: inline-block;
3734
3789
  /* Height is the given dimension and the width comes from the drawing, so a
@@ -3745,20 +3800,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3745
3800
  caller says otherwise. Not `transparent`: these have overlapping shapes, and
3746
3801
  a shirt that lets the arm behind it show through reads as a hole. */
3747
3802
  .illo .paper { fill: var(--illo-paper); }
3748
-
3749
- /* The figure in a whole-surface `Empty`, drawn in the muted grey of the
3750
- sentence under it.
3751
- *
3752
- * A utility cannot do this, and the reason is worth keeping: `.illo` sets
3753
- * `color: inherit` and is unlayered, while every Tailwind utility lives in
3754
- * `@layer utilities` -- and an unlayered declaration beats a layered one
3755
- * whatever the specificity. So `text-muted` on the figure lost, silently,
3756
- * and the shirt came out full text black beside a muted sentence. The
3757
- * original rule already carried a note about needing two classes to beat
3758
- * `.illo`; layers make that impossible rather than merely fiddly. */
3759
- .nothing-figure { color: var(--muted); }
3803
+ }
3760
3804
 
3761
3805
  /* ---- ./field.css ---- */
3806
+ @layer components {
3762
3807
  /* fields --------------------------------------------------------------- */
3763
3808
 
3764
3809
  /* Label, description, input, error -- in the order they are wanted. The
@@ -3766,7 +3811,9 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3766
3811
  to what was typed, so it goes after. */
3767
3812
  .field { display: grid; gap: var(--space-1); }
3768
3813
 
3769
- .field-stack-layout { /* the default: label, description, control, error */ }
3814
+ /* `.field-stack-layout` carries no rules: stacking is what `.field` already
3815
+ does, and the class is emitted only so that both layouts name themselves
3816
+ the same way (`field-${layout}-layout`). Only the row layout overrides. */
3770
3817
 
3771
3818
  .field-row-layout > .field-label { grid-column: 1; }
3772
3819
 
@@ -3787,8 +3834,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3787
3834
 
3788
3835
  /* Not red. The asterisk marks which fields are required; colouring it the same
3789
3836
  as a failure says something has already gone wrong with every one of them. */
3790
- .field-required { color: var(--muted); }
3791
-
3792
3837
  .field-hint,
3793
3838
  .field-error { margin: 0; font-size: var(--text-sm); line-height: 1.45; }
3794
3839
 
@@ -3810,8 +3855,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3810
3855
  /* Fields stacked down a form. Wider than the gap inside one field, so the
3811
3856
  label of the next belongs to the input under it rather than to the error
3812
3857
  above it. */
3813
- .field-stack { display: grid; gap: var(--space-4); }
3814
-
3815
3858
  .field-row > .field { flex: 1; min-width: 0; }
3816
3859
 
3817
3860
  /* A password with an eye -- `Input type="password"`.
@@ -3933,14 +3976,12 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3933
3976
  background: var(--panel-2);
3934
3977
  color: var(--text);
3935
3978
  }
3979
+ }
3936
3980
 
3937
3981
  /* ---- ./tour.css ---- */
3982
+ @layer components {
3938
3983
  /* tour ----------------------------------------------------------------- */
3939
3984
 
3940
- /* The layer sits over everything and catches nothing: the scrim is painted by
3941
- the hole's shadow, so there is no full-screen element to swallow a click. */
3942
- .tour { position: fixed; inset: 0; z-index: 60; pointer-events: none; }
3943
-
3944
3985
  /* The control the tour is currently pointing at.
3945
3986
 
3946
3987
  Much of this chrome is invisible until you touch it -- `.view-add` lives at
@@ -3951,11 +3992,14 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3951
3992
  what every control does at rest, and the alternative is naming each one here
3952
3993
  and watching that list drift from the rules that hide them. One declaration
3953
3994
  that outranks whatever the resting state is beats an enumeration that goes
3954
- stale. It is scoped to a class only ever present on one element at a time. */
3955
- .tour-target {
3956
- opacity: 1 !important;
3957
- color: var(--text) !important;
3958
- }
3995
+ stale. It is scoped to a class only ever present on one element at a time.
3996
+
3997
+ Opacity and nothing else. This forced `color: var(--text)` too, from the
3998
+ original port, with no case on record that needed it -- and it broke the
3999
+ first target that painted its own background, turning a primary button's
4000
+ label dark on accent at 2.2:1. The spotlight makes a control visible; what
4001
+ colour it is at rest is the control's business. */
4002
+ .tour-target { opacity: 1 !important; }
3959
4003
 
3960
4004
  /* The spotlight. An enormous spread shadow dims the whole window except the
3961
4005
  rectangle it is drawn on, which needs no clip path and no arithmetic. */
@@ -3989,19 +4033,19 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3989
4033
 
3990
4034
  .tour-card:focus { outline: none; }
3991
4035
 
3992
- .tour-title { font-size: var(--text-base); }
3993
-
3994
4036
  .tour-body { margin: 0; color: var(--muted); font-size: var(--text-sm); line-height: 1.5; }
3995
4037
 
3996
4038
  .tour-actions { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-2); }
3997
4039
 
3998
4040
  .tour-count { color: var(--muted); font-size: var(--text-xs); }
4041
+ }
3999
4042
 
4000
4043
  /* The mail and forum shell, added in 0.4.0. After the rest, because the
4001
4044
  anchored surfaces (`.pop`, `.menu-sheet`, `.cmd`) sit over everything and a
4002
4045
  rule of equal specificity that came earlier would lose to whatever the
4003
4046
  sheets they cover happen to set. */
4004
4047
  /* ---- ./stat.css ---- */
4048
+ @layer components {
4005
4049
  /* What the utilities on `Stat` cannot say: a tone on the tile colouring the
4006
4050
  figure inside it. An attribute on the parent selecting a child is a
4007
4051
  relationship; a utility is a property on one element.
@@ -4012,8 +4056,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4012
4056
  .stat[data-tone='good'] .stat-value { color: var(--good); }
4013
4057
  .stat[data-tone='warn'] .stat-value { color: var(--warn); }
4014
4058
  .stat[data-tone='bad'] .stat-value { color: var(--bad); }
4059
+ }
4015
4060
 
4016
4061
  /* ---- ./shell.css ---- */
4062
+ @layer components {
4017
4063
  /* The measure, and only the measure.
4018
4064
  *
4019
4065
  * `Shell` draws itself with utilities -- it is layout and nothing else. What
@@ -4040,8 +4086,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4040
4086
  font-size: var(--text-sm);
4041
4087
  white-space: nowrap;
4042
4088
  }
4089
+ }
4043
4090
 
4044
4091
  /* ---- ./scrollarea.css ---- */
4092
+ @layer components {
4045
4093
  /* A box that scrolls, with its edges drawn.
4046
4094
 
4047
4095
  The fades are `::before`/`::after` on the scroller itself, pinned with
@@ -4134,8 +4182,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4134
4182
  .scroller.faded::before,
4135
4183
  .scroller.faded::after { transition: none; }
4136
4184
  }
4185
+ }
4137
4186
 
4138
4187
  /* ---- ./splitpane.css ---- */
4188
+ @layer components {
4139
4189
  /* Two panes and the handle between them. */
4140
4190
  .split {
4141
4191
  display: flex;
@@ -4154,17 +4204,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4154
4204
  the intrinsic width of a mail list is the longest unbroken subject line in
4155
4205
  it. Without this the handle stops partway across and nothing on screen says
4156
4206
  why -- it just refuses, differently for every mailbox. */
4157
- .split-pane {
4158
- min-width: 0;
4159
- min-height: 0;
4160
- overflow: hidden;
4161
- display: flex;
4162
- flex-direction: column;
4163
- }
4164
-
4165
- .split-first { flex: 0 0 var(--split); }
4166
- .split-second { flex: 1 1 0; }
4167
-
4168
4207
  /* The handle is wider than it looks: a hairline is a correct-looking divider
4169
4208
  and an impossible pointer target. The grip is the line, drawn at
4170
4209
  `--border-width` down the middle of a `--space-3` strip, and the strip is
@@ -4235,8 +4274,10 @@ body.splitting * { cursor: inherit !important; }
4235
4274
  @media (prefers-reduced-motion: reduce) {
4236
4275
  .split-grip { transition: none; }
4237
4276
  }
4277
+ }
4238
4278
 
4239
4279
  /* ---- ./popover.css ---- */
4280
+ @layer components {
4240
4281
  /* A small surface anchored to what opened it. Same sheet as the select's list,
4241
4282
  because they are the same object -- something that appeared over the page,
4242
4283
  attached to a control. */
@@ -4259,13 +4300,6 @@ body.splitting * { cursor: inherit !important; }
4259
4300
  z-index: 60;
4260
4301
  }
4261
4302
 
4262
- .pop-body {
4263
- padding: var(--space-3);
4264
- outline: none;
4265
- color: var(--text);
4266
- font-size: var(--text-sm);
4267
- }
4268
-
4269
4303
  /* Entry, and the direction it comes from. `data-placement` is React Aria's,
4270
4304
  set to where the surface actually went rather than where it was asked to go,
4271
4305
  so the movement agrees with the flip. */
@@ -4290,8 +4324,10 @@ body.splitting * { cursor: inherit !important; }
4290
4324
  .pop[data-entering],
4291
4325
  .pop[data-exiting] { animation: none; }
4292
4326
  }
4327
+ }
4293
4328
 
4294
4329
  /* ---- ./menu.css ---- */
4330
+ @layer components {
4295
4331
  /* What is left once `Menu` draws its sheet and its list: the rows, whose
4296
4332
  `data-focused`, `data-pressed` and danger states React Aria stamps on
4297
4333
  them, and the rules that reach from a row into what it holds. The
@@ -4324,8 +4360,6 @@ body.splitting * { cursor: inherit !important; }
4324
4360
  .menu-icon { flex: none; width: var(--space-4); height: var(--space-4); color: var(--muted); }
4325
4361
  .menu-item[data-focused] .menu-icon { color: var(--text); }
4326
4362
 
4327
- .menu-text { flex: 1 1 auto; min-width: 0; display: grid; }
4328
- .menu-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4329
4363
  .menu-desc {
4330
4364
  font-size: var(--text-xs);
4331
4365
  color: var(--muted);
@@ -4373,8 +4407,10 @@ body.splitting * { cursor: inherit !important; }
4373
4407
  }
4374
4408
 
4375
4409
  .menu-sheet { box-shadow: var(--shadow-2); }
4410
+ }
4376
4411
 
4377
4412
  /* ---- ./command.css ---- */
4413
+ @layer components {
4378
4414
  /* The command palette: a field over the app and a list under it. */
4379
4415
  .cmd-scrim {
4380
4416
  position: fixed;
@@ -4402,18 +4438,6 @@ body.splitting * { cursor: inherit !important; }
4402
4438
  flex-direction: column;
4403
4439
  }
4404
4440
 
4405
- .cmd-body { outline: none; display: flex; flex-direction: column; min-height: 0; }
4406
-
4407
- .cmd-field {
4408
- display: flex;
4409
- align-items: center;
4410
- gap: var(--space-2);
4411
- padding: var(--space-3) var(--space-4);
4412
- border-bottom: var(--border-width) solid var(--border);
4413
- }
4414
-
4415
- .cmd-mark { flex: none; width: var(--space-4); height: var(--space-4); color: var(--muted); }
4416
-
4417
4441
  /* No border, no focus ring on the input itself: the whole surface is the
4418
4442
  field, focus is already in it when it opens, and a ring around a box that
4419
4443
  fills the top of the palette is a ring around the palette. */
@@ -4430,22 +4454,6 @@ body.splitting * { cursor: inherit !important; }
4430
4454
 
4431
4455
  .cmd-input::placeholder { color: var(--muted); }
4432
4456
 
4433
- .cmd-list {
4434
- padding: var(--space-2);
4435
- overflow: auto;
4436
- overscroll-behavior: contain;
4437
- outline: none;
4438
- min-height: 0;
4439
- }
4440
-
4441
- .cmd-group-title {
4442
- padding: var(--space-2) var(--space-2) var(--space-1);
4443
- font-size: var(--text-2xs);
4444
- text-transform: uppercase;
4445
- letter-spacing: var(--tracking);
4446
- color: var(--muted);
4447
- }
4448
-
4449
4457
  .cmd-item {
4450
4458
  display: flex;
4451
4459
  align-items: center;
@@ -4481,8 +4489,6 @@ body.splitting * { cursor: inherit !important; }
4481
4489
  .cmd-icon { flex: none; width: var(--space-4); height: var(--space-4); color: var(--muted); }
4482
4490
  .cmd-item[data-focused] .cmd-icon { color: var(--text); }
4483
4491
 
4484
- .cmd-text { flex: 1 1 auto; min-width: 0; display: grid; }
4485
- .cmd-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4486
4492
  .cmd-desc {
4487
4493
  font-size: var(--text-xs);
4488
4494
  color: var(--muted);
@@ -4516,8 +4522,10 @@ body.splitting * { cursor: inherit !important; }
4516
4522
  .cmd-scrim[data-exiting],
4517
4523
  .cmd[data-entering] { animation: none; }
4518
4524
  }
4525
+ }
4519
4526
 
4520
4527
  /* ---- ./pagination.css ---- */
4528
+ @layer components {
4521
4529
  /* What is left once `Pagination` draws its own frame: the step and page
4522
4530
  buttons, whose hover, focus-visible and current states are pseudo-classes
4523
4531
  on two selectors at once, and `.pager-count strong`, which reaches into
@@ -4636,8 +4644,10 @@ body.splitting * { cursor: inherit !important; }
4636
4644
  .pager-page,
4637
4645
  .pager-jump { transition: none; }
4638
4646
  }
4647
+ }
4639
4648
 
4640
4649
  /* ---- ./identity.css ---- */
4650
+ @layer components {
4641
4651
  /* A person, said in one line: initials, name, sometimes the address. */
4642
4652
  .ident {
4643
4653
  display: inline-flex;
@@ -4680,25 +4690,6 @@ body.splitting * { cursor: inherit !important; }
4680
4690
  font-size: var(--text-2xs);
4681
4691
  }
4682
4692
 
4683
- .ident-text { display: grid; min-width: 0; }
4684
-
4685
- .ident-name {
4686
- overflow: hidden;
4687
- text-overflow: ellipsis;
4688
- white-space: nowrap;
4689
- font-weight: var(--weight);
4690
- }
4691
-
4692
- .ident-address {
4693
- overflow: hidden;
4694
- text-overflow: ellipsis;
4695
- white-space: nowrap;
4696
- font-size: var(--text-xs);
4697
- color: var(--muted);
4698
- }
4699
-
4700
- .ident-aside { flex: none; color: var(--muted); font-size: var(--text-xs); }
4701
-
4702
4693
  /* A chip in a composer's To field: boxed, tighter, removable. */
4703
4694
  .ident.kind-chip {
4704
4695
  gap: var(--space-1);
@@ -4738,4 +4729,5 @@ body.splitting * { cursor: inherit !important; }
4738
4729
 
4739
4730
  .ident-remove:hover { background: var(--panel); color: var(--text); }
4740
4731
  .ident-remove:focus-visible { outline: none; box-shadow: var(--focus-ring); }
4732
+ }
4741
4733