@signal9/era-ui 4.6.0 → 4.7.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 (54) hide show
  1. package/dist/ai/error-panel/error-panel.svelte +1 -30
  2. package/dist/ai/index.d.ts +1 -0
  3. package/dist/ai/index.js +1 -0
  4. package/dist/ai/runtime-feed/runtime-feed-item.svelte +4 -1
  5. package/dist/ai/swarm/context.d.ts +26 -0
  6. package/dist/ai/swarm/context.js +14 -0
  7. package/dist/ai/swarm/index.d.ts +16 -0
  8. package/dist/ai/swarm/index.js +15 -0
  9. package/dist/ai/swarm/swarm-agent.svelte +132 -0
  10. package/dist/ai/swarm/swarm-agent.svelte.d.ts +16 -0
  11. package/dist/ai/swarm/swarm-artifact.svelte +45 -0
  12. package/dist/ai/swarm/swarm-artifact.svelte.d.ts +8 -0
  13. package/dist/ai/swarm/swarm-artifacts.svelte +42 -0
  14. package/dist/ai/swarm/swarm-artifacts.svelte.d.ts +15 -0
  15. package/dist/ai/swarm/swarm-barrier.svelte +63 -0
  16. package/dist/ai/swarm/swarm-barrier.svelte.d.ts +10 -0
  17. package/dist/ai/swarm/swarm-counts.svelte +52 -0
  18. package/dist/ai/swarm/swarm-counts.svelte.d.ts +13 -0
  19. package/dist/ai/swarm/swarm-dot.svelte +52 -0
  20. package/dist/ai/swarm/swarm-dot.svelte.d.ts +23 -0
  21. package/dist/ai/swarm/swarm-footer.svelte +63 -0
  22. package/dist/ai/swarm/swarm-footer.svelte.d.ts +16 -0
  23. package/dist/ai/swarm/swarm-grid.svelte +51 -0
  24. package/dist/ai/swarm/swarm-grid.svelte.d.ts +14 -0
  25. package/dist/ai/swarm/swarm-header.svelte +64 -0
  26. package/dist/ai/swarm/swarm-header.svelte.d.ts +14 -0
  27. package/dist/ai/swarm/swarm-node.svelte +258 -0
  28. package/dist/ai/swarm/swarm-node.svelte.d.ts +32 -0
  29. package/dist/ai/swarm/swarm-role.svelte +71 -0
  30. package/dist/ai/swarm/swarm-role.svelte.d.ts +17 -0
  31. package/dist/ai/swarm/swarm-root.svelte +55 -0
  32. package/dist/ai/swarm/swarm-root.svelte.d.ts +16 -0
  33. package/dist/ai/swarm/swarm-tree.svelte +201 -0
  34. package/dist/ai/swarm/swarm-tree.svelte.d.ts +25 -0
  35. package/dist/ai/swarm/swarm.d.ts +218 -0
  36. package/dist/ai/swarm/swarm.js +187 -0
  37. package/dist/ai/swarm/swarm.svelte.d.ts +59 -0
  38. package/dist/ai/swarm/swarm.svelte.js +125 -0
  39. package/dist/apps/llm-shell/index.d.ts +1 -1
  40. package/dist/apps/llm-shell/llm-shell.svelte +65 -1
  41. package/dist/apps/llm-shell/llm-shell.svelte.d.ts +7 -0
  42. package/dist/apps/llm-shell/types.d.ts +22 -0
  43. package/dist/dev/audit/audits/clipped-glyphs.js +7 -0
  44. package/dist/era-ui.css +1 -1
  45. package/dist/generated-docs/llm-shell.md +1 -1
  46. package/dist/generated-docs/llms-full.txt +53 -11
  47. package/dist/generated-docs/llms.txt +1 -0
  48. package/dist/generated-docs/manifest.json +16 -5
  49. package/dist/generated-docs/utilities.json +12 -2
  50. package/dist/generated-docs/utilities.md +51 -10
  51. package/dist/styles/index.css +63 -9
  52. package/dist/styles/themes.css +27 -15
  53. package/dist/ui/step/step-content.svelte +5 -34
  54. package/package.json +1 -1
@@ -40,6 +40,7 @@ Classes, not components — they need no import beyond the stylesheet. Reach for
40
40
  - `truncate` — single-line horizontal truncation with an ellipsis — safe inside a trimmed or leading-none row; it will not clip ascenders or descenders.
41
41
  - `era-text-trim-caps` — the era-text-trim variant for ALL-CAPS single-line strings (kbd hints, acronym badges) — never use it for mixed-case text.
