@signal9/era-ui 31.0.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 (59) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/ai/artifact/artifact.svelte +1 -1
  3. package/dist/ai/web-preview/web-preview.svelte +1 -1
  4. package/dist/ai/workflow/node.svelte +1 -1
  5. package/dist/docs/css-utilities.js +2 -2
  6. package/dist/era-ui.css +1 -1
  7. package/dist/generated-docs/llms-full.txt +61 -22
  8. package/dist/generated-docs/llms.txt +4 -1
  9. package/dist/generated-docs/manifest.json +41 -8
  10. package/dist/generated-docs/utilities.json +36 -6
  11. package/dist/generated-docs/utilities.md +57 -21
  12. package/dist/os/notification-center.svelte +1 -1
  13. package/dist/os/taskbar.svelte +1 -1
  14. package/dist/os/toast.svelte +1 -1
  15. package/dist/styles/index.css +42 -21
  16. package/dist/styles/motion.css +1 -1
  17. package/dist/styles/prose.css +1 -1
  18. package/dist/styles/surfaces/bevel.css +16 -19
  19. package/dist/styles/surfaces/contract.css +50 -0
  20. package/dist/styles/surfaces/flat.css +27 -0
  21. package/dist/styles/surfaces/glass.css +11 -8
  22. package/dist/ui/alert-dialog/alert-dialog-content.svelte +1 -1
  23. package/dist/ui/button/button.svelte +1 -0
  24. package/dist/ui/button/variants.d.ts +3 -3
  25. package/dist/ui/button/variants.js +3 -3
  26. package/dist/ui/card/card.svelte +4 -4
  27. package/dist/ui/card/card.svelte.d.ts +3 -3
  28. package/dist/ui/combobox/combobox-content.svelte +1 -1
  29. package/dist/ui/command/command-root.svelte +3 -3
  30. package/dist/ui/command-bar/command-bar.svelte +3 -3
  31. package/dist/ui/context-menu/context-menu-content.svelte +1 -4
  32. package/dist/ui/context-menu/context-menu-sub-content.svelte +1 -4
  33. package/dist/ui/date-picker/date-picker-content.svelte +1 -1
  34. package/dist/ui/date-range-picker/date-range-picker-content.svelte +1 -1
  35. package/dist/ui/dialog/dialog-content.svelte +1 -1
  36. package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
  37. package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -4
  38. package/dist/ui/link-preview/link-preview-content.svelte +1 -1
  39. package/dist/ui/menubar/menubar-content.svelte +1 -1
  40. package/dist/ui/menubar/menubar-sub-content.svelte +1 -4
  41. package/dist/ui/meter/meter.svelte +1 -2
  42. package/dist/ui/nav/nav-item.svelte +1 -0
  43. package/dist/ui/pane/pane-root.svelte +1 -1
  44. package/dist/ui/popover/popover-content.svelte +1 -1
  45. package/dist/ui/provider/surfaces.d.ts +1 -1
  46. package/dist/ui/provider/surfaces.js +1 -1
  47. package/dist/ui/select/select-content.svelte +1 -1
  48. package/dist/ui/sheet/sheet-content.svelte +1 -1
  49. package/dist/ui/sheet/sheet-content.svelte.d.ts +1 -1
  50. package/dist/ui/switch/switch.svelte +1 -1
  51. package/dist/ui/toggle-group/toggle-group-item.svelte +1 -1
  52. package/dist/ui/toggle-group/toggle-group-item.svelte.d.ts +3 -3
  53. package/dist/ui/tooltip/tooltip-content.svelte +1 -1
  54. package/dist/ui/video-player/video-player.svelte +2 -2
  55. package/dist/utils/tw-merge-config.d.ts +3 -1
  56. package/dist/utils/tw-merge-config.js +2 -2
  57. package/package.json +2 -1
  58. package/skill/SKILL.md +9 -4
  59. package/dist/styles/surfaces/base.css +0 -257
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Era UI — material contract
3
+ *
4
+ * Components choose a MATERIAL ROLE; surface implementations provide values.
5
+ * This file contains only relationships shared by every implementation and the
6
+ * global consumers of those values. It deliberately contains no flat, glass,
7
+ * or bevel palette and no component selector.
8
+ *
9
+ * Required primitives (enforced by check:surfaces):
10
+ * paint --era-surface-bg, --era-surface-bg-elevated, --era-field-bg
11
+ * relief --era-shadow, --era-shadow-lg
12
+ * interaction --era-highlight, --era-fill, full/compact press travel
13
+ * frame --era-divider-color, --era-panel-inset, --era-backdrop-filter
14
+ * environment --era-border-color, --era-overlay-bg
15
+ * selection --era-selection-bg/fg/shadow
16
+ *
17
+ * Derived values remain live var() expressions so a nested surface re-resolves
18
+ * them from its own primitives. A surface overrides a derived value only when
19
+ * its material genuinely needs a different treatment.
20
+ */
21
+ :root,
22
+ [data-surface] {
23
+ --era-divider-faded: color-mix(in oklch, var(--era-divider-color) 40%, transparent);
24
+ --era-field-highlight: color-mix(in oklch, var(--era-field-bg) 96%, var(--color-bright));
25
+ --era-shadow-well: var(--era-shadow);
26
+ --era-shadow-pressed: var(--era-shadow);
27
+ --era-highlight-shadow: var(--era-shadow);
28
+ }
29
+
30
+ /* Radius re-derives per element because density/corners may change without a
31
+ * new surface boundary. No surface-specific derived shadow belongs here. */
32
+ * {
33
+ --era-item-rd: max(0px, calc(var(--era-rd-control) - var(--era-panel-inset)));
34
+ }
35
+
36
+ ::selection {
37
+ background-color: var(--era-selection-bg);
38
+ color: var(--era-selection-fg);
39
+ text-shadow: var(--era-selection-shadow);
40
+ }
41
+
42
+ /* Translucent materials become opaque without changing component markup. */
43
+ @media (prefers-reduced-transparency: reduce) {
44
+ :root,
45
+ [data-surface] {
46
+ --era-highlight: var(--color-hover);
47
+ --era-fill: var(--color-hover);
48
+ --era-backdrop-filter: none;
49
+ }
50
+ }
@@ -0,0 +1,27 @@
1
+ /* Flat — solid tone steps with no relief chrome. */
2
+ :root,
3
+ [data-surface='flat'] {
4
+ --era-border-color: transparent;
5
+ --era-divider-color: var(--color-border);
6
+ --era-surface-bg: var(--color-2);
7
+ /* A symmetric tone step separates floating content without drawing an edge. */
8
+ --era-surface-bg-elevated: color-mix(in oklch, var(--era-surface-bg) 94%, var(--color-bright));
9
+ --era-field-bg: var(--color-hover);
10
+ --era-shadow: none;
11
+ --era-shadow-lg: none;
12
+ --era-highlight: light-dark(
13
+ color-mix(in oklch, var(--color-bright) 9%, transparent),
14
+ color-mix(in oklch, var(--color-bright) 8%, transparent)
15
+ );
16
+ --era-fill: var(--era-highlight);
17
+ --era-press-sink-x: 0px;
18
+ --era-press-sink-y: 0px;
19
+ --era-press-sink-compact-x: 0px;
20
+ --era-press-sink-compact-y: 0px;
21
+ --era-panel-inset: 0px;
22
+ --era-overlay-bg: color-mix(in oklch, var(--color-1) 60%, transparent);
23
+ --era-backdrop-filter: none;
24
+ --era-selection-bg: var(--color-bright);
25
+ --era-selection-fg: var(--color-1);
26
+ --era-selection-shadow: none;
27
+ }
@@ -16,6 +16,7 @@
16
16
  * treatment (macOS-style) so row highlights never touch the rim or arcs.
