@echelon-foundry/design-system 0.9.0 → 0.10.0-main.257.1

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ published as GitHub release tarballs
7
7
  and to npm. Earlier releases are described in `docs/CONSUMING-FORMA.md` and
8
8
  `HANDOFF.md`.
9
9
 
10
+ ## 0.10.0
11
+
12
+ Minor release: layout constraint enforcement (LCE-001, `experiments/LCE-001/REPORT.md`). `@echelon-foundry/forma-workflow` 1.1.0 ships the matching engine.
13
+
14
+ ### Fixed
15
+ - **Pill diagram nodes.** The curve no longer cuts multi-line text. The corner radius is bounded by the node padding, so a short pill stays a stadium.
16
+ - **Ellipse diagram nodes.** Padding is the inscribed bound (14.65% plus clearance).
17
+ - **Diamond diagram nodes.** Padding is now 25% plus clearance; at 20%, the content corners fell outside the diamond.
18
+ - **Marketing card, badge and button.** A brand `--ef-shape-radius` is capped by each component's padding, so a pill or 50% brand radius cannot cut multi-line content.
19
+ - **Marketing button and badge.** They use `overflow-wrap: anywhere`, so a word wider than the row breaks instead of overflowing.
20
+ - **Diagrams inside `.ef-site`.** They keep their own type metrics: `.ef-diagram` pins its type context, and `revert-layer` stops site prose defaults at the diagram.
21
+ - **Wizard step indicators.** They widen into a pill for multi-digit text instead of overflowing the circle.
22
+ - **Connector labels.** They wrap at 12rem.
23
+
24
+ ### Changed
25
+ - **Node sizing in `Forma.Workflow`.** Sizing is shape-aware and uses a script-aware text estimate (`LayoutText`) that reserves WCAG 1.4.12 text-spacing room. Nodes can be taller than before.
26
+ - **Rank gaps.** They fit the widest connector label.
27
+ - **Connector routing.** Connectors avoid unrelated nodes.
28
+ - **Label placement.** Labels are placed without collisions.
29
+ - **Diagram geometry.** It is emitted in rem (1rem = 16 canvas units), so it scales with enlarged text. The exported example documents were regenerated.
30
+
31
+ ### Added
32
+ - **`Forma.Workflow.LayoutGeometry`.** Shape geometry: CSS radius normalisation, inner borders, curved and rotated boundaries.
33
+ - **`Forma.Workflow.LayoutCheck`.** Static layout validation: authored sizes too small for their content, node overlap, connectors through nodes, and label collisions.
34
+ - **`tests/layout` and `npm run test:layout`.** An adversarial F# layout probe with browser measurement, plus 50 regression fixtures, including the three Causa historical diagrams.
35
+
36
+ ## 0.9.1
37
+
38
+ Patch release: deterministic browser runtime version diagnostics.
39
+
40
+ ### Added
41
+ - The `@echelon-foundry/design-system/runtime` entry point, generated from the package version, registers Forma 0.9.1 in `window.__ECHELON_RUNTIME__` and emits one compact console diagnostic.
42
+ - Cross-browser Playwright coverage for actual build metadata, independent load order, duplicate initialization, conflict diagnostics, and the combined Echelon registry.
43
+
44
+ Component HTML and CSS remain behavior-free. The diagnostic does not inspect or modify the DOM and adds no production dependency.
45
+
10
46
  ## 0.9.0
11
47
 
12
48
  Minor release: public semantic color contracts and rendered state validation.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "format": "forma.color-contract/1.0.0",
3
- "formaVersion": "0.9.0",
3
+ "formaVersion": "0.10.0",
4
4
  "validationScope": "CSS var() references to --ef-color-* plus reserved internal color-related prefixes; other Forma APIs are governed by their own contracts.",
5
5
  "compatibility": {
6
6
  "policy": "Validate with the contract shipped by the pinned Forma package version.",
@@ -81,6 +81,7 @@
81
81
  "canvasHeight": "--ef-diagram-canvas-h",
82
82
  "portOffset": "--ef-diagram-port-offset",
83
83
  "unit": "px",
84
+ "unitNote": "Any CSS length is accepted. Forma.Workflow emits rem (1rem = 16 canvas px), so diagram geometry scales with enlarged text (LCE-001).",
84
85
  "axes": "physical (does not mirror in right-to-left documents)"
85
86
  },
86
87
  "color": {
package/dist/all.css CHANGED
@@ -1709,6 +1709,11 @@
1709
1709
  vertical-align: middle;
1710
1710
  }
1711
1711
 
1712
+ /*
1713
+ * The trigger is a circle for a single glyph (the canonical "?"). LCE-001
1714
+ * measured multi-character text ("1.5k") crossing the curve; that is outside
1715
+ * this contract, not a supported variant.
1716
+ */
1712
1717
  .ef-tooltip__trigger {
1713
1718
  min-inline-size: 2.75rem;
1714
1719
  min-block-size: 2.75rem;
@@ -2840,13 +2845,16 @@
2840
2845
  font-size: 0.8125rem;
2841
2846
  }
2842
2847
 
2848
+ /* A circle for one or two digits; longer step text widens it into a pill (LCE-001). */
2843
2849
  .ef-wizard__steps li > span {
2844
2850
  display: grid;
2845
2851
  place-items: center;
2846
- inline-size: 1.75rem;
2852
+ min-inline-size: 1.75rem;
2847
2853
  block-size: 1.75rem;
2854
+ padding-inline: 0.375rem;
2848
2855
  border: 1px solid var(--ef-color-border-functional, #3a403c);
2849
- border-radius: 50%;
2856
+ border-radius: 999px;
2857
+ white-space: nowrap;
2850
2858
  }
2851
2859
 
2852
2860
  .ef-wizard__steps [data-state="complete"] > span::after { content: "✓"; position: absolute; }
@@ -5476,7 +5484,25 @@
5476
5484
  * Non-color cues: the visible node kind text, data-ef-shape, data-ef-line and
5477
5485
  * the textual relationship list. Focus never reads authored color.
5478
5486
  */
5479
- .ef-diagram { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ef-primitive-spacing-4, 1rem); min-inline-size: 0; margin: 0; }
5487
+ /*
5488
+ * The diagram pins its own type context: node geometry is computed for these
5489
+ * metrics, so a host's font, line height or spacing must not change them
5490
+ * (LCE-001: a diagram inside a marketing card inherited its typography and
5491
+ * outgrew its layout). User text-spacing overrides still apply.
5492
+ */
5493
+ .ef-diagram {
5494
+ display: grid;
5495
+ grid-template-columns: minmax(0, 1fr);
5496
+ gap: var(--ef-primitive-spacing-4, 1rem);
5497
+ min-inline-size: 0;
5498
+ margin: 0;
5499
+ font-family: var(--ef-primitive-font-family-sans, system-ui, sans-serif);
5500
+ font-size: 1rem;
5501
+ font-weight: 400;
5502
+ line-height: 1.6;
5503
+ letter-spacing: normal;
5504
+ word-spacing: normal;
5505
+ }
5480
5506
  .ef-diagram > figcaption { font-weight: 700; }
5481
5507
  .ef-diagram__viewport {
5482
5508
  max-inline-size: 100%;
@@ -5516,7 +5542,18 @@
5516
5542
  overflow-wrap: anywhere;
5517
5543
  }
5518
5544
  .ef-diagram-node[data-ef-shape="rounded"] { border-radius: var(--ef-primitive-spacing-3, 0.75rem); }
