@wtfalch/design 0.9.0 → 0.9.1

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.
@@ -81,5 +81,5 @@ export default function Select({ block = false, size, className, children, value
81
81
  return (_jsxs(AriaSelect, { className: `sel${block ? ' ctl-block' : ''}${className ? ` ${className}` : ''}`, selectedKey: value !== undefined ? String(value) : undefined, defaultSelectedKey: defaultValue !== undefined ? String(defaultValue) : (options[0]?.value ?? undefined), onSelectionChange: (key) => {
82
82
  if (key !== null)
83
83
  onChange?.({ target: { value: String(key) } });
84
- }, isDisabled: disabled, name: name, form: form, "aria-label": ariaLabel, "aria-labelledby": wiredLabelledBy, children: [_jsxs(Button, { ref: control, id: wiredId, "aria-describedby": ariaDescribedBy ?? field?.['aria-describedby'], className: `sel-control${size ? ` size-${size}` : ''}`, children: [_jsx(SelectValue, { className: "sel-value flex-1 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: ({ selectedText, defaultChildren }) => selectedText ?? defaultChildren }), _jsx("svg", { className: "sel-caret flex-none opacity-60", viewBox: "0 0 24 24", width: "14", height: "14", "aria-hidden": "true", children: _jsx("path", { fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", d: "m6 9 6 6 6-6" }) })] }), _jsx(Popover, { className: "sel-list z-[60] max-h-[280px] overflow-y-auto m-0 p-1 list-none border border-border-strong rounded-md surface-panel", placement: "bottom start", offset: 4, maxHeight: 280, children: _jsx(ListBox, { children: options.map((o) => (_jsxs(ListBoxItem, { id: o.value, textValue: o.text, isDisabled: o.disabled, className: "sel-item flex items-center gap-2 p-2 rounded-sm cursor-pointer whitespace-nowrap", children: [_jsx("span", { className: "flex-1 min-w-0 overflow-hidden text-ellipsis", children: o.label }), _jsx("svg", { className: "sel-tick flex-none opacity-0", viewBox: "0 0 24 24", width: "13", height: "13", "aria-hidden": "true", children: _jsx("path", { fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", d: "m5 13 4 4 10-10" }) }), aside?.(o.value)] }, o.value))) }) })] }));
84
+ }, isDisabled: disabled, name: name, form: form, "aria-label": ariaLabel, "aria-labelledby": wiredLabelledBy, children: [_jsxs(Button, { ref: control, id: wiredId, "aria-describedby": ariaDescribedBy ?? field?.['aria-describedby'], className: `sel-control${size ? ` size-${size}` : ''}`, children: [_jsx(SelectValue, { className: "sel-value flex-1 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: ({ selectedText, defaultChildren }) => selectedText ?? defaultChildren }), _jsx("svg", { className: "sel-caret flex-none opacity-60", viewBox: "0 0 24 24", width: "14", height: "14", "aria-hidden": "true", children: _jsx("path", { fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", d: "m6 9 6 6 6-6" }) })] }), _jsx(Popover, { className: "sel-list z-[60] max-h-[280px] overflow-y-auto m-0 p-1 list-none border border-border-strong rounded-md surface-panel", placement: "bottom start", offset: 4, maxHeight: 280, children: _jsx(ListBox, { children: options.map((o) => (_jsxs(ListBoxItem, { id: o.value, textValue: o.text, isDisabled: o.disabled, className: "sel-item flex items-center gap-2 p-2 rounded-sm cursor-pointer whitespace-nowrap", children: [_jsx("span", { className: "flex-1 min-w-0 overflow-hidden text-ellipsis", children: o.label }), _jsx("svg", { className: "sel-tick flex-none", viewBox: "0 0 24 24", width: "13", height: "13", "aria-hidden": "true", children: _jsx("path", { fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", d: "m5 13 4 4 10-10" }) }), aside?.(o.value)] }, o.value))) }) })] }));
85
85
  }
@@ -47,10 +47,14 @@ export default function SizeGrid({ value, max = { cols: 3, rows: 3 }, disabled =
47
47
  cell with no contents, so it says it is a block rather than
48
48
  taking a control's arrangement -- is here. */
49
49
  className: [
50
- 'size-cell block w-[18px] h-[14px] p-0 rounded-sm border border-border surface-panel-2 cursor-pointer',
50
+ /* Geometry here; colour in `sizegrid.css`. The cell's resting border and
51
+ surface are the thing `on` and `chosen` override, and a utility
52
+ beats a component rule once the sheets are layered -- so stated
53
+ here they would win permanently and no cell would ever light up. */
54
+ 'size-cell block w-[18px] h-[14px] p-0 rounded-sm cursor-pointer',
51
55
  'disabled:cursor-default disabled:opacity-50',
52
56
  on ? 'on' : '',
53
- chosen && !hover ? 'border-accent' : '',
57
+ chosen && !hover ? 'chosen' : '',
54
58
  ]
55
59
  .filter(Boolean)
56
60
  .join(' '), disabled: disabled, "aria-label": `${col} by ${row}`, onMouseEnter: () => !disabled && setHover({ cols: col, rows: row }), onFocus: () => !disabled && setHover({ cols: col, rows: row }), onClick: () => onChange({ cols: col, rows: row }) }, `${col}x${row}`));
@@ -17,12 +17,9 @@
17
17
  --font-sans: var(--font-sans);
18
18
  --font-mono: var(--font-mono);
19
19
  --text-xs: var(--text-xs);
20
- --text-xs--line-height: inherit;
21
20
  --text-sm: var(--text-sm);
22
- --text-sm--line-height: inherit;
23
21
  --text-base: var(--text-base);
24
22
  --text-lg: var(--text-lg);
25
- --text-lg--line-height: inherit;
26
23
  --text-xl: var(--text-xl);
27
24
  --font-weight-normal: var(--weight);
28
25
  --tracking-normal: var(--tracking);
@@ -37,14 +34,12 @@
37
34
  --spacing-5: var(--space-5);
38
35
  --spacing-6: var(--space-6);
39
36
  --spacing-10: var(--space-10);
40
- --text-2xs--line-height: inherit;
41
37
  --text-2xs: var(--text-2xs);
42
38
  --text-md: var(--text-md);
43
39
  --font-weight-strong: var(--weight-strong);
44
40
  --radius-pill: var(--radius-pill);
45
41
  --color-text: var(--text);
46
42
  --color-muted: var(--muted);
47
- --color-accent: var(--accent);
48
43
  --color-border: var(--border);
49
44
  --color-border-strong: var(--border-strong);
50
45
  }
@@ -300,9 +295,6 @@
300
295
  --tw-border-style: dashed;
301
296
  border-style: dashed;
302
297
  }
303
- .border-accent {
304
- border-color: var(--color-accent);
305
- }
306
298
  .border-border {
307
299
  border-color: var(--color-border);
308
300
  }
@@ -353,19 +345,15 @@
353
345
  }
354
346
  .text-2xs {
355
347
  font-size: var(--text-2xs);
356
- line-height: var(--tw-leading, var(--text-2xs--line-height));
357
348
  }
358
349
  .text-lg {
359
350
  font-size: var(--text-lg);
360
- line-height: var(--tw-leading, var(--text-lg--line-height));
361
351
  }
362
352
  .text-sm {
363
353
  font-size: var(--text-sm);
364
- line-height: var(--tw-leading, var(--text-sm--line-height));
365
354
  }
366
355
  .text-xs {
367
356
  font-size: var(--text-xs);
368
- line-height: var(--tw-leading, var(--text-xs--line-height));
369
357
  }
370
358
  .leading-\[0\] {
371
359
  --tw-leading: 0;
@@ -420,9 +408,6 @@
420
408
  --tw-numeric-spacing: tabular-nums;
421
409
  font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
422
410
  }
423
- .opacity-0 {
424
- opacity: 0%;
425
- }
426
411
  .opacity-60 {
427
412
  opacity: 60%;
428
413
  }
@@ -465,9 +450,6 @@
465
450
  .surface-panel {
466
451
  background: var(--panel);
467
452
  }
468
- .surface-panel-2 {
469
- background: var(--panel-2);
470
- }
471
453
  .disabled\:cursor-default:disabled {
472
454
  cursor: default;
473
455
  }
@@ -707,7 +689,19 @@
707
689
  }
708
690
  }
709
691
 
710
- /* ---- ./base.css ---- */
692
+ /* ---- ./_system-light.css ---- */
693
+ /* The `system` theme's light half.
694
+ *
695
+ * Its own file, and outside the components layer, because it is a *theme*
696
+ * rather than a component rule. `tokens.css` states the dark base on `:root`
697
+ * and is unlayered; the moment this sat inside `@layer components` it lost to
698
+ * that, and every `system` specimen rendered dark on a light-mode machine.
699
+ * Unlayered beats layered whatever the selector says, which is exactly the
700
+ * trap this repository keeps finding in different costumes.
701
+ *
702
+ * The underscore marks it the same way `_tailwind.built.css` is marked:
703
+ * `copy-css.mjs` passes both through without wrapping them.
704
+ */
711
705
  /* Light, only under the `system` theme. This used to override `:root`
712
706
  unconditionally, so choosing a dark theme on a light-mode laptop got
713
707
  silently repainted. Every other theme states its own palette. */
@@ -736,6 +730,8 @@
736
730
  }
737
731
  }
738
732
 
733
+ /* ---- ./base.css ---- */
734
+ @layer components {
739
735
  /* Derived from the two surfaces every theme already defines, so a theme gets a
740
736
  control colour without knowing this exists. */
741
737
  /* Pseudo-elements too. `*` does not match them, and `box-sizing` is not
@@ -1596,8 +1592,10 @@ input:active, textarea:active, select:active { transform: none; }
1596
1592
  color: var(--muted);
1597
1593
  opacity: 1;
1598
1594
  }
1595
+ }
1599
1596
 
1600
1597
  /* ---- ./select.css ---- */
1598
+ @layer components {
1601
1599
  /* How big a control is, asked for rather than inherited.
1602
1600
  Four sizes existed and every one of them came from a descendant selector:
1603
1601
  `.set-row button` was 11px in 3px of padding, `.approval-actions button` was
@@ -1692,16 +1690,19 @@ input:active, textarea:active, select:active { transform: none; }
1692
1690
  /* State is read off React Aria's attributes: `data-open` on the root,
1693
1691
  `data-focused` / `data-selected` / `data-disabled` on an item, where
1694
1692
  `.open`, `[data-active]`, `.on` and `.off` used to be. */
1695
- .sel-list { scroll-padding-block: var(--space-1); box-shadow: var(--shadow-2); })` brings up ended flush against the
1696
- border and read as clipped rather than as one of a set -- and this list
1697
- opens scrolled to whatever is chosen, so that was the ordinary state.
1698
- `scroll-padding` insets what counts as "in view", so it stops short by
1699
- the same four the sides have. */
1693
+ /* The list keeps two things the utilities on `Select` cannot say.
1694
+ *
1695
+ * `scroll-padding-block`: the same room while the list is scrolled as it has
1696
+ * at rest. Padding at the top of a scroll container scrolls away with the
1697
+ * content, so the row `scrollIntoView({block: 'nearest'})` brings up ended
1698
+ * flush against the border and read as clipped rather than as one of a set --
1699
+ * and this list opens scrolled to whatever is chosen, so that was the
1700
+ * ordinary state.
1701
+ *
1702
+ * `box-shadow`: `--shadow-2` is a token, and a utility would have to inline
1703
+ * the value, which is the one thing the vocabulary exists to stop. */
1704
+ .sel-list {
1700
1705
  scroll-padding-block: var(--space-1);
1701
- list-style: none;
1702
- border: var(--border-width) solid var(--border-strong);
1703
- border-radius: var(--radius-md);
1704
- background: var(--panel);
1705
1706
  box-shadow: var(--shadow-2);
1706
1707
  }
1707
1708
 
@@ -1716,6 +1717,19 @@ input:active, textarea:active, select:active { transform: none; }
1716
1717
 
1717
1718
  /* The tick marks the chosen row. Colour alone would not: a list where the only
1718
1719
  difference is a hue is a list you cannot read past. */
1720
+ /* The tick's resting state lives here, beside the state that overrides it.
1721
+ *
1722
+ * It was `opacity-0` on the element and `opacity: 1` here, which worked only
1723
+ * while this sheet was unlayered. A utility is in `@layer utilities` and
1724
+ * beats `@layer components` whatever the selector, so the moment the sheets
1725
+ * moved into a layer the tick stopped appearing on the selected row -- the
1726
+ * resting state won permanently.
1727
+ *
1728
+ * The rule generalises: a utility for a resting state whose active state is a
1729
+ * relationship between elements will always beat that active state. The pair
1730
+ * has to sit in the same layer. */
1731
+ .sel-tick { opacity: 0; }
1732
+
1719
1733
  .sel-item[data-selected] .sel-tick { opacity: 1; }
1720
1734
 
1721
1735
  /* The icon on a speaker's row in the open list -- `Select`'s `aside`. Last in
@@ -1750,8 +1764,10 @@ input:active, textarea:active, select:active { transform: none; }
1750
1764
  .field > input,
1751
1765
  .field > textarea,
1752
1766
  .field > .sel { width: 100%; }
1767
+ }
1753
1768
 
1754
1769
  /* ---- ./dangerzone.css ---- */
1770
+ @layer components {
1755
1771
  /* Destructive, and legible as such standing still. */
1756
1772
  .btn.danger {
1757
1773
  color: var(--bad);
@@ -1863,8 +1879,10 @@ input:active, textarea:active, select:active { transform: none; }
1863
1879
  controls in the same column at the same indent that do not line up read as
1864
1880
  an accident rather than as a pair. */
1865
1881
  .field-row > button { min-width: 9ch; }
1882
+ }
1866
1883
 
1867
1884
  /* ---- ./callout.css ---- */
1885
+ @layer components {
1868
1886
  /* One shape for everything that tells you something.
1869
1887
  `.banner` and `.callout` were the same box drawn twice: a bordered, rounded,
1870
1888
  12px panel with a tint. They differed in padding by a pixel in each direction
@@ -2003,8 +2021,10 @@ input:active, textarea:active, select:active { transform: none; }
2003
2021
  }
2004
2022
 
2005
2023
  .callout button { flex: none; }
2024
+ }
2006
2025
 
2007
2026
  /* ---- ./card.css ---- */
2027
+ @layer components {
2008
2028
  /* `.card` is public API, not this component's private styling.
2009
2029
  *
2010
2030
  * Consumers apply it to their own markup -- the gallery's skeleton specimen
@@ -2146,8 +2166,10 @@ input:active, textarea:active, select:active { transform: none; }
2146
2166
  }
2147
2167
 
2148
2168
  .card[aria-pressed='true']:hover { border-color: var(--accent); }
2169
+ }
2149
2170
 
2150
2171
  /* ---- ./toggle.css ---- */
2172
+ @layer components {
2151
2173
  .card-action .switch-row { padding: 0; gap: var(--space-2); }
2152
2174
 
2153
2175
  .card-action .switch-row .switch-body { flex: none; }
@@ -2378,8 +2400,10 @@ input[type='radio']:active:not(:disabled) {
2378
2400
  @media (prefers-reduced-motion: reduce) {
2379
2401
  .toggle-sweep::before { animation: none; background: color-mix(in oklab, var(--accent) 45%, transparent); }
2380
2402
  }
2403
+ }
2381
2404
 
2382
2405
  /* ---- ./skeleton.css ---- */
2406
+ @layer components {
2383
2407
  .muted { color: var(--muted); }
2384
2408
 
2385
2409
  .pulse-block {
@@ -2434,8 +2458,10 @@ input[type='radio']:active:not(:disabled) {
2434
2458
  /* Still distinguishable from a real block of colour, and still still. */
2435
2459
  .skel-pulse { animation: none; opacity: 0.8; }
2436
2460
  }
2461
+ }
2437
2462
 
2438
2463
  /* ---- ./rows.css ---- */
2464
+ @layer components {
2439
2465
  .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2440
2466
 
2441
2467
  /* A name and the pills that qualify it, on one line, where the name is the only
@@ -2696,8 +2722,10 @@ input[type='radio']:active:not(:disabled) {
2696
2722
  .row-hit:hover .truncate { color: var(--accent); }
2697
2723
 
2698
2724
  .row-hit:focus-visible { outline: none; box-shadow: var(--focus-ring); }
2725
+ }
2699
2726
 
2700
2727
  /* ---- ./progress.css ---- */
2728
+ @layer components {
2701
2729
  /* progress ----------------------------------------------------------- */
2702
2730
 
2703
2731
  /* The bar itself: a track with a child it fills, an animation and a
@@ -2739,8 +2767,10 @@ input[type='radio']:active:not(:disabled) {
2739
2767
  /* Still distinguishable from empty, and from full. */
2740
2768
  .bar-indeterminate > i { animation: none; width: 100%; opacity: 0.35; }
2741
2769
  }
2770
+ }
2742
2771
 
2743
2772
  /* ---- ./tabs.css ---- */
2773
+ @layer components {
2744
2774
  /* ---------------------------------------------------------------- tabs */
2745
2775
  .tabs { position: relative; display: flex; gap: var(--space-1); }
2746
2776
 
@@ -2841,8 +2871,10 @@ input[type='radio']:active:not(:disabled) {
2841
2871
  }
2842
2872
 
2843
2873
  .tabs-vertical .tab:active:not(:disabled) { border-color: transparent; }
2874
+ }
2844
2875
 
2845
2876
  /* ---- ./toast.css ---- */
2877
+ @layer components {
2846
2878
  /* ---------------------------------------------------------------- toasts */
2847
2879
  .toasts {
2848
2880
  position: fixed;
@@ -2940,8 +2972,10 @@ input[type='radio']:active:not(:disabled) {
2940
2972
  outline: 2px solid var(--accent);
2941
2973
  outline-offset: 2px;
2942
2974
  }
2975
+ }
2943
2976
 
2944
2977
  /* ---- ./modal.css ---- */
2978
+ @layer components {
2945
2979
  /* modal --------------------------------------------------------------- */
2946
2980
 
2947
2981
  /* `hidden` needs saying out loud here. The browser's own `[hidden]` rule is a
@@ -3172,8 +3206,10 @@ input[type='radio']:active:not(:disabled) {
3172
3206
  .modal.sheet[data-entering],
3173
3207
  .modal.sheet[data-exiting] { animation: none; }
3174
3208
  }
3209
+ }
3175
3210
 
3176
3211
  /* ---- ./slider.css ---- */
3212
+ @layer components {
3177
3213
  /* slider ----------------------------------------------------------------- */
3178
3214
  /* A number from a range -- `Slider.tsx` says when one is the right control.
3179
3215
  The row is laid out like a switch row: words on the left, the control on the
@@ -3395,8 +3431,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3395
3431
  the scale, and the two line up; an explicit `size-*` still wins. */
3396
3432
  :where(.set-row) button,
3397
3433
  :where(.set-row) .slider-box { font-size: var(--text-xs); }
3434
+ }
3398
3435
 
3399
3436
  /* ---- ./checkbox.css ---- */
3437
+ @layer components {
3400
3438
  /* A row you choose. There is no checkbox in it.
3401
3439
  Every one of the fourteen in this app sits beside a name and a line of
3402
3440
  explanation, so the row was always the thing being designed and the 17px
@@ -3566,8 +3604,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3566
3604
  .choice[data-disabled] .choice-why { opacity: 0.5; }
3567
3605
 
3568
3606
  .choice[data-disabled]::after { opacity: 0.4; }
3607
+ }
3569
3608
 
3570
3609
  /* ---- ./table.css ---- */
3610
+ @layer components {
3571
3611
  /* A second line under a row's name, inside the `<th scope="row">` that makes it
3572
3612
  one. The cell is bold because it is a heading; what hangs under it is not,
3573
3613
  and inherited the weight -- so a pinned version read as loud as the thing it
@@ -3615,27 +3655,48 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3615
3655
  .data-table .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
3616
3656
 
3617
3657
  .data-table thead th[style*='right'] { text-align: right; }
3658
+ }
3618
3659
 
3619
3660
  /* ---- ./sizegrid.css ---- */
3620
- /* What is left of the size grid once it draws itself.
3661
+ @layer components {
3662
+ /* The cell's colour, all of it, in one place.
3663
+ *
3664
+ * `SizeGrid` draws the cell's geometry with utilities and leaves every colour
3665
+ * here, because `on` and `chosen` override the resting state -- and a utility
3666
+ * is in `@layer utilities`, which beats `@layer components` whatever the
3667
+ * selector. Stated as `surface-panel-2` and `border-border` on the element,
3668
+ * the resting state won permanently and no cell ever lit up.
3621
3669
  *
3622
- * `on` follows the pointer; `chosen` is what is actually saved, shown only
3623
- * when nothing is hovered so the two can never contradict each other.
3624
- * `chosen` is a border colour and says so at the call site; `on` is a fill
3625
- * mixed from the accent, and `color-mix` in an arbitrary value is less
3626
- * readable than the rule it replaces. */
3670
+ * That is the third time this pattern has bitten: `Select`'s tick and
3671
+ * `Empty`'s figure were the other two. The rule: a resting state whose
3672
+ * active state is a CSS rule has to be a CSS rule too. */
3673
+ .size-cell {
3674
+ border: var(--border-width) solid var(--border);
3675
+ background: var(--panel-2);
3676
+ }
3677
+
3678
+ /* `on` follows the pointer; `chosen` is what is actually saved, shown only
3679
+ when nothing is hovered so the two can never contradict each other. */
3627
3680
  .size-cell.on {
3628
3681
  border-color: var(--accent);
3629
3682
  background: color-mix(in srgb, var(--accent) 45%, transparent);
3630
3683
  }
3631
3684
 
3685
+ .size-cell.chosen { border-color: var(--accent); }
3686
+ }
3687
+
3632
3688
  /* ---- ./tooltip.css ---- */
3689
+ @layer components {
3633
3690
  /* A callout: something worth knowing, with nothing to answer. Distinct from a
3634
3691
  `?`, which is an offer to read more, and from a banner, which is a problem.
3635
3692
  The mark is a round `i` rather than an emoji so it takes the theme's colours
3636
3693
  and stays the same size as the text beside it. */
3637
- /* An info-toned `.callout`, spelled the old way. Identical to it now rather than
3638
- nearly identical to it, which is the whole point of the consolidation: the
3694
+ /* An info-toned `.callout`, spelled the old way. Identical to it now rather
3695
+ than nearly identical to it, which is the whole point of the consolidation.
3696
+ (This comment has been unterminated since before the Tailwind branch -- it
3697
+ stops mid-sentence and never closes, so the CSS parser merged it with the
3698
+ one below and recovered. Nothing was lost from the stylesheet; the rest of
3699
+ the sentence was. Closed here rather than guessed at.) */
3639
3700
 
3640
3701
  /* What is left once `Tooltip` draws its own mark: the hover and focus that
3641
3702
  reach the mark from its parent, and the tip -- whose closed and open
@@ -3718,8 +3779,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3718
3779
  }
3719
3780
 
3720
3781
  .explain-tip strong { color: var(--text); font-weight: 600; }
3782
+ }
3721
3783
 
3722
3784
  /* ---- ./illustration.css ---- */
3785
+ @layer components {
3723
3786
  /* illustrations -------------------------------------------------------- */
3724
3787
 
3725
3788
  /* Open Peeps, two colours, wearing whatever the theme is.
@@ -3757,8 +3820,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3757
3820
  * original rule already carried a note about needing two classes to beat
3758
3821
  * `.illo`; layers make that impossible rather than merely fiddly. */
3759
3822
  .nothing-figure { color: var(--muted); }
3823
+ }
3760
3824
 
3761
3825
  /* ---- ./field.css ---- */
3826
+ @layer components {
3762
3827
  /* fields --------------------------------------------------------------- */
3763
3828
 
3764
3829
  /* Label, description, input, error -- in the order they are wanted. The
@@ -3933,8 +3998,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3933
3998
  background: var(--panel-2);
3934
3999
  color: var(--text);
3935
4000
  }
4001
+ }
3936
4002
 
3937
4003
  /* ---- ./tour.css ---- */
4004
+ @layer components {
3938
4005
  /* tour ----------------------------------------------------------------- */
3939
4006
 
3940
4007
  /* The layer sits over everything and catches nothing: the scrim is painted by
@@ -3996,12 +4063,14 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3996
4063
  .tour-actions { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-2); }
3997
4064
 
3998
4065
  .tour-count { color: var(--muted); font-size: var(--text-xs); }
4066
+ }
3999
4067
 
4000
4068
  /* The mail and forum shell, added in 0.4.0. After the rest, because the
4001
4069
  anchored surfaces (`.pop`, `.menu-sheet`, `.cmd`) sit over everything and a
4002
4070
  rule of equal specificity that came earlier would lose to whatever the
4003
4071
  sheets they cover happen to set. */
4004
4072
  /* ---- ./stat.css ---- */
4073
+ @layer components {
4005
4074
  /* What the utilities on `Stat` cannot say: a tone on the tile colouring the
4006
4075
  figure inside it. An attribute on the parent selecting a child is a
4007
4076
  relationship; a utility is a property on one element.
@@ -4012,8 +4081,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4012
4081
  .stat[data-tone='good'] .stat-value { color: var(--good); }
4013
4082
  .stat[data-tone='warn'] .stat-value { color: var(--warn); }
4014
4083
  .stat[data-tone='bad'] .stat-value { color: var(--bad); }
4084
+ }
4015
4085
 
4016
4086
  /* ---- ./shell.css ---- */
4087
+ @layer components {
4017
4088
  /* The measure, and only the measure.
4018
4089
  *
4019
4090
  * `Shell` draws itself with utilities -- it is layout and nothing else. What
@@ -4040,8 +4111,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4040
4111
  font-size: var(--text-sm);
4041
4112
  white-space: nowrap;
4042
4113
  }
4114
+ }
4043
4115
 
4044
4116
  /* ---- ./scrollarea.css ---- */
4117
+ @layer components {
4045
4118
  /* A box that scrolls, with its edges drawn.
4046
4119
 
4047
4120
  The fades are `::before`/`::after` on the scroller itself, pinned with
@@ -4134,8 +4207,10 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
4134
4207
  .scroller.faded::before,
4135
4208
  .scroller.faded::after { transition: none; }
4136
4209
  }
4210
+ }
4137
4211
 
4138
4212
  /* ---- ./splitpane.css ---- */
4213
+ @layer components {
4139
4214
  /* Two panes and the handle between them. */
4140
4215
  .split {
4141
4216
  display: flex;
@@ -4235,8 +4310,10 @@ body.splitting * { cursor: inherit !important; }
4235
4310
  @media (prefers-reduced-motion: reduce) {
4236
4311
  .split-grip { transition: none; }
4237
4312
  }
4313
+ }
4238
4314
 
4239
4315
  /* ---- ./popover.css ---- */
4316
+ @layer components {
4240
4317
  /* A small surface anchored to what opened it. Same sheet as the select's list,
4241
4318
  because they are the same object -- something that appeared over the page,
4242
4319
  attached to a control. */
@@ -4290,8 +4367,10 @@ body.splitting * { cursor: inherit !important; }
4290
4367
  .pop[data-entering],
4291
4368
  .pop[data-exiting] { animation: none; }
4292
4369
  }
4370
+ }
4293
4371
 
4294
4372
  /* ---- ./menu.css ---- */
4373
+ @layer components {
4295
4374
  /* What is left once `Menu` draws its sheet and its list: the rows, whose
4296
4375
  `data-focused`, `data-pressed` and danger states React Aria stamps on
4297
4376
  them, and the rules that reach from a row into what it holds. The
@@ -4373,8 +4452,10 @@ body.splitting * { cursor: inherit !important; }
4373
4452
  }
4374
4453
 
4375
4454
  .menu-sheet { box-shadow: var(--shadow-2); }
4455
+ }
4376
4456
 
4377
4457
  /* ---- ./command.css ---- */
4458
+ @layer components {
4378
4459
  /* The command palette: a field over the app and a list under it. */
4379
4460
  .cmd-scrim {
4380
4461
  position: fixed;
@@ -4516,8 +4597,10 @@ body.splitting * { cursor: inherit !important; }
4516
4597
  .cmd-scrim[data-exiting],
4517
4598
  .cmd[data-entering] { animation: none; }
4518
4599
  }
4600
+ }
4519
4601
 
4520
4602
  /* ---- ./pagination.css ---- */
4603
+ @layer components {
4521
4604
  /* What is left once `Pagination` draws its own frame: the step and page
4522
4605
  buttons, whose hover, focus-visible and current states are pseudo-classes
4523
4606
  on two selectors at once, and `.pager-count strong`, which reaches into
@@ -4636,8 +4719,10 @@ body.splitting * { cursor: inherit !important; }
4636
4719
  .pager-page,
4637
4720
  .pager-jump { transition: none; }
4638
4721
  }
4722
+ }
4639
4723
 
4640
4724
  /* ---- ./identity.css ---- */
4725
+ @layer components {
4641
4726
  /* A person, said in one line: initials, name, sometimes the address. */
4642
4727
  .ident {
4643
4728
  display: inline-flex;
@@ -4738,4 +4823,5 @@ body.splitting * { cursor: inherit !important; }
4738
4823
 
4739
4824
  .ident-remove:hover { background: var(--panel); color: var(--text); }
4740
4825
  .ident-remove:focus-visible { outline: none; box-shadow: var(--focus-ring); }
4826
+ }
4741
4827