@signal9/era-ui 20.0.0 → 22.0.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.
@@ -6,45 +6,43 @@
6
6
  * TIER LADDER
7
7
  * ===========
8
8
  *
9
- * xs = sp + 6 (icon tierthe glyph itself; thumbs, dismiss, icon button)
10
- * xxs = sp + 10 (pill that nests inside md badge, chip in a button/trigger)
11
- * sm = 2·sp + 6 (standalone pill switch track, KV row, free badge/chip)
12
- * md = 3·sp (buttons, inputs, selects, triggers)
13
- * lg = 4·sp (bars, containers)
9
+ * The rungs icon, pill, chip, control, bar are listed with their roles and
10
+ * per-mode values where they are declared, below. What belongs up here is why
11
+ * each formula has the shape it does:
14
12
  *
15
- * xs carries a +6 floor so icons stay legible (≥14px) at every sp. xxs sits
16
- * 4px above xs so body text has just enough pill around it while fitting
17
- * inside an md container with a visible concentric gap (≥3px at dense). sm
18
- * keeps the generous 2·sp + 6 that gives switch thumbs their sp/2 slot and
19
- * KV rows their padded feel. md and lg are pure sp multiples so button and
20
- * bar heights don't inflate.
13
+ * icon (sp + 6) a +6 floor so a glyph stays legible (≥14px) at every sp.
14
+ * pill (sp + 10) 4px above icon: enough box around body text to read as
15
+ * a pill, while still fitting inside a control with a
16
+ * visible concentric gap (≥3px at dense).
17
+ * chip (2·sp + 6) the generous rung gives switch thumbs their sp/2 slot
18
+ * and KV rows their padded feel.
19
+ * control (3·sp) pure sp multiples, so button and bar heights don't
20
+ * bar (4·sp) inflate as the modes loosen.
21
21
  *
22
22
  * Concentric nesting gaps:
23
- * sm xs = sp/2 (4/5/6 at dense/balanced/spacious)
24
- * lg md = sp/2 (4/5/6)
25
- * md sm = (sp−6)/2 (1/2/3 — tight but present)
26
- * md xxs = (2sp−10)/2 (3/5/7 — the "pill inside a button" gap)
27
- * xxs xs = 2 (flat; xxs is sized around text, not as a
28
- * container for xs content)
23
+ * chip icon = sp/2 (4/5/6 at dense/balanced/spacious)
24
+ * bar control = sp/2 (4/5/6)
25
+ * control chip = (sp−6)/2 (1/2/3 — tight but present)
26
+ * control pill = (2sp−10)/2 (3/5/7 — the "pill inside a button" gap)
27
+ * pill icon = 2 (flat; pill is sized around text, not as a
28
+ * container for icon content)
29
29
  *
30
- * Invariant xs < xxs < sm < md < lg holds for sp ≥ 7. Canonical range is
31
- * sp ∈ [8, 16] so this is mode-bounded.
30
+ * Invariant icon < pill < chip < control < bar holds for sp ≥ 7. Canonical
31
+ * range is sp ∈ [8, 16] so this is mode-bounded.
32
32
  *
33
- * xs IS the icon tier — no separate "icon" token. "Container for an icon"
34
- * and "the icon itself" are the same thing; any padding lives on the parent
35
- * (xxs / sm / md / lg).
36
- *
37
- * Dense values: 14, 18, 22, 24, 32.
38
- * Touch values: 22, 26, 38, 48, 64 (md ≥ 44px tap-target floor).
33
+ * icon IS the icon tier — there is no separate token for "a box holding one
34
+ * glyph", because that box and the glyph are the same size; any padding lives
35
+ * on the parent rung.
39
36
  *
40
37
  * INSETS
41
38
  * ======
42
39
  *
43
- * --era-px-{sm,md,lg} sp/2 tier-to-tier nesting padding
44
- * --era-gap sp/2 — inter-element gap
45
- * --era-inset-{xxs,sm,md} (T − text)/2 — text-symmetric content padding
46
- * (text is 12px at dense, 14 above)
47
- * --era-icon-inset-chip (smxs)/2 = sp/2 centering an xs element inside sm
40
+ * --era-gap sp/2 inter-element gap, and the
41
+ * tier-to-tier nesting padding
42
+ * --era-inset-{pill,chip,control} (T − text)/2 — text-symmetric content padding
43
+ * (text is 12px at dense, 14 above)
44
+ * --era-icon-inset-chip (chipicon)/2 — centring an icon-tier element
45
+ * = sp/2 inside a chip
48
46
  *