42
42
  - `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.
43
+ - `era-collapse` — the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).
43
44
  - `h-xs · h-xxs · h-sm · h-md · h-lg` — the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm (standalone pill) · h-md (control) · h-lg (bar); the named form of h-(--era-h-*).
44
45
  - `min-h-xs · min-h-xxs · min-h-sm · min-h-md · min-h-lg` — minimum tier height for containers that may grow — a bar that is at least lg-tier but can wrap (min-h-lg).
45
46
  - `size-xs · size-xxs · size-sm · size-md · size-lg` — a tier-sized square — size-xs is THE icon size (a Lucide glyph beside body text), size-md a square control face.
@@ -147,6 +148,7 @@ enough to stay var-form — `min-w-(--era-h-md)` etc. remain the escape hatch.
147
148
  | `truncate` | single-line horizontal truncation with an ellipsis — safe inside a trimmed or leading-none row; it will not clip ascenders or descenders. |
148
149
  | `era-text-trim-caps` | the era-text-trim variant for ALL-CAPS single-line strings (kbd hints, acronym badges) — never use it for mixed-case text. |
149
150
  | `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. |
151
+ | `era-collapse` | the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden). |
150
152
  | `h-xs` `h-xxs` `h-sm` `h-md` `h-lg` | the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm (standalone pill) · h-md (control) · h-lg (bar); the named form of h-(--era-h-*). |
151
153
  | `min-h-xs` `min-h-xxs` `min-h-sm` `min-h-md` `min-h-lg` | minimum tier height for containers that may grow — a bar that is at least lg-tier but can wrap (min-h-lg). |
152
154
  | `size-xs` `size-xxs` `size-sm` `size-md` `size-lg` | a tier-sized square — size-xs is THE icon size (a Lucide glyph beside body text), size-md a square control face. |
@@ -354,6 +356,39 @@ Declared in `index.css`.
354
356
  }
355
357
  ```
356
358
 
359
+ ## era-collapse
360
+
361
+ **Use it for:** the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).
362
+
363
+ The open/close animation for a bits-ui Collapsible.Content — the height
364
+ sweep every disclosure in the library uses (a Step's body, an error panel's
365
+ detail, a swarm node's children).
366
+
367
+ It has to be a keyframe animation rather than a transition because the
368
+ natural height is only knowable at open time: bits measures the content and
369
+ publishes `--bits-collapsible-content-height`, which the `to` frame reads.
370
+ The class also owns `overflow: hidden` — animating height without it just
371
+ spills the content out of the shrinking box.
372
+
373
+ Timing rides the motion axis, so `instant` collapses it to a cut (0s) and
374
+ `extra` stretches it, for free. Three components had a byte-identical copy of
375
+ these keyframes under three different names before this existed; the panel
376
+ that needs different motion should override `animation`, not re-declare it.
377
+
378
+ Declared in `index.css`.
379
+
380
+ ```css
381
+ @utility era-collapse {
382
+ overflow: hidden;
383
+ &[data-state='open'] {
384
+ animation: era-collapse-open var(--era-duration) var(--era-ease);
385
+ }
386
+ &[data-state='closed'] {
387
+ animation: era-collapse-close var(--era-duration) var(--era-ease);
388
+ }
389
+ }
390
+ ```
391
+
357
392
  ## h-xs · h-xxs · h-sm · h-md · h-lg
358
393
 
359
394
  **Use it for:** the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm (standalone pill) · h-md (control) · h-lg (bar); the named form of h-(--era-h-*).
@@ -551,22 +586,29 @@ Declared in `index.css`.
551
586
  ## p-content
552
587
 
553
588
  A content panel always follows a section header ROW, and that row already
554
- contributes its own slack: an h-lg bar holding one line of 16px text leaves
555
- ~5px between the glyphs and the bottom of the bar. Padding the panel evenly
556
- on top of that spends the gap twice — measured 22px under a heading against
557
- 20px above it, so the header floated between the section above and the
558
- paragraph it introduces rather than binding to it.
559
-
560
- The block-start is therefore one gap unit shorter, which is what makes a
561
- heading read as belonging to the text beneath it. Scales with density like
562
- everything else, since both terms do.
589
+ contributes its own slack: the heading's text sits in an h-lg bar, so there
590
+ is (h-lg text)/2 of empty bar under its glyphs before the panel even
591
+ starts. Padding the panel evenly on top of that spends the space twice — the
592
+ header ends up floating between the section above and the paragraph it
593
+ introduces rather than binding to it.
594
+
595
+ So the block-start pays only the REMAINDER. The target is the indent: the
596
+ space under a heading should measure the same as the space beside the text,
597
+ which is --era-content-p. The header row already supplies (h-lg − text)/2 of
598
+ it, so the panel adds what's left, and heading-to-paragraph reads equal to
599
+ the paragraph's own inset. Subtracting one --era-gap (the previous rule) left
600
+ the header's slack unaccounted for and the heading still detached.
601
+
602
+ Both terms are density-derived, so the equality holds at every mode; the
603
+ max() is the floor for a hypothetical mode where the row's slack would
604
+ exceed the inset outright.
563
605
 
564
606
  Declared in `index.css`.
565
607
 
566
608
  ```css
