@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.
package/dist/tf.css CHANGED
@@ -287,12 +287,9 @@
287
287
  --font-sans: var(--font-sans);
288
288
  --font-mono: var(--font-mono);
289
289
  --text-xs: var(--text-xs);
290
- --text-xs--line-height: inherit;
291
290
  --text-sm: var(--text-sm);
292
- --text-sm--line-height: inherit;
293
291
  --text-base: var(--text-base);
294
292
  --text-lg: var(--text-lg);
295
- --text-lg--line-height: inherit;
296
293
  --text-xl: var(--text-xl);
297
294
  --font-weight-normal: var(--weight);
298
295
  --tracking-normal: var(--tracking);
@@ -307,19 +304,20 @@
307
304
  --spacing-5: var(--space-5);
308
305
  --spacing-6: var(--space-6);
309
306
  --spacing-10: var(--space-10);
310
- --text-2xs--line-height: inherit;
311
307
  --text-2xs: var(--text-2xs);
312
308
  --text-md: var(--text-md);
313
309
  --font-weight-strong: var(--weight-strong);
314
310
  --radius-pill: var(--radius-pill);
315
311
  --color-text: var(--text);
316
312
  --color-muted: var(--muted);
317
- --color-accent: var(--accent);
318
313
  --color-border: var(--border);
319
314
  --color-border-strong: var(--border-strong);
320
315
  }
321
316
  }
322
317
  @layer utilities {
318
+ .pointer-events-none {
319
+ pointer-events: none;
320
+ }
323
321
  .collapse {
324
322
  visibility: collapse;
325
323
  }
@@ -352,6 +350,9 @@
352
350
  .sticky {
353
351
  position: sticky;
354
352
  }
353
+ .inset-0 {
354
+ inset: 0px;
355
+ }
355
356
  .z-\[60\] {
356
357
  z-index: 60;
357
358
  }
@@ -385,12 +386,21 @@
385
386
  .mt-1 {
386
387
  margin-top: var(--spacing-1);
387
388
  }
389
+ .mt-2 {
390
+ margin-top: var(--spacing-2);
391
+ }
392
+ .mt-px {
393
+ margin-top: 1px;
394
+ }
388
395
  .mb-3 {
389
396
  margin-bottom: var(--spacing-3);
390
397
  }
391
398
  .ml-2 {
392
399
  margin-left: var(--spacing-2);
393
400
  }
401
+ .ml-auto {
402
+ margin-left: auto;
403
+ }
394
404
  .block {
395
405
  display: block;
396
406
  }
@@ -415,6 +425,9 @@
415
425
  .table {
416
426
  display: table;
417
427
  }
428
+ .h-4 {
429
+ height: var(--spacing-4);
430
+ }
418
431
  .h-\[14px\] {
419
432
  height: 14px;
420
433
  }
@@ -433,6 +446,9 @@
433
446
  .min-h-screen {
434
447
  min-height: 100vh;
435
448
  }
449
+ .w-4 {
450
+ width: var(--spacing-4);
451
+ }
436
452
  .w-\[18px\] {
437
453
  width: 18px;
438
454
  }
@@ -460,6 +476,18 @@
460
476
  .flex-1 {
461
477
  flex: 1;
462
478
  }
479
+ .flex-\[0_0_var\(--split\)\] {
480
+ flex: 0 0 var(--split);
481
+ }
482
+ .flex-\[1_1_0\] {
483
+ flex: 1 1 0;
484
+ }
485
+ .flex-\[1_1_260px\] {
486
+ flex: 1 1 260px;
487
+ }
488
+ .flex-\[1_1_auto\] {
489
+ flex: 1 1 auto;
490
+ }
463
491
  .flex-none {
464
492
  flex: none;
465
493
  }
@@ -490,6 +518,9 @@
490
518
  .flex-wrap {
491
519
  flex-wrap: wrap;
492
520
  }
521
+ .place-items-center {
522
+ place-items: center;
523
+ }
493
524
  .items-baseline {
494
525
  align-items: baseline;
495
526
  }
@@ -523,6 +554,9 @@
523
554
  .gap-\[var\(--border-width\)\] {
524
555
  gap: var(--border-width);
525
556
  }
557
+ .gap-px {
558
+ gap: 1px;
559
+ }
526
560
  .self-center {
527
561
  align-self: center;
528
562
  }
@@ -570,9 +604,6 @@
570
604
  --tw-border-style: dashed;
571
605
  border-style: dashed;
572
606
  }
573
- .border-accent {
574
- border-color: var(--color-accent);
575
- }
576
607
  .border-border {
577
608
  border-color: var(--color-border);
578
609
  }
@@ -588,9 +619,15 @@
588
619
  .p-2 {
589
620
  padding: var(--spacing-2);
590
621
  }
622
+ .p-3 {
623
+ padding: var(--spacing-3);
624
+ }
591
625
  .p-4 {
592
626
  padding: var(--spacing-4);
593
627
  }
628
+ .px-2 {
629
+ padding-inline: var(--spacing-2);
630
+ }
594
631
  .px-4 {
595
632
  padding-inline: var(--spacing-4);
596
633
  }
@@ -609,6 +646,12 @@
609
646
  .py-10 {
610
647
  padding-block: var(--spacing-10);
611
648
  }
649
+ .pt-2 {
650
+ padding-top: var(--spacing-2);
651
+ }
652
+ .pb-1 {
653
+ padding-bottom: var(--spacing-1);
654
+ }
612
655
  .pb-2 {
613
656
  padding-bottom: var(--spacing-2);
614
657
  }
@@ -623,19 +666,18 @@
623
666
  }
624
667
  .text-2xs {
625
668
  font-size: var(--text-2xs);
626
- line-height: var(--tw-leading, var(--text-2xs--line-height));
669
+ }
670
+ .text-base {
671
+ font-size: var(--text-base);
627
672
  }
628
673
  .text-lg {
629
674
  font-size: var(--text-lg);
630
- line-height: var(--tw-leading, var(--text-lg--line-height));
631
675
  }
632
676
  .text-sm {
633
677
  font-size: var(--text-sm);
634
- line-height: var(--tw-leading, var(--text-sm--line-height));
635
678
  }
636
679
  .text-xs {
637
680
  font-size: var(--text-xs);
638
- line-height: var(--tw-leading, var(--text-xs--line-height));
639
681
  }
640
682
  .leading-\[0\] {
641
683
  --tw-leading: 0;
@@ -649,6 +691,10 @@
649
691
  --tw-leading: 1.25;
650
692
  line-height: 1.25;
651
693
  }
