@signal9/era-ui 30.8.0 → 32.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 (116) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/ai/artifact/artifact.svelte +1 -1
  3. package/dist/ai/conversations/conversations-item.svelte +1 -1
  4. package/dist/ai/plan/plan-header.svelte +1 -1
  5. package/dist/ai/prompt-input/prompt-input-attachments.svelte +2 -6
  6. package/dist/ai/swarm/swarm-node.svelte +1 -1
  7. package/dist/ai/todo/todo-header.svelte +1 -1
  8. package/dist/ai/todo/todo-item.svelte +1 -1
  9. package/dist/ai/tool/tool-chip.svelte +1 -1
  10. package/dist/ai/web-preview/web-preview.svelte +2 -2
  11. package/dist/ai/workflow/node.svelte +1 -1
  12. package/dist/dev/audit/audits/wall-is-isotropic.js +1 -1
  13. package/dist/docs/css-utilities.js +9 -5
  14. package/dist/era-ui.css +1 -1
  15. package/dist/generated-docs/badge.md +1 -1
  16. package/dist/generated-docs/bar.md +22 -4
  17. package/dist/generated-docs/button.md +38 -4
  18. package/dist/generated-docs/card.md +35 -5
  19. package/dist/generated-docs/chip.md +16 -0
  20. package/dist/generated-docs/llms-full.txt +370 -70
  21. package/dist/generated-docs/llms.txt +8 -4
  22. package/dist/generated-docs/manifest.json +85 -28
  23. package/dist/generated-docs/meter.md +79 -1
  24. package/dist/generated-docs/nav.md +8 -1
  25. package/dist/generated-docs/separator.md +18 -0
  26. package/dist/generated-docs/utilities.json +68 -15
  27. package/dist/generated-docs/utilities.md +145 -50
  28. package/dist/os/notification-center.svelte +1 -1
  29. package/dist/os/taskbar.svelte +1 -1
  30. package/dist/os/toast.svelte +1 -1
  31. package/dist/styles/density.css +15 -25
  32. package/dist/styles/index.css +74 -48
  33. package/dist/styles/motion.css +1 -1
  34. package/dist/styles/prose.css +1 -1
  35. package/dist/styles/surfaces/bevel.css +16 -19
  36. package/dist/styles/surfaces/contract.css +50 -0
  37. package/dist/styles/surfaces/flat.css +27 -0
  38. package/dist/styles/surfaces/glass.css +11 -8
  39. package/dist/ui/accordion/accordion-trigger.svelte +1 -1
  40. package/dist/ui/alert-dialog/alert-dialog-content.svelte +1 -1
  41. package/dist/ui/badge/badge.svelte +8 -2
  42. package/dist/ui/badge/badge.svelte.d.ts +1 -1
  43. package/dist/ui/bar/bar.svelte +95 -26
  44. package/dist/ui/bar/bar.svelte.d.ts +71 -31
  45. package/dist/ui/button/button.svelte +6 -0
  46. package/dist/ui/button/button.svelte.d.ts +2 -0
  47. package/dist/ui/button/variants.d.ts +3 -3
  48. package/dist/ui/button/variants.js +6 -11
  49. package/dist/ui/calendar/calendar-day.svelte +1 -1
  50. package/dist/ui/card/card.svelte +23 -10
  51. package/dist/ui/card/card.svelte.d.ts +19 -7
  52. package/dist/ui/chip/chip.svelte +5 -2
  53. package/dist/ui/chip/chip.svelte.d.ts +6 -6
  54. package/dist/ui/collapsible/collapsible-trigger.svelte +1 -1
  55. package/dist/ui/combobox/combobox-content.svelte +1 -1
  56. package/dist/ui/combobox/combobox-trigger.svelte +1 -1
  57. package/dist/ui/command/command-item.svelte +1 -1
  58. package/dist/ui/command/command-link-item.svelte +1 -1
  59. package/dist/ui/command/command-root.svelte +5 -7
  60. package/dist/ui/command-bar/command-bar.svelte +3 -3
  61. package/dist/ui/context-menu/context-menu-content.svelte +1 -4
  62. package/dist/ui/context-menu/context-menu-sub-content.svelte +1 -4
  63. package/dist/ui/date-field/date-field-segment.svelte +1 -1
  64. package/dist/ui/date-picker/date-picker-content.svelte +1 -1
  65. package/dist/ui/date-picker/date-picker-day.svelte +1 -1
  66. package/dist/ui/date-picker/date-picker-segment.svelte +1 -1
  67. package/dist/ui/date-picker/date-picker-trigger.svelte +1 -1
  68. package/dist/ui/date-range-field/date-range-field-segment.svelte +1 -1
  69. package/dist/ui/date-range-picker/date-range-picker-content.svelte +1 -1
  70. package/dist/ui/date-range-picker/date-range-picker-day.svelte +1 -1
  71. package/dist/ui/date-range-picker/date-range-picker-segment.svelte +1 -1
  72. package/dist/ui/date-range-picker/date-range-picker-trigger.svelte +1 -1
  73. package/dist/ui/dialog/dialog-content.svelte +20 -2
  74. package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
  75. package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -4
  76. package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -1
  77. package/dist/ui/file-upload/file-upload.svelte +1 -1
  78. package/dist/ui/input/variants.d.ts +6 -6
  79. package/dist/ui/input/variants.js +2 -2
  80. package/dist/ui/kv/kv.svelte +1 -1
  81. package/dist/ui/link-preview/link-preview-content.svelte +1 -1
  82. package/dist/ui/menubar/menubar-content.svelte +1 -1
  83. package/dist/ui/menubar/menubar-root.svelte +1 -1
  84. package/dist/ui/menubar/menubar-sub-content.svelte +1 -4
  85. package/dist/ui/menubar/menubar-trigger.svelte +1 -1
  86. package/dist/ui/meter/meter.svelte +1 -2
  87. package/dist/ui/nav/nav-bar.svelte +9 -4
  88. package/dist/ui/nav/nav-bar.svelte.d.ts +2 -1
  89. package/dist/ui/nav/nav-item.svelte +1 -0
  90. package/dist/ui/pane/pane-root.svelte +3 -12
  91. package/dist/ui/pane/pane-tab.svelte +1 -1
  92. package/dist/ui/popover/popover-content.svelte +1 -1
  93. package/dist/ui/provider/surfaces.d.ts +1 -1
  94. package/dist/ui/provider/surfaces.js +1 -1
  95. package/dist/ui/select/select-content.svelte +1 -1
  96. package/dist/ui/select/select-trigger.svelte +1 -1
  97. package/dist/ui/sheet/sheet-content.svelte +1 -1
  98. package/dist/ui/sheet/sheet-content.svelte.d.ts +1 -1
  99. package/dist/ui/slider/slider-thumb.svelte +1 -1
  100. package/dist/ui/step/step-summary.svelte +1 -1
  101. package/dist/ui/switch/switch.svelte +3 -3
  102. package/dist/ui/time-field/time-field-segment.svelte +1 -1
  103. package/dist/ui/time-range-field/time-range-field-segment.svelte +1 -1
  104. package/dist/ui/timeline/timeline-step.svelte +1 -1
  105. package/dist/ui/toggle-group/toggle-group-item.svelte +2 -2
  106. package/dist/ui/toggle-group/toggle-group-item.svelte.d.ts +6 -6
  107. package/dist/ui/toggle-group/toggle-group-root.svelte +1 -2
  108. package/dist/ui/toggle-group/toggle-group-root.svelte.d.ts +3 -3
  109. package/dist/ui/tooltip/tooltip-content.svelte +1 -1
  110. package/dist/ui/tree/tree-item.svelte +1 -1
  111. package/dist/ui/video-player/video-player.svelte +2 -2
  112. package/dist/utils/tw-merge-config.d.ts +6 -1
  113. package/dist/utils/tw-merge-config.js +6 -2
  114. package/package.json +5 -3
  115. package/skill/SKILL.md +26 -21
  116. package/dist/styles/surfaces/base.css +0 -257