567
609
  @utility p-content {
568
610
  padding: var(--era-content-p);
569
- padding-top: calc(var(--era-content-p) - var(--era-gap));
611
+ padding-top: max(0px, calc(var(--era-content-p) - (var(--era-h-lg) - var(--era-text)) / 2));
570
612
  }
571
613
  ```
572
614
 
@@ -642,7 +684,7 @@ Release history — every version, grouped by change type.
642
684
 
643
685
  # LLM Shell
644
686
 
645
- A full chat console — conversation rail, streaming, tools, approvals.
687
+ A full chat console — conversation rail, streaming, tools, swarms.
646
688
 
647
689
  <!-- end: llm-shell -->
648
690
 
@@ -44,6 +44,7 @@ Classes, not components — they need no import beyond the stylesheet. Reach for
44
44
  - `truncate` — single-line horizontal truncation with an ellipsis — safe inside a trimmed or leading-none row; it will not clip ascenders or descenders.
45
45
  - `era-text-trim-caps` — the era-text-trim variant for ALL-CAPS single-line strings (kbd hints, acronym badges) — never use it for mixed-case text.
46
46
  - `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.
47
+ - `era-collapse` — the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).
47
48
  - `h-xs · h-xxs · h-sm · h-md · h-lg` — the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm (standalone pill) · h-md (control) · h-lg (bar); the named form of h-(--era-h-*).
48
49
  - `min-h-xs · min-h-xxs · min-h-sm · min-h-md · min-h-lg` — minimum tier height for containers that may grow — a bar that is at least lg-tier but can wrap (min-h-lg).
49
50
  - `size-xs · size-xxs · size-sm · size-md · size-lg` — a tier-sized square — size-xs is THE icon size (a Lucide glyph beside body text), size-md a square control face.
@@ -44,7 +44,7 @@
44
44
  "slug": "utilities",
45
45
  "title": "Utilities",
46
46
  "summary": "The CSS classes era ships alongside the components — links, ink-centred text, shimmer, hidden scrollbars.",
47
- "tokenEstimate": 4810,
47
+ "tokenEstimate": 5277,
48
48
  "sections": [
49
49
  "Overview",
50
50
  "era-interactive",
@@ -54,6 +54,7 @@
54
54
  "truncate",
55
55
  "era-text-trim-caps",
56
56
  "era-shimmer",
57
+ "era-collapse",
57
58
  "h-xs · h-xxs · h-sm · h-md · h-lg",
58
59
  "min-h-xs · min-h-xxs · min-h-sm · min-h-md · min-h-lg",
59
60
  "size-xs · size-xxs · size-sm · size-md · size-lg",
@@ -97,8 +98,8 @@
97
98
  {
98
99
  "slug": "llm-shell",
99
100
  "title": "LLM Shell",
100
- "summary": "A full chat console — conversation rail, streaming, tools, approvals.",
101
- "tokenEstimate": 21,
101
+ "summary": "A full chat console — conversation rail, streaming, tools, swarms.",
102
+ "tokenEstimate": 20,
102
103
  "sections": [],
103
104
  "file": "llm-shell.md"
104
105
  },
@@ -1029,6 +1030,16 @@
1029
1030
  "description": "In-progress text shimmer — a bright sweep through muted text (the standard\n\"working…\" label treatment in streaming UIs). Timing derives from the motion\naxis (10× --era-duration ≈ 1.5s at normal, 2.6s at extra); at instant the\nduration is 0s and the sweep freezes on its base frame — plain muted text —\nso reduced-motion and data-motion=\"instant\" both kill it for free.",
1030
1031
  "css": "@utility era-shimmer {\n\tbackground: linear-gradient(\n\t\t110deg,\n\t\tvar(--color-muted) 0% 43%,\n\t\tvar(--color-bright) 50%,\n\t\tvar(--color-muted) 57% 100%\n\t);\n\tbackground-size: 200% 100%;\n\tbackground-clip: text;\n\t-webkit-text-fill-color: transparent;\n\tanimation: era-shimmer calc(var(--era-duration) * 10) cubic-bezier(0.7, 0, 1, 0.4) infinite;\n}"
1031
1032
  },
