@signal9/era-ui 30.7.0 → 31.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.
Files changed (87) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/ai/conversations/conversations-item.svelte +1 -1
  3. package/dist/ai/plan/plan-header.svelte +1 -1
  4. package/dist/ai/prompt-input/prompt-input-attachments.svelte +2 -6
  5. package/dist/ai/swarm/swarm-node.svelte +1 -1
  6. package/dist/ai/todo/todo-header.svelte +1 -1
  7. package/dist/ai/todo/todo-item.svelte +1 -1
  8. package/dist/ai/tool/tool-chip.svelte +1 -1
  9. package/dist/ai/web-preview/web-preview.svelte +1 -1
  10. package/dist/dev/audit/audits/wall-is-isotropic.js +1 -1
  11. package/dist/docs/css-utilities.js +7 -3
  12. package/dist/era-ui.css +1 -1
  13. package/dist/generated-docs/badge.md +1 -1
  14. package/dist/generated-docs/bar.md +22 -4
  15. package/dist/generated-docs/button.md +38 -4
  16. package/dist/generated-docs/card.md +35 -5
  17. package/dist/generated-docs/chip.md +16 -0
  18. package/dist/generated-docs/llms-full.txt +310 -49
  19. package/dist/generated-docs/llms.txt +4 -3
  20. package/dist/generated-docs/manifest.json +45 -21
  21. package/dist/generated-docs/meter.md +79 -1
  22. package/dist/generated-docs/nav.md +8 -1
  23. package/dist/generated-docs/separator.md +18 -0
  24. package/dist/generated-docs/utilities.json +32 -9
  25. package/dist/generated-docs/utilities.md +89 -30
  26. package/dist/styles/density.css +15 -25
  27. package/dist/styles/index.css +32 -27
  28. package/dist/ui/accordion/accordion-trigger.svelte +1 -1
  29. package/dist/ui/alert-dialog/alert-dialog-action.svelte +2 -0
  30. package/dist/ui/alert-dialog/alert-dialog-cancel.svelte +2 -0
  31. package/dist/ui/badge/badge.svelte +8 -2
  32. package/dist/ui/badge/badge.svelte.d.ts +1 -1
  33. package/dist/ui/bar/bar.svelte +95 -26
  34. package/dist/ui/bar/bar.svelte.d.ts +71 -31
  35. package/dist/ui/button/button.svelte +38 -2
  36. package/dist/ui/button/button.svelte.d.ts +29 -0
  37. package/dist/ui/button/index.d.ts +2 -0
  38. package/dist/ui/button/variants.js +4 -9
  39. package/dist/ui/calendar/calendar-day.svelte +1 -1
  40. package/dist/ui/card/card.svelte +22 -9
  41. package/dist/ui/card/card.svelte.d.ts +19 -7
  42. package/dist/ui/chip/chip.svelte +5 -2
  43. package/dist/ui/chip/chip.svelte.d.ts +6 -6
  44. package/dist/ui/collapsible/collapsible-trigger.svelte +1 -1
  45. package/dist/ui/combobox/combobox-trigger.svelte +1 -1
  46. package/dist/ui/command/command-item.svelte +1 -1
  47. package/dist/ui/command/command-link-item.svelte +1 -1
  48. package/dist/ui/command/command-root.svelte +3 -5
  49. package/dist/ui/date-field/date-field-segment.svelte +1 -1
  50. package/dist/ui/date-picker/date-picker-day.svelte +1 -1
  51. package/dist/ui/date-picker/date-picker-segment.svelte +1 -1
  52. package/dist/ui/date-picker/date-picker-trigger.svelte +1 -1
  53. package/dist/ui/date-range-field/date-range-field-segment.svelte +1 -1
  54. package/dist/ui/date-range-picker/date-range-picker-day.svelte +1 -1
  55. package/dist/ui/date-range-picker/date-range-picker-segment.svelte +1 -1
  56. package/dist/ui/date-range-picker/date-range-picker-trigger.svelte +1 -1
  57. package/dist/ui/dialog/dialog-close.svelte +2 -0
  58. package/dist/ui/dialog/dialog-content.svelte +19 -1
  59. package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -1
  60. package/dist/ui/file-upload/file-upload.svelte +1 -1
  61. package/dist/ui/input/variants.d.ts +6 -6
  62. package/dist/ui/input/variants.js +2 -2
  63. package/dist/ui/kv/kv.svelte +1 -1
  64. package/dist/ui/menubar/menubar-root.svelte +1 -1
  65. package/dist/ui/menubar/menubar-trigger.svelte +1 -1
  66. package/dist/ui/nav/nav-bar.svelte +9 -4
  67. package/dist/ui/nav/nav-bar.svelte.d.ts +2 -1
  68. package/dist/ui/pane/pane-root.svelte +3 -12
  69. package/dist/ui/pane/pane-tab.svelte +1 -1
  70. package/dist/ui/select/select-trigger.svelte +1 -1
  71. package/dist/ui/slider/slider-thumb.svelte +1 -1
  72. package/dist/ui/step/step-summary.svelte +1 -1
  73. package/dist/ui/switch/switch.svelte +2 -2
  74. package/dist/ui/time-field/time-field-segment.svelte +1 -1
  75. package/dist/ui/time-range-field/time-range-field-segment.svelte +1 -1
  76. package/dist/ui/timeline/timeline-step.svelte +1 -1
  77. package/dist/ui/toggle-group/toggle-group-item.svelte +2 -2
  78. package/dist/ui/toggle-group/toggle-group-item.svelte.d.ts +6 -6
  79. package/dist/ui/toggle-group/toggle-group-root.svelte +1 -2
  80. package/dist/ui/toggle-group/toggle-group-root.svelte.d.ts +3 -3
  81. package/dist/ui/tooltip/tooltip-content.svelte +1 -1
  82. package/dist/ui/tree/tree-item.svelte +1 -1
  83. package/dist/ui/video-player/video-player.svelte +1 -1
  84. package/dist/utils/tw-merge-config.d.ts +3 -0
  85. package/dist/utils/tw-merge-config.js +4 -0
  86. package/package.json +4 -3
  87. package/skill/SKILL.md +17 -17