@@ -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
@@ -69,9 +73,13 @@ source uses it where a name exists.
69
73
  | `era-ink-center` | on a native single-line <input> (or the text layer that must mirror one) inside a fixed-height field — never alongside era-text-trim. |
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
- | `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. |
76
+ | `surface-backdrop` | the surface-aware backdrop treatment without choosing a fill or relief. Prefer material-popover/material-panel when the element is a complete surface. |
77
+ | `material-popover` | a floating transient surface: menus, popovers, tooltips, pickers, toasts. Binds its raised fill, floating relief, and backdrop treatment atomically. |
78
+ | `material-panel` | a floating persistent surface: dialogs, sheets, panes, inspectors. Binds its resting fill, floating relief, and backdrop treatment atomically. |
79
+ | `pressable` | the physical press contract on a control whose direct content plane is marked data-era-press-content. The surface owns relief/travel; the component may scale travel with --era-press-scale. |
80
+ | `tier-icon` `tier-pill` `tier-chip` `tier-control` `tier-bar` | — |
74
81
  | `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. |
82
+ | `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
83
  | `era-skeleton-pulse` | — |
76
84
  | `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
85
  | `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 +100,12 @@ source uses it where a name exists.
92
100
  | `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
101
  | `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
102
  | `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. |
103
+ | `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
104
  | `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