5519
- .ef-diagram-node[data-ef-shape="pill"] { border-radius: 999px; padding-inline: var(--ef-primitive-spacing-5, 1.5rem); }
5545
+ /*
5546
+ * A pill is a stadium while it is short. A taller (multi-line) pill keeps
5547
+ * the largest corner radius whose curve stays 4px clear of the content box
5548
+ * (LCE-001), instead of letting the curve cut through the text: for inline
5549
+ * padding pi, block padding pb (pb <= pi), border b and clearance c the
5550
+ * bound is pi + pb·(1 + √2) - c·(1 + √2) + b, about 45px with Forma's
5551
+ * spacing tokens. It follows the padding tokens, so it holds at any text size.
5552
+ */
5553
+ .ef-diagram-node[data-ef-shape="pill"] {
5554
+ border-radius: min(999px, calc(var(--ef-primitive-spacing-5, 1.5rem) + var(--ef-primitive-spacing-3, 0.75rem) * 2.414 - 7.66px));
5555
+ padding-inline: var(--ef-primitive-spacing-5, 1.5rem);
5556
+ }
5520
5557
  /*
5521
5558
  * FMD-M2 (forma#88). Ellipse and diamond keep a real border (never a
5522
5559
  * background-only shape) so the boundary survives backgrounds-off print and
@@ -5527,7 +5564,8 @@
5527
5564
  border-radius: 50%;
5528
5565
  border-inline-start-width: 2px;
5529
5566
  border-block-end: 6px solid var(--ef-diagram-node-accent);
5530
- padding: var(--ef-primitive-spacing-4, 1rem) calc(var(--ef-diagram-w, 12rem) * 0.14);
5567
+ /* The content's corners stay inside the ellipse: 14.65% is (1 - 1/√2) / 2 of the width, plus clearance (LCE-001). */
5568
+ padding: var(--ef-primitive-spacing-4, 1rem) calc(var(--ef-diagram-w, 12rem) * 0.1465 + 0.25rem);
5531
5569
  place-content: center;
5532
5570
  text-align: center;
5533
5571
  }
@@ -5535,12 +5573,14 @@
5535
5573
  .ef-diagram-node[data-ef-shape="diamond"] .ef-diagram-node__meta { grid-template-columns: auto auto; justify-content: center; text-align: start; }
5536
5574
  /*
5537
5575
  * A square box whose ::before is the diamond; the content sits in the
5538
- * inscribed square. Padding is derived from --ef-diagram-w because percentage
5576
+ * inscribed square, which is half the side, so the padding is a quarter of
5577
+ * the width plus clearance (LCE-001; 0.2 put content corners outside the
5578
+ * diamond). Padding is derived from --ef-diagram-w because percentage
5539
5579
  * padding would resolve against the canvas, not the node.
5540
5580
  */
5541
5581
  .ef-diagram-node[data-ef-shape="diamond"] {
5542
5582
  aspect-ratio: 1;
5543
- padding: calc(var(--ef-diagram-w, 12rem) * 0.2);
5583
+ padding: calc(var(--ef-diagram-w, 12rem) * 0.25 + 0.25rem);
5544
5584
  border: 0;
5545
5585
  background: transparent;
5546
5586
  place-content: center;
@@ -5580,7 +5620,10 @@
5580
5620
  background: var(--ef-color-surface-primary, #f2efe7);
5581
5621
  color: var(--ef-color-text-primary, #171a18);
5582
5622
  font-size: 0.875rem;
5583
- white-space: nowrap;
5623
+ /* One line up to 12rem, then wrapping: the layout reserves that box (LCE-001). */
5624
+ inline-size: max-content;
5625
+ max-inline-size: 12rem;
5626
+ overflow-wrap: anywhere;
5584
5627
  }
5585
5628
 
5586
5629
  /* The text equivalent: every relationship in reading order. */
@@ -7786,6 +7829,14 @@
7786
7829
  opacity: var(--ef-site-backdrop-opacity, 1);
7787
7830
  }
7788
7831
 
7832
+ /*
7833
+ * Prose defaults stop at an embedded diagram (LCE-001). Diagram geometry is
7834
+ * computed for the component layer's type metrics; a site heading size or
7835
+ * paragraph margin made nodes outgrow their layout. revert-layer hands these
7836
+ * elements back to the earlier layers.
7837
+ */
7838
+ .ef-site .ef-diagram :where(h1, h2, h3, h4, h5, h6, p, ul, ol, li, a, svg, dl, dt, dd, strong, b) { all: revert-layer; }
7839
+
7789
7840
  .ef-site :where(h1, h2, h3, h4, h5, h6) {
7790
7841
  margin-block: 0;
7791
7842
  color: var(--ef-tone-heading);
@@ -8084,7 +8135,8 @@
8084
8135
  min-block-size: calc(var(--ef-layout-target-min) + var(--ef-space-2));
8085
8136
  padding: var(--ef-space-3) var(--ef-space-5);
8086
8137
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
8087
- border-radius: var(--ef-shape-radius);
8138
+ /* Bounded so a brand radius never reaches the content (LCE-001, see .ef-card). */
8139
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-space-5) + var(--ef-space-3) * 2.414 - 2.414px));
8088
8140
  background: transparent;
8089
8141
  color: var(--ef-tone-text);
8090
8142
  font: inherit;
@@ -8096,7 +8148,10 @@
8096
8148
  /* Never wider than its row and never split mid-word unless one word is
8097
8149
  wider than the row (ULI-L05, L06). */
8098
8150
  max-inline-size: 100%;
8099
- overflow-wrap: break-word;
8151
+ /* anywhere, not break-word: inside inline-flex only anywhere lets the
8152
+ min-content width shrink, so a word wider than the row breaks instead of
8153
+ overflowing the button (LCE-001). Words still break only when they must. */
8154
+ overflow-wrap: anywhere;
8100
8155
  cursor: pointer;
8101
8156
  /* Hover and press are perceptual emphasis on the surface only: the hit
8102
8157
  target never moves under the pointer (MOT-014). */
@@ -8141,11 +8196,13 @@
8141
8196
  display: inline-flex;
8142
8197
  align-items: center;
8143
8198
  gap: var(--ef-space-1);
8199
+ max-inline-size: 100%;
8144
8200
  padding: var(--ef-space-1) var(--ef-space-2);
8145
8201
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
8146
- border-radius: var(--ef-shape-radius);
8202
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-space-2) + var(--ef-space-1) * 2.414 - 2.414px));
8147
8203
  color: var(--ef-tone-text);
8148
8204
  font-family: var(--ef-type-family-mono);
8205
+ overflow-wrap: anywhere;
8149
8206
  font-size: var(--ef-type-size-label);
8150
8207
  line-height: var(--ef-type-leading-snug);
8151
8208
  text-decoration: none;
@@ -8191,7 +8248,15 @@
8191
8248
  min-inline-size: 0;
8192
8249
  padding: var(--ef-layout-block-space);
8193
8250
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
8194
- border-radius: var(--ef-shape-radius);
8251
+ /*
8252
+ * A brand radius is capped by the padding (LCE-001). Content whose corner
8253
+ * sits at padding p from both edges stays clear of a circular corner of
8254
+ * radius r while r <= (2 + √2)·(p - c) + c for clearance c; with c = 1px
8255
+ * that is p·3.414 - 2.414px. A badge or button uses its own two paddings:
8256
+ * pi + pb·2.414 - 2.414px (pb the smaller). A larger brand radius (a pill
8257
+ * or 50%) would otherwise cut through multi-line content.
8258
+ */
8259
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-layout-block-space) * 3.414 - 2.414px));
8195
8260
  }
