@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/valet.css CHANGED
@@ -291,12 +291,9 @@
291
291
  --font-sans: var(--font-sans);
292
292
  --font-mono: var(--font-mono);
293
293
  --text-xs: var(--text-xs);
294
- --text-xs--line-height: inherit;
295
294
  --text-sm: var(--text-sm);
296
- --text-sm--line-height: inherit;
297
295
  --text-base: var(--text-base);
298
296
  --text-lg: var(--text-lg);
299
- --text-lg--line-height: inherit;
300
297
  --text-xl: var(--text-xl);
301
298
  --font-weight-normal: var(--weight);
302
299
  --tracking-normal: var(--tracking);
@@ -311,19 +308,20 @@
311
308
  --spacing-5: var(--space-5);
312
309
  --spacing-6: var(--space-6);
313
310
  --spacing-10: var(--space-10);
314
- --text-2xs--line-height: inherit;
315
311
  --text-2xs: var(--text-2xs);
316
312
  --text-md: var(--text-md);
317
313
  --font-weight-strong: var(--weight-strong);
318
314
  --radius-pill: var(--radius-pill);
319
315
  --color-text: var(--text);
320
316
  --color-muted: var(--muted);
321
- --color-accent: var(--accent);
322
317
  --color-border: var(--border);
323
318
  --color-border-strong: var(--border-strong);
324
319
  }
325
320
  }
326
321
  @layer utilities {
322
+ .pointer-events-none {
323
+ pointer-events: none;
324
+ }
327
325
  .collapse {
328
326
  visibility: collapse;
329
327
  }
@@ -356,6 +354,9 @@
356
354
  .sticky {
357
355
  position: sticky;
358
356
  }
357
+ .inset-0 {
358
+ inset: 0px;
359
+ }
359
360
  .z-\[60\] {
360
361
  z-index: 60;
361
362
  }
@@ -389,12 +390,21 @@
389
390
  .mt-1 {
390
391
  margin-top: var(--spacing-1);
391
392
  }
393
+ .mt-2 {
394
+ margin-top: var(--spacing-2);
395
+ }
396
+ .mt-px {
397
+ margin-top: 1px;
398
+ }
392
399
  .mb-3 {
393
400
  margin-bottom: var(--spacing-3);
394
401
  }
395
402
  .ml-2 {
396
403
  margin-left: var(--spacing-2);
397
404
  }
405
+ .ml-auto {
406
+ margin-left: auto;
407
+ }
398
408
  .block {
399
409
  display: block;
400
410
  }
@@ -419,6 +429,9 @@
419
429
  .table {
420
430
  display: table;
421
431
  }
432
+ .h-4 {
433
+ height: var(--spacing-4);
434
+ }
422
435
  .h-\[14px\] {
423
436
  height: 14px;
424
437
  }
@@ -437,6 +450,9 @@
437
450
  .min-h-screen {
438
451
  min-height: 100vh;
439
452
  }
453
+ .w-4 {
454
+ width: var(--spacing-4);
455
+ }
440
456
  .w-\[18px\] {
441
457
  width: 18px;
442
458
  }
@@ -464,6 +480,18 @@
464
480
  .flex-1 {
465
481
  flex: 1;
466
482
  }
483
+ .flex-\[0_0_var\(--split\)\] {
484
+ flex: 0 0 var(--split);
485
+ }
486
+ .flex-\[1_1_0\] {
487
+ flex: 1 1 0;
488
+ }
489
+ .flex-\[1_1_260px\] {
490
+ flex: 1 1 260px;
491
+ }
492
+ .flex-\[1_1_auto\] {
493
+ flex: 1 1 auto;
494
+ }
467
495
  .flex-none {
468
496
  flex: none;
469
497
  }
@@ -494,6 +522,9 @@
494
522
  .flex-wrap {
495
523
  flex-wrap: wrap;
496
524
  }
525
+ .place-items-center {
526
+ place-items: center;
527
+ }
497
528
  .items-baseline {
498
529
  align-items: baseline;
499
530
  }
@@ -527,6 +558,9 @@
527
558
  .gap-\[var\(--border-width\)\] {
528
559
  gap: var(--border-width);
529
560
  }
561
+ .gap-px {
562
+ gap: 1px;
563
+ }
530
564
  .self-center {
531
565
  align-self: center;
532
566
  }
@@ -574,9 +608,6 @@
574
608
  --tw-border-style: dashed;
575
609
  border-style: dashed;
576
610
  }
577
- .border-accent {
578
- border-color: var(--color-accent);
579
- }
580
611
  .border-border {
581
612
  border-color: var(--color-border);
582
613
  }
@@ -592,9 +623,15 @@
592
623
  .p-2 {
593
624
  padding: var(--spacing-2);
594
625
  }
626
+ .p-3 {
627
+ padding: var(--spacing-3);
628
+ }
595
629
  .p-4 {
596
630
  padding: var(--spacing-4);
597
631
  }
632
+ .px-2 {
633
+ padding-inline: var(--spacing-2);
634
+ }
598
635
  .px-4 {
599
636
  padding-inline: var(--spacing-4);
600
637
  }
@@ -613,6 +650,12 @@
613
650
  .py-10 {
614
651
  padding-block: var(--spacing-10);
615
652
  }
653
+ .pt-2 {
654
+ padding-top: var(--spacing-2);
655
+ }
656
+ .pb-1 {
657
+ padding-bottom: var(--spacing-1);
658
+ }
616
659
  .pb-2 {
617
660
  padding-bottom: var(--spacing-2);
618
661
  }
@@ -627,19 +670,18 @@
627
670
  }
628
671
  .text-2xs {
629
672
  font-size: var(--text-2xs);
630
- line-height: var(--tw-leading, var(--text-2xs--line-height));
673
+ }
674
+ .text-base {
675
+ font-size: var(--text-base);
631
676
  }
632
677
  .text-lg {
633
678
  font-size: var(--text-lg);
634
- line-height: var(--tw-leading, var(--text-lg--line-height));
635
679
  }
636
680
  .text-sm {
637
681
  font-size: var(--text-sm);
638
- line-height: var(--tw-leading, var(--text-sm--line-height));
639
682
  }
640
683
  .text-xs {
641
684
  font-size: var(--text-xs);
642
- line-height: var(--tw-leading, var(--text-xs--line-height));
643
685
  }
644
686
  .leading-\[0\] {
645
687
  --tw-leading: 0;
@@ -653,6 +695,10 @@
653
695
  --tw-leading: 1.25;
654
696
  line-height: 1.25;
655
697
  }