49
47
  * OPTICAL PADDING (unbounded content containers)
50
48
  * ==============================================
@@ -56,7 +54,7 @@
56
54
  * grow linearly while text barely moves (12px at dense, 14 above), so
57
55
  * whitespace must grow SUPER-linearly
58
56
  * to hold the same perceived balance. (The tokens that already feel right
59
- * across modes — inset-md 5→17, content-p 16→48 — are exactly the convex
57
+ * across modes — inset-control 6→17, pad-content 16→48 — are exactly the convex
60
58
  * ones; the cramped ones — card's old sp/2 = 4→8 — are the linear ones.)
61
59
  *
62
60
  * The optical unit is the mean of the linear and quadratic scales:
@@ -73,17 +71,17 @@
73
71
  * --era-pad-card so 8 / 11.25 / 15 / 24 — cards, card sections, dialogs, popovers
74
72
  * --era-pad-content 2·so 16 / 22.5 / 30 / 48 — full content panels (docs sections)
75
73
  *
76
- * Whitespace share rises with mode: pad-md / h-control = 0.33 dense → 0.50 touch.
74
+ * Whitespace share rises with mode: pad-card / h-control = 0.33 dense → 0.50 touch.
77
75
  *
78
76
  * IDENTITY USED AT NESTING SITES
79
77
  * ==============================
80
78
  *
81
- * A flush pill at an md-container edge needs −(inset-xxs) margin so
79
+ * A flush pill at a control-container edge needs −(inset-pill) margin so
82
80
  * the horizontal gap equals the vertical concentric gap. Proof:
83
- * inset-mdxxs-inset-md
84
- * = (md − text)/2 − (mdxxs)/2
85
- * = (xxs − text)/2
86
- * = inset-xxs.
81
+ * inset-controlpill-inset-control
82
+ * = (control − text)/2 − (controlpill)/2
83
+ * = (pill − text)/2
84
+ * = inset-pill.
87
85
  *
88
86
  * RADII (concentric progression, sp-derived)
89
87
  * ==========================================
@@ -146,12 +144,13 @@
146
144
  --era-glyph-stroke-width: 1.75; /* Lucide SVG stroke, tuned to sit beside 14px body text */
147
145
 
148
146
  /* Icon-button glyph sub-tiers. The glyph normally IS the icon tier (h-icon, the
149
- sp+6 legibility floor) — that's what every text button's icon and every md/sm
150
- icon button uses. But when the BOX collapses toward the icon tier — xxs, and
151
- xs/icon where box == h-icon — an h-icon glyph would fill it edge to edge. So the
152
- glyph steps below the floor by 2px per tier, preserving a concentric gap:
153
- sp+4 in an xxs box (pad 3), sp+2 in an xs box (pad 2). The dense pads then run
154
- 2·3·4·5 across icon·pill·chip·control — a unit step off the floor, per Button. */
147
+ sp+6 legibility floor) — that's what every text button's icon and every
148
+ control/chip icon button uses. But when the BOX collapses toward the icon tier
149
+ — a pill box, and an icon box where box == h-icon — an h-icon glyph would fill
150
+ it edge to edge. So the glyph steps below the floor by 2px per tier, preserving
151
+ a concentric gap: sp+4 in a pill box (pad 3), sp+2 in an icon box (pad 2). The
152
+ dense pads then run 2·3·4·5 across icon·pill·chip·control — a unit step off the
153
+ floor, per Button. */
155
154
  --era-glyph-pill: calc(
156
155
  var(--era-sp) + 0.25rem
157
156
  ); /* 12/14/16 = sp + 4 (glyph in a pill-tier button) */
@@ -211,33 +210,33 @@
211
210
  --era-pill-px: max(0px, calc((var(--era-h-pill) - 0.6 * var(--era-text)) / 2)); /* 5.4/5.8/6.8 */
212
211
  --era-chip-px: max(0px, calc((var(--era-h-chip) - 0.6 * var(--era-text)) / 2)); /* 7.4/8.8/10.8 */
213
212
 