8196
8261
 
8197
8262
  /* Inside a grid, cards share hairline rules instead of doubling borders. */
@@ -337,16 +337,17 @@
337
337
  "patterns/wizard.html",
338
338
  "patterns/work-queue.html",
339
339
  "patterns/workspace-shell.html",
340
+ "runtime.js",
340
341
  "skins.css",
341
342
  "tokens.css"
342
343
  ],
343
344
  "bytes": {
344
- "all.css": 332985,
345
+ "all.css": 336382,
345
346
  "assessment.css": 36343,
346
347
  "brands/echelon.css": 4213,
347
348
  "brands/example-harbor.css": 4229,
348
349
  "brands/index.json": 335,
349
- "components.css": 211421,
350
+ "components.css": 213356,
350
351
  "foundations.css": 15326,
351
352
  "icons/add.svg": 216,
352
353
  "icons/agent.svg": 354,
@@ -486,7 +487,7 @@
486
487
  "icons/phone.svg": 274,
487
488
  "icons/print.svg": 318,
488
489
  "icons/refresh.svg": 277,
489
- "icons/registry.json": 28723,
490
+ "icons/registry.json": 28724,
490
491
  "icons/reply-all.svg": 268,
491
492
  "icons/reply.svg": 234,
492
493
  "icons/save.svg": 275,
@@ -509,7 +510,7 @@
509
510
  "icons/warning.svg": 324,
510
511
  "icons/wifi-off.svg": 269,
511
512
  "icons/workflow.svg": 345,
512
- "marketing.css": 46156,
513
+ "marketing.css": 47618,
513
514
  "patterns/active-filter-summary.html": 532,
514
515
  "patterns/alert.html": 384,
515
516
  "patterns/allocation.html": 1033,
@@ -678,6 +679,7 @@
678
679
  "patterns/wizard.html": 817,
679
680
  "patterns/work-queue.html": 971,
680
681
  "patterns/workspace-shell.html": 479,
682
+ "runtime.js": 3030,
681
683
  "skins.css": 924,
682
684
  "tokens.css": 23735
683
685
  }
@@ -799,6 +799,11 @@
799
799
  vertical-align: middle;
800
800
  }
801
801
 
802
+ /*
803
+ * The trigger is a circle for a single glyph (the canonical "?"). LCE-001
804
+ * measured multi-character text ("1.5k") crossing the curve; that is outside
805
+ * this contract, not a supported variant.
806
+ */
802
807
  .ef-tooltip__trigger {
803
808
  min-inline-size: 2.75rem;
804
809
  min-block-size: 2.75rem;
@@ -1930,13 +1935,16 @@
1930
1935
  font-size: 0.8125rem;
1931
1936
  }
1932
1937
 
1938
+ /* A circle for one or two digits; longer step text widens it into a pill (LCE-001). */
1933
1939
  .ef-wizard__steps li > span {
1934
1940
  display: grid;
1935
1941
  place-items: center;
1936
- inline-size: 1.75rem;
1942
+ min-inline-size: 1.75rem;
1937
1943
  block-size: 1.75rem;
1944
+ padding-inline: 0.375rem;
1938
1945
  border: 1px solid var(--ef-color-border-functional, #3a403c);
1939
- border-radius: 50%;
1946
+ border-radius: 999px;
1947
+ white-space: nowrap;
1940
1948
  }
1941
1949
 
1942
1950
  .ef-wizard__steps [data-state="complete"] > span::after { content: "✓"; position: absolute; }
@@ -4566,7 +4574,25 @@
4566
4574
  * Non-color cues: the visible node kind text, data-ef-shape, data-ef-line and
4567
4575
  * the textual relationship list. Focus never reads authored color.
4568
4576
  */
4569
- .ef-diagram { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ef-primitive-spacing-4, 1rem); min-inline-size: 0; margin: 0; }
4577
+ /*
4578
+ * The diagram pins its own type context: node geometry is computed for these
4579
+ * metrics, so a host's font, line height or spacing must not change them
4580
+ * (LCE-001: a diagram inside a marketing card inherited its typography and
4581
+ * outgrew its layout). User text-spacing overrides still apply.
4582
+ */
4583
+ .ef-diagram {
4584
+ display: grid;
4585
+ grid-template-columns: minmax(0, 1fr);
4586
+ gap: var(--ef-primitive-spacing-4, 1rem);
4587
+ min-inline-size: 0;
4588
+ margin: 0;
4589
+ font-family: var(--ef-primitive-font-family-sans, system-ui, sans-serif);
4590
+ font-size: 1rem;
4591
+ font-weight: 400;
4592
+ line-height: 1.6;
4593
+ letter-spacing: normal;
4594
+ word-spacing: normal;
4595
+ }
4570
4596
  .ef-diagram > figcaption { font-weight: 700; }
4571
4597
  .ef-diagram__viewport {
4572
4598
  max-inline-size: 100%;
@@ -4606,7 +4632,18 @@
4606
4632
  overflow-wrap: anywhere;
4607
4633
  }
4608
4634
  .ef-diagram-node[data-ef-shape="rounded"] { border-radius: var(--ef-primitive-spacing-3, 0.75rem); }
4609
- .ef-diagram-node[data-ef-shape="pill"] { border-radius: 999px; padding-inline: var(--ef-primitive-spacing-5, 1.5rem); }
4635
+ /*
4636
+ * A pill is a stadium while it is short. A taller (multi-line) pill keeps
4637
+ * the largest corner radius whose curve stays 4px clear of the content box
4638
+ * (LCE-001), instead of letting the curve cut through the text: for inline
4639
+ * padding pi, block padding pb (pb <= pi), border b and clearance c the
4640
+ * bound is pi + pb·(1 + √2) - c·(1 + √2) + b, about 45px with Forma's
4641
+ * spacing tokens. It follows the padding tokens, so it holds at any text size.
4642
+ */
4643
+ .ef-diagram-node[data-ef-shape="pill"] {
4644
+ border-radius: min(999px, calc(var(--ef-primitive-spacing-5, 1.5rem) + var(--ef-primitive-spacing-3, 0.75rem) * 2.414 - 7.66px));
4645
+ padding-inline: var(--ef-primitive-spacing-5, 1.5rem);
4646
+ }
4610
4647
  /*
4611
4648
  * FMD-M2 (forma#88). Ellipse and diamond keep a real border (never a
4612
4649
  * background-only shape) so the boundary survives backgrounds-off print and
@@ -4617,7 +4654,8 @@
4617
4654
  border-radius: 50%;
4618
4655
  border-inline-start-width: 2px;
4619
4656
  border-block-end: 6px solid var(--ef-diagram-node-accent);
4620
- padding: var(--ef-primitive-spacing-4, 1rem) calc(var(--ef-diagram-w, 12rem) * 0.14);
4657
+ /* The content's corners stay inside the ellipse: 14.65% is (1 - 1/√2) / 2 of the width, plus clearance (LCE-001). */
4658
+ padding: var(--ef-primitive-spacing-4, 1rem) calc(var(--ef-diagram-w, 12rem) * 0.1465 + 0.25rem);
4621
4659
  place-content: center;
4622
4660
  text-align: center;
4623
4661
  }