105
  | `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
106
  | `ease-base` | — |
99
107
  | `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` | — |
108
+ | `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
109
 
102
110
  ## era-interactive
103
111
 
@@ -450,64 +458,104 @@ Declared in `index.css`.
450
458
  }
451
459
  ```
452
460
 
453
- ## glass-blur
461
+ ## surface-backdrop
454
462
 
455
- **Use it for:** 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.
463
+ **Use it for:** the surface-aware backdrop treatment without choosing a fill or relief. Prefer material-popover/material-panel when the element is a complete surface.
456
464
 
457
- Tailwind v4 cannot resolve var() inside backdrop-blur-[], so the filter
458
- is a registered utility, gated to glass subtrees.
465
+ Declared in `index.css`.
459
466
 
460
- It lives HERE rather than in surfaces/glass.css, where a surface's own
461
- chrome would naturally sit, because glass.css is imported into a cascade
462
- layer and `@utility` cannot be nested inside one. Surface-scoped RULES stay
463
- in their surface file; surface-scoped UTILITIES have to come out.
467
+ ```css
468
+ @utility surface-backdrop {
469
+ backdrop-filter: var(--era-backdrop-filter);
470
+ -webkit-backdrop-filter: var(--era-backdrop-filter);
471
+ }
472
+ ```
473
+
474
+ ## material-popover
475
+
476
+ **Use it for:** a floating transient surface: menus, popovers, tooltips, pickers, toasts. Binds its raised fill, floating relief, and backdrop treatment atomically.
464
477
 
465
478
  Declared in `index.css`.
466
479
 
467
480
  ```css
468
- @utility` cannot be nested inside one. Surface-scoped RULES stay
469
- * in their surface file; surface-scoped UTILITIES have to come out.
470
- *
471
- * @use the backdrop blur for a floating panel (popover, dialog, taskbar) — it
472
- * applies only inside a data-surface="glass" subtree and is inert elsewhere,
473
- * so it is safe to leave on a panel that renders under every surface. */
474
- @utility glass-blur {
475
- [data-surface='glass'] & {
476
- backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
477
- -webkit-backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
478
- }
481
+ @utility material-popover {
482
+ background-color: var(--era-surface-bg-elevated);
483
+ box-shadow: var(--era-shadow-lg);
484
+ backdrop-filter: var(--era-backdrop-filter);
485
+ -webkit-backdrop-filter: var(--era-backdrop-filter);
479
486
  }
480
487
  ```
481
488
 
482
- ## rounded-row
489
+ ## material-panel
483
490
 
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.
491
+ **Use it for:** a floating persistent surface: dialogs, sheets, panes, inspectors. Binds its resting fill, floating relief, and backdrop treatment atomically.
485
492
 
486
- THE ROW RADIUS — concentricity for a row that is inset, WITH its inset intact.
493
+ Declared in `index.css`.
487
494
 
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.
495
+ ```css
496
+ @utility material-panel {
497
+ background-color: var(--era-surface-bg);
498
+ box-shadow: var(--era-shadow-lg);
499
+ backdrop-filter: var(--era-backdrop-filter);
500
+ -webkit-backdrop-filter: var(--era-backdrop-filter);
501
+ }
502
+ ```
494
503
 
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.
504
+ ## pressable
498
505
 
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.
506
+ **Use it for:** the physical press contract on a control whose direct content plane is marked data-era-press-content. The surface owns relief/travel; the component may scale travel with --era-press-scale.
505
507
 