17
17
  */
18
18
  [data-surface='glass'] {
19
+ --era-border-color: transparent;
19
20
  --era-divider-color: light-dark(
20
21
  color-mix(in oklch, var(--color-bright) 12%, transparent),
21
22
  color-mix(in oklch, var(--color-bright) 8%, transparent)
@@ -33,13 +34,13 @@
33
34
  color-mix(in oklch, var(--color-3) 85%, transparent)
34
35
  );
35
36
 
36
- /* The same tone the base defines, held at 72% so the frost still reads
37
+ /* The same tone the flat implementation defines, held at 72% so the frost still reads
37
38
  * through it — the material is the point. Restated rather than inherited
38
39
  * because a surface cannot reference the token it is overriding.
39
40
  * Light mode is why a field needs its own value here at all: at the panel's
40
41
  * own opacity it measured 1.00:1 against its backdrop, i.e. exactly nothing.
41
42
  *
42
- * --era-field-highlight is NOT restated: the base expresses it against
43
+ * --era-field-highlight is NOT restated: the contract derives it against
43
44
  * --era-field-bg, so it picks this value up and stays translucent. */
44
45
  --era-field-bg: color-mix(in oklch, var(--color-hover) 72%, transparent);
45
46
 
@@ -89,6 +90,10 @@
89
90
  * surface invented). Its highlight was always a wash, so pills inside lit
90
91
  * containers were never at risk here. */
91
92
  --era-fill: var(--color-hover);