@@ -4625,12 +4663,14 @@
4625
4663
  .ef-diagram-node[data-ef-shape="diamond"] .ef-diagram-node__meta { grid-template-columns: auto auto; justify-content: center; text-align: start; }
4626
4664
  /*
4627
4665
  * A square box whose ::before is the diamond; the content sits in the
4628
- * inscribed square. Padding is derived from --ef-diagram-w because percentage
4666
+ * inscribed square, which is half the side, so the padding is a quarter of
4667
+ * the width plus clearance (LCE-001; 0.2 put content corners outside the
4668
+ * diamond). Padding is derived from --ef-diagram-w because percentage
4629
4669
  * padding would resolve against the canvas, not the node.
4630
4670
  */
4631
4671
  .ef-diagram-node[data-ef-shape="diamond"] {
4632
4672
  aspect-ratio: 1;
4633
- padding: calc(var(--ef-diagram-w, 12rem) * 0.2);
4673
+ padding: calc(var(--ef-diagram-w, 12rem) * 0.25 + 0.25rem);
4634
4674
  border: 0;
4635
4675
  background: transparent;
4636
4676
  place-content: center;
@@ -4670,7 +4710,10 @@
4670
4710
  background: var(--ef-color-surface-primary, #f2efe7);
4671
4711
  color: var(--ef-color-text-primary, #171a18);
4672
4712
  font-size: 0.875rem;
4673
- white-space: nowrap;
4713
+ /* One line up to 12rem, then wrapping: the layout reserves that box (LCE-001). */
4714
+ inline-size: max-content;
4715
+ max-inline-size: 12rem;
4716
+ overflow-wrap: anywhere;
4674
4717
  }
4675
4718
 
4676
4719
  /* The text equivalent: every relationship in reading order. */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "formaVersion": "0.9.0",
3
+ "formaVersion": "0.10.0",
4
4
  "grid": 24,
5
5
  "icons": [
6
6
  {
@@ -1,4 +1,4 @@
1
- /*! Forma 0.9.0 | forma-all.css | bundle echelon-marketing
1
+ /*! Forma 0.10.0 | forma-all.css | bundle echelon-marketing
2
2
  Complete Forma surface (application components, assessment, and marketing layers) for sites that also present Forma application patterns such as data grids or status lozenges.
3
3
  Generated by tools/PresentationBundler. Do not edit; pin by version and sha256. */
4
4
 
@@ -1714,6 +1714,11 @@
1714
1714
  vertical-align: middle;
1715
1715
  }
1716
1716
 
1717
+ /*
1718
+ * The trigger is a circle for a single glyph (the canonical "?"). LCE-001
1719
+ * measured multi-character text ("1.5k") crossing the curve; that is outside
1720
+ * this contract, not a supported variant.
1721
+ */
1717
1722
  .ef-tooltip__trigger {
1718
1723
  min-inline-size: 2.75rem;
1719
1724
  min-block-size: 2.75rem;
@@ -2845,13 +2850,16 @@
2845
2850
  font-size: 0.8125rem;
2846
2851
  }
2847
2852
 
2853
+ /* A circle for one or two digits; longer step text widens it into a pill (LCE-001). */
2848
2854
  .ef-wizard__steps li > span {
2849
2855
  display: grid;
2850
2856
  place-items: center;
2851
- inline-size: 1.75rem;
2857
+ min-inline-size: 1.75rem;
2852
2858
  block-size: 1.75rem;
2859
+ padding-inline: 0.375rem;
2853
2860
  border: 1px solid var(--ef-color-border-functional, #3a403c);
2854
- border-radius: 50%;
2861
+ border-radius: 999px;
2862
+ white-space: nowrap;
2855
2863
  }
2856
2864
 
2857
2865
  .ef-wizard__steps [data-state="complete"] > span::after { content: "✓"; position: absolute; }
@@ -5481,7 +5489,25 @@
5481
5489
  * Non-color cues: the visible node kind text, data-ef-shape, data-ef-line and
5482
5490
  * the textual relationship list. Focus never reads authored color.
5483
5491
  */
5484
- .ef-diagram { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ef-primitive-spacing-4, 1rem); min-inline-size: 0; margin: 0; }
5492
+ /*
5493
+ * The diagram pins its own type context: node geometry is computed for these
5494
+ * metrics, so a host's font, line height or spacing must not change them
5495
+ * (LCE-001: a diagram inside a marketing card inherited its typography and
5496
+ * outgrew its layout). User text-spacing overrides still apply.
5497
+ */
5498
+ .ef-diagram {
5499
+ display: grid;
5500
+ grid-template-columns: minmax(0, 1fr);
5501
+ gap: var(--ef-primitive-spacing-4, 1rem);
5502
+ min-inline-size: 0;
5503
+ margin: 0;
5504
+ font-family: var(--ef-primitive-font-family-sans, system-ui, sans-serif);
5505
+ font-size: 1rem;
5506
+ font-weight: 400;
5507
+ line-height: 1.6;
5508
+ letter-spacing: normal;
5509
+ word-spacing: normal;
5510
+ }
5485
5511
  .ef-diagram > figcaption { font-weight: 700; }
5486
5512
  .ef-diagram__viewport {
5487
5513
  max-inline-size: 100%;
@@ -5521,7 +5547,18 @@
5521
5547
  overflow-wrap: anywhere;
5522
5548
  }
5523
5549
  .ef-diagram-node[data-ef-shape="rounded"] { border-radius: var(--ef-primitive-spacing-3, 0.75rem); }
5524
- .ef-diagram-node[data-ef-shape="pill"] { border-radius: 999px; padding-inline: var(--ef-primitive-spacing-5, 1.5rem); }
5550
+ /*
5551
+ * A pill is a stadium while it is short. A taller (multi-line) pill keeps
5552
+ * the largest corner radius whose curve stays 4px clear of the content box
5553
+ * (LCE-001), instead of letting the curve cut through the text: for inline
5554
+ * padding pi, block padding pb (pb <= pi), border b and clearance c the
5555
+ * bound is pi + pb·(1 + √2) - c·(1 + √2) + b, about 45px with Forma's
5556
+ * spacing tokens. It follows the padding tokens, so it holds at any text size.
5557
+ */
5558
+ .ef-diagram-node[data-ef-shape="pill"] {
5559
+ border-radius: min(999px, calc(var(--ef-primitive-spacing-5, 1.5rem) + var(--ef-primitive-spacing-3, 0.75rem) * 2.414 - 7.66px));
5560
+ padding-inline: var(--ef-primitive-spacing-5, 1.5rem);
5561
+ }
5525
5562
  /*
5526
5563
  * FMD-M2 (forma#88). Ellipse and diamond keep a real border (never a
5527
5564
  * background-only shape) so the boundary survives backgrounds-off print and
@@ -5532,7 +5569,8 @@
5532
5569
  border-radius: 50%;
5533
5570
  border-inline-start-width: 2px;
5534
5571
  border-block-end: 6px solid var(--ef-diagram-node-accent);
5535
- padding: var(--ef-primitive-spacing-4, 1rem) calc(var(--ef-diagram-w, 12rem) * 0.14);
5572
+ /* The content's corners stay inside the ellipse: 14.65% is (1 - 1/√2) / 2 of the width, plus clearance (LCE-001). */
5573
+ padding: var(--ef-primitive-spacing-4, 1rem) calc(var(--ef-diagram-w, 12rem) * 0.1465 + 0.25rem);
5536
5574
  place-content: center;
5537
5575
  text-align: center;
5538
5576
  }
@@ -5540,12 +5578,14 @@
5540
5578
  .ef-diagram-node[data-ef-shape="diamond"] .ef-diagram-node__meta { grid-template-columns: auto auto; justify-content: center; text-align: start; }
5541
5579
  /*
5542
5580
  * A square box whose ::before is the diamond; the content sits in the
5543
- * inscribed square. Padding is derived from --ef-diagram-w because percentage
5581
+ * inscribed square, which is half the side, so the padding is a quarter of
5582
+ * the width plus clearance (LCE-001; 0.2 put content corners outside the
5583
+ * diamond). Padding is derived from --ef-diagram-w because percentage
5544
5584
  * padding would resolve against the canvas, not the node.
5545
5585
  */
5546
5586
  .ef-diagram-node[data-ef-shape="diamond"] {
5547
5587
  aspect-ratio: 1;
5548
- padding: calc(var(--ef-diagram-w, 12rem) * 0.2);
5588
+ padding: calc(var(--ef-diagram-w, 12rem) * 0.25 + 0.25rem);
5549
5589
  border: 0;
5550
5590
  background: transparent;
5551
5591
  place-content: center;
@@ -5585,7 +5625,10 @@
5585
5625
  background: var(--ef-color-surface-primary, #f2efe7);
5586
5626
  color: var(--ef-color-text-primary, #171a18);
5587
5627
  font-size: 0.875rem;
5588
- white-space: nowrap;
5628
+ /* One line up to 12rem, then wrapping: the layout reserves that box (LCE-001). */
5629
+ inline-size: max-content;
5630
+ max-inline-size: 12rem;
5631
+ overflow-wrap: anywhere;
5589
5632
  }
5590
5633
 
5591
5634
  /* The text equivalent: every relationship in reading order. */
@@ -7791,6 +7834,14 @@
7791
7834
  opacity: var(--ef-site-backdrop-opacity, 1);
7792
7835
  }
7793
7836
 
7837
+ /*
7838
+ * Prose defaults stop at an embedded diagram (LCE-001). Diagram geometry is
7839
+ * computed for the component layer's type metrics; a site heading size or
7840
+ * paragraph margin made nodes outgrow their layout. revert-layer hands these
7841
+ * elements back to the earlier layers.
7842
+ */
7843
+ .ef-site .ef-diagram :where(h1, h2, h3, h4, h5, h6, p, ul, ol, li, a, svg, dl, dt, dd, strong, b) { all: revert-layer; }
7844
+
7794
7845
  .ef-site :where(h1, h2, h3, h4, h5, h6) {
7795
7846
  margin-block: 0;
7796
7847
  color: var(--ef-tone-heading);
@@ -8089,7 +8140,8 @@
8089
8140
  min-block-size: calc(var(--ef-layout-target-min) + var(--ef-space-2));
8090
8141
  padding: var(--ef-space-3) var(--ef-space-5);
8091
8142
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
8092
- border-radius: var(--ef-shape-radius);
8143
+ /* Bounded so a brand radius never reaches the content (LCE-001, see .ef-card). */
8144
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-space-5) + var(--ef-space-3) * 2.414 - 2.414px));
8093
8145
  background: transparent;
8094
8146
  color: var(--ef-tone-text);
8095
8147
  font: inherit;
@@ -8101,7 +8153,10 @@
8101
8153
  /* Never wider than its row and never split mid-word unless one word is
8102
8154
  wider than the row (ULI-L05, L06). */
8103
8155
  max-inline-size: 100%;
8104
- overflow-wrap: break-word;
8156
+ /* anywhere, not break-word: inside inline-flex only anywhere lets the
8157
+ min-content width shrink, so a word wider than the row breaks instead of
8158
+ overflowing the button (LCE-001). Words still break only when they must. */
8159
+ overflow-wrap: anywhere;
8105
8160
  cursor: pointer;
8106
8161
  /* Hover and press are perceptual emphasis on the surface only: the hit
8107
8162
  target never moves under the pointer (MOT-014). */
@@ -8146,11 +8201,13 @@
8146
8201
  display: inline-flex;
8147
8202
  align-items: center;
8148
8203
  gap: var(--ef-space-1);
8204
+ max-inline-size: 100%;
8149
8205
  padding: var(--ef-space-1) var(--ef-space-2);
8150
8206
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
8151
- border-radius: var(--ef-shape-radius);
8207
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-space-2) + var(--ef-space-1) * 2.414 - 2.414px));
8152
8208
  color: var(--ef-tone-text);