@@ -4,7 +4,7 @@ The CSS classes era ships alongside the components — links, ink-centred text,
4
4
 
5
5
  ## Overview
6
6
 
7
- era's styling API is pure class names — write `h-control rounded-control px-gutter shadow`,
7
+ era's styling API is pure class names — write `tier-control px-gutter shadow`,
8
8
  never the `h-(--era-h-control)` var-class spelling. Everything below rides along
9
9
  with the stylesheet and needs no import:
10
10
 
@@ -13,7 +13,7 @@ import "@signal9/era-ui/css";
13
13
  ```
14
14
 
15
15
  ```svelte
16
- <button class="h-control rounded-control px-inset-control shadow text-body">era styled</button>
16
+ <button class="tier-control px-inset-control shadow text-body">era styled</button>
17
17
  ```
18
18
 
19
19
  Alongside the `@utility` classes referenced below, era registers named theme
@@ -23,6 +23,7 @@ motion):
23
23
 
24
24
  | Stock-looking class | What it resolves to |
25
25
  |---|---|
26
+ | `tier-icon/pill/chip/control/bar` | indivisible bounded height + concentric radius |
26
27
  | `rounded-icon/pill/chip/control/bar` | the concentric radius ladder (collapses on `data-corners="square"`) |
27
28
  | `rounded-menu-item` | menu-row radius, concentric inside a `rounded-control` panel |
28
29
  | `shadow` / `shadow-lg` | resting control edge / floating panel chrome, per surface |
@@ -48,10 +49,13 @@ The spacing names (`gutter`, `card`, `panel`, `content`, `menu`, `field`, the
48
49
  `inset-*` and concentric `*-inset-*` insets, `glyph-*`) are registered as a
49
50
  Tailwind spacing scale, so every prefix derives automatically: `p-`, `px-`,
50
51
  `pt-`, `mt-`, `gap-`, `gap-x-`, `inset-`, `translate-`, and the rest. The TIER
51
- names (`icon`, `pill`, `chip`, `control`, `bar`) are heights, not spacing, so
52
+ names (`icon`, `pill`, `chip`, `control`, `bar`) are dimensions, not spacing, so
52
53
  they are registered per prefix instead — `h-`, `w-`, `min-h-`, `min-w-` and
53
54
  `size-` — and the two vocabularies deliberately share no word. Putting a tier
54
55
  in the spacing scale would silently make `px-control` mean 24px of padding.
56
+ For chrome, use `tier-T`, which binds `h-T` and `rounded-T`; use the separate
57
+ dimension/radius names only for width, minimums, unbounded surfaces, or
58
+ asymmetric/docked corners.
55
59
 
56
60
  The var form (`h-(--era-h-control)`) still works and is the escape hatch for a
57
61
  token with no registered name — `--era-sp`, the press-sink offsets, anything
@@ -70,8 +74,9 @@ source uses it where a name exists.
70
74
  | `era-token-ink` | the ink centring for a token pill — a badge, a KV value, any short caps-or-digits label in a fixed-height box, CENTRED horizontally and with no icon sibling. For a left-aligned sentence-case label use era-label-ink; when an icon really must sit beside the text, see the note there. |
71
75
  | `era-label-ink` | the ink centring for a fixed-height control holding ONE line of sentence-case text and no icon — a menu item, a nav trigger, a button label. Never alongside `truncate` (the trimmed box becomes the clip). |
72
76
  | `glass-blur` | the backdrop blur for a floating panel (popover, dialog, taskbar) — it applies only inside a data-surface="glass" subtree and is inert elsewhere, so it is safe to leave on a panel that renders under every surface. |
73
- | `rounded-row` | the radius for a row inside a container that pads it by the gutter a nav rail, a command list, any scrolling list of controls. For a row in a menu PANEL, whose inset is the surface-owned frame rather than the gutter, use rounded-menu-item. |
77
+ | `tier-icon` `tier-pill` `tier-chip` `tier-control` `tier-bar` | — |
74
78
  | `h-line` | the height of one line of the element's own text — a skeleton standing in for a line, a spacer that must match a line box. |
79
+ | `era-track-well` | instead of `shadow-well` on a track whose value is painted by a child that covers the box — a progress/meter/slider track, a custom gauge. For a well holding ordinary content, `shadow-well` is still right. |
75
80
  | `era-skeleton-pulse` | — |
76
81
  | `era-shimmer` | the in-progress text treatment — put it on a "working…" / "thinking…" label while a task streams, instead of a spinner beside the text. |
77
82
  | `era-collapse` | the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden). |
@@ -92,12 +97,12 @@ source uses it where a name exists.
92
97
  | `bg-well` `text-well` `border-well` `bg-elevated` `text-elevated` `border-elevated` `bg-highlight` `text-highlight` `border-highlight` `bg-overlay` `text-overlay` `border-overlay` | — |
93
98
  | `p-gutter` `px-gutter` `py-gutter` `gap-gutter` `p-panel` `px-panel` `py-panel` `gap-panel` `p-card` `px-card` `py-card` `gap-card` `p-content` `px-content` `py-content` `gap-content` `p-menu` `px-menu` `py-menu` `gap-menu` `p-field` `px-field` `py-field` `gap-field` | — |
94
99
  | `p-inset-pill` `px-inset-pill` `py-inset-pill` `gap-inset-pill` `p-inset-chip` `px-inset-chip` `py-inset-chip` `gap-inset-chip` `p-inset-control` `px-inset-control` `py-inset-control` `gap-inset-control` | text-symmetric content padding — (tier − text)/2, the inset that vertically centres body text in its tier; px-inset-control is a text button's side padding, p-inset-control a code block's. |
95
- | `p-icon-inset-pill` `px-icon-inset-pill` `py-icon-inset-pill` `gap-icon-inset-pill` `p-icon-inset-chip` `px-icon-inset-chip` `py-icon-inset-chip` `gap-icon-inset-chip` `p-icon-inset-control` `px-icon-inset-control` `py-icon-inset-control` `gap-icon-inset-control` `p-icon-inset-bar` `px-icon-inset-bar` `py-icon-inset-bar` `gap-icon-inset-bar` `p-pill-inset-chip` `px-pill-inset-chip` `py-pill-inset-chip` `gap-pill-inset-chip` `p-pill-inset-control` `px-pill-inset-control` `py-pill-inset-control` `gap-pill-inset-control` `p-pill-inset-bar` `px-pill-inset-bar` `py-pill-inset-bar` `gap-pill-inset-bar` `p-chip-inset-control` `px-chip-inset-control` `py-chip-inset-control` `gap-chip-inset-control` `p-chip-inset-bar` `px-chip-inset-bar` `py-chip-inset-bar` `gap-chip-inset-bar` | the concentric inset for a nested pair — (h-container − h-child)/2, the gap that makes a child control sit evenly inside its container, and the radius step that keeps their corners concentric. Named for the pair it joins: gap-icon-inset-control seats an icon in a control. |
100
+ | `p-icon-inset-pill` `px-icon-inset-pill` `py-icon-inset-pill` `gap-icon-inset-pill` `p-icon-inset-chip` `px-icon-inset-chip` `py-icon-inset-chip` `gap-icon-inset-chip` `p-icon-inset-control` `px-icon-inset-control` `py-icon-inset-control` `gap-icon-inset-control` `p-icon-inset-bar` `px-icon-inset-bar` `py-icon-inset-bar` `gap-icon-inset-bar` `p-pill-inset-chip` `px-pill-inset-chip` `py-pill-inset-chip` `gap-pill-inset-chip` `p-pill-inset-control` `px-pill-inset-control` `py-pill-inset-control` `gap-pill-inset-control` `p-pill-inset-bar` `px-pill-inset-bar` `py-pill-inset-bar` `gap-pill-inset-bar` `p-chip-inset-control` `px-chip-inset-control` `py-chip-inset-control` `gap-chip-inset-control` `p-chip-inset-bar` `px-chip-inset-bar` `py-chip-inset-bar` `gap-chip-inset-bar` `p-control-inset-bar` `px-control-inset-bar` `py-control-inset-bar` `gap-control-inset-bar` | the concentric inset for a nested pair — (h-container − h-child)/2, the gap that makes a child control sit evenly inside its container, and the radius step that keeps their corners concentric. Named for the pair it joins: gap-icon-inset-control seats an icon in a control. |
96
101
  | `p-glyph-icon` `px-glyph-icon` `py-glyph-icon` `gap-glyph-icon` `p-glyph-pill` `px-glyph-pill` `py-glyph-pill` `gap-glyph-pill` | the glyph size for a tier — the INK inside the box, not the box (size-glyph-pill is the dismiss × in a pill, size-pill is the pill). |
97
102
  | `bg-field` `text-field` `border-field` `bg-field-highlight` `text-field-highlight` `border-field-highlight` | the recessed field fill — bg-field is a text input's resting well, bg-field-highlight its focused/hovered face. |
98
103
  | `ease-base` | — |
99
104
  | `bg-divider` `text-divider` `border-divider` `bg-divider-faded` `text-divider-faded` `border-divider-faded` `bg-background` `text-background` `border-background` `bg-foreground` `text-foreground` `border-foreground` `bg-1` `text-1` `border-1` `bg-2` `text-2` `border-2` `bg-3` `text-3` `border-3` `bg-4` `text-4` `border-4` `bg-5` `text-5` `border-5` `bg-6` `text-6` `border-6` `bg-7` `text-7` `border-7` `bg-8` `text-8` `border-8` `bg-9` `text-9` `border-9` `bg-10` `text-10` `border-10` `bg-11` `text-11` `border-11` `bg-12` `text-12` `border-12` `bg-border` `text-border` `border-border` `bg-surface` `text-surface` `border-surface` `bg-hover` `text-hover` `border-hover` `bg-fg` `text-fg` `border-fg` `bg-bright` `text-bright` `border-bright` `bg-muted` `text-muted` `border-muted` | — |
100
- | `bg-fill` `text-fill` `border-fill` `bg-link` `text-link` `border-link` `bg-primary` `text-primary` `border-primary` `bg-primary-fg` `text-primary-fg` `border-primary-fg` `bg-primary-hover` `text-primary-hover` `border-primary-hover` `bg-destructive` `text-destructive` `border-destructive` `bg-destructive-fg` `text-destructive-fg` `border-destructive-fg` `bg-success` `text-success` `border-success` `bg-success-fg` `text-success-fg` `border-success-fg` `bg-warning` `text-warning` `border-warning` `bg-warning-fg` `text-warning-fg` `border-warning-fg` | — |
105
+ | `bg-fill` `text-fill` `border-fill` `bg-link` `text-link` `border-link` `bg-primary` `text-primary` `border-primary` `bg-primary-fg` `text-primary-fg` `border-primary-fg` `bg-primary-hover` `text-primary-hover` `border-primary-hover` `bg-destructive` `text-destructive` `border-destructive` `bg-destructive-fg` `text-destructive-fg` `border-destructive-fg` `bg-success` `text-success` `border-success` `bg-success-fg` `text-success-fg` `border-success-fg` `bg-warning` `text-warning` `border-warning` `bg-warning-fg` `text-warning-fg` `border-warning-fg` `bg-info` `text-info` `border-info` `bg-info-fg` `text-info-fg` `border-info-fg` | — |
101
106
 
102
107
  ## era-interactive
103
108
 
@@ -479,35 +484,42 @@ Declared in `index.css`.
479
484
  }
480
485
  ```