1033
+ {
1034
+ "name": "era-collapse",
1035
+ "names": [
1036
+ "era-collapse"
1037
+ ],
1038
+ "file": "index.css",
1039
+ "useCase": "the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).",
1040
+ "description": "The open/close animation for a bits-ui Collapsible.Content — the height\nsweep every disclosure in the library uses (a Step's body, an error panel's\ndetail, a swarm node's children).\n\nIt has to be a keyframe animation rather than a transition because the\nnatural height is only knowable at open time: bits measures the content and\npublishes `--bits-collapsible-content-height`, which the `to` frame reads.\nThe class also owns `overflow: hidden` — animating height without it just\nspills the content out of the shrinking box.\n\nTiming rides the motion axis, so `instant` collapses it to a cut (0s) and\n`extra` stretches it, for free. Three components had a byte-identical copy of\nthese keyframes under three different names before this existed; the panel\nthat needs different motion should override `animation`, not re-declare it.",
1041
+ "css": "@utility era-collapse {\n\toverflow: hidden;\n\t&[data-state='open'] {\n\t\tanimation: era-collapse-open var(--era-duration) var(--era-ease);\n\t}\n\t&[data-state='closed'] {\n\t\tanimation: era-collapse-close var(--era-duration) var(--era-ease);\n\t}\n}"
1042
+ },
1032
1043
  {
1033
1044
  "name": "h-xs",
1034
1045
  "names": [
@@ -1152,8 +1163,8 @@
1152
1163
  "p-content"
1153
1164
  ],
1154
1165
  "file": "index.css",
1155
- "description": "A content panel always follows a section header ROW, and that row already\ncontributes its own slack: an h-lg bar holding one line of 16px text leaves\n~5px between the glyphs and the bottom of the bar. Padding the panel evenly\non top of that spends the gap twice — measured 22px under a heading against\n20px above it, so the header floated between the section above and the\nparagraph it introduces rather than binding to it.\n\nThe block-start is therefore one gap unit shorter, which is what makes a\nheading read as belonging to the text beneath it. Scales with density like\neverything else, since both terms do.",
1156
- "css": "@utility p-content {\n\tpadding: var(--era-content-p);\n\tpadding-top: calc(var(--era-content-p) - var(--era-gap));\n}"
1166
+ "description": "A content panel always follows a section header ROW, and that row already\ncontributes its own slack: the heading's text sits in an h-lg bar, so there\nis (h-lg − text)/2 of empty bar under its glyphs before the panel even\nstarts. Padding the panel evenly on top of that spends the space twice — the\nheader ends up floating between the section above and the paragraph it\nintroduces rather than binding to it.\n\nSo the block-start pays only the REMAINDER. The target is the indent: the\nspace under a heading should measure the same as the space beside the text,\nwhich is --era-content-p. The header row already supplies (h-lg − text)/2 of\nit, so the panel adds what's left, and heading-to-paragraph reads equal to\nthe paragraph's own inset. Subtracting one --era-gap (the previous rule) left\nthe header's slack unaccounted for and the heading still detached.\n\nBoth terms are density-derived, so the equality holds at every mode; the\nmax() is the floor for a hypothetical mode where the row's slack would\nexceed the inset outright.",
1167
+ "css": "@utility p-content {\n\tpadding: var(--era-content-p);\n\tpadding-top: max(0px, calc(var(--era-content-p) - (var(--era-h-lg) - var(--era-text)) / 2));\n}"
1157
1168
  },
1158
1169
  {
1159
1170
  "name": "p-menu",
@@ -70,6 +70,16 @@
70
70
  "description": "In-progress text shimmer — a bright sweep through muted text (the standard\n\"working…\" label treatment in streaming UIs). Timing derives from the motion\naxis (10× --era-duration ≈ 1.5s at normal, 2.6s at extra); at instant the\nduration is 0s and the sweep freezes on its base frame — plain muted text —\nso reduced-motion and data-motion=\"instant\" both kill it for free.",
71
71
  "css": "@utility era-shimmer {\n\tbackground: linear-gradient(\n\t\t110deg,\n\t\tvar(--color-muted) 0% 43%,\n\t\tvar(--color-bright) 50%,\n\t\tvar(--color-muted) 57% 100%\n\t);\n\tbackground-size: 200% 100%;\n\tbackground-clip: text;\n\t-webkit-text-fill-color: transparent;\n\tanimation: era-shimmer calc(var(--era-duration) * 10) cubic-bezier(0.7, 0, 1, 0.4) infinite;\n}"
72
72
  },