694
+ .leading-\[1\.35\] {
695
+ --tw-leading: 1.35;
696
+ line-height: 1.35;
697
+ }
652
698
  .leading-\[16px\] {
653
699
  --tw-leading: 16px;
654
700
  line-height: 16px;
@@ -690,12 +736,12 @@
690
736
  --tw-numeric-spacing: tabular-nums;
691
737
  font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
692
738
  }
693
- .opacity-0 {
694
- opacity: 0%;
695
- }
696
739
  .opacity-60 {
697
740
  opacity: 60%;
698
741
  }
742
+ .opacity-90 {
743
+ opacity: 90%;
744
+ }
699
745
  .opacity-\[0\.55\] {
700
746
  opacity: 0.55;
701
747
  }
@@ -735,9 +781,6 @@
735
781
  .surface-panel {
736
782
  background: var(--panel);
737
783
  }
738
- .surface-panel-2 {
739
- background: var(--panel-2);
740
- }
741
784
  .disabled\:cursor-default:disabled {
742
785
  cursor: default;
743
786
  }
@@ -977,7 +1020,19 @@
977
1020
  }
978
1021
  }
979
1022
 
980
- /* ---- ./base.css ---- */
1023
+ /* ---- ./_system-light.css ---- */
1024
+ /* The `system` theme's light half.
1025
+ *
1026
+ * Its own file, and outside the components layer, because it is a *theme*
1027
+ * rather than a component rule. `tokens.css` states the dark base on `:root`
1028
+ * and is unlayered; the moment this sat inside `@layer components` it lost to
1029
+ * that, and every `system` specimen rendered dark on a light-mode machine.
1030
+ * Unlayered beats layered whatever the selector says, which is exactly the
1031
+ * trap this repository keeps finding in different costumes.
1032
+ *
1033
+ * The underscore marks it the same way `_tailwind.built.css` is marked:
1034
+ * `copy-css.mjs` passes both through without wrapping them.
1035
+ */
981
1036
  /* Light, only under the `system` theme. This used to override `:root`
982
1037
  unconditionally, so choosing a dark theme on a light-mode laptop got
983
1038
  silently repainted. Every other theme states its own palette. */
@@ -1006,6 +1061,8 @@
1006
1061
  }
1007
1062
  }
1008
1063
 
1064
+ /* ---- ./base.css ---- */
1065
+ @layer components {
1009
1066
  /* Derived from the two surfaces every theme already defines, so a theme gets a
1010
1067
  control colour without knowing this exists. */
1011
1068
  /* Pseudo-elements too. `*` does not match them, and `box-sizing` is not
@@ -1723,23 +1780,6 @@ input:active, textarea:active, select:active { transform: none; }
1723
1780
  template are allowed to diverge, and this is only saying that they have. */
1724
1781
  .set-hint.drifted { color: var(--warn); }
1725
1782
 
1726
- /* What an applet drew, read back. A tree rather than prose, so it is left as
1727
- preformatted text and given a ceiling -- a busy applet is a hundred lines and
1728
- this sits inside a settings pane. */
1729
- .drawn {
1730
- margin: var(--space-2) 0 0;
1731
- padding: var(--space-2) var(--space-2);
1732
- max-height: 220px;
1733
- overflow: auto;
1734
- font-size: var(--text-xs);
1735
- line-height: 1.45;
1736
- white-space: pre-wrap;
1737
- overflow-wrap: anywhere;
1738
- border: 1px solid var(--border);
1739
- border-radius: var(--radius);
1740
- background: var(--panel-2);
1741
- }
1742
-
1743
1783
  /* One applet, alone on the page, at `/applet/<id>`. Fills the window: there is
1744
1784
  no grid here to decide a height, and a screenshot of an applet letterboxed
1745
1785
  inside a page is mostly not the applet. */
@@ -1786,11 +1826,6 @@ input:active, textarea:active, select:active { transform: none; }
1786
1826
  that belongs to a conversation lives here any more, so it can breathe. */
1787
1827
  .topbar .ctl-grow { flex: 1; min-width: 0; }
1788
1828
 
1789
- /* Back on the left, Next on the right, with the gap between them doing the
1790
- separating -- so the button that goes forward is in the same place on both
1791
- pages instead of sliding when Back appears. */
1792
- .first-run-actions { align-items: center; }
1793
-
1794
1829
  @keyframes listening-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .35 } }
1795
1830
 
1796
1831
  /* table ---------------------------------------------------------------- */
@@ -1866,8 +1901,10 @@ input:active, textarea:active, select:active { transform: none; }
1866
1901
  color: var(--muted);
1867
1902
  opacity: 1;
1868
1903
  }
1904
+ }
1869
1905
 
1870
1906
  /* ---- ./select.css ---- */