481
486
 
482
- ## rounded-row
487
+ ## tier-icon · tier-pill · tier-chip · tier-control · tier-bar
483
488
 
484
- **Use it for:** the radius for a row inside a container that pads it by the gutter a nav rail, a command list, any scrolling list of controls. For a row in a menu PANEL, whose inset is the surface-owned frame rather than the gutter, use rounded-menu-item.
489
+ BOUNDED GEOMETRYheight and radius are one decision.
485
490
 
486
- THE ROW RADIUS concentricity for a row that is inset, WITH its inset intact.
491
+ A tier is a geometric type, not two classes a caller must remember to keep in
492
+ sync. `tier-control` establishes h-control AND rd-control; the radius ladder
493
+ then guarantees rd_outer − rd_inner = (h_outer − h_inner)/2 for every pair.
494
+ Width remains the component's decision: a row stretches, a button adds
495
+ min-w-control, and a square control adds w-control.
487
496
 
488
- The law is rd_outer − rd_inner = gap. A row inside a container that pads it by
489
- the gutter therefore takes the container's radius MINUS the gutter — and no
490
- tier radius equals that at more than one density, which is why reaching for
491
- `rounded-control` and hoping looks wrong: in a control-radius pane the row and
492
- the pane end up with identical curvature separated by 5px of gap, two arcs at
493
- one corner with no relationship.
497
+ Declared in `index.css`.
494
498
 