73
+ {
74
+ "name": "era-collapse",
75
+ "names": [
76
+ "era-collapse"
77
+ ],
78
+ "file": "index.css",
79
+ "useCase": "the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).",
80
+ "description": "The open/close animation for a bits-ui Collapsible.Content — the height\nsweep every disclosure in the library uses (a Step's body, an error panel's\ndetail, a swarm node's children).\n\nIt has to be a keyframe animation rather than a transition because the\nnatural height is only knowable at open time: bits measures the content and\npublishes `--bits-collapsible-content-height`, which the `to` frame reads.\nThe class also owns `overflow: hidden` — animating height without it just\nspills the content out of the shrinking box.\n\nTiming rides the motion axis, so `instant` collapses it to a cut (0s) and\n`extra` stretches it, for free. Three components had a byte-identical copy of\nthese keyframes under three different names before this existed; the panel\nthat needs different motion should override `animation`, not re-declare it.",
81
+ "css": "@utility era-collapse {\n\toverflow: hidden;\n\t&[data-state='open'] {\n\t\tanimation: era-collapse-open var(--era-duration) var(--era-ease);\n\t}\n\t&[data-state='closed'] {\n\t\tanimation: era-collapse-close var(--era-duration) var(--era-ease);\n\t}\n}"
82
+ },
73
83
  {
74
84
  "name": "h-xs",
75
85
  "names": [
@@ -193,8 +203,8 @@
193
203
  "p-content"
194
204
  ],
195
205
  "file": "index.css",
196
- "description": "A content panel always follows a section header ROW, and that row already\ncontributes its own slack: an h-lg bar holding one line of 16px text leaves\n~5px between the glyphs and the bottom of the bar. Padding the panel evenly\non top of that spends the gap twice — measured 22px under a heading against\n20px above it, so the header floated between the section above and the\nparagraph it introduces rather than binding to it.\n\nThe block-start is therefore one gap unit shorter, which is what makes a\nheading read as belonging to the text beneath it. Scales with density like\neverything else, since both terms do.",
197
- "css": "@utility p-content {\n\tpadding: var(--era-content-p);\n\tpadding-top: calc(var(--era-content-p) - var(--era-gap));\n}"
206
+ "description": "A content panel always follows a section header ROW, and that row already\ncontributes its own slack: the heading's text sits in an h-lg bar, so there\nis (h-lg − text)/2 of empty bar under its glyphs before the panel even\nstarts. Padding the panel evenly on top of that spends the space twice — the\nheader ends up floating between the section above and the paragraph it\nintroduces rather than binding to it.\n\nSo the block-start pays only the REMAINDER. The target is the indent: the\nspace under a heading should measure the same as the space beside the text,\nwhich is --era-content-p. The header row already supplies (h-lg − text)/2 of\nit, so the panel adds what's left, and heading-to-paragraph reads equal to\nthe paragraph's own inset. Subtracting one --era-gap (the previous rule) left\nthe header's slack unaccounted for and the heading still detached.\n\nBoth terms are density-derived, so the equality holds at every mode; the\nmax() is the floor for a hypothetical mode where the row's slack would\nexceed the inset outright.",
207
+ "css": "@utility p-content {\n\tpadding: var(--era-content-p);\n\tpadding-top: max(0px, calc(var(--era-content-p) - (var(--era-h-lg) - var(--era-text)) / 2));\n}"
198
208
  },