698
+ .leading-\[1\.35\] {
699
+ --tw-leading: 1.35;
700
+ line-height: 1.35;
701
+ }
656
702
  .leading-\[16px\] {
657
703
  --tw-leading: 16px;
658
704
  line-height: 16px;
@@ -694,12 +740,12 @@
694
740
  --tw-numeric-spacing: tabular-nums;
695
741
  font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
696
742
  }
697
- .opacity-0 {
698
- opacity: 0%;
699
- }
700
743
  .opacity-60 {
701
744
  opacity: 60%;
702
745
  }
746
+ .opacity-90 {
747
+ opacity: 90%;
748
+ }
703
749
  .opacity-\[0\.55\] {
704
750
  opacity: 0.55;
705
751
  }
@@ -739,9 +785,6 @@
739
785
  .surface-panel {
740
786
  background: var(--panel);
741
787
  }
742
- .surface-panel-2 {
743
- background: var(--panel-2);
744
- }
745
788
  .disabled\:cursor-default:disabled {
746
789
  cursor: default;
747
790
  }
@@ -981,7 +1024,19 @@
981
1024
  }
982
1025
  }
983
1026
 
984
- /* ---- ./base.css ---- */
1027
+ /* ---- ./_system-light.css ---- */
1028
+ /* The `system` theme's light half.
1029
+ *
1030
+ * Its own file, and outside the components layer, because it is a *theme*
1031
+ * rather than a component rule. `tokens.css` states the dark base on `:root`
1032
+ * and is unlayered; the moment this sat inside `@layer components` it lost to
1033
+ * that, and every `system` specimen rendered dark on a light-mode machine.
1034
+ * Unlayered beats layered whatever the selector says, which is exactly the
1035
+ * trap this repository keeps finding in different costumes.
1036
+ *
1037
+ * The underscore marks it the same way `_tailwind.built.css` is marked:
1038
+ * `copy-css.mjs` passes both through without wrapping them.
1039
+ */
985
1040
  /* Light, only under the `system` theme. This used to override `:root`
986
1041
  unconditionally, so choosing a dark theme on a light-mode laptop got
987
1042
  silently repainted. Every other theme states its own palette. */
@@ -1010,6 +1065,8 @@
1010
1065
  }
1011
1066
  }
1012
1067
 
1068
+ /* ---- ./base.css ---- */
1069
+ @layer components {
1013
1070
  /* Derived from the two surfaces every theme already defines, so a theme gets a
1014
1071
  control colour without knowing this exists. */
1015
1072
  /* Pseudo-elements too. `*` does not match them, and `box-sizing` is not
@@ -1727,23 +1784,6 @@ input:active, textarea:active, select:active { transform: none; }
1727
1784
  template are allowed to diverge, and this is only saying that they have. */
1728
1785
  .set-hint.drifted { color: var(--warn); }
1729
1786
 
1730
- /* What an applet drew, read back. A tree rather than prose, so it is left as
1731
- preformatted text and given a ceiling -- a busy applet is a hundred lines and
1732
- this sits inside a settings pane. */
1733
- .drawn {
1734
- margin: var(--space-2) 0 0;
1735
- padding: var(--space-2) var(--space-2);
1736
- max-height: 220px;
1737
- overflow: auto;
1738
- font-size: var(--text-xs);
1739
- line-height: 1.45;
1740
- white-space: pre-wrap;
1741
- overflow-wrap: anywhere;
1742
- border: 1px solid var(--border);
1743
- border-radius: var(--radius);
1744
- background: var(--panel-2);
1745
- }
1746
-
1747
1787
  /* One applet, alone on the page, at `/applet/<id>`. Fills the window: there is
1748
1788
  no grid here to decide a height, and a screenshot of an applet letterboxed
1749
1789
  inside a page is mostly not the applet. */
@@ -1790,11 +1830,6 @@ input:active, textarea:active, select:active { transform: none; }
1790
1830
  that belongs to a conversation lives here any more, so it can breathe. */
1791
1831
  .topbar .ctl-grow { flex: 1; min-width: 0; }
1792
1832
 
1793
- /* Back on the left, Next on the right, with the gap between them doing the
1794
- separating -- so the button that goes forward is in the same place on both
1795
- pages instead of sliding when Back appears. */
1796
- .first-run-actions { align-items: center; }
1797
-
1798
1833
  @keyframes listening-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .35 } }
1799
1834
 
1800
1835
  /* table ---------------------------------------------------------------- */
@@ -1870,8 +1905,10 @@ input:active, textarea:active, select:active { transform: none; }
1870
1905
  color: var(--muted);
1871
1906
  opacity: 1;
1872
1907
  }
1908
+ }
1873
1909
 
1874
1910
  /* ---- ./select.css ---- */