495
- The alternative — deleting the gap so equal radii become legal — is the wrong
496
- trade and was briefly shipped. The ladder exists so a gap and concentric
497
- corners COEXIST; spending one to buy the other gives up the thing it is for.
499
+ ```css
500
+ @utility tier-icon {
501
+ height: var(--era-h-icon);
502
+ border-radius: var(--era-rd-icon);
503
+ }
498
504
 
499
- The calc runs on the ROW, not on the container, which is what makes it
500
- automatic: `--era-container-rd` is inherited, so a container that is not
501
- control-radius publishes its own and every row inside re-derives. era's
502
- command palette does exactly that — it is rd-bar, so its rows resolve to
503
- rd-bar − gap, which IS rd-control (control-inset-bar is the gutter), and the
504
- palette's geometry is unchanged by this.
505
+ @utility tier-pill {
506
+ height: var(--era-h-pill);
507
+ border-radius: var(--era-rd-pill);
508
+ }
505
509
 
506
- Declared in `index.css`.
510
+ @utility tier-chip {
511
+ height: var(--era-h-chip);
512
+ border-radius: var(--era-rd-chip);
513
+ }
507
514
 
508
- ```css
509
- @utility rounded-row {
510
- border-radius: max(0px, calc(var(--era-container-rd, var(--era-rd-control)) - var(--era-gap)));
515
+ @utility tier-control {
516
+ height: var(--era-h-control);
517
+ border-radius: var(--era-rd-control);
518
+ }
519
+
520
+ @utility tier-bar {
521
+ height: var(--era-h-bar);
522
+ border-radius: var(--era-rd-bar);
511
523
  }
512
524
  ```
513
525
 
@@ -532,6 +544,50 @@ Declared in `index.css`.
532
544
  }
533
545
  ```
534
546
 
547
+ ## era-track-well
548
+
549
+ **Use it for:** instead of `shadow-well` on a track whose value is painted by a child that covers the box — a progress/meter/slider track, a custom gauge. For a well holding ordinary content, `shadow-well` is still right.
550
+
551
+ A WELL THAT SURVIVES BEING FILLED.
552
+
553
+ `shadow-well` is an INSET shadow, and an inset shadow belongs to the
554
+ element's background layer — it paints under the element's own descendants.
555
+ That is invisible on a well holding text (a checkbox, a code block: the
556
+ chrome runs around the content) and wrong the moment something paints the
557
+ whole box. Meter, Progress and the Slider track all do exactly that: an
558
+ opaque child, `translateX`-ed to the value. So the well survived only where
559
+ the track was EMPTY — a half-full meter on bevel had a chisel to the right
560
+ of the value and none to the left, which reads as a hole with a sticker
561
+ over it rather than a level inside a well.
562
+
563
+ The fix is painting order, not geometry: the same `--era-shadow-well` on a
564
+ pseudo-element that is the element's LAST child, so it lands above the fill.
565
+ The fill still spans the full box — nothing is inset, no radius is
566
+ re-derived, and 0% and 100% keep their exact ends. The `position: relative`
567
+ is load-bearing (the overlay resolves against this box) and `inherit` on the
568
+ radius keeps it honest under `data-corners="square"`.
569
+
570
+ Surface-agnostic on purpose. It reads the same token every well reads, so
571
+ glass's hairline rim is restored by the same line that restores bevel's
572
+ chisel, and flat — where the token is `none` — renders exactly as before.
573
+ Reported against bevel by a consumer; it was never a bevel bug.
574
+
575
+ Declared in `index.css`.
576
+
577
+ ```css
578
+ @utility era-track-well {
579
+ position: relative;
580
+ &::after {
581
+ content: '';
582
+ position: absolute;
583
+ inset: 0;
584
+ border-radius: inherit;
585
+ box-shadow: var(--era-shadow-well);
586
+ pointer-events: none;
587
+ }
588
+ }
589
+ ```
590
+
535
591
  ## era-skeleton-pulse
536
592
 
537
593
  Skeleton's pulse, on the MOTION AXIS.
@@ -1021,7 +1077,7 @@ Declared in `index.css`.
1021
1077
  }