93
+ --era-press-sink-x: 0px;
94
+ --era-press-sink-y: 0px;
95
+ --era-press-sink-compact-x: 0px;
96
+ --era-press-sink-compact-y: 0px;
92
97
  --era-highlight-shadow:
93
98
  inset 1.5px 1.5px 0 -1px
94
99
  light-dark(
@@ -127,10 +132,9 @@
127
132
  );
128
133
 
129
134
  --era-panel-inset: 2px;
130
- --era-blur: 20px;
131
- /* Blur alone reads muddy/gray; saturation restores the backdrop's color
132
- * the way Apple's vibrancy does. */
133
- --era-saturate: 180%;
135
+ --era-overlay-bg: color-mix(in oklch, var(--color-1) 60%, transparent);
136
+ /* Blur alone reads muddy/gray; saturation restores the backdrop's color. */
137
+ --era-backdrop-filter: blur(20px) saturate(180%);
134
138
  }
135
139
 
136
140
  /* Accessibility — honor reduced-transparency: solid fills, no filter,
@@ -139,8 +143,7 @@
139
143
  [data-surface='glass'] {
140
144
  --era-surface-bg: var(--color-2);
141
145
  --era-surface-bg-elevated: var(--color-3);
142
- --era-blur: 0px;
143
- --era-saturate: 100%;
146
+ --era-backdrop-filter: none;
144
147
  /* The field goes solid with everything else — it was left translucent
145
148
  * here, so a field kept frosting over its backdrop on the one setting
146
149
  * that asks for no frost. */
@@ -32,7 +32,7 @@
32
32
  bind:ref
33
33
  {...relay?.attrs}
34
34
  class={cn(
35
- 'fixed top-1/2 left-1/2 z-overlay flex max-h-[85dvh] w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-gutter overflow-y-auto rounded-bar bg-well p-card shadow-lg glass-blur',
35
+ 'fixed top-1/2 left-1/2 z-overlay flex max-h-[85dvh] w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-gutter overflow-y-auto rounded-bar material-panel p-card',
36
36
  className
37
37
  )}
38
38
  {...restProps}
@@ -200,6 +200,7 @@
200
200
  -->
201
201
  <span
202
202
  data-button-content