1911
+ @layer components {
1875
1912
  /* How big a control is, asked for rather than inherited.
1876
1913
  Four sizes existed and every one of them came from a descendant selector:
1877
1914
  `.set-row button` was 11px in 3px of padding, `.approval-actions button` was
@@ -1966,16 +2003,19 @@ input:active, textarea:active, select:active { transform: none; }
1966
2003
  /* State is read off React Aria's attributes: `data-open` on the root,
1967
2004
  `data-focused` / `data-selected` / `data-disabled` on an item, where
1968
2005
  `.open`, `[data-active]`, `.on` and `.off` used to be. */
1969
- .sel-list { scroll-padding-block: var(--space-1); box-shadow: var(--shadow-2); })` brings up ended flush against the
1970
- border and read as clipped rather than as one of a set -- and this list
1971
- opens scrolled to whatever is chosen, so that was the ordinary state.
1972
- `scroll-padding` insets what counts as "in view", so it stops short by
1973
- the same four the sides have. */
2006
+ /* The list keeps two things the utilities on `Select` cannot say.
2007
+ *
2008
+ * `scroll-padding-block`: the same room while the list is scrolled as it has
2009
+ * at rest. Padding at the top of a scroll container scrolls away with the
2010
+ * content, so the row `scrollIntoView({block: 'nearest'})` brings up ended
2011
+ * flush against the border and read as clipped rather than as one of a set --
2012
+ * and this list opens scrolled to whatever is chosen, so that was the
2013
+ * ordinary state.
2014
+ *
2015
+ * `box-shadow`: `--shadow-2` is a token, and a utility would have to inline
2016
+ * the value, which is the one thing the vocabulary exists to stop. */
2017
+ .sel-list {
1974
2018
  scroll-padding-block: var(--space-1);
1975
- list-style: none;
1976
- border: var(--border-width) solid var(--border-strong);
1977
- border-radius: var(--radius-md);
1978
- background: var(--panel);
1979
2019
  box-shadow: var(--shadow-2);
1980
2020
  }
1981
2021
 
@@ -1990,6 +2030,19 @@ input:active, textarea:active, select:active { transform: none; }
1990
2030
 
1991
2031
  /* The tick marks the chosen row. Colour alone would not: a list where the only
1992
2032
  difference is a hue is a list you cannot read past. */
2033
+ /* The tick's resting state lives here, beside the state that overrides it.
2034
+ *
2035
+ * It was `opacity-0` on the element and `opacity: 1` here, which worked only
2036
+ * while this sheet was unlayered. A utility is in `@layer utilities` and
2037
+ * beats `@layer components` whatever the selector, so the moment the sheets
2038
+ * moved into a layer the tick stopped appearing on the selected row -- the
2039
+ * resting state won permanently.
2040
+ *
2041
+ * The rule generalises: a utility for a resting state whose active state is a
2042
+ * relationship between elements will always beat that active state. The pair
2043
+ * has to sit in the same layer. */
2044
+ .sel-tick { opacity: 0; }
2045
+
1993
2046
  .sel-item[data-selected] .sel-tick { opacity: 1; }
1994
2047
 
1995
2048
  /* The icon on a speaker's row in the open list -- `Select`'s `aside`. Last in
@@ -2024,8 +2077,10 @@ input:active, textarea:active, select:active { transform: none; }
2024
2077
  .field > input,
2025
2078
  .field > textarea,
2026
2079
  .field > .sel { width: 100%; }
2080
+ }
2027
2081
 
2028
2082
  /* ---- ./dangerzone.css ---- */
2083
+ @layer components {
2029
2084
  /* Destructive, and legible as such standing still. */
2030
2085
  .btn.danger {
2031
2086
  color: var(--bad);
@@ -2115,8 +2170,6 @@ input:active, textarea:active, select:active { transform: none; }
2115
2170
  border-radius: 4px;
2116
2171
  }
2117
2172
 
2118
- .danger-unavailable { margin-top: var(--space-2); opacity: 0.85; }
2119
-
2120
2173
  /* Label beside the control, for a settings pane: a column of names on the left
2121
2174
  and their controls on the right reads as a list of what is set, where the
2122
2175
  same rows stacked read as a form waiting to be filled in. The description
@@ -2137,8 +2190,10 @@ input:active, textarea:active, select:active { transform: none; }
2137
2190
  controls in the same column at the same indent that do not line up read as
2138
2191
  an accident rather than as a pair. */
2139
2192
  .field-row > button { min-width: 9ch; }
2193
+ }
2140
2194
 
2141
2195
  /* ---- ./callout.css ---- */
2196
+ @layer components {
2142
2197
  /* One shape for everything that tells you something.
2143
2198
  `.banner` and `.callout` were the same box drawn twice: a bordered, rounded,
2144
2199
  12px panel with a tint. They differed in padding by a pixel in each direction
@@ -2192,8 +2247,6 @@ input:active, textarea:active, select:active { transform: none; }
2192
2247
  /* A callout that leaves on its own.
2193
2248
  Room at the bottom for the bar, and `position: relative` so the bar can sit
2194
2249
  on the border rather than inside the text. */
2195
- .callout-timed { position: relative; padding-bottom: var(--space-5); overflow: hidden; }
2196
-
2197
2250
  /* The countdown. Drawn as a shrinking bar rather than a number: nobody wants to
2198
2251
  read a clock, they want to know whether they have time. Animated width rather
2199
2252
  than a transition, so it starts the moment it is painted. */
@@ -2254,14 +2307,6 @@ input:active, textarea:active, select:active { transform: none; }
2254
2307
  is the operating system's font drawing a glyph beside an app that has an icon
2255
2308
  set for exactly this. The ring went with it: a circle around a warning
2256
2309
  triangle is two shapes saying one thing. */
2257
- .callout-mark {
2258
- flex: none;
2259
- display: grid;
2260
- place-items: center;
2261
- margin-top: 1px;
2262
- opacity: 0.9;
2263
- }
2264
-
2265
2310
  /* Held-back history when a local conversation would cross to a hosted model.
2266
2311
  Deliberately loud: the cost of missing it is uploading work that was kept
2267
2312
  local on purpose. */
@@ -2277,8 +2322,23 @@ input:active, textarea:active, select:active { transform: none; }
2277
2322
  }
2278
2323
 
2279
2324
  .callout button { flex: none; }
2325
+ /* A state, so it stays a class.
2326
+ *
2327
+ * `Callout` applies this as `running ? ' callout-timed' : ''` inside a
2328
+ * template literal -- which is not a class list a rewrite can read, and the
2329
+ * first pass at converting it deleted the rule while leaving the usage. The
2330
+ * box lost five spacing steps of padding and the countdown bar had nothing
2331
+ * to sit in. A class that appears only inside a conditional is a state; the
2332
+ * sheet is where those belong. */
2333
+ .callout-timed {
2334
+ position: relative;
2335
+ padding-bottom: var(--space-5);
2336
+ overflow: hidden;
2337
+ }
2338
+ }
2280
2339
 
2281
2340
  /* ---- ./card.css ---- */
2341
+ @layer components {
2282
2342
  /* `.card` is public API, not this component's private styling.
2283
2343
  *
2284
2344
  * Consumers apply it to their own markup -- the gallery's skeleton specimen
@@ -2333,16 +2393,6 @@ input:active, textarea:active, select:active { transform: none; }
2333
2393
  which reads as a different column of content. */
2334
2394
  .card:has(.card-icon) .card-inline { padding-left: calc(var(--card-icon) + var(--space-3)); }
2335
2395
 
2336
- /* Its own line, above the sentence it introduces. */
2337
- .card-inline-label {
2338
- display: block;
2339
- font-size: var(--text-xs);
2340
- text-transform: uppercase;
2341
- letter-spacing: 0.04em;
2342
- color: var(--muted);
2343
- margin-bottom: 2px;
2344
- }
2345
-
2346
2396
  .card-title { display: block; font-size: var(--text-base); }
2347
2397
 
2348
2398
  .card-desc { margin-top: var(--space-1); color: var(--muted); font-size: var(--text-sm); line-height: 1.5; }
@@ -2420,8 +2470,10 @@ input:active, textarea:active, select:active { transform: none; }
2420
2470
  }
2421
2471
 
2422
2472
  .card[aria-pressed='true']:hover { border-color: var(--accent); }
2473
+ }
2423
2474
 
2424
2475
  /* ---- ./toggle.css ---- */
2476
+ @layer components {
2425
2477
  .card-action .switch-row { padding: 0; gap: var(--space-2); }
2426
2478
 
2427
2479
  .card-action .switch-row .switch-body { flex: none; }
@@ -2478,12 +2530,8 @@ input:active, textarea:active, select:active { transform: none; }
2478
2530
  tooltip ended up orphaned under it. */
2479
2531
  .set-row > .switch-row { flex: 1; min-width: 0; padding-block: 0; }
2480
2532
 
2481
- .switch-label { color: var(--text); }
2482
-
2483
2533
  .switch-hint { font-size: var(--text-sm); color: var(--muted); }
2484
2534
 
2485
- .switch-said { flex: none; font-size: var(--text-xs); color: var(--muted); }
2486
-
2487
2535
  /* Three sizes, the same names every other control uses. The switch scales with
2488
2536
  the row: a 30x18 track beside 12px text is a different control from the same
2489
2537
  track beside 16px text. */
@@ -2652,19 +2700,10 @@ input[type='radio']:active:not(:disabled) {
2652
2700
  @media (prefers-reduced-motion: reduce) {
2653
2701
  .toggle-sweep::before { animation: none; background: color-mix(in oklab, var(--accent) 45%, transparent); }
2654
2702
  }
2655
-
2656
- /* ---- ./skeleton.css ---- */
2657
- .muted { color: var(--muted); }
2658
-
2659
- .pulse-block {
2660
- display: inline-block;
2661
- height: 8px;
2662
- border-radius: var(--radius-sm);
2663
- background: var(--muted);
2664
- animation: ds-pulse 1.4s ease-in-out infinite;
2665
- vertical-align: middle;
2666
2703
  }
2667
2704
 
2705
+ /* ---- ./skeleton.css ---- */
2706
+ @layer components {
2668
2707
  /* skeletons ------------------------------------------------------------ */
2669
2708
 
2670
2709
  /* Ink at low alpha, not a named surface.
@@ -2708,8 +2747,10 @@ input[type='radio']:active:not(:disabled) {
2708
2747
  /* Still distinguishable from a real block of colour, and still still. */
2709
2748
  .skel-pulse { animation: none; opacity: 0.8; }
2710
2749
  }
2750
+ }
2711
2751
 
2712
2752
  /* ---- ./rows.css ---- */
2753
+ @layer components {
2713
2754
  .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2714
2755
 
2715
2756
  /* A name and the pills that qualify it, on one line, where the name is the only
@@ -2921,8 +2962,6 @@ input[type='radio']:active:not(:disabled) {
2921
2962
 
2922
2963
  .rows-split:first-of-type { margin-top: var(--space-2); }
2923
2964
 
2924
- .rows-empty { padding: var(--space-3) 0; }
2925
-
2926
2965
  /* The name, the pills and the line underneath. `min-width: 0` is the whole
2927
2966
  reason this is a class: a flex item will not shrink below its content, so
2928
2967
  without it the name pushes the pills off the end -- and the pills are the
@@ -2939,8 +2978,6 @@ input[type='radio']:active:not(:disabled) {
2939
2978
 
2940
2979
  /* The aligned column. `margin-left: auto` rather than a grid, because the rows
2941
2980
  are flex and a size that varies by two characters does not need a track. */
2942
- .row-trail { flex: none; margin-left: auto; white-space: nowrap; }
2943
-
2944
2981
  .rows-row.rows-top { align-items: flex-start; }
2945
2982
 
2946
2983
  .rows-row.rows-bad .truncate { color: var(--bad); }
@@ -2970,8 +3007,10 @@ input[type='radio']:active:not(:disabled) {
2970
3007
  .row-hit:hover .truncate { color: var(--accent); }
2971
3008
 
2972
3009
  .row-hit:focus-visible { outline: none; box-shadow: var(--focus-ring); }
3010
+ }
2973
3011
 
2974
3012
  /* ---- ./progress.css ---- */
3013
+ @layer components {
2975
3014
  /* progress ----------------------------------------------------------- */
2976
3015
 
2977
3016
  /* The bar itself: a track with a child it fills, an animation and a
@@ -3013,8 +3052,10 @@ input[type='radio']:active:not(:disabled) {
3013
3052
  /* Still distinguishable from empty, and from full. */
3014
3053
  .bar-indeterminate > i { animation: none; width: 100%; opacity: 0.35; }
3015
3054
  }
3055
+ }
3016
3056
 
3017
3057
  /* ---- ./tabs.css ---- */
3058
+ @layer components {
3018
3059
  /* ---------------------------------------------------------------- tabs */
3019
3060
  .tabs { position: relative; display: flex; gap: var(--space-1); }
3020
3061
 
@@ -3082,14 +3123,6 @@ input[type='radio']:active:not(:disabled) {
3082
3123
 
3083
3124
  .tabs-vertical .tab.on .tab-label { color: var(--accent); }
3084
3125
 
3085
- .tab-hint {
3086
- display: block;
3087
- margin-top: 1px;
3088
- font-size: var(--text-xs);
3089
- color: var(--muted);
3090
- line-height: 1.35;
3091
- }
3092
-
3093
3126
  /* A heading above a run of tabs. Set apart from the run above by space, not
3094
3127
  a rule -- the rail already has one edge -- and the tabs under it step in
3095
3128
  by the label's own padding so the label reads as theirs. */
@@ -3100,8 +3133,6 @@ input[type='radio']:active:not(:disabled) {
3100
3133
 
3101
3134
  .tabs-vertical .tab-group:first-child { padding-top: var(--space-2); }
3102
3135
 
3103
- .tab-group-label { display: block; font-size: var(--text-sm); font-weight: 600; color: var(--text); }
3104
-
3105
3136
  .tabs-vertical .tab.in-group { padding-left: var(--space-5); }
3106
3137
 
3107
3138
  /* No line under a vertical strip -- its edge is the border on the right. */
@@ -3115,8 +3146,10 @@ input[type='radio']:active:not(:disabled) {
3115
3146
  }
3116
3147
 
3117
3148
  .tabs-vertical .tab:active:not(:disabled) { border-color: transparent; }
3149
+ }
3118
3150
 
3119
3151
  /* ---- ./toast.css ---- */
3152
+ @layer components {
3120
3153
  /* ---------------------------------------------------------------- toasts */
3121
3154
  .toasts {
3122
3155
  position: fixed;
@@ -3214,8 +3247,10 @@ input[type='radio']:active:not(:disabled) {
3214
3247
  outline: 2px solid var(--accent);
3215
3248
  outline-offset: 2px;
3216
3249
  }
3250
+ }
3217
3251
 
3218
3252
  /* ---- ./modal.css ---- */
3253
+ @layer components {
3219
3254
  /* modal --------------------------------------------------------------- */
3220
3255
 
3221
3256
  /* `hidden` needs saying out loud here. The browser's own `[hidden]` rule is a
@@ -3307,8 +3342,6 @@ input[type='radio']:active:not(:disabled) {
3307
3342
  `<strong>` rule above is the one that sets the size. */
3308
3343
  /* Under the title, on the header's padding rather than the body's, so it reads
3309
3344
  as part of the heading rather than as the first line of content. */
3310
- .modal-desc { line-height: 1.5; }
3311
-
3312
3345
  .modal-body { padding: var(--space-2) var(--space-4) var(--space-3); overflow-y: auto; }
3313
3346
 
3314
3347
  /* The close cross. Its rule stayed behind in tf's stylesheet when the split
@@ -3446,8 +3479,10 @@ input[type='radio']:active:not(:disabled) {
3446
3479
  .modal.sheet[data-entering],
3447
3480
  .modal.sheet[data-exiting] { animation: none; }
3448
3481
  }
3482
+ }
3449
3483
 
3450
3484
  /* ---- ./slider.css ---- */
3485
+ @layer components {
3451
3486
  /* slider ----------------------------------------------------------------- */
3452
3487
  /* A number from a range -- `Slider.tsx` says when one is the right control.
3453
3488
  The row is laid out like a switch row: words on the left, the control on the
@@ -3474,12 +3509,8 @@ input[type='radio']:active:not(:disabled) {
3474
3509
 
3475
3510
  .slider-row.is-disabled { opacity: 0.55; }
3476
3511
 
3477
- .slider-body { flex: 1 1 260px; min-width: 0; display: grid; gap: 1px; }
3478
-
3479
3512
  .set-row > .slider-row { flex: 1; min-width: 0; padding-block: 0; }
3480
3513
 
3481
- .slider-label { color: var(--text); }
3482
-
3483
3514
  .slider-hint { font-size: var(--text-sm); color: var(--muted); }
3484
3515
 
3485
3516
  /* The box, with the value printed inside it at the right where a select
@@ -3669,8 +3700,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3669
3700
  the scale, and the two line up; an explicit `size-*` still wins. */
3670
3701
  :where(.set-row) button,
3671
3702
  :where(.set-row) .slider-box { font-size: var(--text-xs); }
3703
+ }
3672
3704
 
3673
3705
  /* ---- ./checkbox.css ---- */
3706
+ @layer components {
3674
3707
  /* A row you choose. There is no checkbox in it.
3675
3708
  Every one of the fourteen in this app sits beside a name and a line of
3676
3709
  explanation, so the row was always the thing being designed and the 17px
@@ -3703,8 +3736,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3703
3736
  /* A run of checkboxes is one choice, not several settings that happen to be
3704
3737
  adjacent. Tight enough that the group reads as a block -- at the spacing a
3705
3738
  form field wants, four tools look like four unrelated questions. */
3706
- .choice-group { display: grid; gap: var(--space-1); }
3707
-
3708
3739
  /* The same choice, laid out as tiles instead of a column.
3709
3740
 
3710
3741
  Four short names down the full width of a card is four wide bands for four
@@ -3828,8 +3859,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3828
3859
  second line is what the first line is actually asking for. */
3829
3860
  .choice-why { display: block; color: var(--muted); font-size: var(--text-sm); margin-top: 1px; }
3830
3861
 
3831
- .choice-meta { color: var(--muted); font-size: var(--text-xs); font-family: var(--font-mono); }
3832
-
3833
3862
  .choice[data-disabled] {
3834
3863
  cursor: not-allowed;
3835
3864
  border-color: var(--border);
@@ -3840,8 +3869,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3840
3869
  .choice[data-disabled] .choice-why { opacity: 0.5; }
3841
3870
 
3842
3871
  .choice[data-disabled]::after { opacity: 0.4; }
3872
+ }
3843
3873
 
3844
3874
  /* ---- ./table.css ---- */
3875
+ @layer components {
3845
3876
  /* A second line under a row's name, inside the `<th scope="row">` that makes it
3846
3877
  one. The cell is bold because it is a heading; what hangs under it is not,
3847
3878
  and inherited the weight -- so a pinned version read as loud as the thing it
@@ -3889,27 +3920,48 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3889
3920
  .data-table .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
3890
3921
 
3891
3922
  .data-table thead th[style*='right'] { text-align: right; }
3923
+ }
3892
3924
 
3893
3925
  /* ---- ./sizegrid.css ---- */
3894
- /* What is left of the size grid once it draws itself.
3926
+ @layer components {
3927
+ /* The cell's colour, all of it, in one place.
3928
+ *
3929
+ * `SizeGrid` draws the cell's geometry with utilities and leaves every colour
3930
+ * here, because `on` and `chosen` override the resting state -- and a utility
3931
+ * is in `@layer utilities`, which beats `@layer components` whatever the
3932
+ * selector. Stated as `surface-panel-2` and `border-border` on the element,
3933
+ * the resting state won permanently and no cell ever lit up.
3895
3934
  *
3896
- * `on` follows the pointer; `chosen` is what is actually saved, shown only
3897
- * when nothing is hovered so the two can never contradict each other.
3898
- * `chosen` is a border colour and says so at the call site; `on` is a fill
3899
- * mixed from the accent, and `color-mix` in an arbitrary value is less
3900
- * readable than the rule it replaces. */
3935
+ * That is the third time this pattern has bitten: `Select`'s tick and
3936
+ * `Empty`'s figure were the other two. The rule: a resting state whose
3937
+ * active state is a CSS rule has to be a CSS rule too. */
3938
+ .size-cell {
3939
+ border: var(--border-width) solid var(--border);
3940
+ background: var(--panel-2);
3941
+ }
3942
+
3943
+ /* `on` follows the pointer; `chosen` is what is actually saved, shown only
3944
+ when nothing is hovered so the two can never contradict each other. */
3901
3945
  .size-cell.on {
3902
3946
  border-color: var(--accent);
3903
3947
  background: color-mix(in srgb, var(--accent) 45%, transparent);
3904
3948
  }
3905
3949
 
3950
+ .size-cell.chosen { border-color: var(--accent); }
3951
+ }
3952
+
3906
3953
  /* ---- ./tooltip.css ---- */
3954
+ @layer components {
3907
3955
  /* A callout: something worth knowing, with nothing to answer. Distinct from a
3908
3956
  `?`, which is an offer to read more, and from a banner, which is a problem.
3909
3957
  The mark is a round `i` rather than an emoji so it takes the theme's colours
3910
3958
  and stays the same size as the text beside it. */
3911
- /* An info-toned `.callout`, spelled the old way. Identical to it now rather than
3912
- nearly identical to it, which is the whole point of the consolidation: the
3959
+ /* An info-toned `.callout`, spelled the old way. Identical to it now rather
3960
+ than nearly identical to it, which is the whole point of the consolidation.
3961
+ (This comment has been unterminated since before the Tailwind branch -- it
3962
+ stops mid-sentence and never closes, so the CSS parser merged it with the
3963
+ one below and recovered. Nothing was lost from the stylesheet; the rest of
3964
+ the sentence was. Closed here rather than guessed at.) */
3913
3965
 
3914
3966
  /* What is left once `Tooltip` draws its own mark: the hover and focus that
3915
3967
  reach the mark from its parent, and the tip -- whose closed and open
@@ -3992,8 +4044,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3992
4044
  }
3993
4045
 
3994
4046
  .explain-tip strong { color: var(--text); font-weight: 600; }
4047
+ }
3995
4048
 
3996
4049
  /* ---- ./illustration.css ---- */
4050
+ @layer components {
3997
4051
  /* illustrations -------------------------------------------------------- */
3998
4052
 
3999
4053
  /* Open Peeps, two colours, wearing whatever the theme is.
@@ -4001,8 +4055,9 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4001
4055
  reason they can live in an app with four palettes: `ink` becomes the current
4002
4056
  text colour and `paper` becomes the surface behind it, so one file covers
4003
4057
  Night, Paper and both halves of System. Give the wrapper a `color` and
4004
- the figure follows it -- `.muted` for a quiet empty state, `--accent` for the
4005
- one thing on screen worth looking at. */
4058
+ the figure follows it: `--muted` for a quiet empty state, `--accent` for the
4059
+ one thing on screen worth looking at. Set that colour on the wrapper --
4060
+ `Empty` reaches for `text-muted`, and any token works. */
4006
4061
  .illo {
4007
4062
  display: inline-block;
4008
4063
  /* Height is the given dimension and the width comes from the drawing, so a
@@ -4019,20 +4074,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4019
4074
  caller says otherwise. Not `transparent`: these have overlapping shapes, and
4020
4075
  a shirt that lets the arm behind it show through reads as a hole. */
4021
4076
  .illo .paper { fill: var(--illo-paper); }
4022
-
4023
- /* The figure in a whole-surface `Empty`, drawn in the muted grey of the
4024
- sentence under it.
4025
- *
4026
- * A utility cannot do this, and the reason is worth keeping: `.illo` sets
4027
- * `color: inherit` and is unlayered, while every Tailwind utility lives in
4028
- * `@layer utilities` -- and an unlayered declaration beats a layered one
4029
- * whatever the specificity. So `text-muted` on the figure lost, silently,
4030
- * and the shirt came out full text black beside a muted sentence. The
4031
- * original rule already carried a note about needing two classes to beat
4032
- * `.illo`; layers make that impossible rather than merely fiddly. */
4033
- .nothing-figure { color: var(--muted); }
4077
+ }
4034
4078
 
4035
4079
  /* ---- ./field.css ---- */
4080
+ @layer components {
4036
4081
  /* fields --------------------------------------------------------------- */
4037
4082
 
4038
4083
  /* Label, description, input, error -- in the order they are wanted. The
@@ -4040,7 +4085,9 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4040
4085
  to what was typed, so it goes after. */
4041
4086
  .field { display: grid; gap: var(--space-1); }
4042
4087
 
4043
- .field-stack-layout { /* the default: label, description, control, error */ }
4088
+ /* `.field-stack-layout` carries no rules: stacking is what `.field` already
4089
+ does, and the class is emitted only so that both layouts name themselves
4090
+ the same way (`field-${layout}-layout`). Only the row layout overrides. */
4044
4091
 
4045
4092
  .field-row-layout > .field-label { grid-column: 1; }
4046
4093
 
@@ -4061,8 +4108,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4061
4108
 
4062
4109
  /* Not red. The asterisk marks which fields are required; colouring it the same
4063
4110
  as a failure says something has already gone wrong with every one of them. */
4064
- .field-required { color: var(--muted); }
4065
-
4066
4111
  .field-hint,
4067
4112
  .field-error { margin: 0; font-size: var(--text-sm); line-height: 1.45; }
4068
4113
 
@@ -4084,8 +4129,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4084
4129
  /* Fields stacked down a form. Wider than the gap inside one field, so the
4085
4130
  label of the next belongs to the input under it rather than to the error
4086
4131
  above it. */
4087
- .field-stack { display: grid; gap: var(--space-4); }
4088
-
4089
4132
  .field-row > .field { flex: 1; min-width: 0; }
4090
4133
 
4091
4134
  /* A password with an eye -- `Input type="password"`.
@@ -4207,14 +4250,12 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4207
4250
  background: var(--panel-2);
4208
4251
  color: var(--text);
4209
4252
  }
4253
+ }
4210
4254
 
4211
4255
  /* ---- ./tour.css ---- */
4256
+ @layer components {
4212
4257
  /* tour ----------------------------------------------------------------- */
4213
4258
 
4214
- /* The layer sits over everything and catches nothing: the scrim is painted by
4215
- the hole's shadow, so there is no full-screen element to swallow a click. */
4216
- .tour { position: fixed; inset: 0; z-index: 60; pointer-events: none; }
4217
-
4218
4259
  /* The control the tour is currently pointing at.
4219
4260
 
4220
4261
  Much of this chrome is invisible until you touch it -- `.view-add` lives at
@@ -4225,11 +4266,14 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4225
4266
  what every control does at rest, and the alternative is naming each one here
4226
4267
  and watching that list drift from the rules that hide them. One declaration
4227
4268
  that outranks whatever the resting state is beats an enumeration that goes
4228
- stale. It is scoped to a class only ever present on one element at a time. */
4229
- .tour-target {
4230
- opacity: 1 !important;
4231
- color: var(--text) !important;
4232
- }
4269
+ stale. It is scoped to a class only ever present on one element at a time.
4270
+
4271
+ Opacity and nothing else. This forced `color: var(--text)` too, from the
4272
+ original port, with no case on record that needed it -- and it broke the
4273
+ first target that painted its own background, turning a primary button's
4274
+ label dark on accent at 2.2:1. The spotlight makes a control visible; what
4275
+ colour it is at rest is the control's business. */
4276
+ .tour-target { opacity: 1 !important; }
4233
4277
 
4234
4278
  /* The spotlight. An enormous spread shadow dims the whole window except the
4235
4279
  rectangle it is drawn on, which needs no clip path and no arithmetic. */
@@ -4263,19 +4307,19 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4263
4307
 
4264
4308
  .tour-card:focus { outline: none; }
4265
4309
 
4266
- .tour-title { font-size: var(--text-base); }
4267
-
4268
4310
  .tour-body { margin: 0; color: var(--muted); font-size: var(--text-sm); line-height: 1.5; }
4269
4311
 
4270
4312
  .tour-actions { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-2); }
4271
4313
 
4272
4314
  .tour-count { color: var(--muted); font-size: var(--text-xs); }
4315
+ }
4273
4316
 
4274
4317
  /* The mail and forum shell, added in 0.4.0. After the rest, because the
4275
4318
  anchored surfaces (`.pop`, `.menu-sheet`, `.cmd`) sit over everything and a
4276
4319
  rule of equal specificity that came earlier would lose to whatever the
4277
4320
  sheets they cover happen to set. */
4278
4321
  /* ---- ./stat.css ---- */
4322
+ @layer components {
4279
4323
  /* What the utilities on `Stat` cannot say: a tone on the tile colouring the
4280
4324
  figure inside it. An attribute on the parent selecting a child is a
4281
4325
  relationship; a utility is a property on one element.
@@ -4286,8 +4330,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4286
4330
  .stat[data-tone='good'] .stat-value { color: var(--good); }
4287
4331
  .stat[data-tone='warn'] .stat-value { color: var(--warn); }
4288
4332
  .stat[data-tone='bad'] .stat-value { color: var(--bad); }
4333
+ }
4289
4334
 
4290
4335
  /* ---- ./shell.css ---- */
4336
+ @layer components {
4291
4337
  /* The measure, and only the measure.
4292
4338
  *
4293
4339
  * `Shell` draws itself with utilities -- it is layout and nothing else. What
@@ -4314,8 +4360,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4314
4360
  font-size: var(--text-sm);
4315
4361
  white-space: nowrap;
4316
4362
  }
4363
+ }
4317
4364
 
4318
4365
  /* ---- ./scrollarea.css ---- */
4366
+ @layer components {
4319
4367
  /* A box that scrolls, with its edges drawn.
4320
4368
 
4321
4369
  The fades are `::before`/`::after` on the scroller itself, pinned with
@@ -4408,8 +4456,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4408
4456
  .scroller.faded::before,
4409
4457
  .scroller.faded::after { transition: none; }
4410
4458
  }
4459
+ }
4411
4460
 
4412
4461
  /* ---- ./splitpane.css ---- */
4462
+ @layer components {
4413
4463
  /* Two panes and the handle between them. */
4414
4464
  .split {
4415
4465
  display: flex;
@@ -4428,17 +4478,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4428
4478
  the intrinsic width of a mail list is the longest unbroken subject line in
4429
4479
  it. Without this the handle stops partway across and nothing on screen says
4430
4480
  why -- it just refuses, differently for every mailbox. */
4431
- .split-pane {
4432
- min-width: 0;
4433
- min-height: 0;
4434
- overflow: hidden;
4435
- display: flex;
4436
- flex-direction: column;
4437
- }
4438
-
4439
- .split-first { flex: 0 0 var(--split); }
4440
- .split-second { flex: 1 1 0; }
4441
-
4442
4481
  /* The handle is wider than it looks: a hairline is a correct-looking divider
4443
4482
  and an impossible pointer target. The grip is the line, drawn at
4444
4483
  `--border-width` down the middle of a `--space-3` strip, and the strip is
@@ -4509,8 +4548,10 @@ body.splitting * { cursor: inherit !important; }
4509
4548
  @media (prefers-reduced-motion: reduce) {
4510
4549
  .split-grip { transition: none; }
4511
4550
  }
4551
+ }
4512
4552
 
4513
4553
  /* ---- ./popover.css ---- */
4554
+ @layer components {
4514
4555
  /* A small surface anchored to what opened it. Same sheet as the select's list,
4515
4556
  because they are the same object -- something that appeared over the page,
4516
4557
  attached to a control. */
@@ -4533,13 +4574,6 @@ body.splitting * { cursor: inherit !important; }
4533
4574
  z-index: 60;
4534
4575
  }
4535
4576
 
4536
- .pop-body {
4537
- padding: var(--space-3);
4538
- outline: none;
4539
- color: var(--text);
4540
- font-size: var(--text-sm);
4541
- }
4542
-
4543
4577
  /* Entry, and the direction it comes from. `data-placement` is React Aria's,
4544
4578
  set to where the surface actually went rather than where it was asked to go,
4545
4579
  so the movement agrees with the flip. */
@@ -4564,8 +4598,10 @@ body.splitting * { cursor: inherit !important; }
4564
4598
  .pop[data-entering],
4565
4599
  .pop[data-exiting] { animation: none; }
4566
4600
  }
4601
+ }
4567
4602
 
4568
4603
  /* ---- ./menu.css ---- */
4604
+ @layer components {
4569
4605
  /* What is left once `Menu` draws its sheet and its list: the rows, whose
4570
4606
  `data-focused`, `data-pressed` and danger states React Aria stamps on
4571
4607
  them, and the rules that reach from a row into what it holds. The
@@ -4598,8 +4634,6 @@ body.splitting * { cursor: inherit !important; }
4598
4634
  .menu-icon { flex: none; width: var(--space-4); height: var(--space-4); color: var(--muted); }
4599
4635
  .menu-item[data-focused] .menu-icon { color: var(--text); }
4600
4636
 
4601
- .menu-text { flex: 1 1 auto; min-width: 0; display: grid; }
4602
- .menu-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4603
4637
  .menu-desc {
4604
4638
  font-size: var(--text-xs);
4605
4639
  color: var(--muted);
@@ -4647,8 +4681,10 @@ body.splitting * { cursor: inherit !important; }
4647
4681
  }
4648
4682
 
4649
4683
  .menu-sheet { box-shadow: var(--shadow-2); }
4684
+ }
4650
4685
 
4651
4686
  /* ---- ./command.css ---- */
4687
+ @layer components {
4652
4688
  /* The command palette: a field over the app and a list under it. */
4653
4689
  .cmd-scrim {
4654
4690
  position: fixed;
@@ -4676,18 +4712,6 @@ body.splitting * { cursor: inherit !important; }
4676
4712
  flex-direction: column;
4677
4713
  }
4678
4714
 
4679
- .cmd-body { outline: none; display: flex; flex-direction: column; min-height: 0; }
4680
-
4681
- .cmd-field {
4682
- display: flex;
4683
- align-items: center;
4684
- gap: var(--space-2);
4685
- padding: var(--space-3) var(--space-4);
4686
- border-bottom: var(--border-width) solid var(--border);
4687
- }
4688
-
4689
- .cmd-mark { flex: none; width: var(--space-4); height: var(--space-4); color: var(--muted); }
4690
-
4691
4715
  /* No border, no focus ring on the input itself: the whole surface is the
4692
4716
  field, focus is already in it when it opens, and a ring around a box that
4693
4717
  fills the top of the palette is a ring around the palette. */
@@ -4704,22 +4728,6 @@ body.splitting * { cursor: inherit !important; }
4704
4728
 
4705
4729
  .cmd-input::placeholder { color: var(--muted); }
4706
4730
 
4707
- .cmd-list {
4708
- padding: var(--space-2);
4709
- overflow: auto;
4710
- overscroll-behavior: contain;
4711
- outline: none;
4712
- min-height: 0;
4713
- }
4714
-
4715
- .cmd-group-title {
4716
- padding: var(--space-2) var(--space-2) var(--space-1);
4717
- font-size: var(--text-2xs);
4718
- text-transform: uppercase;
4719
- letter-spacing: var(--tracking);
4720
- color: var(--muted);
4721
- }
4722
-
4723
4731
  .cmd-item {
4724
4732
  display: flex;
4725
4733
  align-items: center;
@@ -4755,8 +4763,6 @@ body.splitting * { cursor: inherit !important; }
4755
4763
  .cmd-icon { flex: none; width: var(--space-4); height: var(--space-4); color: var(--muted); }
4756
4764
  .cmd-item[data-focused] .cmd-icon { color: var(--text); }
4757
4765
 
4758
- .cmd-text { flex: 1 1 auto; min-width: 0; display: grid; }
4759
- .cmd-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4760
4766
  .cmd-desc {
4761
4767
  font-size: var(--text-xs);
4762
4768
  color: var(--muted);
@@ -4790,8 +4796,10 @@ body.splitting * { cursor: inherit !important; }
4790
4796
  .cmd-scrim[data-exiting],
4791
4797
  .cmd[data-entering] { animation: none; }
4792
4798
  }
4799
+ }
4793
4800
 
4794
4801
  /* ---- ./pagination.css ---- */
4802
+ @layer components {
4795
4803
  /* What is left once `Pagination` draws its own frame: the step and page
4796
4804
  buttons, whose hover, focus-visible and current states are pseudo-classes
4797
4805
  on two selectors at once, and `.pager-count strong`, which reaches into
@@ -4910,8 +4918,10 @@ body.splitting * { cursor: inherit !important; }
4910
4918
  .pager-page,
4911
4919
  .pager-jump { transition: none; }
4912
4920
  }
4921
+ }
4913
4922
 
4914
4923
  /* ---- ./identity.css ---- */
4924
+ @layer components {
4915
4925
  /* A person, said in one line: initials, name, sometimes the address. */
4916
4926
  .ident {
4917
4927
  display: inline-flex;
@@ -4954,25 +4964,6 @@ body.splitting * { cursor: inherit !important; }
4954
4964
  font-size: var(--text-2xs);
4955
4965
  }
4956
4966
 
4957
- .ident-text { display: grid; min-width: 0; }
4958
-
4959
- .ident-name {
4960
- overflow: hidden;
4961
- text-overflow: ellipsis;
4962
- white-space: nowrap;
4963
- font-weight: var(--weight);
4964
- }
4965
-
4966
- .ident-address {
4967
- overflow: hidden;
4968
- text-overflow: ellipsis;
4969
- white-space: nowrap;
4970
- font-size: var(--text-xs);
4971
- color: var(--muted);
4972
- }
4973
-
4974
- .ident-aside { flex: none; color: var(--muted); font-size: var(--text-xs); }
4975
-
4976
4967
  /* A chip in a composer's To field: boxed, tighter, removable. */
4977
4968
  .ident.kind-chip {
4978
4969
  gap: var(--space-1);
@@ -5012,6 +5003,7 @@ body.splitting * { cursor: inherit !important; }
5012
5003
 
5013
5004
  .ident-remove:hover { background: var(--panel); color: var(--text); }
5014
5005
  .ident-remove:focus-visible { outline: none; box-shadow: var(--focus-ring); }
5006
+ }
5015
5007
  /* ---- valet: the themes ---- */
5016
5008
  :root[data-theme='valet']{--bg:#f4f5f8;--panel:#ffffff;--panel-2:#eceef3;--border:#dcdfe7;--border-strong:#7b8597;--text:#171a21;--muted:#5b6474;--accent:#4f46e5;--accent-dim:#a9a4f0;--on-accent:#ffffff;--good:#1b7f4b;--warn:#8a5f0a;--bad:#bf3a31;--info:#0e6f8e;--app-bg:#f4f5f8;--shadow-1:0 4px 14px rgba(23, 26, 33, 0.08);--shadow-2:0 8px 24px rgba(23, 26, 33, 0.10);--shadow-3:0 12px 32px rgba(23, 26, 33, 0.12);--scrim:rgba(23, 26, 33, 0.32);color-scheme:light}
5017
5009
  :root[data-theme='valet-night']{--bg:#0c0f14;--panel:#141820;--panel-2:#1b2029;--border:#262c37;--border-strong:#616b7d;--text:#e8eaf0;--muted:#9ba4b5;--accent:#8f88ff;--accent-dim:#3f3a8f;--on-accent:#0d0b2e;--good:#5fcb8f;--warn:#e2ae58;--bad:#f28b84;--info:#57c4e8;--app-bg:#0c0f14;--shadow-1:0 6px 20px rgba(0, 0, 0, 0.28);--shadow-2:0 8px 28px rgba(0, 0, 0, 0.34);--shadow-3:0 10px 34px rgba(0, 0, 0, 0.38);--scrim:rgba(0, 0, 0, 0.5);color-scheme:dark}