1022
1078
  ```
1023
1079
 
1024
- ## p-icon-inset-pill · px-icon-inset-pill · py-icon-inset-pill · gap-icon-inset-pill · p-icon-inset-chip · px-icon-inset-chip · py-icon-inset-chip · gap-icon-inset-chip · … (36 in all)
1080
+ ## p-icon-inset-pill · px-icon-inset-pill · py-icon-inset-pill · gap-icon-inset-pill · p-icon-inset-chip · px-icon-inset-chip · py-icon-inset-chip · gap-icon-inset-chip · … (40 in all)
1025
1081
 
1026
1082
  **Use it for:** the concentric inset for a nested pair — (h-container − h-child)/2, the gap that makes a child control sit evenly inside its container, and the radius step that keeps their corners concentric. Named for the pair it joins: gap-icon-inset-control seats an icon in a control.
1027
1083
 
@@ -1038,6 +1094,7 @@ Declared in `index.css`.
1038
1094
  --spacing-pill-inset-bar: var(--era-pill-inset-bar);
1039
1095
  --spacing-chip-inset-control: var(--era-chip-inset-control);
1040
1096
  --spacing-chip-inset-bar: var(--era-chip-inset-bar);
1097
+ --spacing-control-inset-bar: var(--era-control-inset-bar);
1041
1098
  }
1042
1099
  ```