203
+ data-era-press-content
203
204
  class={cn(
204
205
  'inline-flex items-center justify-center [gap:inherit]',
205
206
  // THE LABEL KEEPS ITS BOX AND LOSES ITS INK. opacity, not `hidden` or
@@ -1,6 +1,6 @@
1
1
  export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
2
2
  variant: {
3
- filled: "flex justify-center shadow-sm hover:shadow-highlight active:shadow-pressed [&:active>*]:translate-x-(--era-press-sink-x) [&:active>*]:translate-y-(--era-press-sink-y)";
3
+ filled: "pressable flex justify-center shadow-sm hover:shadow-highlight";
4
4
  link: "inline era-link [line-height:inherit]";
5
5
  };
6
6
  tone: {
@@ -25,7 +25,7 @@ export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
25
25
  };
26
26
  }, undefined, "items-center gap-gutter text-body era-interactive leading-none", {
27
27
  variant: {
28
- filled: "flex justify-center shadow-sm hover:shadow-highlight active:shadow-pressed [&:active>*]:translate-x-(--era-press-sink-x) [&:active>*]:translate-y-(--era-press-sink-y)";
28
+ filled: "pressable flex justify-center shadow-sm hover:shadow-highlight";
29
29
  link: "inline era-link [line-height:inherit]";
30
30
  };
31
31
  tone: {
@@ -50,7 +50,7 @@ export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
50
50
  };
51
51
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
52
52
  variant: {
53
- filled: "flex justify-center shadow-sm hover:shadow-highlight active:shadow-pressed [&:active>*]:translate-x-(--era-press-sink-x) [&:active>*]:translate-y-(--era-press-sink-y)";
53
+ filled: "pressable flex justify-center shadow-sm hover:shadow-highlight";
54
54
  link: "inline era-link [line-height:inherit]";
55
55
  };
56
56
  tone: {
@@ -9,10 +9,10 @@ export const buttonVariants = tv({
9
9
  // The active: press treatment is inert outside the bevel surface —
10
10
  // flat/glass define --era-shadow-pressed as the resting shadow and 0px
11
11
  // sinks. Bevel inverts the chisel and sinks the CONTENT straight down
12
- // (press-sink, `[&:active>*]` the one content-wrapper span from
12
+ // (`pressable` targets the one content-wrapper span from
13
13
  // button.svelte) so the icon AND label depress together like a keycap
14
14
  // while the frame holds still.
15
- filled: 'flex justify-center shadow-sm hover:shadow-highlight active:shadow-pressed [&:active>*]:translate-x-(--era-press-sink-x) [&:active>*]:translate-y-(--era-press-sink-y)',
15
+ filled: 'pressable flex justify-center shadow-sm hover:shadow-highlight',
16
16
  // THE LINK VARIANT IS era-link, not a second implementation of it.
17
17
  // It used to hand-roll `underline-offset-2 hover:underline`, which is a
18
18
  // different link from the one `era-link` defines for prose: no underline
@@ -55,7 +55,7 @@ export const buttonVariants = tv({
55
55
  {
56
56
  variant: 'filled',
57
57
  size: 'icon',
58
- class: 'min-w-icon tier-icon'
58
+ class: 'min-w-icon tier-icon [--era-press-effective-x:var(--era-press-sink-compact-x)] [--era-press-effective-y:var(--era-press-sink-compact-y)]'
59
59
  },
60
60
  // filled insets; icon buttons go square instead. When an icon is present,
61
61
  // gap widens to match the wall inset so the icon sits in rhythm with it.
@@ -32,7 +32,7 @@
32
32
  // was: the even-gap law, broken by default, in the component that
33
33
  // bounds more consumer content than any other.
34
34
  sectioned: { true: 'p-0', false: 'p-card' },
35
- // A CARD YOU CAN PRESS DEPRESSES. `active:shadow-pressed` is on every
35
+ // A CARD YOU CAN PRESS DEPRESSES. The pressed material is on every
36
36
  // other era control and was missing here, so a pressable card was the
37
37
  // one thing in the library that lit up on hover and then did nothing
38
38
  // under the finger — on bevel, where the pressed face is an inverted
@@ -43,7 +43,7 @@
43
43
  // consumer children separately would pull a card's sections apart, while no
44
44
  // wrapper leaves the frame depressed around stationary ink.
45
45
  interactive: {
46
- true: 'cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight active:shadow-pressed [&:active>[data-card-content]]:translate-x-(--era-press-sink-x) [&:active>[data-card-content]]:translate-y-(--era-press-sink-y) era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50'
46
+ true: 'pressable cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50'
47
47
  }
48
48
  },
49
49
  defaultVariants: { tone: 'elevated', sectioned: false, interactive: false }
@@ -191,7 +191,7 @@
191
191
  class={cn(cardVariants({ tone, sectioned, interactive: true }), className)}
192
192
  {...restProps}
193
193
  >
194
- <div data-card-content>{@render body()}</div>
194
+ <div data-card-content data-era-press-content>{@render body()}</div>
195
195
  </a>
196
196
  <!-- eslint-enable svelte/no-navigation-without-resolve -->
197
197
  {:else if onclick != null}
@@ -211,7 +211,7 @@
211
211
  class={cn(cardVariants({ tone, sectioned, interactive: true }), className)}
212
212
  {...restProps}
213
213
  >
214
- <div data-card-content>{@render body()}</div>
214
+ <div data-card-content data-era-press-content>{@render body()}</div>
215
215
  </button>
216
216
  {:else}
217
217
  <div
@@ -10,7 +10,7 @@ export declare const cardVariants: import("tailwind-variants").TVReturnType<{
10
10
  false: "p-card";
11
11
  };
12
12
  interactive: {
13
- true: "cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight active:shadow-pressed [&:active>[data-card-content]]:translate-x-(--era-press-sink-x) [&:active>[data-card-content]]:translate-y-(--era-press-sink-y) era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
13
+ true: "pressable cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
14
14
  };
15
15
  }, undefined, "block w-full rounded-bar text-left text-fg", {
16
16
  tone: {
@@ -24,7 +24,7 @@ export declare const cardVariants: import("tailwind-variants").TVReturnType<{
24
24
  false: "p-card";
25
25
  };
26
26
  interactive: {
27
- true: "cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight active:shadow-pressed [&:active>[data-card-content]]:translate-x-(--era-press-sink-x) [&:active>[data-card-content]]:translate-y-(--era-press-sink-y) era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
27
+ true: "pressable cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
28
28
  };
29
29
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
30
30
  tone: {
@@ -38,7 +38,7 @@ export declare const cardVariants: import("tailwind-variants").TVReturnType<{
38
38
  false: "p-card";
39
39
  };
40
40
  interactive: {
41
- true: "cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight active:shadow-pressed [&:active>[data-card-content]]:translate-x-(--era-press-sink-x) [&:active>[data-card-content]]:translate-y-(--era-press-sink-y) era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
41
+ true: "pressable cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
42
42
  };
43
43
  }, undefined>>;
44
44
  import type { Snippet } from 'svelte';
@@ -15,7 +15,7 @@
15
15
  bind:ref
16
16
  align="start"
17
17
  class={cn(
18
- 'z-overlay w-max min-w-(--bits-combobox-anchor-width) overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
18
+ 'z-overlay w-max min-w-(--bits-combobox-anchor-width) overflow-hidden rounded-control material-popover p-menu data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
19
19
  className
20
20
  )}
21
21
  {sideOffset}
@@ -21,14 +21,14 @@
21
21
  bind:value
22
22
  class={cn(
23
23
  'flex flex-col gap-gutter',
24
- // glass-blur, like every other floating panel (menus, dialogs, pickers):
25
- // inert on opaque surfaces, blurs the backdrop when the palette floats.
24
+ // material-panel, like every persistent floating surface: opaque on flat/
25
+ // bevel and frosted on glass without naming an implementation here.
26
26
  !bare &&
27
27
  // The panel is unbounded chrome; its bounded command rows carry their own
28
28
  // control tier rather than inheriting geometry from this ancestor.
29
29
  // No border: --era-shadow-lg is the edge on every surface, and a border
30
30
  // alongside it draws two hairlines of the same colour on flat.
31
- 'overflow-hidden rounded-bar bg-well p-gutter shadow-lg glass-blur',
31
+ 'overflow-hidden rounded-bar material-panel p-gutter',
32
32
  className
33
33
  )}
34
34
  {...restProps}
@@ -186,8 +186,8 @@
186
186
 
187
187
  <!-- The terminal command bar: a bare prompt line, not a floating palette. The
188
188
  dropdown anchors under it with no radius (a full-width sheet, like the shell
189
- it mirrors), but its chrome is surface-adaptive via --era-shadow-lg +
190
- glass-blur: a 1px hairline at the divider colour on flat, the chisel frame
189
+ it mirrors), but its chrome is surface-adaptive via material-popover:
190
+ a tone step on flat, the chisel frame
191
191
  on bevel, frost on glass — so it reads as "dedicated" on every surface, not
192
192
  an undifferentiated rectangle.
193
193
 
@@ -273,7 +273,7 @@
273
273
  role="listbox"
274
274
  tabindex="-1"
275
275
  aria-label="Commands"
276
- class="absolute top-full left-0 z-overlay max-h-64 w-full overflow-x-hidden overflow-y-auto bg-elevated shadow-lg glass-blur"
276
+ class="absolute top-full left-0 z-overlay max-h-64 w-full overflow-x-hidden overflow-y-auto material-popover"
277
277
  onmousedown={(e) => e.preventDefault()}
278
278
  >
279
279
  {#if !filtered.length}
@@ -24,10 +24,7 @@
24
24
  // anchor bar (the reference terminal look) — while glass and bevel keep the
25
25
  // 2px frame that stops a full-width highlight from breaking their chrome.
26
26
  // A fixed gutter was tried here and rejected: it padded flat menus too.
27
- class={cn(
28
- 'z-overlay w-max overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur',
29
- className
30
- )}
27
+ class={cn('z-overlay w-max overflow-hidden rounded-control material-popover p-menu', className)}
31
28
  {sideOffset}
32
29
  {...restProps}
33
30
  >
@@ -18,10 +18,7 @@
18
18
  <ContextMenu.SubContent
19
19
  bind:ref
20
20
  {...relay?.attrs}
21
- class={cn(
22
- 'z-overlay w-max overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur',
23
- className
24
- )}
21
+ class={cn('z-overlay w-max overflow-hidden rounded-control material-popover p-menu', className)}
25
22
  {sideOffset}
26
23
  {...restProps}
27
24
  >
@@ -14,7 +14,7 @@
14
14
  <DatePicker.Content
15
15
  bind:ref
16
16
  align="start"
17
- class={cn('z-overlay rounded-bar bg-elevated p-content shadow-lg glass-blur', className)}
17
+ class={cn('z-overlay rounded-bar material-popover p-content', className)}
18
18
  {sideOffset}
19
19
  {...restProps}
20
20
  >
@@ -14,7 +14,7 @@
14
14
  <DateRangePicker.Content
15
15
  bind:ref
16
16
  align="start"
17
- class={cn('z-overlay rounded-bar bg-elevated p-content shadow-lg glass-blur', className)}
17
+ class={cn('z-overlay rounded-bar material-popover p-content', className)}
18
18
  {sideOffset}
19
19
  {...restProps}
20
20
  >
@@ -75,7 +75,7 @@
75
75
  {...relay?.attrs}
76
76
  class={cn(
77
77
  relay?.schemeClass,
78
- 'fixed top-1/2 left-1/2 z-overlay flex max-h-[85dvh] w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col bg-well shadow-lg glass-blur',
78
+ 'fixed top-1/2 left-1/2 z-overlay flex max-h-[85dvh] w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col material-panel',
79
79
  // No header: the panel itself is the body, so the panel scrolls.
80
80
  header ? 'rounded-control' : 'gap-gutter overflow-y-auto rounded-bar p-card',
81
81
  className
@@ -33,7 +33,7 @@
33
33
  // floating menu, like the context menu it should match: full radius, a
34
34
  // gutter of air, shadow doing the separating.
35
35
  class={cn(
36
- 'z-overlay w-max min-w-(--bits-dropdown-menu-anchor-width) overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur',
36
+ 'z-overlay w-max min-w-(--bits-dropdown-menu-anchor-width) overflow-hidden rounded-control material-popover p-menu',
37
37
  className
38
38
  )}
39
39
  {sideOffset}
@@ -18,10 +18,7 @@
18
18
  <DropdownMenu.SubContent
19
19
  bind:ref
20
20
  {...relay?.attrs}
21
- class={cn(
22
- 'z-overlay w-max overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur',
23
- className
24
- )}
21
+ class={cn('z-overlay w-max overflow-hidden rounded-control material-popover p-menu', className)}
25
22
  {sideOffset}
26
23
  {...restProps}
27
24
  >
@@ -19,7 +19,7 @@
19
19
  bind:ref
20
20
  {...relay?.attrs}
21
21
  class={cn(
22
- 'z-overlay max-w-xs rounded-control bg-elevated p-inset-control text-body shadow-lg glass-blur',
22
+ 'z-overlay max-w-xs rounded-control material-popover p-inset-control text-body',
23
23
  className
24
24
  )}
25
25
  {sideOffset}
@@ -33,7 +33,7 @@
33
33
  // floating menu, like the context menu it should match: full radius, a
34
34
  // gutter of air, shadow doing the separating.
35
35
  class={cn(
36
- 'z-overlay w-max min-w-(--bits-menubar-menu-anchor-width) overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur',
36
+ 'z-overlay w-max min-w-(--bits-menubar-menu-anchor-width) overflow-hidden rounded-control material-popover p-menu',
37
37
  className
38
38
  )}
39
39
  {sideOffset}
@@ -18,10 +18,7 @@
18
18
  <Menubar.SubContent
19
19
  bind:ref
20
20
  {...relay?.attrs}
21
- class={cn(
22
- 'z-overlay w-max overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur',
23
- className
24
- )}
21
+ class={cn('z-overlay w-max overflow-hidden rounded-control material-popover p-menu', className)}
25
22
  {sideOffset}
26
23
  {...restProps}
27
24
  >
@@ -239,8 +239,7 @@
239
239
  THE ORDER IS CURRENTLY UNOBSERVABLE, and that is worth knowing rather
240
240
  than discovering. Swapping these two changes no pixel: the buffer is
241
241
  the fill's OWN paint at 30%, and the same colour composited over
242
- itself is that colour. Checked by swapping them and re-running
243
- meter-buffer.spec.ts, which stayed green. So the order is kept for
242
+ itself is that colour. A control swap was pixel-identical. So the order is kept for
244
243
  correctness — it is what makes the contract true for any future paint
245
244
  that is not derived from the fill — and not because a test can see it.
246
245
  Do not read the passing spec as covering it.
@@ -214,6 +214,7 @@
214
214
  >
215
215
  <span
216
216
  data-button-content
217
+ data-era-press-content
217
218
  class={cn(
218
219
  'inline-flex items-center justify-center [gap:inherit]',
219
220
  // Same reason Button gives: era-label-ink blockifies the box and trims
@@ -532,7 +532,7 @@
532
532
  // outward, and paint containment clips them).
533
533
  // The pane is unbounded chrome. Bounded descendants carry their own tier;
534
534
  // no inherited radius is allowed to rewrite a row from the outside in.
535
- 'group/pane absolute isolate flex flex-col rounded-control border border-divider bg-elevated shadow-lg glass-blur [contain:layout_style]',
535
+ 'group/pane absolute isolate flex flex-col rounded-control border border-divider material-popover [contain:layout_style]',
536
536
  // Auto-size to content until an explicit size is set (by the grip or a preset).
537
537
  size ? '' : 'w-max',
538
538
  // While Ctrl is held the whole pane is grabbable: a grab cursor hints it, and
@@ -22,7 +22,7 @@
22
22
  // floating menu, like the context menu it should match: full radius, a
23
23
  // gutter of air, shadow doing the separating.
24
24
  class={cn(
25
- 'z-overlay w-max min-w-(--bits-popover-anchor-width) overflow-hidden rounded-control bg-elevated p-card shadow-lg glass-blur',
25
+ 'z-overlay w-max min-w-(--bits-popover-anchor-width) overflow-hidden rounded-control material-popover p-card',
26
26
  className
27
27
  )}
28
28
  {sideOffset}
@@ -22,7 +22,7 @@ export declare const surfaceRegistry: {
22
22
  readonly flat: {
23
23
  readonly label: "Flat";
24
24
  readonly description: "Solid fills, no chrome — the quiet default.";
25
- readonly css: "base.css";
25
+ readonly css: "flat.css";
26
26
  };
27
27
  readonly glass: {
28
28
  readonly label: "Glass";
@@ -2,7 +2,7 @@ export const surfaceRegistry = {
2
2
  flat: {
3
3
  label: 'Flat',
4
4
  description: 'Solid fills, no chrome — the quiet default.',
5
- css: 'base.css'
5
+ css: 'flat.css'
6
6
  },
7
7
  glass: {
8
8
  label: 'Glass',
@@ -29,7 +29,7 @@
29
29
  // 2px frame that stops a full-width highlight from breaking their chrome.
30
30
  // A fixed gutter was tried here and rejected: it padded flat menus too.
31
31
  class={cn(
32
- 'z-overlay w-max min-w-(--bits-select-anchor-width) overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
32
+ 'z-overlay w-max min-w-(--bits-select-anchor-width) overflow-hidden rounded-control material-popover p-menu data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
33
33
  className
34
34
  )}
35
35
  {sideOffset}
@@ -11,7 +11,7 @@
11
11
  // the viewport boundary, so only the open edge is visible and it was the
12
12
  // thick one.
13
13
  export const sheetVariants = tv({
14
- base: 'fixed z-overlay flex flex-col bg-well shadow-lg glass-blur',
14
+ base: 'fixed z-overlay material-panel flex flex-col',
15
15
  variants: {
16
16
  // The two axes meet at the corners: the SIDE picks which corners are
17
17
  // detached from the screen edge, the HEADER picks their radius. Stated
@@ -17,7 +17,7 @@ export declare const sheetVariants: import("tailwind-variants").TVReturnType<{
17
17
  true: "[--sheet-rd:var(--era-rd-control)]";
18
18
  false: "[--sheet-rd:var(--era-rd-bar)] gap-gutter overflow-y-auto p-card";
19
19
  };
20
- }, undefined, "fixed z-overlay flex flex-col bg-well shadow-lg glass-blur", {
20
+ }, undefined, "fixed z-overlay material-panel flex flex-col", {
21
21
  side: {
22
22
  top: "inset-x-0 top-0 max-h-[85dvh] rounded-b-(--sheet-rd)";
23
23
  bottom: "inset-x-0 bottom-0 max-h-[85dvh] rounded-t-(--sheet-rd)";
@@ -41,7 +41,7 @@
41
41
  may retune the feel by overriding --era-ease in its [data-surface]
42
42
  block; a surface wanting a different STRATEGY (e.g. glass crossfade
43
43
  vs bevel track-slide) scopes a rule to its own surface — see the
44
- surface-animation recipe in surfaces/base.css. -->
44
+ material contract in surfaces/contract.css. -->
45
45
  <Switch.Thumb
46
46
  class="pointer-events-none block tier-icon w-icon -translate-x-[calc(var(--era-h-icon)/2)] bg-fg transition-transform duration-base ease-base data-[state=checked]:translate-x-[calc(var(--era-h-icon)/2)] data-[state=checked]:bg-2"
47
47
  />
@@ -14,7 +14,7 @@
14
14
  variants: {
15
15
  variant: {
16
16
  plain:
17
- 'min-w-control tier-control px-inset-control shadow-sm hover:shadow-highlight active:shadow-pressed',
17
+ 'pressable min-w-control tier-control px-inset-control shadow-sm hover:shadow-highlight',
18
18
  segmented: 'min-w-chip tier-chip px-inset-chip'
19
19
  }
20
20
  },
@@ -1,16 +1,16 @@
1
1
  export declare const toggleGroupItemVariants: import("tailwind-variants").TVReturnType<{
2
2
  variant: {
3
- plain: "min-w-control tier-control px-inset-control shadow-sm hover:shadow-highlight active:shadow-pressed";
3
+ plain: "pressable min-w-control tier-control px-inset-control shadow-sm hover:shadow-highlight";
4
4
  segmented: "min-w-chip tier-chip px-inset-chip";
5
5
  };
6
6
  }, undefined, "inline-flex items-center justify-center text-body text-fg era-interactive not-has-[svg]:era-label-ink not-has-[svg]:text-center hover:bg-highlight focus:outline-none disabled:opacity-50 data-[state=on]:bg-fill data-[state=on]:text-bright data-[state=on]:shadow-pressed data-[state=on]:hover:shadow-pressed", {
7
7
  variant: {
8
- plain: "min-w-control tier-control px-inset-control shadow-sm hover:shadow-highlight active:shadow-pressed";
8
+ plain: "pressable min-w-control tier-control px-inset-control shadow-sm hover:shadow-highlight";
9
9
  segmented: "min-w-chip tier-chip px-inset-chip";
10
10
  };
11
11
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
12
12
  variant: {
13
- plain: "min-w-control tier-control px-inset-control shadow-sm hover:shadow-highlight active:shadow-pressed";
13
+ plain: "pressable min-w-control tier-control px-inset-control shadow-sm hover:shadow-highlight";
14
14
  segmented: "min-w-chip tier-chip px-inset-chip";
15
15
  };
16
16
  }, undefined>>;
@@ -19,7 +19,7 @@
19
19
  bind:ref
20
20
  {...relay?.attrs}
21
21
  class={cn(
22
- 'z-overlay inline-flex tier-pill items-center gap-gutter bg-elevated px-inset-pill text-body leading-none whitespace-nowrap text-fg shadow-lg glass-blur has-[svg]:gap-inset-pill',
22
+ 'z-overlay inline-flex tier-pill items-center gap-gutter material-popover px-inset-pill text-body leading-none whitespace-nowrap text-fg has-[svg]:gap-inset-pill',
23
23
  className
24
24
  )}
25
25
  {sideOffset}
@@ -828,7 +828,7 @@
828
828
  // against the mute button below. Visibility is in the scoped style block.
829
829
  const volumeSleeveClass = $derived(
830
830
  resolvedVolumeOrientation === 'vertical'
831
- ? 'absolute bottom-full left-1/2 z-20 h-[calc(var(--era-h-bar)*3)] w-control -translate-x-1/2 rounded-t-control bg-hover shadow-sm glass-blur'
831
+ ? 'surface-backdrop absolute bottom-full left-1/2 z-20 h-[calc(var(--era-h-bar)*3)] w-control -translate-x-1/2 rounded-t-control bg-hover shadow-sm'
832
832
  : 'relative h-chip w-[calc(var(--era-h-control)*3)]'
833
833
  );
834
834
  // Slider IS the visible track. Range and Thumb (both bg-fg) live inside
@@ -1058,7 +1058,7 @@
1058
1058
  -->
1059
1059
  <span
1060
1060
  aria-hidden="true"
1061
- class="pointer-events-none absolute bottom-[calc(100%+(var(--era-h-bar)-var(--era-h-chip))/2+var(--era-gap))] left-(--era-video-hover-pct) inline-flex tier-pill -translate-x-1/2 items-center gap-gutter bg-elevated px-inset-pill font-mono text-body leading-none whitespace-nowrap text-fg shadow-lg glass-blur"
1061
+ class="pointer-events-none absolute bottom-[calc(100%+(var(--era-h-bar)-var(--era-h-chip))/2+var(--era-gap))] left-(--era-video-hover-pct) inline-flex tier-pill -translate-x-1/2 items-center gap-gutter material-popover px-inset-pill font-mono text-body leading-none whitespace-nowrap text-fg"
1062
1062
  >
1063
1063
  <span class="tabular-nums">{hoverTimeLabel}</span>
1064
1064
  {#if hoverChapter}
@@ -8,7 +8,9 @@ export declare const twMergeConfig: {
8
8
  'era-tier': {
9
9
  tier: string[];
10
10
  }[];
11
- 'backdrop-blur': string[];
11
+ 'material-role': {
12
+ material: string[];
13
+ }[];
12
14
  'font-size': {
13
15
  text: string[];
14
16
  }[];
@@ -126,8 +126,8 @@ export const twMergeConfig = {
126
126
  // A tier utility owns height and radius as one indivisible geometry choice.
127
127
  // The last tier-* supplied by a consumer must replace the component default.
128
128
  'era-tier': [{ tier: TIERS }],
129
- // glass-blur sets backdrop-filter, so it conflicts with backdrop-blur.
130
- 'backdrop-blur': ['glass-blur'],
129
+ // A component has one complete floating material role at a time.
130
+ 'material-role': [{ material: ['popover', 'panel'] }],
131
131
  // Registered font sizes — without this they read as colours.
132
132
  'font-size': [{ text: ['body', 'micro', 'lede', 'heading', 'title', 'display'] }],
133
133
  leading: [{ leading: ['body'] }],