214
- /* Text-field side padding — the horizontal room where typed text starts in an
215
- md-tier input (Input, Textarea, combobox/command inputs, date/time fields).
216
- Purpose-named, NOT "pill": an input is not a pill, and it lives on the md
217
- tier while the pill tokens are sm/xxs.
213
+ /* Text-field side padding — the horizontal room where typed text starts in a
214
+ control-tier input (Input, Textarea, combobox/command inputs, date/time fields).
215
+ Purpose-named, NOT "pill": an input is not a pill, and it lives on the control
216
+ tier while the pill tokens are chip/pill.
218
217
 
219
- It borrows pill-sm's glyph-advance derivation (the right shape for a STRING
220
- of text vs a single centered glyph) but floors it at 0.5rem. pill-sm is
221
- algebraically sp − 1.2px, so it grows slower than the md height (3·sp) and is
222
- proportionally TIGHTEST at dense — 6.8px in a 24px box read cramped. The
223
- floor only bites at dense (pill-sm ≥ 8.8 at every looser mode), lifting dense
218
+ It borrows --era-chip-px's glyph-advance derivation (the right shape for a
219
+ STRING of text vs a single centred glyph) but floors it at 0.5rem. chip-px is
220
+ algebraically sp − 1.2px, so it grows slower than the control height (3·sp) and
221
+ is proportionally TIGHTEST at dense — 6.8px in a 24px box read cramped. The
222
+ floor only bites at dense (chip-px ≥ 8.8 at every looser mode), lifting dense
224
223
  to 8px and leaving balanced/spacious/touch exactly as they were. */
225
224
  --era-field-px: max(var(--era-chip-px), 0.5rem); /* 8/8.8/10.8/14.8 — floor lifts dense only */
226
225
 
227
226
  /* Concentric insets — center a smaller tier element inside a larger one. */
228
- /* --era-icon-inset-chip stays at sp/2 by construction (smxs = sp), which is
229
- what switch thumbs and KV icons rely on. --era-pill-inset-control is the "pill
230
- inside a button" concentric gap used both vertically (trigger padding)
227
+ /* --era-icon-inset-chip stays at sp/2 by construction (chipicon = sp), which
228
+ is what switch thumbs and KV icons rely on. --era-pill-inset-control is the
229
+ "pill inside a button" concentric gap used both vertically (trigger padding)
231
230
  and horizontally (inter-chip gap inside multi-select Value). */
232
231
  --era-icon-inset-chip: calc((var(--era-h-chip) - var(--era-h-icon)) / 2); /* 4/5/6 = sp/2 */
233
232
  --era-icon-inset-pill: calc(
234
233
  (var(--era-h-pill) - var(--era-h-icon)) / 2
235
- ); /* 2 (flat) — center an xs element (dismiss button) inside xxs */
234
+ ); /* 2 (flat) — centre an icon-tier element (dismiss button) inside a pill */
236
235
  --era-pill-inset-control: calc(
237
236
  (var(--era-h-control) - var(--era-h-pill)) / 2
238
237
  ); /* 3/5/7 = sp − 5 */
239
- /* Centre an pill inside an sm one — a dismiss chip in a standalone chip, an
240
- xxs control in an sm bar.
238
+ /* Centre a pill inside a chip — a dismiss chip in a standalone chip, a
239
+ pill-tier control in a chip-tier bar.
241
240
  It reads like a duplicate of --era-inset-pill (3/3/4/6) and used to BE one:
242
241
  while text was 14px everywhere, (h-chip − h-pill)/2 and (h-pill − text)/2 were
243
242
  the same number, and callers borrowed the text inset rather than declaring
@@ -246,16 +245,18 @@
246
245
  --era-pill-inset-chip: calc((var(--era-h-chip) - var(--era-h-pill)) / 2); /* 2/3/4/6 */