1907
+ @layer components {
1871
1908
  /* How big a control is, asked for rather than inherited.
1872
1909
  Four sizes existed and every one of them came from a descendant selector:
1873
1910
  `.set-row button` was 11px in 3px of padding, `.approval-actions button` was
@@ -1962,16 +1999,19 @@ input:active, textarea:active, select:active { transform: none; }
1962
1999
  /* State is read off React Aria's attributes: `data-open` on the root,
1963
2000
  `data-focused` / `data-selected` / `data-disabled` on an item, where
1964
2001
  `.open`, `[data-active]`, `.on` and `.off` used to be. */
1965
- .sel-list { scroll-padding-block: var(--space-1); box-shadow: var(--shadow-2); })` brings up ended flush against the
1966
- border and read as clipped rather than as one of a set -- and this list
1967
- opens scrolled to whatever is chosen, so that was the ordinary state.
1968
- `scroll-padding` insets what counts as "in view", so it stops short by
1969
- the same four the sides have. */
2002
+ /* The list keeps two things the utilities on `Select` cannot say.
2003
+ *
2004
+ * `scroll-padding-block`: the same room while the list is scrolled as it has
2005
+ * at rest. Padding at the top of a scroll container scrolls away with the
2006
+ * content, so the row `scrollIntoView({block: 'nearest'})` brings up ended
2007
+ * flush against the border and read as clipped rather than as one of a set --
2008
+ * and this list opens scrolled to whatever is chosen, so that was the
2009
+ * ordinary state.
2010
+ *
2011
+ * `box-shadow`: `--shadow-2` is a token, and a utility would have to inline
2012
+ * the value, which is the one thing the vocabulary exists to stop. */
2013
+ .sel-list {
1970
2014
  scroll-padding-block: var(--space-1);
1971
- list-style: none;
1972
- border: var(--border-width) solid var(--border-strong);
1973
- border-radius: var(--radius-md);
1974
- background: var(--panel);
1975
2015
  box-shadow: var(--shadow-2);
1976
2016
  }
1977
2017
 
@@ -1986,6 +2026,19 @@ input:active, textarea:active, select:active { transform: none; }
1986
2026
 
1987
2027
  /* The tick marks the chosen row. Colour alone would not: a list where the only
1988
2028
  difference is a hue is a list you cannot read past. */
2029
+ /* The tick's resting state lives here, beside the state that overrides it.
2030
+ *
2031
+ * It was `opacity-0` on the element and `opacity: 1` here, which worked only
2032
+ * while this sheet was unlayered. A utility is in `@layer utilities` and
2033
+ * beats `@layer components` whatever the selector, so the moment the sheets
2034
+ * moved into a layer the tick stopped appearing on the selected row -- the
2035
+ * resting state won permanently.
2036
+ *
2037
+ * The rule generalises: a utility for a resting state whose active state is a
2038
+ * relationship between elements will always beat that active state. The pair
2039
+ * has to sit in the same layer. */
2040
+ .sel-tick { opacity: 0; }
2041
+
1989
2042
  .sel-item[data-selected] .sel-tick { opacity: 1; }
1990
2043
 
1991
2044
  /* The icon on a speaker's row in the open list -- `Select`'s `aside`. Last in
@@ -2020,8 +2073,10 @@ input:active, textarea:active, select:active { transform: none; }
2020
2073
  .field > input,
2021
2074
  .field > textarea,
2022
2075
  .field > .sel { width: 100%; }
2076
+ }
2023
2077
 
2024
2078
  /* ---- ./dangerzone.css ---- */
2079
+ @layer components {
2025
2080
  /* Destructive, and legible as such standing still. */
2026
2081
  .btn.danger {
2027
2082
  color: var(--bad);
@@ -2111,8 +2166,6 @@ input:active, textarea:active, select:active { transform: none; }
2111
2166
  border-radius: 4px;
2112
2167
  }
2113
2168
 
2114
- .danger-unavailable { margin-top: var(--space-2); opacity: 0.85; }
2115
-
2116
2169
  /* Label beside the control, for a settings pane: a column of names on the left
2117
2170
  and their controls on the right reads as a list of what is set, where the
2118
2171
  same rows stacked read as a form waiting to be filled in. The description
@@ -2133,8 +2186,10 @@ input:active, textarea:active, select:active { transform: none; }
2133
2186
  controls in the same column at the same indent that do not line up read as
2134
2187
  an accident rather than as a pair. */
2135
2188
  .field-row > button { min-width: 9ch; }
2189
+ }
2136
2190
 
2137
2191
  /* ---- ./callout.css ---- */
2192
+ @layer components {
2138
2193
  /* One shape for everything that tells you something.
2139
2194
  `.banner` and `.callout` were the same box drawn twice: a bordered, rounded,
2140
2195
  12px panel with a tint. They differed in padding by a pixel in each direction
@@ -2188,8 +2243,6 @@ input:active, textarea:active, select:active { transform: none; }
2188
2243
  /* A callout that leaves on its own.
2189
2244
  Room at the bottom for the bar, and `position: relative` so the bar can sit
2190
2245
  on the border rather than inside the text. */
2191
- .callout-timed { position: relative; padding-bottom: var(--space-5); overflow: hidden; }
2192
-
2193
2246
  /* The countdown. Drawn as a shrinking bar rather than a number: nobody wants to
2194
2247
  read a clock, they want to know whether they have time. Animated width rather
2195
2248
  than a transition, so it starts the moment it is painted. */
@@ -2250,14 +2303,6 @@ input:active, textarea:active, select:active { transform: none; }
2250
2303
  is the operating system's font drawing a glyph beside an app that has an icon
2251
2304
  set for exactly this. The ring went with it: a circle around a warning
2252
2305
  triangle is two shapes saying one thing. */
2253
- .callout-mark {
2254
- flex: none;
2255
- display: grid;
2256
- place-items: center;
2257
- margin-top: 1px;
2258
- opacity: 0.9;
2259
- }
2260
-
2261
2306
  /* Held-back history when a local conversation would cross to a hosted model.
2262
2307
  Deliberately loud: the cost of missing it is uploading work that was kept
2263
2308
  local on purpose. */
@@ -2273,8 +2318,23 @@ input:active, textarea:active, select:active { transform: none; }
2273
2318
  }
2274
2319
 
2275
2320
  .callout button { flex: none; }
2321
+ /* A state, so it stays a class.
2322
+ *
2323
+ * `Callout` applies this as `running ? ' callout-timed' : ''` inside a
2324
+ * template literal -- which is not a class list a rewrite can read, and the
2325
+ * first pass at converting it deleted the rule while leaving the usage. The
2326
+ * box lost five spacing steps of padding and the countdown bar had nothing
2327
+ * to sit in. A class that appears only inside a conditional is a state; the
2328
+ * sheet is where those belong. */
2329
+ .callout-timed {
2330
+ position: relative;
2331
+ padding-bottom: var(--space-5);
2332
+ overflow: hidden;
2333
+ }
2334
+ }
2276
2335
 
2277
2336
  /* ---- ./card.css ---- */
2337
+ @layer components {
2278
2338
  /* `.card` is public API, not this component's private styling.
2279
2339
  *
2280
2340
  * Consumers apply it to their own markup -- the gallery's skeleton specimen
@@ -2329,16 +2389,6 @@ input:active, textarea:active, select:active { transform: none; }
2329
2389
  which reads as a different column of content. */
2330
2390
  .card:has(.card-icon) .card-inline { padding-left: calc(var(--card-icon) + var(--space-3)); }
2331
2391
 
2332
- /* Its own line, above the sentence it introduces. */
2333
- .card-inline-label {
2334
- display: block;
2335
- font-size: var(--text-xs);
2336
- text-transform: uppercase;
2337
- letter-spacing: 0.04em;
2338
- color: var(--muted);
2339
- margin-bottom: 2px;
2340
- }
2341
-
2342
2392
  .card-title { display: block; font-size: var(--text-base); }
2343
2393
 
2344
2394
  .card-desc { margin-top: var(--space-1); color: var(--muted); font-size: var(--text-sm); line-height: 1.5; }
@@ -2416,8 +2466,10 @@ input:active, textarea:active, select:active { transform: none; }
2416
2466
  }
2417
2467
 
2418
2468
  .card[aria-pressed='true']:hover { border-color: var(--accent); }
2469
+ }
2419
2470
 
2420
2471
  /* ---- ./toggle.css ---- */
2472
+ @layer components {
2421
2473
  .card-action .switch-row { padding: 0; gap: var(--space-2); }
2422
2474
 
2423
2475
  .card-action .switch-row .switch-body { flex: none; }
@@ -2474,12 +2526,8 @@ input:active, textarea:active, select:active { transform: none; }
2474
2526
  tooltip ended up orphaned under it. */
2475
2527
  .set-row > .switch-row { flex: 1; min-width: 0; padding-block: 0; }
2476
2528
 
2477
- .switch-label { color: var(--text); }
2478
-
2479
2529
  .switch-hint { font-size: var(--text-sm); color: var(--muted); }
2480
2530
 
2481
- .switch-said { flex: none; font-size: var(--text-xs); color: var(--muted); }
2482
-
2483
2531
  /* Three sizes, the same names every other control uses. The switch scales with
2484
2532
  the row: a 30x18 track beside 12px text is a different control from the same
2485
2533
  track beside 16px text. */
@@ -2648,19 +2696,10 @@ input[type='radio']:active:not(:disabled) {
2648
2696
  @media (prefers-reduced-motion: reduce) {
2649
2697
  .toggle-sweep::before { animation: none; background: color-mix(in oklab, var(--accent) 45%, transparent); }
2650
2698
  }
2651
-
2652
- /* ---- ./skeleton.css ---- */
2653
- .muted { color: var(--muted); }
2654
-
2655
- .pulse-block {
2656
- display: inline-block;
2657
- height: 8px;
2658
- border-radius: var(--radius-sm);
2659
- background: var(--muted);
2660
- animation: ds-pulse 1.4s ease-in-out infinite;
2661
- vertical-align: middle;
2662
2699
  }
2663
2700
 
2701
+ /* ---- ./skeleton.css ---- */
2702
+ @layer components {
2664
2703
  /* skeletons ------------------------------------------------------------ */
2665
2704
 
2666
2705
  /* Ink at low alpha, not a named surface.
@@ -2704,8 +2743,10 @@ input[type='radio']:active:not(:disabled) {
2704
2743
  /* Still distinguishable from a real block of colour, and still still. */
2705
2744
  .skel-pulse { animation: none; opacity: 0.8; }
2706
2745
  }
2746
+ }
2707
2747
 
2708
2748
  /* ---- ./rows.css ---- */
2749
+ @layer components {
2709
2750
  .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2710
2751
 
2711
2752
  /* A name and the pills that qualify it, on one line, where the name is the only
@@ -2917,8 +2958,6 @@ input[type='radio']:active:not(:disabled) {
2917
2958
 
2918
2959
  .rows-split:first-of-type { margin-top: var(--space-2); }
2919
2960
 
2920
- .rows-empty { padding: var(--space-3) 0; }
2921
-
2922
2961
  /* The name, the pills and the line underneath. `min-width: 0` is the whole
2923
2962
  reason this is a class: a flex item will not shrink below its content, so
2924
2963
  without it the name pushes the pills off the end -- and the pills are the
@@ -2935,8 +2974,6 @@ input[type='radio']:active:not(:disabled) {
2935
2974
 
2936
2975
  /* The aligned column. `margin-left: auto` rather than a grid, because the rows
2937
2976
  are flex and a size that varies by two characters does not need a track. */
2938
- .row-trail { flex: none; margin-left: auto; white-space: nowrap; }
2939
-
2940
2977
  .rows-row.rows-top { align-items: flex-start; }
2941
2978
 
2942
2979
  .rows-row.rows-bad .truncate { color: var(--bad); }
@@ -2966,8 +3003,10 @@ input[type='radio']:active:not(:disabled) {
2966
3003
  .row-hit:hover .truncate { color: var(--accent); }
2967
3004
 
2968
3005
  .row-hit:focus-visible { outline: none; box-shadow: var(--focus-ring); }
3006
+ }
2969
3007
 
2970
3008
  /* ---- ./progress.css ---- */
3009
+ @layer components {
2971
3010
  /* progress ----------------------------------------------------------- */
2972
3011
 
2973
3012
  /* The bar itself: a track with a child it fills, an animation and a
@@ -3009,8 +3048,10 @@ input[type='radio']:active:not(:disabled) {
3009
3048
  /* Still distinguishable from empty, and from full. */
3010
3049
  .bar-indeterminate > i { animation: none; width: 100%; opacity: 0.35; }
3011
3050
  }
3051
+ }
3012
3052
 
3013
3053
  /* ---- ./tabs.css ---- */
3054
+ @layer components {
3014
3055
  /* ---------------------------------------------------------------- tabs */
3015
3056
  .tabs { position: relative; display: flex; gap: var(--space-1); }
3016
3057
 
@@ -3078,14 +3119,6 @@ input[type='radio']:active:not(:disabled) {
3078
3119
 
3079
3120
  .tabs-vertical .tab.on .tab-label { color: var(--accent); }
3080
3121
 
3081
- .tab-hint {
3082
- display: block;
3083
- margin-top: 1px;
3084
- font-size: var(--text-xs);
3085
- color: var(--muted);
3086
- line-height: 1.35;
3087
- }
3088
-
3089
3122
  /* A heading above a run of tabs. Set apart from the run above by space, not
3090
3123
  a rule -- the rail already has one edge -- and the tabs under it step in
3091
3124
  by the label's own padding so the label reads as theirs. */
@@ -3096,8 +3129,6 @@ input[type='radio']:active:not(:disabled) {
3096
3129
 
3097
3130
  .tabs-vertical .tab-group:first-child { padding-top: var(--space-2); }
3098
3131
 
3099
- .tab-group-label { display: block; font-size: var(--text-sm); font-weight: 600; color: var(--text); }
3100
-
3101
3132
  .tabs-vertical .tab.in-group { padding-left: var(--space-5); }
3102
3133
 
3103
3134
  /* No line under a vertical strip -- its edge is the border on the right. */
@@ -3111,8 +3142,10 @@ input[type='radio']:active:not(:disabled) {
3111
3142
  }
3112
3143
 
3113
3144
  .tabs-vertical .tab:active:not(:disabled) { border-color: transparent; }
3145
+ }
3114
3146
 
3115
3147
  /* ---- ./toast.css ---- */
3148
+ @layer components {
3116
3149
  /* ---------------------------------------------------------------- toasts */
3117
3150
  .toasts {
3118
3151
  position: fixed;
@@ -3210,8 +3243,10 @@ input[type='radio']:active:not(:disabled) {
3210
3243
  outline: 2px solid var(--accent);
3211
3244
  outline-offset: 2px;
3212
3245
  }
3246
+ }
3213
3247
 
3214
3248
  /* ---- ./modal.css ---- */
3249
+ @layer components {
3215
3250
  /* modal --------------------------------------------------------------- */
3216
3251
 
3217
3252
  /* `hidden` needs saying out loud here. The browser's own `[hidden]` rule is a
@@ -3303,8 +3338,6 @@ input[type='radio']:active:not(:disabled) {
3303
3338
  `<strong>` rule above is the one that sets the size. */
3304
3339
  /* Under the title, on the header's padding rather than the body's, so it reads
3305
3340
  as part of the heading rather than as the first line of content. */
3306
- .modal-desc { line-height: 1.5; }
3307
-
3308
3341
  .modal-body { padding: var(--space-2) var(--space-4) var(--space-3); overflow-y: auto; }
3309
3342
 
3310
3343
  /* The close cross. Its rule stayed behind in tf's stylesheet when the split
@@ -3442,8 +3475,10 @@ input[type='radio']:active:not(:disabled) {
3442
3475
  .modal.sheet[data-entering],
3443
3476
  .modal.sheet[data-exiting] { animation: none; }
3444
3477
  }
3478
+ }
3445
3479
 
3446
3480
  /* ---- ./slider.css ---- */
3481
+ @layer components {
3447
3482
  /* slider ----------------------------------------------------------------- */
3448
3483
  /* A number from a range -- `Slider.tsx` says when one is the right control.
3449
3484
  The row is laid out like a switch row: words on the left, the control on the
@@ -3470,12 +3505,8 @@ input[type='radio']:active:not(:disabled) {
3470
3505
 
3471
3506
  .slider-row.is-disabled { opacity: 0.55; }
3472
3507
 
3473
- .slider-body { flex: 1 1 260px; min-width: 0; display: grid; gap: 1px; }
3474
-
3475
3508
  .set-row > .slider-row { flex: 1; min-width: 0; padding-block: 0; }
3476
3509
 
3477
- .slider-label { color: var(--text); }
3478
-
3479
3510
  .slider-hint { font-size: var(--text-sm); color: var(--muted); }
3480
3511
 
3481
3512
  /* The box, with the value printed inside it at the right where a select
@@ -3665,8 +3696,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3665
3696
  the scale, and the two line up; an explicit `size-*` still wins. */
3666
3697
  :where(.set-row) button,
3667
3698
  :where(.set-row) .slider-box { font-size: var(--text-xs); }
3699
+ }
3668
3700
 
3669
3701
  /* ---- ./checkbox.css ---- */
3702
+ @layer components {
3670
3703
  /* A row you choose. There is no checkbox in it.
3671
3704
  Every one of the fourteen in this app sits beside a name and a line of
3672
3705
  explanation, so the row was always the thing being designed and the 17px
@@ -3699,8 +3732,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3699
3732
  /* A run of checkboxes is one choice, not several settings that happen to be
3700
3733
  adjacent. Tight enough that the group reads as a block -- at the spacing a
3701
3734
  form field wants, four tools look like four unrelated questions. */
3702
- .choice-group { display: grid; gap: var(--space-1); }
3703
-
3704
3735
  /* The same choice, laid out as tiles instead of a column.
3705
3736
 
3706
3737
  Four short names down the full width of a card is four wide bands for four
@@ -3824,8 +3855,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3824
3855
  second line is what the first line is actually asking for. */
3825
3856
  .choice-why { display: block; color: var(--muted); font-size: var(--text-sm); margin-top: 1px; }
3826
3857
 
3827
- .choice-meta { color: var(--muted); font-size: var(--text-xs); font-family: var(--font-mono); }
3828
-
3829
3858
  .choice[data-disabled] {
3830
3859
  cursor: not-allowed;
3831
3860
  border-color: var(--border);
@@ -3836,8 +3865,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3836
3865
  .choice[data-disabled] .choice-why { opacity: 0.5; }
3837
3866
 
3838
3867
  .choice[data-disabled]::after { opacity: 0.4; }
3868
+ }
3839
3869
 
3840
3870
  /* ---- ./table.css ---- */
3871
+ @layer components {
3841
3872
  /* A second line under a row's name, inside the `<th scope="row">` that makes it
3842
3873
  one. The cell is bold because it is a heading; what hangs under it is not,
3843
3874
  and inherited the weight -- so a pinned version read as loud as the thing it
@@ -3885,27 +3916,48 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3885
3916
  .data-table .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
3886
3917
 
3887
3918
  .data-table thead th[style*='right'] { text-align: right; }
3919
+ }
3888
3920
 
3889
3921
  /* ---- ./sizegrid.css ---- */
3890
- /* What is left of the size grid once it draws itself.
3922
+ @layer components {
3923
+ /* The cell's colour, all of it, in one place.
3924
+ *
3925
+ * `SizeGrid` draws the cell's geometry with utilities and leaves every colour
3926
+ * here, because `on` and `chosen` override the resting state -- and a utility
3927
+ * is in `@layer utilities`, which beats `@layer components` whatever the
3928
+ * selector. Stated as `surface-panel-2` and `border-border` on the element,
3929
+ * the resting state won permanently and no cell ever lit up.
3891
3930
  *
3892
- * `on` follows the pointer; `chosen` is what is actually saved, shown only
3893
- * when nothing is hovered so the two can never contradict each other.
3894
- * `chosen` is a border colour and says so at the call site; `on` is a fill
3895
- * mixed from the accent, and `color-mix` in an arbitrary value is less
3896
- * readable than the rule it replaces. */
3931
+ * That is the third time this pattern has bitten: `Select`'s tick and
3932
+ * `Empty`'s figure were the other two. The rule: a resting state whose
3933
+ * active state is a CSS rule has to be a CSS rule too. */
3934
+ .size-cell {
3935
+ border: var(--border-width) solid var(--border);
3936
+ background: var(--panel-2);
3937
+ }
3938
+
3939
+ /* `on` follows the pointer; `chosen` is what is actually saved, shown only
3940
+ when nothing is hovered so the two can never contradict each other. */
3897
3941
  .size-cell.on {
3898
3942
  border-color: var(--accent);
3899
3943
  background: color-mix(in srgb, var(--accent) 45%, transparent);
3900
3944
  }
3901
3945
 
3946
+ .size-cell.chosen { border-color: var(--accent); }
3947
+ }
3948
+
3902
3949
  /* ---- ./tooltip.css ---- */
3950
+ @layer components {
3903
3951
  /* A callout: something worth knowing, with nothing to answer. Distinct from a
3904
3952
  `?`, which is an offer to read more, and from a banner, which is a problem.
3905
3953
  The mark is a round `i` rather than an emoji so it takes the theme's colours
3906
3954
  and stays the same size as the text beside it. */
3907
- /* An info-toned `.callout`, spelled the old way. Identical to it now rather than
3908
- nearly identical to it, which is the whole point of the consolidation: the
3955
+ /* An info-toned `.callout`, spelled the old way. Identical to it now rather
3956
+ than nearly identical to it, which is the whole point of the consolidation.
3957
+ (This comment has been unterminated since before the Tailwind branch -- it
3958
+ stops mid-sentence and never closes, so the CSS parser merged it with the
3959
+ one below and recovered. Nothing was lost from the stylesheet; the rest of
3960
+ the sentence was. Closed here rather than guessed at.) */
3909
3961
 
3910
3962
  /* What is left once `Tooltip` draws its own mark: the hover and focus that
3911
3963
  reach the mark from its parent, and the tip -- whose closed and open
@@ -3988,8 +4040,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3988
4040
  }
3989
4041
 
3990
4042
  .explain-tip strong { color: var(--text); font-weight: 600; }
4043
+ }
3991
4044
 
3992
4045
  /* ---- ./illustration.css ---- */
4046
+ @layer components {
3993
4047
  /* illustrations -------------------------------------------------------- */
3994
4048
 
3995
4049
  /* Open Peeps, two colours, wearing whatever the theme is.
@@ -3997,8 +4051,9 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3997
4051
  reason they can live in an app with four palettes: `ink` becomes the current
3998
4052
  text colour and `paper` becomes the surface behind it, so one file covers
3999
4053
  Night, Paper and both halves of System. Give the wrapper a `color` and
4000
- the figure follows it -- `.muted` for a quiet empty state, `--accent` for the
4001
- one thing on screen worth looking at. */
4054
+ the figure follows it: `--muted` for a quiet empty state, `--accent` for the
4055
+ one thing on screen worth looking at. Set that colour on the wrapper --
4056
+ `Empty` reaches for `text-muted`, and any token works. */
4002
4057
  .illo {
4003
4058
  display: inline-block;
4004
4059
  /* Height is the given dimension and the width comes from the drawing, so a
@@ -4015,20 +4070,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4015
4070
  caller says otherwise. Not `transparent`: these have overlapping shapes, and
4016
4071
  a shirt that lets the arm behind it show through reads as a hole. */
4017
4072
  .illo .paper { fill: var(--illo-paper); }
4018
-
4019
- /* The figure in a whole-surface `Empty`, drawn in the muted grey of the
4020
- sentence under it.
4021
- *
4022
- * A utility cannot do this, and the reason is worth keeping: `.illo` sets
4023
- * `color: inherit` and is unlayered, while every Tailwind utility lives in
4024
- * `@layer utilities` -- and an unlayered declaration beats a layered one
4025
- * whatever the specificity. So `text-muted` on the figure lost, silently,
4026
- * and the shirt came out full text black beside a muted sentence. The
4027
- * original rule already carried a note about needing two classes to beat
4028
- * `.illo`; layers make that impossible rather than merely fiddly. */
4029
- .nothing-figure { color: var(--muted); }
4073
+ }
4030
4074
 
4031
4075
  /* ---- ./field.css ---- */
4076
+ @layer components {
4032
4077
  /* fields --------------------------------------------------------------- */
4033
4078
 
4034
4079
  /* Label, description, input, error -- in the order they are wanted. The
@@ -4036,7 +4081,9 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4036
4081
  to what was typed, so it goes after. */
4037
4082
  .field { display: grid; gap: var(--space-1); }
4038
4083
 
4039
- .field-stack-layout { /* the default: label, description, control, error */ }
4084
+ /* `.field-stack-layout` carries no rules: stacking is what `.field` already
4085
+ does, and the class is emitted only so that both layouts name themselves
4086
+ the same way (`field-${layout}-layout`). Only the row layout overrides. */
4040
4087
 
4041
4088
  .field-row-layout > .field-label { grid-column: 1; }
4042
4089
 
@@ -4057,8 +4104,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4057
4104
 
4058
4105
  /* Not red. The asterisk marks which fields are required; colouring it the same
4059
4106
  as a failure says something has already gone wrong with every one of them. */
4060
- .field-required { color: var(--muted); }
4061
-
4062
4107
  .field-hint,
4063
4108
  .field-error { margin: 0; font-size: var(--text-sm); line-height: 1.45; }
4064
4109
 
@@ -4080,8 +4125,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4080
4125
  /* Fields stacked down a form. Wider than the gap inside one field, so the
4081
4126
  label of the next belongs to the input under it rather than to the error
4082
4127
  above it. */
4083
- .field-stack { display: grid; gap: var(--space-4); }
4084
-
4085
4128
  .field-row > .field { flex: 1; min-width: 0; }
4086
4129
 
4087
4130
  /* A password with an eye -- `Input type="password"`.
@@ -4203,14 +4246,12 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4203
4246
  background: var(--panel-2);
4204
4247
  color: var(--text);
4205
4248
  }
4249
+ }
4206
4250
 
4207
4251
  /* ---- ./tour.css ---- */
4252
+ @layer components {
4208
4253
  /* tour ----------------------------------------------------------------- */
4209
4254
 
4210
- /* The layer sits over everything and catches nothing: the scrim is painted by
4211
- the hole's shadow, so there is no full-screen element to swallow a click. */
4212
- .tour { position: fixed; inset: 0; z-index: 60; pointer-events: none; }
4213
-
4214
4255
  /* The control the tour is currently pointing at.
4215
4256
 
4216
4257
  Much of this chrome is invisible until you touch it -- `.view-add` lives at
@@ -4221,11 +4262,14 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4221
4262
  what every control does at rest, and the alternative is naming each one here
4222
4263
  and watching that list drift from the rules that hide them. One declaration
4223
4264
  that outranks whatever the resting state is beats an enumeration that goes
4224
- stale. It is scoped to a class only ever present on one element at a time. */
4225
- .tour-target {
4226
- opacity: 1 !important;
4227
- color: var(--text) !important;
4228
- }
4265
+ stale. It is scoped to a class only ever present on one element at a time.
4266
+
4267
+ Opacity and nothing else. This forced `color: var(--text)` too, from the
4268
+ original port, with no case on record that needed it -- and it broke the
4269
+ first target that painted its own background, turning a primary button's
4270
+ label dark on accent at 2.2:1. The spotlight makes a control visible; what
4271
+ colour it is at rest is the control's business. */
4272
+ .tour-target { opacity: 1 !important; }
4229
4273
 
4230
4274
  /* The spotlight. An enormous spread shadow dims the whole window except the
4231
4275
  rectangle it is drawn on, which needs no clip path and no arithmetic. */
@@ -4259,19 +4303,19 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4259
4303
 
4260
4304
  .tour-card:focus { outline: none; }
4261
4305
 
4262
- .tour-title { font-size: var(--text-base); }
4263
-
4264
4306
  .tour-body { margin: 0; color: var(--muted); font-size: var(--text-sm); line-height: 1.5; }
4265
4307
 
4266
4308
  .tour-actions { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-2); }
4267
4309
 
4268
4310
  .tour-count { color: var(--muted); font-size: var(--text-xs); }
4311
+ }
4269
4312
 
4270
4313
  /* The mail and forum shell, added in 0.4.0. After the rest, because the
4271
4314
  anchored surfaces (`.pop`, `.menu-sheet`, `.cmd`) sit over everything and a
4272
4315
  rule of equal specificity that came earlier would lose to whatever the
4273
4316
  sheets they cover happen to set. */
4274
4317
  /* ---- ./stat.css ---- */
4318
+ @layer components {
4275
4319
  /* What the utilities on `Stat` cannot say: a tone on the tile colouring the
4276
4320
  figure inside it. An attribute on the parent selecting a child is a
4277
4321
  relationship; a utility is a property on one element.
@@ -4282,8 +4326,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4282
4326
  .stat[data-tone='good'] .stat-value { color: var(--good); }
4283
4327
  .stat[data-tone='warn'] .stat-value { color: var(--warn); }
4284
4328
  .stat[data-tone='bad'] .stat-value { color: var(--bad); }
4329
+ }
4285
4330
 
4286
4331
  /* ---- ./shell.css ---- */
4332
+ @layer components {
4287
4333
  /* The measure, and only the measure.
4288
4334
  *
4289
4335
  * `Shell` draws itself with utilities -- it is layout and nothing else. What
@@ -4310,8 +4356,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4310
4356
  font-size: var(--text-sm);
4311
4357
  white-space: nowrap;
4312
4358
  }
4359
+ }
4313
4360
 
4314
4361
  /* ---- ./scrollarea.css ---- */
4362
+ @layer components {
4315
4363
  /* A box that scrolls, with its edges drawn.
4316
4364
 
4317
4365
  The fades are `::before`/`::after` on the scroller itself, pinned with
@@ -4404,8 +4452,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4404
4452
  .scroller.faded::before,
4405
4453
  .scroller.faded::after { transition: none; }
4406
4454
  }
4455
+ }
4407
4456
 
4408
4457
  /* ---- ./splitpane.css ---- */
4458
+ @layer components {
4409
4459
  /* Two panes and the handle between them. */
4410
4460
  .split {
4411
4461
  display: flex;
@@ -4424,17 +4474,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4424
4474
  the intrinsic width of a mail list is the longest unbroken subject line in
4425
4475
  it. Without this the handle stops partway across and nothing on screen says
4426
4476
  why -- it just refuses, differently for every mailbox. */
4427
- .split-pane {
4428
- min-width: 0;
4429
- min-height: 0;
4430
- overflow: hidden;
4431
- display: flex;
4432
- flex-direction: column;
4433
- }
4434
-
4435
- .split-first { flex: 0 0 var(--split); }
4436
- .split-second { flex: 1 1 0; }
4437
-
4438
4477
  /* The handle is wider than it looks: a hairline is a correct-looking divider
4439
4478
  and an impossible pointer target. The grip is the line, drawn at
4440
4479
  `--border-width` down the middle of a `--space-3` strip, and the strip is
@@ -4505,8 +4544,10 @@ body.splitting * { cursor: inherit !important; }
4505
4544
  @media (prefers-reduced-motion: reduce) {
4506
4545
  .split-grip { transition: none; }
4507
4546
  }
4547
+ }
4508
4548
 
4509
4549
  /* ---- ./popover.css ---- */
4550
+ @layer components {
4510
4551
  /* A small surface anchored to what opened it. Same sheet as the select's list,
4511
4552
  because they are the same object -- something that appeared over the page,
4512
4553
  attached to a control. */
@@ -4529,13 +4570,6 @@ body.splitting * { cursor: inherit !important; }
4529
4570
  z-index: 60;
4530
4571
  }
4531
4572
 
4532
- .pop-body {
4533
- padding: var(--space-3);
4534
- outline: none;
4535
- color: var(--text);
4536
- font-size: var(--text-sm);
4537
- }
4538
-
4539
4573
  /* Entry, and the direction it comes from. `data-placement` is React Aria's,
4540
4574
  set to where the surface actually went rather than where it was asked to go,
4541
4575
  so the movement agrees with the flip. */
@@ -4560,8 +4594,10 @@ body.splitting * { cursor: inherit !important; }
4560
4594
  .pop[data-entering],
4561
4595
  .pop[data-exiting] { animation: none; }
4562
4596
  }
4597
+ }
4563
4598
 
4564
4599
  /* ---- ./menu.css ---- */
4600
+ @layer components {
4565
4601
  /* What is left once `Menu` draws its sheet and its list: the rows, whose
4566
4602
  `data-focused`, `data-pressed` and danger states React Aria stamps on
4567
4603
  them, and the rules that reach from a row into what it holds. The
@@ -4594,8 +4630,6 @@ body.splitting * { cursor: inherit !important; }
4594
4630
  .menu-icon { flex: none; width: var(--space-4); height: var(--space-4); color: var(--muted); }
4595
4631
  .menu-item[data-focused] .menu-icon { color: var(--text); }
4596
4632
 
4597
- .menu-text { flex: 1 1 auto; min-width: 0; display: grid; }
4598
- .menu-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4599
4633
  .menu-desc {
4600
4634
  font-size: var(--text-xs);
4601
4635
  color: var(--muted);
@@ -4643,8 +4677,10 @@ body.splitting * { cursor: inherit !important; }
4643
4677
  }
4644
4678
 
4645
4679
  .menu-sheet { box-shadow: var(--shadow-2); }
4680
+ }
4646
4681
 
4647
4682
  /* ---- ./command.css ---- */
4683
+ @layer components {
4648
4684
  /* The command palette: a field over the app and a list under it. */
4649
4685
  .cmd-scrim {
4650
4686
  position: fixed;
@@ -4672,18 +4708,6 @@ body.splitting * { cursor: inherit !important; }
4672
4708
  flex-direction: column;
4673
4709
  }
4674
4710
 
4675
- .cmd-body { outline: none; display: flex; flex-direction: column; min-height: 0; }
4676
-
4677
- .cmd-field {
4678
- display: flex;
4679
- align-items: center;
4680
- gap: var(--space-2);
4681
- padding: var(--space-3) var(--space-4);
4682
- border-bottom: var(--border-width) solid var(--border);
4683
- }
4684
-
4685
- .cmd-mark { flex: none; width: var(--space-4); height: var(--space-4); color: var(--muted); }
4686
-
4687
4711
  /* No border, no focus ring on the input itself: the whole surface is the
4688
4712
  field, focus is already in it when it opens, and a ring around a box that
4689
4713
  fills the top of the palette is a ring around the palette. */
@@ -4700,22 +4724,6 @@ body.splitting * { cursor: inherit !important; }
4700
4724
 
4701
4725
  .cmd-input::placeholder { color: var(--muted); }
4702
4726
 
4703
- .cmd-list {
4704
- padding: var(--space-2);
4705
- overflow: auto;
4706
- overscroll-behavior: contain;
4707
- outline: none;
4708
- min-height: 0;
4709
- }
4710
-
4711
- .cmd-group-title {
4712
- padding: var(--space-2) var(--space-2) var(--space-1);
4713
- font-size: var(--text-2xs);
4714
- text-transform: uppercase;
4715
- letter-spacing: var(--tracking);
4716
- color: var(--muted);
4717
- }
4718
-
4719
4727
  .cmd-item {
4720
4728
  display: flex;
4721
4729
  align-items: center;
@@ -4751,8 +4759,6 @@ body.splitting * { cursor: inherit !important; }
4751
4759
  .cmd-icon { flex: none; width: var(--space-4); height: var(--space-4); color: var(--muted); }
4752
4760
  .cmd-item[data-focused] .cmd-icon { color: var(--text); }
4753
4761
 
4754
- .cmd-text { flex: 1 1 auto; min-width: 0; display: grid; }
4755
- .cmd-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4756
4762
  .cmd-desc {
4757
4763
  font-size: var(--text-xs);
4758
4764
  color: var(--muted);
@@ -4786,8 +4792,10 @@ body.splitting * { cursor: inherit !important; }
4786
4792
  .cmd-scrim[data-exiting],
4787
4793
  .cmd[data-entering] { animation: none; }
4788
4794
  }
4795
+ }
4789
4796
 
4790
4797
  /* ---- ./pagination.css ---- */
4798
+ @layer components {
4791
4799
  /* What is left once `Pagination` draws its own frame: the step and page
4792
4800
  buttons, whose hover, focus-visible and current states are pseudo-classes
4793
4801
  on two selectors at once, and `.pager-count strong`, which reaches into
@@ -4906,8 +4914,10 @@ body.splitting * { cursor: inherit !important; }
4906
4914
  .pager-page,
4907
4915
  .pager-jump { transition: none; }
4908
4916
  }
4917
+ }
4909
4918
 
4910
4919
  /* ---- ./identity.css ---- */
4920
+ @layer components {
4911
4921
  /* A person, said in one line: initials, name, sometimes the address. */
4912
4922
  .ident {
4913
4923
  display: inline-flex;
@@ -4950,25 +4960,6 @@ body.splitting * { cursor: inherit !important; }
4950
4960
  font-size: var(--text-2xs);
4951
4961
  }
4952
4962
 
4953
- .ident-text { display: grid; min-width: 0; }
4954
-
4955
- .ident-name {
4956
- overflow: hidden;
4957
- text-overflow: ellipsis;
4958
- white-space: nowrap;
4959
- font-weight: var(--weight);
4960
- }
4961
-
4962
- .ident-address {
4963
- overflow: hidden;
4964
- text-overflow: ellipsis;
4965
- white-space: nowrap;
4966
- font-size: var(--text-xs);
4967
- color: var(--muted);
4968
- }
4969
-
4970
- .ident-aside { flex: none; color: var(--muted); font-size: var(--text-xs); }
4971
-
4972
4963
  /* A chip in a composer's To field: boxed, tighter, removable. */
4973
4964
  .ident.kind-chip {
4974
4965
  gap: var(--space-1);
@@ -5008,6 +4999,7 @@ body.splitting * { cursor: inherit !important; }
5008
4999
 
5009
5000
  .ident-remove:hover { background: var(--panel); color: var(--text); }
5010
5001
  .ident-remove:focus-visible { outline: none; box-shadow: var(--focus-ring); }
5002
+ }
5011
5003
  /* ---- tf: the themes ---- */
5012
5004
  :root[data-theme='night']{--bg:#0f1115;--panel:#161a21;--panel-2:#1c222b;--border:#262d38;--text:#e6e9ef;--muted:#8b94a4;--accent:#5b9dff;--accent-dim:#2a4877;--app-bg:#0f1115;color-scheme:dark}
5013
5005
  :root[data-theme='paper']{--bg:#f6f7f9;--panel:#ffffff;--panel-2:#f0f2f5;--border:#e4e8ec;--border-strong:#8792a1;--text:#191d23;--muted:#5d6773;--accent:#0e7872;--accent-dim:#7fbdb8;--on-accent:#ffffff;--good:#1c7a4a;--warn:#8a6216;--bad:#b3312c;--info:#216bc9;--shadow-1:0 4px 14px rgba(16, 24, 40, 0.08);--shadow-2:0 8px 24px rgba(16, 24, 40, 0.10);--shadow-3:0 12px 32px rgba(16, 24, 40, 0.12);--scrim:rgba(16, 24, 40, 0.32);color-scheme:light}