1043
1100
 
@@ -1116,7 +1173,7 @@ Declared in `index.css`.
1116
1173
  }
1117
1174
  ```
1118
1175
 
1119
- ## bg-fill · text-fill · border-fill · bg-link · text-link · border-link · bg-primary · text-primary · … (33 in all)
1176
+ ## bg-fill · text-fill · border-fill · bg-link · text-link · border-link · bg-primary · text-primary · … (39 in all)
1120
1177
 
1121
1178
  The standing pill fill — --era-fill, the latched counterpart of the
1122
1179
  transient highlight. Surface-owned: a composing wash on flat/bevel, the
@@ -1137,5 +1194,7 @@ Declared in `index.css`.
1137
1194
  --color-success-fg: var(--color-success-fg);
1138
1195
  --color-warning: var(--color-warning);
1139
1196
  --color-warning-fg: var(--color-warning-fg);
1197
+ --color-info: var(--color-info);
1198
+ --color-info-fg: var(--color-info-fg);
1140
1199
  }
1141
1200
  ```
@@ -89,10 +89,10 @@
89
89
  * rd-T = scale · (R + (h-T − h-icon) / 2) where R = sp / φ²
90
90
  *
91
91
  * Both R and the per-tier concentric term are multiplied by scale. The corners
92
- * toggle flips scale 1 0 so every rd collapses together. Because scale is
93
- * declared via @property as <number>, a transition on :root animates it when
94
- * the toggle fires sp changes don't trigger the transition, so scrubbing
95
- * stays snappy while the corners flip is smooth.
92
+ * toggle flips scale discretely between 1 and 0 so every radius collapses
93
+ * together. Intermediate scale values are forbidden: they make the radius step
94
+ * `scale × gap` while the physical gap stays fixed, breaking concentricity for
95
+ * the duration of the morph.
96
96
  *
97
97
  * SUBTREE OVERRIDES
98
98
  * =================
@@ -110,13 +110,6 @@
110
110
  * 2rem and no subtree override of --era-sp could retroactively change it.
111
111
  */
112
112
 
113
- /* Typed custom property so the corners toggle animates smoothly. */
114
- @property --era-roundness-scale {
115
- syntax: '<number>';
116
- inherits: true;
117
- initial-value: 1;
118
- }
119
-
120
113
  /* -------------------------------------------------- */
121
114
  /* Derivation — one formula per token, re-evaluated */
122
115
  /* per element so data-mode / data-corners overrides */
@@ -263,6 +256,10 @@
263
256
  (var(--era-h-bar) - var(--era-h-chip)) / 2
264
257
  ); /* 5/7/9 = sp − 3 — inset a chip panel (drag bar) concentrically in a bar-tier
265
258
  pane (= rd-bar − rd-chip) */
259
+ --era-control-inset-bar: calc(
260
+ (var(--era-h-bar) - var(--era-h-control)) / 2
261
+ ); /* 4/5/6/8 — centre a control inside a bar. This happens to equal --era-gap,
262
+ but names the geometry pair instead of borrowing a layout concept. */
266
263
 
267
264
  /* Roundness ladder — golden base + concentric progression, both gated by scale. */