506
508
  Declared in `index.css`.
507
509
 
508
510
  ```css
509
- @utility rounded-row {
510
- border-radius: max(0px, calc(var(--era-container-rd, var(--era-rd-control)) - var(--era-gap)));
511
+ @utility pressable {
512
+ &:active {
513
+ box-shadow: var(--era-shadow-pressed);
514
+ }
515
+
516
+ &:active > [data-era-press-content] {
517
+ translate: var(--era-press-effective-x, var(--era-press-sink-x))
518
+ var(--era-press-effective-y, var(--era-press-sink-y));
519
+ }
520
+ }
521
+ ```
522
+
523
+ ## tier-icon · tier-pill · tier-chip · tier-control · tier-bar
524
+
525
+ BOUNDED GEOMETRY — height and radius are one decision.
526
+
527
+ A tier is a geometric type, not two classes a caller must remember to keep in
528
+ sync. `tier-control` establishes h-control AND rd-control; the radius ladder
529
+ then guarantees rd_outer − rd_inner = (h_outer − h_inner)/2 for every pair.
530
+ Width remains the component's decision: a row stretches, a button adds
531
+ min-w-control, and a square control adds w-control.
532
+
533
+ Declared in `index.css`.
534
+
535
+ ```css
536
+ @utility tier-icon {
537
+ height: var(--era-h-icon);
538
+ border-radius: var(--era-rd-icon);
539
+ }
540
+
541
+ @utility tier-pill {
542
+ height: var(--era-h-pill);
543
+ border-radius: var(--era-rd-pill);
544
+ }
545
+
546
+ @utility tier-chip {
547
+ height: var(--era-h-chip);
548
+ border-radius: var(--era-rd-chip);
549
+ }
550
+
551
+ @utility tier-control {
552
+ height: var(--era-h-control);
553
+ border-radius: var(--era-rd-control);
554
+ }
555
+
556
+ @utility tier-bar {
557
+ height: var(--era-h-bar);
558
+ border-radius: var(--era-rd-bar);
511
559
  }
512
560
  ```
513
561
 
@@ -532,6 +580,50 @@ Declared in `index.css`.
532
580
  }
533
581
  ```
534
582
 
583
+ ## era-track-well
584
+
585
+ **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.
586
+
587
+ A WELL THAT SURVIVES BEING FILLED.
588
+
589
+ `shadow-well` is an INSET shadow, and an inset shadow belongs to the
590
+ element's background layer — it paints under the element's own descendants.
591
+ That is invisible on a well holding text (a checkbox, a code block: the
592
+ chrome runs around the content) and wrong the moment something paints the
593
+ whole box. Meter, Progress and the Slider track all do exactly that: an
594
+ opaque child, `translateX`-ed to the value. So the well survived only where
595
+ the track was EMPTY — a half-full meter on bevel had a chisel to the right
596
+ of the value and none to the left, which reads as a hole with a sticker
597
+ over it rather than a level inside a well.
598
+
599
+ The fix is painting order, not geometry: the same `--era-shadow-well` on a
600
+ pseudo-element that is the element's LAST child, so it lands above the fill.
601
+ The fill still spans the full box — nothing is inset, no radius is
602
+ re-derived, and 0% and 100% keep their exact ends. The `position: relative`
603
+ is load-bearing (the overlay resolves against this box) and `inherit` on the
604
+ radius keeps it honest under `data-corners="square"`.
605
+
606
+ Surface-agnostic on purpose. It reads the same token every well reads, so
607
+ glass's hairline rim is restored by the same line that restores bevel's
608
+ chisel, and flat — where the token is `none` — renders exactly as before.
609
+ Reported against bevel by a consumer; it was never a bevel bug.
610
+
611
+ Declared in `index.css`.
612
+
613
+ ```css
614
+ @utility era-track-well {
615
+ position: relative;
616
+ &::after {
617
+ content: '';
618
+ position: absolute;
619
+ inset: 0;
620
+ border-radius: inherit;
621
+ box-shadow: var(--era-shadow-well);
622
+ pointer-events: none;
623
+ }
624
+ }
625
+ ```
626
+
535
627
  ## era-skeleton-pulse
536
628
 
537
629
  Skeleton's pulse, on the MOTION AXIS.
@@ -957,8 +1049,8 @@ Declared in `index.css`.
957
1049
  ## bg-well · text-well · border-well · bg-elevated · text-elevated · border-elevated · bg-highlight · text-highlight · … (12 in all)
958
1050
 
959
1051
  Surface fills — bg-well is the surface's own resting fill (pairs with
960
- shadow-well for recessed chrome), bg-elevated the raised tier (pairs with
961
- shadow-lg + glass-blur for floating chrome), bg-highlight the transient
1052
+ shadow-well for recessed chrome), bg-elevated the raised tier,
1053
+ bg-highlight the transient
962
1054
  hover/focus/keyboard wash (pairs with shadow-highlight; latched states
963
1055
  stay on opaque bg-hover), bg-overlay the modal scrim.
964
1056
 
@@ -1021,7 +1113,7 @@ Declared in `index.css`.
1021
1113
  }
1022
1114
  ```