199
209
  {
200
210
  "name": "p-menu",
@@ -45,6 +45,7 @@ enough to stay var-form — `min-w-(--era-h-md)` etc. remain the escape hatch.
45
45
  | `truncate` | single-line horizontal truncation with an ellipsis — safe inside a trimmed or leading-none row; it will not clip ascenders or descenders. |
46
46
  | `era-text-trim-caps` | the era-text-trim variant for ALL-CAPS single-line strings (kbd hints, acronym badges) — never use it for mixed-case text. |
47
47
  | `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. |
48
+ | `era-collapse` | the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden). |
48
49
  | `h-xs` `h-xxs` `h-sm` `h-md` `h-lg` | the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm (standalone pill) · h-md (control) · h-lg (bar); the named form of h-(--era-h-*). |
49
50
  | `min-h-xs` `min-h-xxs` `min-h-sm` `min-h-md` `min-h-lg` | minimum tier height for containers that may grow — a bar that is at least lg-tier but can wrap (min-h-lg). |
50
51
  | `size-xs` `size-xxs` `size-sm` `size-md` `size-lg` | a tier-sized square — size-xs is THE icon size (a Lucide glyph beside body text), size-md a square control face. |
@@ -252,6 +253,39 @@ Declared in `index.css`.
252
253
  }
253
254
  ```
254
255
 
256
+ ## era-collapse
257
+
258
+ **Use it for:** the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).
259
+
260
+ The open/close animation for a bits-ui Collapsible.Content — the height
261
+ sweep every disclosure in the library uses (a Step's body, an error panel's
262
+ detail, a swarm node's children).
263
+
264
+ It has to be a keyframe animation rather than a transition because the
265
+ natural height is only knowable at open time: bits measures the content and
266
+ publishes `--bits-collapsible-content-height`, which the `to` frame reads.
267
+ The class also owns `overflow: hidden` — animating height without it just
268
+ spills the content out of the shrinking box.
269
+
270
+ Timing rides the motion axis, so `instant` collapses it to a cut (0s) and
271
+ `extra` stretches it, for free. Three components had a byte-identical copy of
272
+ these keyframes under three different names before this existed; the panel
273
+ that needs different motion should override `animation`, not re-declare it.
274
+
275
+ Declared in `index.css`.
276
+
277
+ ```css
278
+ @utility era-collapse {
279
+ overflow: hidden;
280
+ &[data-state='open'] {
281
+ animation: era-collapse-open var(--era-duration) var(--era-ease);
282
+ }
283
+ &[data-state='closed'] {
284
+ animation: era-collapse-close var(--era-duration) var(--era-ease);
285
+ }
286
+ }
287
+ ```
288
+
255
289
  ## h-xs · h-xxs · h-sm · h-md · h-lg
256
290
 
257
291
  **Use it for:** the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm (standalone pill) · h-md (control) · h-lg (bar); the named form of h-(--era-h-*).
@@ -449,22 +483,29 @@ Declared in `index.css`.
449
483
  ## p-content
450
484
 
451
485
  A content panel always follows a section header ROW, and that row already
452
- contributes its own slack: an h-lg bar holding one line of 16px text leaves
453
- ~5px between the glyphs and the bottom of the bar. Padding the panel evenly
454
- on top of that spends the gap twice — measured 22px under a heading against
455
- 20px above it, so the header floated between the section above and the
456
- paragraph it introduces rather than binding to it.
457
-
458
- The block-start is therefore one gap unit shorter, which is what makes a
459
- heading read as belonging to the text beneath it. Scales with density like
460
- everything else, since both terms do.
486
+ contributes its own slack: the heading's text sits in an h-lg bar, so there
487
+ is (h-lg text)/2 of empty bar under its glyphs before the panel even
488
+ starts. Padding the panel evenly on top of that spends the space twice — the
489
+ header ends up floating between the section above and the paragraph it
490
+ introduces rather than binding to it.
491
+
492
+ So the block-start pays only the REMAINDER. The target is the indent: the
493
+ space under a heading should measure the same as the space beside the text,
494
+ which is --era-content-p. The header row already supplies (h-lg − text)/2 of
495
+ it, so the panel adds what's left, and heading-to-paragraph reads equal to
496
+ the paragraph's own inset. Subtracting one --era-gap (the previous rule) left
497
+ the header's slack unaccounted for and the heading still detached.
498
+
499
+ Both terms are density-derived, so the equality holds at every mode; the
500
+ max() is the floor for a hypothetical mode where the row's slack would
501
+ exceed the inset outright.
461
502
 
462
503
  Declared in `index.css`.
463
504
 
464
505
  ```css
465
506
  @utility p-content {
466
507
  padding: var(--era-content-p);
467
- padding-top: calc(var(--era-content-p) - var(--era-gap));
508
+ padding-top: max(0px, calc(var(--era-content-p) - (var(--era-h-lg) - var(--era-text)) / 2));
468
509
  }
469
510
  ```
470
511
 
@@ -223,6 +223,53 @@
223
223
  }
224
224
  }
225
225
 
226
+ /* The open/close animation for a bits-ui Collapsible.Content — the height
227
+ * sweep every disclosure in the library uses (a Step's body, an error panel's
228
+ * detail, a swarm node's children).
229
+ *
230
+ * It has to be a keyframe animation rather than a transition because the
231
+ * natural height is only knowable at open time: bits measures the content and
232
+ * publishes `--bits-collapsible-content-height`, which the `to` frame reads.
233
+ * The class also owns `overflow: hidden` — animating height without it just
234
+ * spills the content out of the shrinking box.
235
+ *
236
+ * Timing rides the motion axis, so `instant` collapses it to a cut (0s) and
237
+ * `extra` stretches it, for free. Three components had a byte-identical copy of
238
+ * these keyframes under three different names before this existed; the panel
239
+ * that needs different motion should override `animation`, not re-declare it.
240
+ *
241
+ * @use the height sweep for a bits-ui Collapsible.Content — put it on the
242
+ * Content element itself (it carries its own overflow: hidden). */
243
+ @utility era-collapse {
244
+ overflow: hidden;
245
+ &[data-state='open'] {
246
+ animation: era-collapse-open var(--era-duration) var(--era-ease);
247
+ }
248
+ &[data-state='closed'] {
249
+ animation: era-collapse-close var(--era-duration) var(--era-ease);
250
+ }
251
+ }
252
+ @keyframes era-collapse-open {
253
+ from {
254
+ height: 0;
255
+ opacity: 0;
256
+ }
257
+ to {
258
+ height: var(--bits-collapsible-content-height);
259
+ opacity: 1;
260
+ }
261
+ }
262
+ @keyframes era-collapse-close {
263
+ from {
264
+ height: var(--bits-collapsible-content-height);
265
+ opacity: 1;
266
+ }
267
+ to {
268
+ height: 0;
269
+ opacity: 0;
270
+ }
271
+ }
272
+
226
273
  /* ------------------------------------------------------------------ */
227
274
  /* THE PUBLIC CLASS API */
228
275
  /* */
@@ -478,18 +525,25 @@
478
525
  padding: var(--era-pad-md);
479
526
  }