268
265
  /* rd-T = scale · (R + (h-T − h-icon) / 2). R = sp / φ². max(0px, …) clamps tier
@@ -275,12 +272,10 @@
275
272
  center children with the U-inset-T tokens above — nested corners then share
276
273
  a center of curvature with zero arithmetic.
277
274
 
278
- The one trap: a CONTAINER's radius follows its corner REGION's tier, never
279
- its overall bulk. A pane capped by a chip-tier handle bar (icon controls at
280
- icon-inset-chip) is a chip corner region rd-chip, however large the pane.
281
- Choosing rd by visual importance is how concentricity breaks. Precedents:
282
- Chip (chip shell + icon dismiss), Bar (h-T ↔ rd-T), Pane.Root (rd-chip), menu
283
- panels (rd-control, items at era-item-rd = rd-control − panel-inset). */
275
+ Unbounded surfaces do not acquire a tier from their bulk. Their exposed
276
+ corner is either optical (independent of bounded nesting) or declared by a
277
+ corner-bearing child and derived OUTWARD from that child's fixed tier. Never
278
+ derive a bounded child's radius inward from an ancestor. */
284
279
  --era-roundness: calc(var(--era-roundness-scale) * var(--era-sp) / 2.618);
285
280
  --era-rd-icon: max(0px, var(--era-roundness));
286
281
  --era-rd-pill: max(
@@ -433,11 +428,6 @@
433
428
  --era-mask-glyph: disc;
434
429
  }
435
430
 
436
- /* Animate the roundness scale itself not border-radius. sp changes snap
437
- instantly (responsive scrub); the corners toggle animates scale 1 ↔ 0,
438
- which cascades through the rd-* calc chain to every element at 60 fps.
439
- Timed by the motion axis, so instant motion (or reduced-motion) snaps the
440
- corners flip too. */
441
- :root {
442
- transition: --era-roundness-scale var(--era-duration) var(--era-ease);
443
- }
431
+ /* Rounded and square are different corner topologies, so the axis snaps. A
432
+ numeric interpolation would be visibly smooth but mathematically false at
433
+ every intermediate frame. */
@@ -461,32 +461,36 @@
461
461
  }
462
462
  }
463
463
 