1023
1115
 
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)
1116
+ ## 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
1117
 
1026
1118
  **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
1119
 
@@ -1038,6 +1130,7 @@ Declared in `index.css`.
1038
1130
  --spacing-pill-inset-bar: var(--era-pill-inset-bar);
1039
1131
  --spacing-chip-inset-control: var(--era-chip-inset-control);
1040
1132
  --spacing-chip-inset-bar: var(--era-chip-inset-bar);
1133
+ --spacing-control-inset-bar: var(--era-control-inset-bar);
1041
1134
  }
1042
1135
  ```
1043
1136
 
@@ -1116,7 +1209,7 @@ Declared in `index.css`.
1116
1209
  }
1117
1210
  ```
1118
1211
 
1119
- ## bg-fill · text-fill · border-fill · bg-link · text-link · border-link · bg-primary · text-primary · … (33 in all)
1212
+ ## bg-fill · text-fill · border-fill · bg-link · text-link · border-link · bg-primary · text-primary · … (39 in all)
1120
1213
 
1121
1214
  The standing pill fill — --era-fill, the latched counterpart of the
1122
1215
  transient highlight. Surface-owned: a composing wash on flat/bevel, the
@@ -1137,5 +1230,7 @@ Declared in `index.css`.
1137
1230
  --color-success-fg: var(--color-success-fg);
1138
1231
  --color-warning: var(--color-warning);
1139
1232
  --color-warning-fg: var(--color-warning-fg);
1233
+ --color-info: var(--color-info);
1234
+ --color-info-fg: var(--color-info-fg);
1140
1235
  }