8153
8209
  font-family: var(--ef-type-family-mono);
8210
+ overflow-wrap: anywhere;
8154
8211
  font-size: var(--ef-type-size-label);
8155
8212
  line-height: var(--ef-type-leading-snug);
8156
8213
  text-decoration: none;
@@ -8196,7 +8253,15 @@
8196
8253
  min-inline-size: 0;
8197
8254
  padding: var(--ef-layout-block-space);
8198
8255
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
8199
- border-radius: var(--ef-shape-radius);
8256
+ /*
8257
+ * A brand radius is capped by the padding (LCE-001). Content whose corner
8258
+ * sits at padding p from both edges stays clear of a circular corner of
8259
+ * radius r while r <= (2 + √2)·(p - c) + c for clearance c; with c = 1px
8260
+ * that is p·3.414 - 2.414px. A badge or button uses its own two paddings:
8261
+ * pi + pb·2.414 - 2.414px (pb the smaller). A larger brand radius (a pill
8262
+ * or 50%) would otherwise cut through multi-line content.
8263
+ */
8264
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-layout-block-space) * 3.414 - 2.414px));
8200
8265
  }
8201
8266
 
8202
8267
  /* Inside a grid, cards share hairline rules instead of doubling borders. */
@@ -1,4 +1,4 @@
1
- /*! Forma 0.9.0 | forma-echelon-marketing.css | bundle echelon-marketing
1
+ /*! Forma 0.10.0 | forma-echelon-marketing.css | bundle echelon-marketing
2
2
  Single-file Echelon marketing presentation: forma-marketing.css followed by the Echelon theme. Most sites load only this file.
3
3
  Generated by tools/PresentationBundler. Do not edit; pin by version and sha256. */
4
4
 
@@ -1137,6 +1137,14 @@
1137
1137
  opacity: var(--ef-site-backdrop-opacity, 1);
1138
1138
  }
1139
1139
 
1140
+ /*
1141
+ * Prose defaults stop at an embedded diagram (LCE-001). Diagram geometry is
1142
+ * computed for the component layer's type metrics; a site heading size or
1143
+ * paragraph margin made nodes outgrow their layout. revert-layer hands these
1144
+ * elements back to the earlier layers.
1145
+ */
1146
+ .ef-site .ef-diagram :where(h1, h2, h3, h4, h5, h6, p, ul, ol, li, a, svg, dl, dt, dd, strong, b) { all: revert-layer; }
1147
+
1140
1148
  .ef-site :where(h1, h2, h3, h4, h5, h6) {
1141
1149
  margin-block: 0;
1142
1150
  color: var(--ef-tone-heading);
@@ -1436,7 +1444,8 @@
1436
1444
  min-block-size: calc(var(--ef-layout-target-min) + var(--ef-space-2));
1437
1445
  padding: var(--ef-space-3) var(--ef-space-5);
1438
1446
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
1439
- border-radius: var(--ef-shape-radius);
1447
+ /* Bounded so a brand radius never reaches the content (LCE-001, see .ef-card). */
1448
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-space-5) + var(--ef-space-3) * 2.414 - 2.414px));
1440
1449
  background: transparent;