247
246
  --era-icon-inset-control: calc(
248
247
  (var(--era-h-control) - var(--era-h-icon)) / 2
249
- ); /* 5/7/9 = sp − 3 — center an xs control inside an md bar (= rd-md − rd-xs) */
248
+ ); /* 5/7/9 = sp − 3 — centre an icon control inside a control (= rd-control − rd-icon) */
250
249
  --era-icon-inset-bar: calc(
251
250
  (var(--era-h-bar) - var(--era-h-icon)) / 2
252
- ); /* 9/12/15 — center an xs control inside an lg bar (= rd-lg − rd-xs) */
251
+ ); /* 9/12/15 — centre an icon control inside a bar (= rd-bar − rd-icon) */
253
252
  --era-chip-inset-control: calc(
254
253
  (var(--era-h-control) - var(--era-h-chip)) / 2
255
- ); /* 1/2/3 — center an sm control inside an md field (= rd-md − rd-sm), e.g. the date picker's trailing trigger */
254
+ ); /* 1/2/3 — centre a chip control inside a control field (= rd-control − rd-chip),
255
+ e.g. the date picker's trailing trigger */
256
256
  --era-chip-inset-bar: calc(
257
257
  (var(--era-h-bar) - var(--era-h-chip)) / 2
258
- ); /* 5/7/9 = sp − 3 — inset an sm panel (drag bar) concentrically in an lg pane (= rd-lg − rd-sm) */
258
+ ); /* 5/7/9 = sp − 3 — inset a chip panel (drag bar) concentrically in a bar-tier
259
+ pane (= rd-bar − rd-chip) */
259
260
 
260
261
  /* Roundness ladder — golden base + concentric progression, both gated by scale. */
261
262
  /* rd-T = scale · (R + (h-T − h-icon) / 2). R = sp / φ². max(0px, …) clamps tier
@@ -269,11 +270,11 @@
269
270
  a center of curvature with zero arithmetic.
270
271
 
271
272
  The one trap: a CONTAINER's radius follows its corner REGION's tier, never
272
- its overall bulk. A pane capped by an sm handle bar (xs controls at
273
- xs-inset-sm) is an sm corner region → rd-sm, however large the pane.
273
+ its overall bulk. A pane capped by a chip-tier handle bar (icon controls at
274
+ icon-inset-chip) is a chip corner region → rd-chip, however large the pane.
274
275
  Choosing rd by visual importance is how concentricity breaks. Precedents:
275
- Chip (sm shell + xs dismiss), Bar (h-T ↔ rd-T), Pane.Root (rd-sm), menu
276
- panels (rd-md, items at era-item-rd = rd-md − panel-inset). */
276
+ Chip (chip shell + icon dismiss), Bar (h-T ↔ rd-T), Pane.Root (rd-chip), menu
277
+ panels (rd-control, items at era-item-rd = rd-control − panel-inset). */
277
278
  --era-roundness: calc(var(--era-roundness-scale) * var(--era-sp) / 2.618);
278
279
  --era-rd-icon: max(0px, var(--era-roundness));
279
280
  --era-rd-pill: max(
@@ -384,7 +385,7 @@
384
385
  }
385
386
 
386
387
  [data-mode='touch'] {
387
- --era-sp: 1rem; /* 16px — md tier becomes 48px to clear the 44px tap-target floor */
388
+ --era-sp: 1rem; /* 16px — the control tier becomes 48px, clearing the 44px tap-target floor */
388
389
  --era-density: 2;
389
390
  }
390
391
 
@@ -37,19 +37,58 @@
37
37
  --era-font-serif: ui-serif, Georgia, 'Times New Roman', serif;
38
38
  }
39
39
 