480
527
  /* A content panel always follows a section header ROW, and that row already
481
- * contributes its own slack: an h-lg bar holding one line of 16px text leaves
482
- * ~5px between the glyphs and the bottom of the bar. Padding the panel evenly
483
- * on top of that spends the gap twice — measured 22px under a heading against
484
- * 20px above it, so the header floated between the section above and the
485
- * paragraph it introduces rather than binding to it.
528
+ * contributes its own slack: the heading's text sits in an h-lg bar, so there
529
+ * is (h-lg text)/2 of empty bar under its glyphs before the panel even
530
+ * starts. Padding the panel evenly on top of that spends the space twice — the
531
+ * header ends up floating between the section above and the paragraph it
532
+ * introduces rather than binding to it.
533
+ *
534
+ * So the block-start pays only the REMAINDER. The target is the indent: the
535
+ * space under a heading should measure the same as the space beside the text,
536
+ * which is --era-content-p. The header row already supplies (h-lg − text)/2 of
537
+ * it, so the panel adds what's left, and heading-to-paragraph reads equal to
538
+ * the paragraph's own inset. Subtracting one --era-gap (the previous rule) left
539
+ * the header's slack unaccounted for and the heading still detached.
486
540
  *
487
- * The block-start is therefore one gap unit shorter, which is what makes a
488
- * heading read as belonging to the text beneath it. Scales with density like
489
- * everything else, since both terms do. */
541
+ * Both terms are density-derived, so the equality holds at every mode; the
542
+ * max() is the floor for a hypothetical mode where the row's slack would
543
+ * exceed the inset outright. */
490
544
  @utility p-content {
491
545
  padding: var(--era-content-p);
492
- padding-top: calc(var(--era-content-p) - var(--era-gap));
546
+ padding-top: max(0px, calc(var(--era-content-p) - (var(--era-h-lg) - var(--era-text)) / 2));
493
547
  }
494
548
 
495
549
  /* @use menu-panel padding — the surface-owned inset (0 on flat, 2px on
@@ -496,9 +496,21 @@
496
496
  /* counterpart on the page and are harmonised, not */
497
497
  /* sampled. */
498
498
  /* */
499
- /* Border contrast is 2.11:1 against the page below */
500
- /* the 2.49–4.05 the other light themes sit at, but */
501
- /* that IS Yahoo's rule colour, specified exactly. */
499
+ /* The blues are then all taken one small step DARKER */
500
+ /* than the sample L-0.02 on the chrome steps, -0.04 */
501
+ /* on the ink ones (7, 10, primary), with chroma nudged */
502
+ /* up to hold the hue's saturation at the lower */
503
+ /* lightness. Yahoo's palette is tuned for its own type */
504
+ /* at its own size; against era's text it read washed */
505
+ /* out. Every step keeps its measured hue and its place */
506
+ /* in the ramp, so the theme still reads as the sampled */
507
+ /* one — it just carries more ink. */
508
+ /* */
509
+ /* What that buys: muted text 3.50 -> 4.08:1 (body copy */
510
+ /* now clears AA at large sizes), link 10.17 -> 11.40, */
511
+ /* primary 4.22 -> 4.79, border 2.10 -> 2.27 against */
512
+ /* the page — still under the 2.49-4.05 the other light */
513
+ /* themes sit at, because it is still Yahoo's rule. */
502
514
  /* -------------------------------------------------- */