1441
1450
  color: var(--ef-tone-text);
1442
1451
  font: inherit;
@@ -1448,7 +1457,10 @@
1448
1457
  /* Never wider than its row and never split mid-word unless one word is
1449
1458
  wider than the row (ULI-L05, L06). */
1450
1459
  max-inline-size: 100%;
1451
- overflow-wrap: break-word;
1460
+ /* anywhere, not break-word: inside inline-flex only anywhere lets the
1461
+ min-content width shrink, so a word wider than the row breaks instead of
1462
+ overflowing the button (LCE-001). Words still break only when they must. */
1463
+ overflow-wrap: anywhere;
1452
1464
  cursor: pointer;
1453
1465
  /* Hover and press are perceptual emphasis on the surface only: the hit
1454
1466
  target never moves under the pointer (MOT-014). */
@@ -1493,11 +1505,13 @@
1493
1505
  display: inline-flex;
1494
1506
  align-items: center;
1495
1507
  gap: var(--ef-space-1);
1508
+ max-inline-size: 100%;
1496
1509
  padding: var(--ef-space-1) var(--ef-space-2);
1497
1510
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
1498
- border-radius: var(--ef-shape-radius);
1511
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-space-2) + var(--ef-space-1) * 2.414 - 2.414px));
1499
1512
  color: var(--ef-tone-text);
1500
1513
  font-family: var(--ef-type-family-mono);
1514
+ overflow-wrap: anywhere;
1501
1515
  font-size: var(--ef-type-size-label);
1502
1516
  line-height: var(--ef-type-leading-snug);
1503
1517
  text-decoration: none;
@@ -1543,7 +1557,15 @@
1543
1557
  min-inline-size: 0;
1544
1558
  padding: var(--ef-layout-block-space);
1545
1559
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
1546
- border-radius: var(--ef-shape-radius);
1560
+ /*
1561
+ * A brand radius is capped by the padding (LCE-001). Content whose corner
1562
+ * sits at padding p from both edges stays clear of a circular corner of
1563
+ * radius r while r <= (2 + √2)·(p - c) + c for clearance c; with c = 1px
1564
+ * that is p·3.414 - 2.414px. A badge or button uses its own two paddings:
1565
+ * pi + pb·2.414 - 2.414px (pb the smaller). A larger brand radius (a pill
1566
+ * or 50%) would otherwise cut through multi-line content.
1567
+ */
1568
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-layout-block-space) * 3.414 - 2.414px));
1547
1569
  }
1548
1570
 
1549
1571
  /* Inside a grid, cards share hairline rules instead of doubling borders. */
@@ -1,4 +1,4 @@
1
- /*! Forma 0.9.0 | forma-marketing-theme-echelon.css | bundle echelon-marketing
1
+ /*! Forma 0.10.0 | forma-marketing-theme-echelon.css | bundle echelon-marketing
2
2
  Echelon Marketing Theme: compiled theme tokens and theme hooks. Load after forma-marketing.css.
3
3
  Generated by tools/PresentationBundler. Do not edit; pin by version and sha256. */
4
4
 
@@ -1,4 +1,4 @@
1
- /*! Forma 0.9.0 | forma-marketing.css | bundle echelon-marketing
1
+ /*! Forma 0.10.0 | forma-marketing.css | bundle echelon-marketing
2
2
  Brand-neutral marketing layer: core tokens, role defaults, Forma foundations, marketing foundations, components, and layouts.
3
3
  Generated by tools/PresentationBundler. Do not edit; pin by version and sha256. */
4
4
 
@@ -1136,6 +1136,14 @@
1136
1136
  opacity: var(--ef-site-backdrop-opacity, 1);
1137
1137
  }
1138
1138
 
1139
+ /*
1140
+ * Prose defaults stop at an embedded diagram (LCE-001). Diagram geometry is
1141
+ * computed for the component layer's type metrics; a site heading size or
1142
+ * paragraph margin made nodes outgrow their layout. revert-layer hands these
1143
+ * elements back to the earlier layers.
1144
+ */
1145
+ .ef-site .ef-diagram :where(h1, h2, h3, h4, h5, h6, p, ul, ol, li, a, svg, dl, dt, dd, strong, b) { all: revert-layer; }
1146
+
1139
1147
  .ef-site :where(h1, h2, h3, h4, h5, h6) {
1140
1148
  margin-block: 0;
1141
1149
  color: var(--ef-tone-heading);
@@ -1435,7 +1443,8 @@
1435
1443
  min-block-size: calc(var(--ef-layout-target-min) + var(--ef-space-2));
1436
1444
  padding: var(--ef-space-3) var(--ef-space-5);
1437
1445
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
1438
- border-radius: var(--ef-shape-radius);
1446
+ /* Bounded so a brand radius never reaches the content (LCE-001, see .ef-card). */
1447
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-space-5) + var(--ef-space-3) * 2.414 - 2.414px));
1439
1448
  background: transparent;
1440
1449
  color: var(--ef-tone-text);
1441
1450
  font: inherit;
@@ -1447,7 +1456,10 @@
1447
1456
  /* Never wider than its row and never split mid-word unless one word is
1448
1457
  wider than the row (ULI-L05, L06). */
1449
1458
  max-inline-size: 100%;
1450
- overflow-wrap: break-word;
1459
+ /* anywhere, not break-word: inside inline-flex only anywhere lets the
1460
+ min-content width shrink, so a word wider than the row breaks instead of
1461
+ overflowing the button (LCE-001). Words still break only when they must. */
1462
+ overflow-wrap: anywhere;
1451
1463
  cursor: pointer;
1452
1464
  /* Hover and press are perceptual emphasis on the surface only: the hit
1453
1465
  target never moves under the pointer (MOT-014). */
@@ -1492,11 +1504,13 @@
1492
1504
  display: inline-flex;
1493
1505
  align-items: center;
1494
1506
  gap: var(--ef-space-1);
1507
+ max-inline-size: 100%;
1495
1508
  padding: var(--ef-space-1) var(--ef-space-2);
1496
1509
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
1497
- border-radius: var(--ef-shape-radius);
1510
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-space-2) + var(--ef-space-1) * 2.414 - 2.414px));
1498
1511
  color: var(--ef-tone-text);
1499
1512
  font-family: var(--ef-type-family-mono);
1513
+ overflow-wrap: anywhere;
1500
1514
  font-size: var(--ef-type-size-label);
1501
1515
  line-height: var(--ef-type-leading-snug);
1502
1516
  text-decoration: none;
@@ -1542,7 +1556,15 @@
1542
1556
  min-inline-size: 0;
1543
1557
  padding: var(--ef-layout-block-space);
1544
1558
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
1545
- border-radius: var(--ef-shape-radius);
1559
+ /*
1560
+ * A brand radius is capped by the padding (LCE-001). Content whose corner
1561
+ * sits at padding p from both edges stays clear of a circular corner of
1562
+ * radius r while r <= (2 + √2)·(p - c) + c for clearance c; with c = 1px
1563
+ * that is p·3.414 - 2.414px. A badge or button uses its own two paddings:
1564
+ * pi + pb·2.414 - 2.414px (pb the smaller). A larger brand radius (a pill
1565
+ * or 50%) would otherwise cut through multi-line content.
1566
+ */
1567
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-layout-block-space) * 3.414 - 2.414px));
1546
1568
  }