464
- /* THE ROW RADIUS concentricity for a row that is inset, WITH its inset intact.
465
- *
466
- * The law is rd_outer rd_inner = gap. A row inside a container that pads it by
467
- * the gutter therefore takes the container's radius MINUS the gutter — and no
468
- * tier radius equals that at more than one density, which is why reaching for
469
- * `rounded-control` and hoping looks wrong: in a control-radius pane the row and
470
- * the pane end up with identical curvature separated by 5px of gap, two arcs at
471
- * one corner with no relationship.
472
- *
473
- * The alternative — deleting the gap so equal radii become legal — is the wrong
474
- * trade and was briefly shipped. The ladder exists so a gap and concentric
475
- * corners COEXIST; spending one to buy the other gives up the thing it is for.
476
- *
477
- * The calc runs on the ROW, not on the container, which is what makes it
478
- * automatic: `--era-container-rd` is inherited, so a container that is not
479
- * control-radius publishes its own and every row inside re-derives. era's
480
- * command palette does exactly that — it is rd-bar, so its rows resolve to
481
- * rd-bar − gap, which IS rd-control (control-inset-bar is the gutter), and the
482
- * palette's geometry is unchanged by this.
483
- *
484
- * @use the radius for a row inside a container that pads it by the gutter — a
485
- * nav rail, a command list, any scrolling list of controls. For a row in a menu
486
- * PANEL, whose inset is the surface-owned frame rather than the gutter, use
487
- * rounded-menu-item. */
488
- @utility rounded-row {
489
- border-radius: max(0px, calc(var(--era-container-rd, var(--era-rd-control)) - var(--era-gap)));
464
+ /* BOUNDED GEOMETRYheight and radius are one decision.
465
+ *
466
+ * A tier is a geometric type, not two classes a caller must remember to keep in
467
+ * sync. `tier-control` establishes h-control AND rd-control; the radius ladder
468
+ * then guarantees rd_outer rd_inner = (h_outer h_inner)/2 for every pair.
469
+ * Width remains the component's decision: a row stretches, a button adds
470
+ * min-w-control, and a square control adds w-control. */
471
+ @utility tier-icon {
472
+ height: var(--era-h-icon);
473
+ border-radius: var(--era-rd-icon);
474
+ }
475
+
476
+ @utility tier-pill {
477
+ height: var(--era-h-pill);
478
+ border-radius: var(--era-rd-pill);
479
+ }
480
+
481
+ @utility tier-chip {
482
+ height: var(--era-h-chip);
483
+ border-radius: var(--era-rd-chip);
484
+ }
485
+
486
+ @utility tier-control {
487
+ height: var(--era-h-control);
488
+ border-radius: var(--era-rd-control);
489
+ }
490
+
491
+ @utility tier-bar {
492
+ height: var(--era-h-bar);
493
+ border-radius: var(--era-rd-bar);
490
494
  }
491
495
 
492
496
  /* One LINE BOX tall — the height of the text this stands in for.
@@ -650,7 +654,7 @@
650
654
  /* THE PUBLIC CLASS API */
651
655
  /* */
652
656
  /* Era's consumer-facing styling surface is NAMED utilities — a user */
653
- /* of the library writes `h-control rounded-control px-gutter shadow`, never */
657
+ /* of the library writes `tier-control px-gutter shadow`, never */
654
658
  /* `h-(--era-h-control)`-style var classes. Where Tailwind already has a */
655
659
  /* name for the concept (rounded-control, shadow, ease) era OVERLOADS it */
656
660
  /* so stock-looking markup renders on-design; where it doesn't, era */
@@ -805,6 +809,7 @@
805
809
  --spacing-pill-inset-bar: var(--era-pill-inset-bar);
806
810
  --spacing-chip-inset-control: var(--era-chip-inset-control);
807
811
  --spacing-chip-inset-bar: var(--era-chip-inset-bar);
812
+ --spacing-control-inset-bar: var(--era-control-inset-bar);
808
813
  /* @use the glyph size for a tier — the INK inside the box, not the box
809
814
  * (size-glyph-pill is the dismiss × in a pill, size-pill is the pill). */
810
815
  --spacing-glyph-icon: var(--era-glyph-icon);
@@ -14,7 +14,7 @@
14
14
  <Accordion.Trigger
15
15
  bind:ref
16
16
  class={cn(
17
- 'flex h-control w-full items-center justify-between gap-icon-inset-chip rounded-control px-inset-control text-body text-fg hover:bg-highlight hover:shadow-highlight focus:outline-none disabled:opacity-50',
17
+ 'flex tier-control w-full items-center justify-between gap-icon-inset-chip px-inset-control text-body text-fg hover:bg-highlight hover:shadow-highlight focus:outline-none disabled:opacity-50',
18
18
  className
19
19
  )}
20
20
  {...restProps}
@@ -12,6 +12,7 @@
12
12
  variant,
13
13
  icon,
14
14
  loading,
15
+ busy,
15
16
  loadingLabel,
16
17
  children,
17
18
  class: className,
@@ -32,6 +33,7 @@
32
33
  {variant}
33
34
  {icon}
34
35
  {loading}
36
+ {busy}
35
37
  {loadingLabel}
36
38
  class={className}
37
39
  >
@@ -10,6 +10,7 @@
10
10
  variant,
11
11
  icon,
12
12
  loading,
13
+ busy,
13
14
  loadingLabel,
14
15
  children,
15
16
  class: className,
@@ -30,6 +31,7 @@
30
31
  {variant}
31
32
  {icon}
32
33
  {loading}
34
+ {busy}
33
35
  {loadingLabel}
34
36
  class={className}
35
37
  >
@@ -19,7 +19,7 @@
19
19
  // that cost. A badge holding an icon must stay flex to lay the icon out
20
20
  // beside the text, and keeps the general trim — its line-height:1 plus
21
21
  // flex centring, which is the best available when the text is anonymous.
22
- base: 'inline-flex h-pill min-w-pill shrink-0 items-center justify-center gap-gutter rounded-pill px-pill font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-inset-pill era-text-trim not-has-[svg]:era-token-ink',
22
+ base: 'inline-flex min-w-pill shrink-0 items-center justify-center gap-gutter tier-pill px-pill font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-inset-pill era-text-trim not-has-[svg]:era-token-ink',
23
23
  variants: {
24
24
  tone: {
25
25
  default: 'bg-fill text-fg',
@@ -56,7 +56,13 @@
56
56
  } = $props();
57
57
  </script>
58
58
 
59
- <span bind:this={ref} class={cn(badgeVariants({ tone }), className)} {...restProps}>
59
+ <span
60
+ bind:this={ref}
61
+ data-era-geometry="bounded"
62
+ data-era-tier="pill"
63
+ class={cn(badgeVariants({ tone }), className)}
64
+ {...restProps}
65
+ >
60
66
  {#if lead}
61
67
  {@render lead()}
62
68
  {/if}
@@ -7,7 +7,7 @@ export declare const badgeVariants: import("tailwind-variants").TVReturnType<{
7
7
  warning: "bg-warning text-warning-fg";
8
8
  info: "bg-info text-info-fg";
9
9
  };
10
- }, undefined, "inline-flex h-pill min-w-pill shrink-0 items-center justify-center gap-gutter rounded-pill px-pill font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-inset-pill era-text-trim not-has-[svg]:era-token-ink", {
10
+ }, undefined, "inline-flex min-w-pill shrink-0 items-center justify-center gap-gutter tier-pill px-pill font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-inset-pill era-text-trim not-has-[svg]:era-token-ink", {
11
11
  tone: {
12
12
  default: "bg-fill text-fg";
13
13
  accent: "bg-primary text-primary-fg";