40
- [data-font='mono'],
41
- [data-font='mono'] * {
42
- font-family: var(--era-font-mono) !important;
40
+ /*
41
+ * THE AXIS RESOLVES BY DEPTH, WHICH IT DID NOT USED TO.
42
+ *
43
+ * These were three flat rules of the form
44
+ *
45
+ * [data-font='mono'], [data-font='mono'] * { font-family: …!important }
46
+ *
47
+ * and every one of them matched at the same specificity. So for nested axes —
48
+ * mono inside sans — BOTH rules matched the inner element with equal weight and
49
+ * the cascade fell through to SOURCE ORDER: serif beat sans beat mono, whatever
50
+ * the DOM said. `data-font` was not a subtree axis at all, it was a document-wide
51
+ * one that happened to look right whenever only one value was in play. Every
52
+ * other era axis (mode, surface, corners, motion) nests correctly; this one
53
+ * silently did not, and a consumer scoping a serif panel inside a mono app got
54
+ * serif everywhere or nothing, depending on nothing they could see.
55
+ *
56
+ * The split below is what fixes it, and each half is doing a different job:
57
+ *
58
+ * 1. The [data-font='…'] rules set an INHERITED pointer. Inheritance resolves
59
+ * by depth by definition — the nearest ancestor that declares it wins — so
60
+ * nesting is correct for free and no specificity fight exists.
61
+ *
62
+ * 2. The application rule stays on `*` with `!important`, because era's own
63
+ * token components carry Tailwind's `font-mono` utility (design rule 9:
64
+ * Badge, Chip, KV, CodeBlock). A utility sets font-family ON the element,
65
+ * which beats anything inherited, so applying the axis only to the
66
+ * [data-font] host would let those components ignore it. Keeping the
67
+ * application per-element also keeps the axis winning over a consumer's
68
+ * own font utilities, which is what an app-level font switch has to do.
69
+ *
70
+ * THE CONTRACT THIS IMPLIES, stated because it is not guessable: --era-font-*
71
+ * is read where --era-font-stack is declared, which is the [data-font] element.
72
+ * Re-point a stack AT or ABOVE the element that carries the axis. Setting
73
+ * --era-font-mono on a descendant of it does nothing — the pointer was already
74
+ * resolved higher up. (That is why /audit re-asserts the shipped stacks on
75
+ * :root rather than on its own <main>.)
76
+ */
77
+ [data-font='mono'] {
78
+ --era-font-stack: var(--era-font-mono);
79
+ }
80
+
81
+ [data-font='sans'] {
82
+ --era-font-stack: var(--era-font-sans);
43
83
  }
44
84
 
45
- [data-font='sans'],
46
- [data-font='sans'] * {
47
- font-family: var(--era-font-sans) !important;
85
+ [data-font='serif'] {
86
+ --era-font-stack: var(--era-font-serif);
48
87
  }
49
88
 
50
- [data-font='serif'],
51
- [data-font='serif'] * {
52
- font-family: var(--era-font-serif) !important;
89
+ [data-font],
90
+ [data-font] * {
91
+ font-family: var(--era-font-stack) !important;
53
92
  }
54
93
 
55
94
  /*
@@ -1,14 +1,15 @@
1
1
  <script lang="ts" module>
2
2
  import { cn, tv } from '../../utils/index.js';
3
3
 
4
- // A Bar is a flex container sized to a tier height, holding one-tier-smaller
5
- // controls with the even sp/2 gap. The height scales with density; pick the
6
- // tier that fits the content:
7
- // lg (default) — full toolbar / page header of md controls (32/40/48/64)
8
- // md — compact toolbar (24/30/36/48)
9
- // sm — title / handle bar of xs icons+buttons (22/26/30/38)
10
- // Radius stays concentric to the height; gap/px are sp/2 (xs-inset-sm),
11
- // the even gap for one-tier-smaller content at every size.
4
+ // A Bar is a flex container sized to a tier height, holding smaller controls
5
+ // at the even concentric gap. The height scales with density; pick the tier
6
+ // that fits the content:
7
+ // bar (default) — full toolbar / page header of controls (32/40/48/64)
8
+ // control — compact toolbar (24/30/36/48)
9
+ // chip — title / handle bar of icon buttons (22/26/30/38)
10
+ // Radius stays concentric to the height. The gap and the wall are NOT one
11
+ // number across the sizes see the content variant below for why, and what
12
+ // each pairing actually resolves to.
12
13
  export const barVariants = tv({
13
14
  base: 'flex items-center leading-none',
14
15
  variants: {
@@ -25,14 +26,34 @@
25
26
  * had each restated the two-tier value by hand, and the geometry spec had
26
27
  * been rewritten twice chasing which token they were restating.
27
28
  *
28
- * - `tier` (default) — one tier smaller, the pairing above. chip⊃icon, control⊃chip
29
- * and bar⊃control all equal sp/2, which is why one token covers every size.
30
- * - `pill` — the compact pairing: pills and fields (a filter beside an
31
- * xxs icon button, a title bar). Two tiers down, so the inset differs
32
- * per size and comes from the compounds below.
29
+ * - `tier` (default) — one tier smaller, the pairing above. Each size gets
30
+ * its OWN token, from the compounds below.
31
+ * - `pill` — the compact pairing: pills and fields (a filter beside a
32
+ * pill-tier icon button, a title bar). Two tiers down, so the inset
33
+ * differs per size and also comes from the compounds.
34
+ *
35
+ * THIS USED TO BE ONE CLASS FOR ALL THREE SIZES — `px-icon-inset-chip`,
36
+ * on the stated reasoning that "chip⊃icon, control⊃chip and bar⊃control
37
+ * all equal sp/2". Two of those three do. control⊃chip does not:
38
+ *
39
+ * chip ⊃ icon = ((2sp+6) − (sp+6))/2 = sp/2 4/5/6
40
+ * bar ⊃ control = (4sp − 3sp)/2 = sp/2 4/5/6
41
+ * control ⊃ chip = (3sp − (2sp+6))/2 = (sp−6)/2 1/2/3 ← not sp/2
42
+ *
43
+ * chip is the one rung that is not a clean sp multiple (it carries the
44
+ * same +6 legibility floor as icon), so the difference cancels for the
45
+ * pair that spans it and survives for the pair that does not. A control
46
+ * bar therefore padded 4px horizontally while items-center centred its
47
+ * children to 1px — the wall was four times the gap it was meant to
48
+ * match, which is what a reader sees as "more space on the left".
49
+ *
50
+ * No audit caught it: layout/wall-matches-gap compares the horizontal
51
+ * wall to the horizontal gap (4 and 4 — even), and
52
+ * layout/even-container-padding compares top to bottom (equal). Nothing
53
+ * compared ACROSS the axes. layout/wall-is-isotropic now does.
33
54
  */
34
55
  content: {
35
- tier: 'gap-icon-inset-chip px-icon-inset-chip',
56
+ tier: '',
36
57
  pill: ''
37
58
  },
38
59
  /**
@@ -59,6 +80,28 @@
59
80
  }
60
81
  },
61
82
  compoundVariants: [
83
+ // content="tier" — one tier down, each pairing named for itself.
84
+ // chip ⊃ icon = sp/2.
85
+ {
86
+ size: 'chip',
87
+ content: 'tier',
88
+ class: 'gap-icon-inset-chip px-icon-inset-chip'
89
+ },
90
+ // control ⊃ chip = (sp−6)/2 — the tight-but-present rung, 1/2/3.
91
+ {
92
+ size: 'control',
93
+ content: 'tier',
94
+ class: 'gap-chip-inset-control px-chip-inset-control'
95
+ },
96
+ // bar ⊃ control = sp/2, which is exactly --era-gap. era already calls
97
+ // this pairing "the gutter" (see the rounded-row derivation in
98
+ // styles/index.css), so it takes that name rather than borrowing
99
+ // icon-inset-chip's — same number, but a reader can tell why.
100
+ {
101
+ size: 'bar',
102
+ content: 'tier',
103
+ class: 'gap-gutter px-gutter'
104
+ },
62
105
  // control ⊃ pill = (3sp − sp − 10)/2 — the named token.
63
106
  {
64
107
  size: 'control',
@@ -12,14 +12,34 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
12
12
  * had each restated the two-tier value by hand, and the geometry spec had
13
13
  * been rewritten twice chasing which token they were restating.
14
14
  *
15
- * - `tier` (default) — one tier smaller, the pairing above. chip⊃icon, control⊃chip
16
- * and bar⊃control all equal sp/2, which is why one token covers every size.
17
- * - `pill` — the compact pairing: pills and fields (a filter beside an
18
- * xxs icon button, a title bar). Two tiers down, so the inset differs
19
- * per size and comes from the compounds below.
15
+ * - `tier` (default) — one tier smaller, the pairing above. Each size gets
16
+ * its OWN token, from the compounds below.
17
+ * - `pill` — the compact pairing: pills and fields (a filter beside a
18
+ * pill-tier icon button, a title bar). Two tiers down, so the inset
19
+ * differs per size and also comes from the compounds.
20
+ *
21
+ * THIS USED TO BE ONE CLASS FOR ALL THREE SIZES — `px-icon-inset-chip`,
22
+ * on the stated reasoning that "chip⊃icon, control⊃chip and bar⊃control
23
+ * all equal sp/2". Two of those three do. control⊃chip does not:
24
+ *
25
+ * chip ⊃ icon = ((2sp+6) − (sp+6))/2 = sp/2 4/5/6
26
+ * bar ⊃ control = (4sp − 3sp)/2 = sp/2 4/5/6
27
+ * control ⊃ chip = (3sp − (2sp+6))/2 = (sp−6)/2 1/2/3 ← not sp/2
28
+ *
29
+ * chip is the one rung that is not a clean sp multiple (it carries the
30
+ * same +6 legibility floor as icon), so the difference cancels for the
31
+ * pair that spans it and survives for the pair that does not. A control
32
+ * bar therefore padded 4px horizontally while items-center centred its
33
+ * children to 1px — the wall was four times the gap it was meant to
34
+ * match, which is what a reader sees as "more space on the left".
35
+ *
36
+ * No audit caught it: layout/wall-matches-gap compares the horizontal
37
+ * wall to the horizontal gap (4 and 4 — even), and
38
+ * layout/even-container-padding compares top to bottom (equal). Nothing
39
+ * compared ACROSS the axes. layout/wall-is-isotropic now does.
20
40
  */
21
41
  content: {
22
- tier: "gap-icon-inset-chip px-icon-inset-chip";
42
+ tier: "";
23
43
  pill: "";
24
44
  };
25
45
  /**
@@ -58,14 +78,34 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
58
78
  * had each restated the two-tier value by hand, and the geometry spec had
59
79
  * been rewritten twice chasing which token they were restating.
60
80
  *
61
- * - `tier` (default) — one tier smaller, the pairing above. chip⊃icon, control⊃chip
62
- * and bar⊃control all equal sp/2, which is why one token covers every size.
63
- * - `pill` — the compact pairing: pills and fields (a filter beside an
64
- * xxs icon button, a title bar). Two tiers down, so the inset differs
65
- * per size and comes from the compounds below.
81
+ * - `tier` (default) — one tier smaller, the pairing above. Each size gets
82
+ * its OWN token, from the compounds below.
83
+ * - `pill` — the compact pairing: pills and fields (a filter beside a
84
+ * pill-tier icon button, a title bar). Two tiers down, so the inset
85
+ * differs per size and also comes from the compounds.
86
+ *
87
+ * THIS USED TO BE ONE CLASS FOR ALL THREE SIZES — `px-icon-inset-chip`,
88
+ * on the stated reasoning that "chip⊃icon, control⊃chip and bar⊃control
89
+ * all equal sp/2". Two of those three do. control⊃chip does not:
90
+ *
91
+ * chip ⊃ icon = ((2sp+6) − (sp+6))/2 = sp/2 4/5/6
92
+ * bar ⊃ control = (4sp − 3sp)/2 = sp/2 4/5/6
93
+ * control ⊃ chip = (3sp − (2sp+6))/2 = (sp−6)/2 1/2/3 ← not sp/2
94
+ *
95
+ * chip is the one rung that is not a clean sp multiple (it carries the
96
+ * same +6 legibility floor as icon), so the difference cancels for the
97
+ * pair that spans it and survives for the pair that does not. A control
98
+ * bar therefore padded 4px horizontally while items-center centred its
99
+ * children to 1px — the wall was four times the gap it was meant to
100
+ * match, which is what a reader sees as "more space on the left".
101
+ *
102
+ * No audit caught it: layout/wall-matches-gap compares the horizontal
103
+ * wall to the horizontal gap (4 and 4 — even), and
104
+ * layout/even-container-padding compares top to bottom (equal). Nothing
105
+ * compared ACROSS the axes. layout/wall-is-isotropic now does.
66
106
  */
67
107
  content: {
68
- tier: "gap-icon-inset-chip px-icon-inset-chip";
108
+ tier: "";
69
109
  pill: "";
70
110
  };
71
111
  /**
@@ -104,14 +144,34 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
104
144
  * had each restated the two-tier value by hand, and the geometry spec had
105
145
  * been rewritten twice chasing which token they were restating.
106
146
  *
107
- * - `tier` (default) — one tier smaller, the pairing above. chip⊃icon, control⊃chip
108
- * and bar⊃control all equal sp/2, which is why one token covers every size.
109
- * - `pill` — the compact pairing: pills and fields (a filter beside an
110
- * xxs icon button, a title bar). Two tiers down, so the inset differs
111
- * per size and comes from the compounds below.
147
+ * - `tier` (default) — one tier smaller, the pairing above. Each size gets
148
+ * its OWN token, from the compounds below.
149
+ * - `pill` — the compact pairing: pills and fields (a filter beside a
150
+ * pill-tier icon button, a title bar). Two tiers down, so the inset
151
+ * differs per size and also comes from the compounds.
152
+ *
153
+ * THIS USED TO BE ONE CLASS FOR ALL THREE SIZES — `px-icon-inset-chip`,
154
+ * on the stated reasoning that "chip⊃icon, control⊃chip and bar⊃control
155
+ * all equal sp/2". Two of those three do. control⊃chip does not:
156
+ *
157
+ * chip ⊃ icon = ((2sp+6) − (sp+6))/2 = sp/2 4/5/6
158
+ * bar ⊃ control = (4sp − 3sp)/2 = sp/2 4/5/6
159
+ * control ⊃ chip = (3sp − (2sp+6))/2 = (sp−6)/2 1/2/3 ← not sp/2
160
+ *
161
+ * chip is the one rung that is not a clean sp multiple (it carries the
162
+ * same +6 legibility floor as icon), so the difference cancels for the
163
+ * pair that spans it and survives for the pair that does not. A control
164
+ * bar therefore padded 4px horizontally while items-center centred its
165
+ * children to 1px — the wall was four times the gap it was meant to
166
+ * match, which is what a reader sees as "more space on the left".
167
+ *
168
+ * No audit caught it: layout/wall-matches-gap compares the horizontal
169
+ * wall to the horizontal gap (4 and 4 — even), and
170
+ * layout/even-container-padding compares top to bottom (equal). Nothing
171
+ * compared ACROSS the axes. layout/wall-is-isotropic now does.
112
172
  */
113
173
  content: {
114
- tier: "gap-icon-inset-chip px-icon-inset-chip";
174
+ tier: "";
115
175
  pill: "";
116
176
  };
117
177
  /**
@@ -144,6 +144,7 @@
144
144
  aria-disabled={disabled || undefined}
145
145
  tabindex={disabled ? -1 : undefined}
146
146
  onclick={disabled ? (e) => e.preventDefault() : onclick}
147
+ data-era-unbounded
147
148
  class={cn(cardVariants({ tone, sectioned, interactive: true }), className)}
148
149
  {...restProps}
149
150
  >
@@ -163,6 +164,7 @@
163
164
  {name}
164
165
  {value}
165
166
  {onclick}
167
+ data-era-unbounded
166
168
  class={cn(cardVariants({ tone, sectioned, interactive: true }), className)}
167
169
  {...restProps}
168
170
  >
@@ -171,6 +173,7 @@
171
173
  {:else}
172
174
  <div
173
175
  bind:this={ref as HTMLDivElement}
176
+ data-era-unbounded
174
177
  class={cn(cardVariants({ tone, sectioned }), className)}
175
178
  {...restProps}
176
179
  >
@@ -6,8 +6,17 @@
6
6
  let { ref = $bindable(null), class: className, ...restProps }: DateField.InputProps = $props();
7
7
  </script>
8
8
 
9
+ <!-- A TEXT FIELD'S SIDE PADDING IS DELIBERATELY NOT ITS CONCENTRIC INSET.
10
+ --era-field-px is the room where typed text STARTS — purpose-named in
11
+ density.css and floored at 0.5rem because the derived 6.8px read cramped at
12
+ dense — while the segment boxes inside are chip-tier and centre 1px from the
13
+ top. So this row is 8px at the sides against 1px above and below, on
14
+ purpose, and it says so rather than being quietly skipped by
15
+ layout/wall-is-isotropic's heuristics: a claim a reviewer can check beats a
16
+ silence. -->
9
17
  <DateField.Input
10
18
  bind:ref
19
+ data-era-anisotropic
11
20
  data-input-field
12
21
  class={cn(
13
22
  inputVariants(),
@@ -10,8 +10,17 @@
10
10
  }: DateRangeField.InputProps = $props();
11
11
  </script>
12
12
 
13
+ <!-- A TEXT FIELD'S SIDE PADDING IS DELIBERATELY NOT ITS CONCENTRIC INSET.
14
+ --era-field-px is the room where typed text STARTS — purpose-named in
15
+ density.css and floored at 0.5rem because the derived 6.8px read cramped at
16
+ dense — while the segment boxes inside are chip-tier and centre 1px from the
17
+ top. So this row is 8px at the sides against 1px above and below, on
18
+ purpose, and it says so rather than being quietly skipped by
19
+ layout/wall-is-isotropic's heuristics: a claim a reviewer can check beats a
20
+ silence. -->
13
21
  <DateRangeField.Input
14
22
  bind:ref
23
+ data-era-anisotropic
15
24
  data-input-field
16
25
  class={cn(
17
26
  inputVariants(),