1547
1569
 
1548
1570
  /* Inside a grid, cards share hairline rules instead of doubling borders. */
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "forma": "0.9.0",
3
+ "forma": "0.10.0",
4
4
  "bundle": "echelon-marketing",
5
5
  "description": "Forma presentation artifacts for Echelon marketing sites. Every artifact is a single self-contained CSS file with no imports or relative URLs.",
6
- "releaseTag": "v0.9.0",
6
+ "releaseTag": "v0.10.0",
7
7
  "artifacts": [
8
8
  {
9
9
  "file": "forma-marketing.css",
10
10
  "description": "Brand-neutral marketing layer: core tokens, role defaults, Forma foundations, marketing foundations, components, and layouts.",
11
- "bytes": 85780,
12
- "sha256": "cd2e2d259bb5d88de91c00ce7a63d0afa2eb1ca67dbcc7c100b10bffd1bee89e",
11
+ "bytes": 87243,
12
+ "sha256": "d8a900aecd8aa4a72cbda731d3bbcfadbf872bc8aed1a8ef74f5ef3169af4cc6",
13
13
  "sources": [
14
14
  "dist/tokens.css",
15
15
  "src/marketing/roles.css",
@@ -22,8 +22,8 @@
22
22
  {
23
23
  "file": "forma-marketing-theme-echelon.css",
24
24
  "description": "Echelon Marketing Theme: compiled theme tokens and theme hooks. Load after forma-marketing.css.",
25
- "bytes": 6295,
26
- "sha256": "e9e6fa2f3ab1d9950145639f1346441d636638cd028e55c730aee0231de14271",
25
+ "bytes": 6296,
26
+ "sha256": "e3e05cf66b87ac6be686896c353ab87f47ad1be589c1fc2895238d1bd987d9e6",
27
27
  "sources": [
28
28
  "build/marketing/echelon-marketing.tokens.css",
29
29
  "themes/echelon/marketing.css"
@@ -32,8 +32,8 @@
32
32
  {
33
33
  "file": "forma-echelon-marketing.css",
34
34
  "description": "Single-file Echelon marketing presentation: forma-marketing.css followed by the Echelon theme. Most sites load only this file.",
35
- "bytes": 91920,
36
- "sha256": "095a7df7bf669fbc640e064339507695de92d9fc29154ddc7ec74b290a6aa967",
35
+ "bytes": 93383,
36
+ "sha256": "837ddc667b3eccbc6a0512fe767708212a2aeffe18f9c2a27cbedad9be2937f3",
37
37
  "sources": [
38
38
  "artifact:forma-marketing.css",
39
39
  "artifact:forma-marketing-theme-echelon.css"
@@ -42,8 +42,8 @@
42
42
  {
43
43
  "file": "forma-all.css",
44
44
  "description": "Complete Forma surface (application components, assessment, and marketing layers) for sites that also present Forma application patterns such as data grids or status lozenges.",
45
- "bytes": 333347,
46
- "sha256": "9ef1cb63edae74c194936fa623604c18e42a765565346aa50e1081c86f64a96d",
45
+ "bytes": 336745,
46
+ "sha256": "6666ea17b441934dd66807f9aefae86213ca228c92eed8da17faed2b2d816b18",
47
47
  "sources": [
48
48
  "dist/all.css"
49
49
  ]
@@ -1,4 +1,4 @@
1
- cd2e2d259bb5d88de91c00ce7a63d0afa2eb1ca67dbcc7c100b10bffd1bee89e forma-marketing.css
2
- e9e6fa2f3ab1d9950145639f1346441d636638cd028e55c730aee0231de14271 forma-marketing-theme-echelon.css
3
- 095a7df7bf669fbc640e064339507695de92d9fc29154ddc7ec74b290a6aa967 forma-echelon-marketing.css
4
- 9ef1cb63edae74c194936fa623604c18e42a765565346aa50e1081c86f64a96d forma-all.css
1
+ d8a900aecd8aa4a72cbda731d3bbcfadbf872bc8aed1a8ef74f5ef3169af4cc6 forma-marketing.css
2
+ e3e05cf66b87ac6be686896c353ab87f47ad1be589c1fc2895238d1bd987d9e6 forma-marketing-theme-echelon.css
3
+ 837ddc667b3eccbc6a0512fe767708212a2aeffe18f9c2a27cbedad9be2937f3 forma-echelon-marketing.css
4
+ 6666ea17b441934dd66807f9aefae86213ca228c92eed8da17faed2b2d816b18 forma-all.css
@@ -218,6 +218,14 @@
218
218
  opacity: var(--ef-site-backdrop-opacity, 1);
219
219
  }
220
220
 
221
+ /*
222
+ * Prose defaults stop at an embedded diagram (LCE-001). Diagram geometry is
223
+ * computed for the component layer's type metrics; a site heading size or
224
+ * paragraph margin made nodes outgrow their layout. revert-layer hands these
225
+ * elements back to the earlier layers.
226
+ */
227
+ .ef-site .ef-diagram :where(h1, h2, h3, h4, h5, h6, p, ul, ol, li, a, svg, dl, dt, dd, strong, b) { all: revert-layer; }
228
+
221
229
  .ef-site :where(h1, h2, h3, h4, h5, h6) {
222
230
  margin-block: 0;
223
231
  color: var(--ef-tone-heading);
@@ -516,7 +524,8 @@
516
524
  min-block-size: calc(var(--ef-layout-target-min) + var(--ef-space-2));
517
525
  padding: var(--ef-space-3) var(--ef-space-5);
518
526
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
519
- border-radius: var(--ef-shape-radius);
527
+ /* Bounded so a brand radius never reaches the content (LCE-001, see .ef-card). */
528
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-space-5) + var(--ef-space-3) * 2.414 - 2.414px));
520
529
  background: transparent;
521
530
  color: var(--ef-tone-text);
522
531
  font: inherit;
@@ -528,7 +537,10 @@
528
537
  /* Never wider than its row and never split mid-word unless one word is
529
538
  wider than the row (ULI-L05, L06). */
530
539
  max-inline-size: 100%;
531
- overflow-wrap: break-word;
540
+ /* anywhere, not break-word: inside inline-flex only anywhere lets the
541
+ min-content width shrink, so a word wider than the row breaks instead of
542
+ overflowing the button (LCE-001). Words still break only when they must. */
543
+ overflow-wrap: anywhere;
532
544
  cursor: pointer;
533
545
  /* Hover and press are perceptual emphasis on the surface only: the hit
534
546
  target never moves under the pointer (MOT-014). */
@@ -573,11 +585,13 @@
573
585
  display: inline-flex;
574
586
  align-items: center;
575
587
  gap: var(--ef-space-1);
588
+ max-inline-size: 100%;
576
589
  padding: var(--ef-space-1) var(--ef-space-2);
577
590
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
578
- border-radius: var(--ef-shape-radius);
591
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-space-2) + var(--ef-space-1) * 2.414 - 2.414px));
579
592
  color: var(--ef-tone-text);
580
593
  font-family: var(--ef-type-family-mono);
594
+ overflow-wrap: anywhere;
581
595
  font-size: var(--ef-type-size-label);
582
596
  line-height: var(--ef-type-leading-snug);
583
597
  text-decoration: none;
@@ -623,7 +637,15 @@
623
637
  min-inline-size: 0;
624
638
  padding: var(--ef-layout-block-space);
625
639
  border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
626
- border-radius: var(--ef-shape-radius);
640
+ /*
641
+ * A brand radius is capped by the padding (LCE-001). Content whose corner
642
+ * sits at padding p from both edges stays clear of a circular corner of
643
+ * radius r while r <= (2 + √2)·(p - c) + c for clearance c; with c = 1px
644
+ * that is p·3.414 - 2.414px. A badge or button uses its own two paddings:
645
+ * pi + pb·2.414 - 2.414px (pb the smaller). A larger brand radius (a pill
646
+ * or 50%) would otherwise cut through multi-line content.
647
+ */
648
+ border-radius: min(var(--ef-shape-radius), calc(var(--ef-layout-block-space) * 3.414 - 2.414px));
627
649
  }
628
650
 
629
651
  /* Inside a grid, cards share hairline rules instead of doubling borders. */
@@ -0,0 +1,57 @@
1
+ // Generated by tools/build-runtime.mjs from package.json. Do not edit.
2
+ const SCHEMA_VERSION = 1;
3
+ const REGISTRY_KEY = "__ECHELON_RUNTIME__";
4
+ const REGISTER_KEY = "__ECHELON_REGISTER_RUNTIME__";
5
+
6
+ const libraryLabel = name => name.split("-").map(part => part ? part[0].toUpperCase() + part.slice(1) : part).join(" ");
7
+ const diagnostic = (target, method, message) => target.console?.[method]?.(message);
8
+
9
+ export function installEchelonRuntimeRegistrar(target = globalThis) {
10
+ const installed = target[REGISTER_KEY];
11
+ if (typeof installed === "function" && installed.echelonRuntimeSchemaVersion === SCHEMA_VERSION) return installed;
12
+ if (installed !== undefined) {
13
+ diagnostic(target, "warn", "Echelon Runtime registrar conflict: an incompatible global registrar is already installed.");
14
+ return undefined;
15
+ }
16
+
17
+ let registry = target[REGISTRY_KEY];
18
+ if (registry === undefined) {
19
+ registry = { schemaVersion: SCHEMA_VERSION, libraries: {} };
20
+ target[REGISTRY_KEY] = registry;
21
+ } else if (registry === null || typeof registry !== "object" || registry.schemaVersion !== SCHEMA_VERSION || registry.libraries === null || typeof registry.libraries !== "object") {
22
+ diagnostic(target, "warn", "Echelon Runtime registry conflict: schemaVersion 1 is required; the existing registry was left unchanged.");
23
+ return undefined;
24
+ }
25
+
26
+ const register = (name, metadata) => {
27
+ if (typeof name !== "string" || !/^[a-z][a-z0-9-]*$/.test(name)) throw new TypeError("Echelon runtime library names must be lowercase identifiers.");
28
+ if (metadata === null || typeof metadata !== "object" || typeof metadata.version !== "string" || metadata.version.length === 0) throw new TypeError("Echelon runtime metadata requires a version string.");
29
+
30
+ const candidate = { version: metadata.version };
31
+ if (typeof metadata.commit === "string" && metadata.commit.length > 0) candidate.commit = metadata.commit;
32
+ if (typeof metadata.buildTime === "string" && metadata.buildTime.length > 0) candidate.buildTime = metadata.buildTime;
33
+
34
+ const current = registry.libraries[name];
35
+ if (current !== undefined) {
36
+ if (current.version === candidate.version) return current;
37
+ diagnostic(target, "warn", `Echelon Runtime conflict: ${libraryLabel(name)} v${current.version} is already registered; ignored v${candidate.version}.`);
38
+ return current;
39
+ }
40
+
41
+ registry.libraries[name] = candidate;
42
+ diagnostic(target, "info", `Echelon Runtime · ${libraryLabel(name)} v${candidate.version}`);
43
+ return candidate;
44
+ };
45
+
46
+ Object.defineProperty(register, "echelonRuntimeSchemaVersion", { value: SCHEMA_VERSION });
47
+ Object.defineProperty(target, REGISTER_KEY, { value: register, configurable: true });
48
+ return register;
49
+ }
50
+
51
+ export function registerEchelonRuntime(name, metadata, target = globalThis) {
52
+ return installEchelonRuntimeRegistrar(target)?.(name, metadata);
53
+ }
54
+
55
+ export const FORMA_VERSION = "0.10.0";
56
+
57
+ if (typeof window !== "undefined") registerEchelonRuntime("forma", { version: FORMA_VERSION }, window);
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "@echelon-foundry/design-system",
3
- "version": "0.9.0",
3
+ "version": "0.10.0-main.257.1",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "Zero-runtime HTML and CSS design system for Echelon Foundry applications.",
6
+ "description": "Zero-component-runtime HTML and CSS design system for Echelon Foundry applications.",
7
7
  "sideEffects": [
8
+ "./dist/runtime.js",
8
9
  "./dist/*.css",
9
10
  "./dist/brands/*.css",
10
11
  "./dist/marketing.css",
11
12
  "./dist/marketing/*.css"
12
13
  ],
13
14
  "exports": {
15
+ "./runtime": "./dist/runtime.js",
14
16
  "./tokens.css": "./dist/tokens.css",
15
17
  "./foundations.css": "./dist/foundations.css",
16
18
  "./components.css": "./dist/components.css",
@@ -67,7 +69,8 @@
67
69
  "clean": "node tools/clean.mjs",
68
70
  "tokens:build": "dotnet run --project tools/TokenCompiler/TokenCompiler.fsproj -- tokens/echelon.tokens.json dist/tokens.css",
69
71
  "assets:build": "node tools/build-assets.mjs",
70
- "build": "npm run clean && npm run tokens:build && npm run brands:build && npm run color:build && npm run marketing:theme && npm run assets:build && npm run marketing:build && npm run example:install",
72
+ "build": "npm run clean && npm run tokens:build && npm run brands:build && npm run color:build && npm run marketing:theme && npm run runtime:build && npm run assets:build && npm run marketing:build && npm run example:install",
73
+ "runtime:build": "node tools/build-runtime.mjs",
71
74
  "test:tokens": "node --test tests/token-compiler.test.mjs",
72
75
  "test:browser": "playwright test",
73
76
  "test:a11y": "playwright test tests/browser/accessibility.spec.mjs",
@@ -99,6 +102,8 @@
99
102
  "catalog:check": "node tools/catalog/validate.mjs && node tools/catalog/inventory.mjs --check",
100
103
  "catalog:inventory": "node tools/catalog/inventory.mjs",
101
104
  "workflow:test": "dotnet run --project tests/workflow/Forma.Workflow.Tests -c Release",
105
+ "layout:probe": "dotnet run --project tests/layout/Forma.Layout.Probe -c Release --",
106
+ "test:layout": "dotnet run --project tests/layout/Forma.Layout.Probe -c Release -- regress",
102
107
  "workflow:build": "node tools/build-workflow-package.mjs",
103
108
  "workflow:validate": "dotnet run --project src/workflow/Forma.Workflow.Cli -c Release -- validate examples/workflows/forma/workflows/*.forma-workflow.json",
104
109
  "design:validate": "node tools/design-contract/cli.mjs validate examples/design/valid/*.forma-design.json",
@@ -127,7 +132,7 @@
127
132
  "homepage": "https://forma.echelonfoundry.com/",
128
133
  "keywords": [
129
134
  "design-system",
130
- "zero-runtime",
135
+ "zero-component-runtime",
131
136
  "marketing-presentation",
132
137
  "accessibility",
133
138
  "workflow",