1141
1236
  ```
@@ -51,7 +51,7 @@
51
51
  transition:eraFly={{ x: 320, duration: 200 }}
52
52
  style:top="{pm.insets.top}px"
53
53
  style:bottom="{pm.insets.bottom}px"
54
- class="absolute right-0 flex w-80 flex-col bg-well shadow-lg glass-blur"
54
+ class="absolute right-0 flex w-80 flex-col material-panel"
55
55
  >
56
56
  <header
57
57
  class="flex shrink-0 items-center gap-gutter border-b border-divider-faded px-card py-inset-chip"
@@ -210,7 +210,7 @@
210
210
  // shadow-lg only when FLOATING. Flush, the bar's edge is its own
211
211
  // border-b/border-t against the content it docks beside, and the ring would
212
212
  // double it on exactly that side — the one edge a flush bar exposes.
213
- 'absolute flex h-bar items-center gap-gutter bg-elevated px-gutter glass-blur',
213
+ 'absolute flex h-bar items-center gap-gutter bg-elevated px-gutter surface-backdrop',
214
214
  floating && 'shadow-lg',
215
215
  floating
216
216
  ? cn('inset-x-gutter rounded-bar', position === 'top' ? 'top-gutter' : 'bottom-gutter')
@@ -16,7 +16,7 @@
16
16
  </script>
17
17
 
18
18
  <div
19
- class="pointer-events-auto relative flex w-80 gap-inset-chip overflow-hidden rounded-bar bg-elevated p-card shadow-lg glass-blur"
19
+ class="pointer-events-auto relative flex w-80 gap-inset-chip overflow-hidden rounded-bar material-popover p-card"
20
20
  >
21
21
  <span class={cn('absolute inset-y-menu left-menu w-1 rounded-full', accent[n.tone])}></span>
22
22
  {#if n.icon}
@@ -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. */
@@ -36,10 +36,9 @@
36
36
  * surfaces so a [data-surface] block can override --era-ease as a delta.
37
37
  * UNLAYERED: carries the prefers-reduced-motion !important — see above. */
38
38
  @import './motion.css';
39
- /* Surfaces: base contract first, then each surface's deltas. Order matters —
40
- * the base block and a surface's block target the same element, and the
41
- * surface's declarations must win. Same layer, so source order still decides. */
42
- @import './surfaces/base.css' layer(era.tokens);
39
+ /* Material contract first, then complete surface implementations. */
40
+ @import './surfaces/contract.css' layer(era.tokens);
41
+ @import './surfaces/flat.css' layer(era.tokens);
43
42
  @import './surfaces/glass.css' layer(era.tokens);
44
43
  @import './surfaces/bevel.css' layer(era.tokens);
45
44
  /* UNLAYERED: carries the font-axis !important — see above. */
@@ -443,50 +442,76 @@
443
442
  text-box: trim-both ex alphabetic;
444
443
  }
445
444
 
446
- /* Tailwind v4 cannot resolve var() inside backdrop-blur-[], so the filter
447
- * is a registered utility, gated to glass subtrees.
448
- *
449
- * It lives HERE rather than in surfaces/glass.css, where a surface's own
450
- * chrome would naturally sit, because glass.css is imported into a cascade
451
- * layer and `@utility` cannot be nested inside one. Surface-scoped RULES stay
452
- * in their surface file; surface-scoped UTILITIES have to come out.
453
- *
454
- * @use the backdrop blur for a floating panel (popover, dialog, taskbar) it
455
- * applies only inside a data-surface="glass" subtree and is inert elsewhere,
456
- * so it is safe to leave on a panel that renders under every surface. */
457
- @utility glass-blur {
458
- [data-surface='glass'] & {
459
- backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
460
- -webkit-backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
445
+ /* MATERIAL ROLES components choose meaning; surfaces choose appearance. */
446
+ /* @use the surface-aware backdrop treatment without choosing a fill or relief.
447
+ * Prefer material-popover/material-panel when the element is a complete surface. */
448
+ @utility surface-backdrop {
449
+ backdrop-filter: var(--era-backdrop-filter);
450
+ -webkit-backdrop-filter: var(--era-backdrop-filter);
451
+ }
452
+
453
+ /* @use a floating transient surface: menus, popovers, tooltips, pickers, toasts.
454
+ * Binds its raised fill, floating relief, and backdrop treatment atomically. */
455
+ @utility material-popover {
456
+ background-color: var(--era-surface-bg-elevated);
457
+ box-shadow: var(--era-shadow-lg);
458
+ backdrop-filter: var(--era-backdrop-filter);
459
+ -webkit-backdrop-filter: var(--era-backdrop-filter);
460
+ }
461
+
462
+ /* @use a floating persistent surface: dialogs, sheets, panes, inspectors.
463
+ * Binds its resting fill, floating relief, and backdrop treatment atomically. */
464
+ @utility material-panel {
465
+ background-color: var(--era-surface-bg);
466
+ box-shadow: var(--era-shadow-lg);
467
+ backdrop-filter: var(--era-backdrop-filter);
468
+ -webkit-backdrop-filter: var(--era-backdrop-filter);
469
+ }
470
+
471
+ /* @use the physical press contract on a control whose direct content plane is
472
+ * marked data-era-press-content. The surface owns relief/travel; compact
473
+ * components select the compact travel primitives as their effective values. */
474
+ @utility pressable {
475
+ &:active {
476
+ box-shadow: var(--era-shadow-pressed);
477
+ }
478
+
479
+ &:active > [data-era-press-content] {
480
+ translate: var(--era-press-effective-x, var(--era-press-sink-x))
481
+ var(--era-press-effective-y, var(--era-press-sink-y));
461
482
  }
462
483
  }
463
484
 
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)));
485
+ /* BOUNDED GEOMETRYheight and radius are one decision.
486
+ *
487
+ * A tier is a geometric type, not two classes a caller must remember to keep in
488
+ * sync. `tier-control` establishes h-control AND rd-control; the radius ladder
489
+ * then guarantees rd_outer rd_inner = (h_outer h_inner)/2 for every pair.
490
+ * Width remains the component's decision: a row stretches, a button adds
491
+ * min-w-control, and a square control adds w-control. */
492
+ @utility tier-icon {
493
+ height: var(--era-h-icon);
494
+ border-radius: var(--era-rd-icon);
495
+ }
496
+
497
+ @utility tier-pill {
498
+ height: var(--era-h-pill);
499
+ border-radius: var(--era-rd-pill);
500
+ }
501
+
502
+ @utility tier-chip {
503
+ height: var(--era-h-chip);
504
+ border-radius: var(--era-rd-chip);
505
+ }
506
+
507
+ @utility tier-control {
508
+ height: var(--era-h-control);
509
+ border-radius: var(--era-rd-control);
510
+ }
511
+
512
+ @utility tier-bar {
513
+ height: var(--era-h-bar);
514
+ border-radius: var(--era-rd-bar);
490
515
  }
491
516
 
492
517
  /* One LINE BOX tall — the height of the text this stands in for.
@@ -650,7 +675,7 @@
650
675
  /* THE PUBLIC CLASS API */
651
676
  /* */
652
677
  /* Era's consumer-facing styling surface is NAMED utilities — a user */
653
- /* of the library writes `h-control rounded-control px-gutter shadow`, never */
678
+ /* of the library writes `tier-control px-gutter shadow`, never */
654
679
  /* `h-(--era-h-control)`-style var classes. Where Tailwind already has a */
655
680
  /* name for the concept (rounded-control, shadow, ease) era OVERLOADS it */
656
681
  /* so stock-looking markup renders on-design; where it doesn't, era */
@@ -754,8 +779,8 @@
754
779
  --shadow-pressed: var(--era-shadow-pressed);
755
780
  --shadow-highlight: var(--era-highlight-shadow);
756
781
  /* Surface fills — bg-well is the surface's own resting fill (pairs with
757
- * shadow-well for recessed chrome), bg-elevated the raised tier (pairs with
758
- * shadow-lg + glass-blur for floating chrome), bg-highlight the transient
782
+ * shadow-well for recessed chrome), bg-elevated the raised tier,
783
+ * bg-highlight the transient
759
784
  * hover/focus/keyboard wash (pairs with shadow-highlight; latched states
760
785
  * stay on opaque bg-hover), bg-overlay the modal scrim. */
761
786
  --color-well: var(--era-surface-bg);
@@ -805,6 +830,7 @@
805
830
  --spacing-pill-inset-bar: var(--era-pill-inset-bar);
806
831
  --spacing-chip-inset-control: var(--era-chip-inset-control);
807
832
  --spacing-chip-inset-bar: var(--era-chip-inset-bar);
833
+ --spacing-control-inset-bar: var(--era-control-inset-bar);
808
834
  /* @use the glyph size for a tier — the INK inside the box, not the box
809
835
  * (size-glyph-pill is the dismiss × in a pill, size-pill is the pill). */
810
836
  --spacing-glyph-icon: var(--era-glyph-icon);
@@ -22,7 +22,7 @@
22
22
  *
23
23
  * Surfaces may override --era-ease (or a component-scoped ease token) as a
24
24
  * delta — motion.css is imported BEFORE surfaces/, so a [data-surface] block
25
- * wins. See surfaces/base.css for the surface-animation recipe.
25
+ * wins. Surface implementations may override only the easing primitive.
26
26
  */
27
27
 
28
28
  /* Named easing curves — design constants, referenced by the levels below. */
@@ -372,7 +372,7 @@
372
372
  /* NO ::selection rule here, deliberately.
373
373
  *
374
374
  * era owns text selection as a surface token contract — --era-selection-bg /
375
- * -fg / -shadow (surfaces/base.css), which is how flat gets inverse video,
375
+ * -fg / -shadow (surfaces/contract.css), which is how flat gets inverse video,
376
376
  * glass a glowing wash, and bevel an opaque accent bar. This file used to
377
377
  * override it with `background: var(--color-4)` and no `color`, which threw
378
378
  * all of that away AND left body text on a mid-scale grey: the band barely