503
515
  @media (prefers-color-scheme: light) {
504
516
  :root[data-theme='signalnine']:not(.dark) {
@@ -508,12 +520,12 @@
508
520
  --color-2: oklch(0.9439 0.0251 271.14); /* #e6ecfe card fill */
509
521
  --color-3: oklch(0.887 0.0368 265.13); /* #cedaf3 border */
510
522
  --color-4: oklch(0.844 0.0447 265.6); /* #bcccea */
511
- --color-5: oklch(0.7667 0.0612 261.83); /* #9eb4db the measured dominant rule */
512
- --color-6: oklch(0.7 0.074 263);
513
- --color-7: oklch(0.631 0.0915 260.33); /* #698ac1 muted text */
514
- --color-8: oklch(0.57 0.12 262);
523
+ --color-5: oklch(0.745 0.0645 261.83); /* #96add6 the measured rule, darkened */
524
+ --color-6: oklch(0.676 0.078 263);
525
+ --color-7: oklch(0.593 0.0975 260.33); /* #5c7eb8 muted text, darkened */
526
+ --color-8: oklch(0.545 0.126 262);
515
527
  --color-9: oklch(0.51 0 0); /* #666666 */
516
- --color-10: oklch(0.3855 0.1534 266.07); /* #1d3994 link text */
528
+ --color-10: oklch(0.358 0.1534 266.07); /* #17318b link text, darkened */
517
529
  --color-11: oklch(0.16 0.008 266);
518
530
  --color-12: oklch(0 0 0); /* #000000 body text */
519
531
 
@@ -532,7 +544,7 @@
532
544
  /* Cards read --era-surface-bg, which the surface contract defines as
533
545
  * --color-2 — so the card fill follows from the scale, no override. */
534
546
 
535
- --color-primary: oklch(0.596 0.22 265.8); /* #4070ff */
547
+ --color-primary: oklch(0.566 0.222 265.8); /* #3766f6 the CTA blue, darkened */
536
548
  --color-primary-fg: oklch(1 0 0);
537
549
  --color-primary-hover: color-mix(in oklch, var(--color-primary) 85%, black);
538
550
  --color-destructive: oklch(0.59 0.242 27.7); /* #eb0013 */
@@ -551,12 +563,12 @@
551
563
  --color-2: oklch(0.9439 0.0251 271.14); /* #e6ecfe card fill */
552
564
  --color-3: oklch(0.887 0.0368 265.13); /* #cedaf3 border */
553
565
  --color-4: oklch(0.844 0.0447 265.6); /* #bcccea */
554
- --color-5: oklch(0.7667 0.0612 261.83); /* #9eb4db the measured dominant rule */
555
- --color-6: oklch(0.7 0.074 263);
556
- --color-7: oklch(0.631 0.0915 260.33); /* #698ac1 muted text */
557
- --color-8: oklch(0.57 0.12 262);
566
+ --color-5: oklch(0.745 0.0645 261.83); /* #96add6 the measured rule, darkened */
567
+ --color-6: oklch(0.676 0.078 263);
568
+ --color-7: oklch(0.593 0.0975 260.33); /* #5c7eb8 muted text, darkened */
569
+ --color-8: oklch(0.545 0.126 262);
558
570
  --color-9: oklch(0.51 0 0); /* #666666 */
559
- --color-10: oklch(0.3855 0.1534 266.07); /* #1d3994 link text */
571
+ --color-10: oklch(0.358 0.1534 266.07); /* #17318b link text, darkened */
560
572
  --color-11: oklch(0.16 0.008 266);
561
573
  --color-12: oklch(0 0 0); /* #000000 body text */
562
574
 
@@ -575,7 +587,7 @@
575
587
  /* Cards read --era-surface-bg, which the surface contract defines as
576
588
  * --color-2 — so the card fill follows from the scale, no override. */
577
589
 
578
- --color-primary: oklch(0.596 0.22 265.8); /* #4070ff */
590
+ --color-primary: oklch(0.566 0.222 265.8); /* #3766f6 the CTA blue, darkened */
579
591
  --color-primary-fg: oklch(1 0 0);
580
592
  --color-primary-hover: color-mix(in oklch, var(--color-primary) 85%, black);
581
593
  --color-destructive: oklch(0.59 0.242 27.7); /* #eb0013 */
@@ -10,11 +10,11 @@
10
10
  }: PartProps<Collapsible.ContentProps> = $props();
11
11
  </script>
12
12
 
13
- <!-- Height animates on the outer (padding-free) element via bits' measured
14
- content-height var — padding lives on the inner well so the animation
15
- never fights box metrics. The well is recessed (shadow-well): payload the
16
- step produced, not raised chrome. -->
17
- <Collapsible.Content bind:ref data-era-step-content class="overflow-hidden" {...restProps}>
13
+ <!-- Height animates on the outer (padding-free) element via era-collapse, which
14
+ reads bits' measured content-height var — padding lives on the inner well
15
+ so the animation never fights box metrics. The well is recessed
16
+ (shadow-well): payload the step produced, not raised chrome. -->
17
+ <Collapsible.Content bind:ref class="era-collapse" {...restProps}>
18
18
  <div
19
19
  class={cn(
20
20
  'mt-(--era-gap) mb-(--era-gap) flex min-w-0 flex-col gap-(--era-pad-sm) rounded-(--era-rd-md) bg-(--era-surface-bg) p-(--era-pad-sm) shadow-(--era-shadow-well)',
@@ -24,32 +24,3 @@
24
24
  {@render children?.()}
25
25
  </div>
26
26
  </Collapsible.Content>
27
-
28
- <style>
29
- :global([data-era-step-content][data-state='open']) {
30
- animation: era-step-expand var(--era-duration) var(--era-ease);
31
- }
32
- :global([data-era-step-content][data-state='closed']) {
33
- animation: era-step-collapse var(--era-duration) var(--era-ease);
34
- }
35
- @keyframes -global-era-step-expand {
36
- from {
37
- height: 0;
38
- opacity: 0;
39
- }
40
- to {
41
- height: var(--bits-collapsible-content-height);
42
- opacity: 1;
43
- }
44
- }
45
- @keyframes -global-era-step-collapse {
46
- from {
47
- height: var(--bits-collapsible-content-height);
48
- opacity: 1;
49
- }
50
- to {
51
- height: 0;
52
- opacity: 0;
53
- }
54
- }
55
- </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",