@echelon-foundry/design-system 0.2.0 → 0.3.0-main.216.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.
Files changed (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -0
  3. package/dist/all.css +5537 -841
  4. package/dist/assessment.css +55 -12
  5. package/dist/build-metrics.json +181 -5
  6. package/dist/components.css +2965 -36
  7. package/dist/foundations.css +90 -0
  8. package/dist/marketing/forma-all.css +8162 -0
  9. package/dist/marketing/forma-echelon-marketing.css +2109 -0
  10. package/dist/marketing/forma-marketing-theme-echelon.css +163 -0
  11. package/dist/marketing/forma-marketing.css +1947 -0
  12. package/dist/marketing/forma-marketing.manifest.json +52 -0
  13. package/dist/marketing/forma-marketing.sha256 +4 -0
  14. package/dist/marketing.css +1198 -0
  15. package/dist/patterns/active-filter-summary.html +10 -0
  16. package/dist/patterns/attention-path.html +29 -0
  17. package/dist/patterns/badge.html +9 -0
  18. package/dist/patterns/callout.html +23 -0
  19. package/dist/patterns/card-grid.html +25 -0
  20. package/dist/patterns/character-grid-3270.html +39 -0
  21. package/dist/patterns/character-grid-field.html +21 -0
  22. package/dist/patterns/character-grid-keys.html +20 -0
  23. package/dist/patterns/character-grid-reveal.html +16 -0
  24. package/dist/patterns/character-grid-selection.html +30 -0
  25. package/dist/patterns/character-grid-status.html +11 -0
  26. package/dist/patterns/character-grid-workflow.html +147 -0
  27. package/dist/patterns/character-grid.html +16 -0
  28. package/dist/patterns/cluster.html +5 -0
  29. package/dist/patterns/code-sample.html +9 -0
  30. package/dist/patterns/command-group.html +6 -0
  31. package/dist/patterns/comparison-grid.html +7 -0
  32. package/dist/patterns/comparison-pairs.html +13 -0
  33. package/dist/patterns/cta.html +12 -0
  34. package/dist/patterns/dense-ledger.html +7 -0
  35. package/dist/patterns/diagnostic-status.html +7 -0
  36. package/dist/patterns/diagram.html +49 -0
  37. package/dist/patterns/documentation-layout.html +18 -0
  38. package/dist/patterns/emphasis-budget.html +21 -0
  39. package/dist/patterns/entry-index.html +16 -0
  40. package/dist/patterns/evidence-relationship.html +1 -0
  41. package/dist/patterns/facts.html +7 -0
  42. package/dist/patterns/fault-banner.html +19 -0
  43. package/dist/patterns/fault-blocking.html +25 -0
  44. package/dist/patterns/fault-details.html +11 -0
  45. package/dist/patterns/fault-inline.html +17 -0
  46. package/dist/patterns/fault-notification.html +20 -0
  47. package/dist/patterns/fault-reference.html +5 -0
  48. package/dist/patterns/fault-summary.html +20 -0
  49. package/dist/patterns/fault.html +17 -0
  50. package/dist/patterns/feed-item.html +12 -0
  51. package/dist/patterns/focus-stage.html +4 -0
  52. package/dist/patterns/frame.html +4 -0
  53. package/dist/patterns/freshness.html +1 -0
  54. package/dist/patterns/handoff-summary.html +1 -0
  55. package/dist/patterns/hero.html +17 -0
  56. package/dist/patterns/hierarchy-tree.html +11 -0
  57. package/dist/patterns/identifier.html +9 -0
  58. package/dist/patterns/landmark-region.html +4 -0
  59. package/dist/patterns/lane-board.html +10 -0
  60. package/dist/patterns/marketing-shell.html +40 -0
  61. package/dist/patterns/measure.html +4 -0
  62. package/dist/patterns/metric-value-change.html +9 -0
  63. package/dist/patterns/mosaic.html +6 -0
  64. package/dist/patterns/navigation-shell.html +8 -0
  65. package/dist/patterns/overflow-command-disclosure.html +6 -0
  66. package/dist/patterns/overview-disclosure.html +12 -0
  67. package/dist/patterns/path-summary.html +8 -0
  68. package/dist/patterns/priority-stack.html +4 -0
  69. package/dist/patterns/progress-bar.html +15 -0
  70. package/dist/patterns/prose.html +9 -0
  71. package/dist/patterns/rail.html +4 -0
  72. package/dist/patterns/recovery-actions.html +5 -0
  73. package/dist/patterns/relationship-index.html +8 -0
  74. package/dist/patterns/reorder-states.html +31 -0
  75. package/dist/patterns/resizable-split-pane.html +11 -0
  76. package/dist/patterns/responsive-grid.html +5 -0
  77. package/dist/patterns/scope-trail.html +1 -0
  78. package/dist/patterns/scroll-progress.html +6 -0
  79. package/dist/patterns/section-heading.html +12 -0
  80. package/dist/patterns/security-blockers.html +3 -0
  81. package/dist/patterns/security-evidence-age.html +1 -0
  82. package/dist/patterns/security-evidence-chain.html +2 -0
  83. package/dist/patterns/security-exception.html +2 -0
  84. package/dist/patterns/security-invariant-result.html +1 -0
  85. package/dist/patterns/security-posture.html +22 -0
  86. package/dist/patterns/security-state-matrix.html +5 -0
  87. package/dist/patterns/security-trend.html +1 -0
  88. package/dist/patterns/security-unknown.html +2 -0
  89. package/dist/patterns/sidebar.html +4 -0
  90. package/dist/patterns/site-footer.html +12 -0
  91. package/dist/patterns/site-header.html +18 -0
  92. package/dist/patterns/spatial-canvas.html +13 -0
  93. package/dist/patterns/spinner.html +4 -0
  94. package/dist/patterns/split-pane.html +4 -0
  95. package/dist/patterns/stack.html +5 -0
  96. package/dist/patterns/state-survivability.html +19 -0
  97. package/dist/patterns/steps.html +8 -0
  98. package/dist/patterns/switcher.html +4 -0
  99. package/dist/patterns/tooltip.html +48 -0
  100. package/dist/patterns/verification-frame.html +22 -0
  101. package/dist/patterns/workspace-shell.html +6 -0
  102. package/dist/tokens.css +436 -1
  103. package/docs/BRANDING.md +6 -1
  104. package/docs/MARKETING-SITES.md +321 -0
  105. package/figma/component-contracts.json +1544 -0
  106. package/package.json +34 -8
  107. package/patterns/active-filter-summary.html +10 -0
  108. package/patterns/aegis-faults.manifest.md +81 -0
  109. package/patterns/attention-path.html +29 -0
  110. package/patterns/badge.html +9 -0
  111. package/patterns/callout.html +23 -0
  112. package/patterns/card-grid.html +25 -0
  113. package/patterns/character-grid-3270.html +39 -0
  114. package/patterns/character-grid-field.html +21 -0
  115. package/patterns/character-grid-keys.html +20 -0
  116. package/patterns/character-grid-reveal.html +16 -0
  117. package/patterns/character-grid-selection.html +30 -0
  118. package/patterns/character-grid-status.html +11 -0
  119. package/patterns/character-grid-workflow.html +147 -0
  120. package/patterns/character-grid.html +16 -0
  121. package/patterns/cluster.html +5 -0
  122. package/patterns/code-sample.html +9 -0
  123. package/patterns/command-group.html +6 -0
  124. package/patterns/comparison-grid.html +7 -0
  125. package/patterns/comparison-pairs.html +13 -0
  126. package/patterns/cta.html +12 -0
  127. package/patterns/dense-ledger.html +7 -0
  128. package/patterns/diagnostic-status.html +7 -0
  129. package/patterns/diagram.html +49 -0
  130. package/patterns/documentation-layout.html +18 -0
  131. package/patterns/emphasis-budget.html +21 -0
  132. package/patterns/entry-index.html +16 -0
  133. package/patterns/evidence-relationship.html +1 -0
  134. package/patterns/facts.html +7 -0
  135. package/patterns/fault-banner.html +19 -0
  136. package/patterns/fault-blocking.html +25 -0
  137. package/patterns/fault-details.html +11 -0
  138. package/patterns/fault-inline.html +17 -0
  139. package/patterns/fault-notification.html +20 -0
  140. package/patterns/fault-reference.html +5 -0
  141. package/patterns/fault-summary.html +20 -0
  142. package/patterns/fault.html +17 -0
  143. package/patterns/feed-item.html +12 -0
  144. package/patterns/focus-stage.html +4 -0
  145. package/patterns/frame.html +4 -0
  146. package/patterns/freshness.html +1 -0
  147. package/patterns/handoff-summary.html +1 -0
  148. package/patterns/hero.html +17 -0
  149. package/patterns/hierarchy-tree.html +11 -0
  150. package/patterns/identifier.html +9 -0
  151. package/patterns/landmark-region.html +4 -0
  152. package/patterns/lane-board.html +10 -0
  153. package/patterns/manifest.md +2 -0
  154. package/patterns/marketing-shell.html +40 -0
  155. package/patterns/measure.html +4 -0
  156. package/patterns/metric-value-change.html +9 -0
  157. package/patterns/mosaic.html +6 -0
  158. package/patterns/navigation-shell.html +8 -0
  159. package/patterns/overflow-command-disclosure.html +6 -0
  160. package/patterns/overview-disclosure.html +12 -0
  161. package/patterns/path-summary.html +8 -0
  162. package/patterns/priority-stack.html +4 -0
  163. package/patterns/progress-bar.html +15 -0
  164. package/patterns/prose.html +9 -0
  165. package/patterns/rail.html +4 -0
  166. package/patterns/recovery-actions.html +5 -0
  167. package/patterns/relationship-index.html +8 -0
  168. package/patterns/reorder-states.html +31 -0
  169. package/patterns/resizable-split-pane.html +11 -0
  170. package/patterns/responsive-grid.html +5 -0
  171. package/patterns/scope-trail.html +1 -0
  172. package/patterns/scroll-progress.html +6 -0
  173. package/patterns/section-heading.html +12 -0
  174. package/patterns/security-blockers.html +3 -0
  175. package/patterns/security-evidence-age.html +1 -0
  176. package/patterns/security-evidence-chain.html +2 -0
  177. package/patterns/security-exception.html +2 -0
  178. package/patterns/security-invariant-result.html +1 -0
  179. package/patterns/security-posture.html +22 -0
  180. package/patterns/security-posture.manifest.md +20 -0
  181. package/patterns/security-state-matrix.html +5 -0
  182. package/patterns/security-trend.html +1 -0
  183. package/patterns/security-unknown.html +2 -0
  184. package/patterns/sidebar.html +4 -0
  185. package/patterns/site-footer.html +12 -0
  186. package/patterns/site-header.html +18 -0
  187. package/patterns/spatial-canvas.html +13 -0
  188. package/patterns/spinner.html +4 -0
  189. package/patterns/split-pane.html +4 -0
  190. package/patterns/stack.html +5 -0
  191. package/patterns/state-survivability.html +19 -0
  192. package/patterns/steps.html +8 -0
  193. package/patterns/switcher.html +4 -0
  194. package/patterns/tooltip.html +48 -0
  195. package/patterns/verification-frame.html +22 -0
  196. package/patterns/workspace-shell.html +6 -0
  197. package/schemas/brand-manifest.schema.json +17 -3
  198. package/tokens/echelon.tokens.json +2801 -0
@@ -0,0 +1,1198 @@
1
+ /*
2
+ Forma marketing role contract: brand-neutral defaults.
3
+
4
+ Marketing foundations, components, and layouts consume only these role
5
+ tokens and Forma's core semantic tokens. A theme (for example the compiled
6
+ Echelon Marketing Theme) restates the roles at :root with higher
7
+ specificity, so these :where() defaults apply only when no theme does.
8
+
9
+ Defaults are declared on every theme/brand scope as well as :root so that
10
+ derived values recompute inside a nested data-ef-theme or data-ef-brand
11
+ subtree instead of inheriting the root's resolved value.
12
+ */
13
+ @layer echelon.tokens {
14
+ :where(:root, [data-ef-theme], [data-ef-brand]) {
15
+ /* Extended color roles, derived from core semantics. */
16
+ --ef-color-surface-elevated: var(--ef-color-surface-primary);
17
+ --ef-color-text-muted: var(--ef-color-text-secondary);
18
+ --ef-color-accent-hover: var(--ef-color-accent-primary);
19
+ /* Status defaults are deliberately hue-free: meaning is carried by text. */
20
+ --ef-color-status-success: var(--ef-color-text-primary);
21
+ --ef-color-status-warning: var(--ef-color-text-primary);
22
+ --ef-color-status-danger: var(--ef-color-text-primary);
23
+ --ef-color-status-info: var(--ef-color-text-primary);
24
+ }
25
+
26
+ :where(:root) {
27
+ /* Relational spacing scale (rem). Component gaps and padding use only
28
+ these steps; section and block rhythm use the fluid layout roles. */
29
+ --ef-space-1: var(--ef-primitive-space-1);
30
+ --ef-space-2: var(--ef-primitive-space-2);
31
+ --ef-space-3: var(--ef-primitive-space-3);
32
+ --ef-space-4: var(--ef-primitive-space-4);
33
+ --ef-space-5: var(--ef-primitive-space-5);
34
+ --ef-space-6: var(--ef-primitive-space-6);
35
+ --ef-space-7: var(--ef-primitive-space-7);
36
+ --ef-space-8: var(--ef-primitive-space-8);
37
+
38
+ --ef-type-family-body: var(--ef-primitive-font-family-sans);
39
+ --ef-type-family-display: var(--ef-primitive-font-family-display);
40
+ --ef-type-family-mono: var(--ef-primitive-font-family-mono);
41
+ --ef-type-size-label: var(--ef-primitive-font-size-xs);
42
+ --ef-type-size-small: var(--ef-primitive-font-size-sm);
43
+ --ef-type-size-body: var(--ef-primitive-font-size-md);
44
+ --ef-type-size-lead: var(--ef-primitive-font-size-fluid-lead);
45
+ --ef-type-size-h1: var(--ef-primitive-font-size-fluid-display-1);
46
+ --ef-type-size-h2: var(--ef-primitive-font-size-fluid-display-2);
47
+ --ef-type-size-h3: var(--ef-primitive-font-size-lg);
48
+ --ef-type-size-h4: var(--ef-primitive-font-size-md);
49
+ --ef-type-size-statement: var(--ef-primitive-font-size-fluid-display-3);
50
+ --ef-type-leading-body: var(--ef-primitive-line-height-normal);
51
+ --ef-type-leading-lead: var(--ef-primitive-line-height-normal);
52
+ --ef-type-leading-heading: var(--ef-primitive-line-height-snug);
53
+ --ef-type-leading-snug: var(--ef-primitive-line-height-snug);
54
+ --ef-type-weight-body: var(--ef-primitive-font-weight-regular);
55
+ --ef-type-weight-strong: var(--ef-primitive-font-weight-bold);
56
+ --ef-type-weight-heading: var(--ef-primitive-font-weight-semibold);
57
+ --ef-type-weight-label: var(--ef-primitive-font-weight-medium);
58
+ --ef-type-weight-nav: var(--ef-primitive-font-weight-semibold);
59
+ --ef-type-tracking-display: var(--ef-primitive-letter-spacing-normal);
60
+ --ef-type-tracking-label: var(--ef-primitive-letter-spacing-wide);
61
+ --ef-type-tracking-body: var(--ef-primitive-letter-spacing-normal);
62
+
63
+ --ef-layout-measure: var(--ef-primitive-size-measure);
64
+ --ef-layout-measure-narrow: var(--ef-primitive-size-measure-narrow);
65
+ --ef-layout-width-narrow: var(--ef-primitive-size-container-narrow);
66
+ --ef-layout-width-standard: var(--ef-primitive-size-container-standard);
67
+ --ef-layout-width-wide: var(--ef-primitive-size-container-wide);
68
+ --ef-layout-width-max: var(--ef-primitive-size-container-max);
69
+ --ef-layout-gutter: var(--ef-primitive-size-fluid-gutter);
70
+ --ef-layout-section-space: var(--ef-primitive-size-fluid-section-space);
71
+ --ef-layout-block-space: var(--ef-primitive-size-fluid-block-space);
72
+ --ef-layout-grid-gap: var(--ef-primitive-space-6);
73
+ --ef-layout-stack-space: var(--ef-primitive-space-6);
74
+ --ef-layout-target-min: var(--ef-primitive-size-target-min);
75
+ /* Clearance kept below a sticky header for anchors and sticky rails. */
76
+ --ef-layout-header-offset: 6rem;
77
+ /* Card and fact minimums decide when grids drop a column (content-driven). */
78
+ --ef-layout-card-min: 15rem;
79
+ --ef-layout-fact-min: 11rem;
80
+
81
+ --ef-shape-radius: var(--ef-primitive-radius-small);
82
+ --ef-shape-border-width: var(--ef-primitive-border-width-thin);
83
+ --ef-shape-border-width-accent: var(--ef-primitive-border-width-accent);
84
+
85
+ --ef-effect-shadow-raised: var(--ef-primitive-shadow-raised);
86
+ /* Marketing state changes are perceptual interpolation (non-spatial).
87
+ The fallbacks restate Forma's shared perceptual vocabulary for pages
88
+ that load the marketing bundle without components.css. */
89
+ --ef-effect-transition-duration: var(--ef-motion-perceptual-duration, 120ms);
90
+ --ef-effect-transition-easing: var(--ef-motion-perceptual-easing, cubic-bezier(0.2, 0, 0, 1));
91
+
92
+ --ef-focus-ring-width: var(--ef-primitive-focus-ring-width);
93
+ --ef-focus-ring-offset: var(--ef-primitive-focus-ring-offset);
94
+ --ef-focus-halo-width: var(--ef-primitive-focus-halo-width);
95
+ }
96
+ }
97
+
98
+ /*
99
+ Forma marketing foundations.
100
+
101
+ Everything here is scoped to the MarketingShell root (.ef-site) so that
102
+ loading the marketing layer never restyles an application that also uses
103
+ Forma. Values come only from role tokens (src/marketing/roles.css) and core
104
+ semantic tokens; no brand values live in this file.
105
+
106
+ Surface tones: every region that changes background declares a tone with
107
+ data-ef-tone. A tone sets a small, contrast-safe set of context variables
108
+ (--ef-tone-*) that components read, so text, links, labels, and rules stay
109
+ legible on every surface without per-component color overrides.
110
+ */
111
+ @layer echelon.marketing.foundations {
112
+ :where(:root, [data-ef-theme], [data-ef-brand]),
113
+ .ef-site,
114
+ [data-ef-tone="page"] {
115
+ --ef-tone-background: var(--ef-color-surface-primary);
116
+ --ef-tone-text: var(--ef-color-text-primary);
117
+ --ef-tone-heading: var(--ef-color-text-heading);
118
+ --ef-tone-muted: var(--ef-color-text-muted);
119
+ --ef-tone-label: var(--ef-color-accent-secondary);
120
+ --ef-tone-link: var(--ef-color-accent-primary);
121
+ --ef-tone-link-hover: var(--ef-color-accent-hover);
122
+ --ef-tone-rule: var(--ef-color-border-subtle);
123
+ --ef-tone-rule-strong: var(--ef-color-border-functional);
124
+ --ef-tone-action-background: var(--ef-color-surface-inverse);
125
+ --ef-tone-action-text: var(--ef-color-text-inverse);
126
+ --ef-tone-action-hover: var(--ef-color-surface-inverse-secondary);
127
+ --ef-tone-hover: var(--ef-color-surface-secondary);
128
+ }
129
+
130
+ /* Accent colors are not validated against the secondary surface, so links
131
+ and labels on it fall back to text roles that are. Cards default to
132
+ this tone. */
133
+ [data-ef-tone="surface"],
134
+ :where(.ef-card:not([data-ef-tone])) {
135
+ --ef-tone-background: var(--ef-color-surface-secondary);
136
+ --ef-tone-text: var(--ef-color-text-primary);
137
+ --ef-tone-heading: var(--ef-color-text-heading);
138
+ --ef-tone-muted: var(--ef-color-text-on-secondary-surface);
139
+ --ef-tone-label: var(--ef-color-text-on-secondary-surface);
140
+ --ef-tone-link: var(--ef-color-text-primary);
141
+ --ef-tone-link-hover: var(--ef-color-text-primary);
142
+ --ef-tone-rule: var(--ef-color-border-subtle);
143
+ --ef-tone-rule-strong: var(--ef-color-border-functional);
144
+ --ef-tone-hover: var(--ef-color-surface-primary);
145
+ }
146
+
147
+ [data-ef-tone="elevated"] {
148
+ --ef-tone-background: var(--ef-color-surface-elevated);
149
+ --ef-tone-text: var(--ef-color-text-primary);
150
+ --ef-tone-heading: var(--ef-color-text-heading);
151
+ --ef-tone-muted: var(--ef-color-text-primary);
152
+ --ef-tone-label: var(--ef-color-text-primary);
153
+ --ef-tone-link: var(--ef-color-text-primary);
154
+ --ef-tone-link-hover: var(--ef-color-text-primary);
155
+ --ef-tone-rule: var(--ef-color-border-subtle);
156
+ --ef-tone-rule-strong: var(--ef-color-border-functional);
157
+ --ef-tone-hover: var(--ef-color-surface-primary);
158
+ }
159
+
160
+ [data-ef-tone="inverse"] {
161
+ --ef-tone-background: var(--ef-color-surface-inverse);
162
+ --ef-tone-text: var(--ef-color-text-inverse);
163
+ --ef-tone-heading: var(--ef-color-text-inverse);
164
+ --ef-tone-muted: var(--ef-color-text-inverse);
165
+ --ef-tone-label: var(--ef-color-text-inverse);
166
+ --ef-tone-link: var(--ef-color-text-inverse);
167
+ --ef-tone-link-hover: var(--ef-color-text-inverse);
168
+ --ef-tone-rule: var(--ef-color-surface-inverse-secondary);
169
+ --ef-tone-rule-strong: var(--ef-color-text-inverse);
170
+ --ef-tone-action-background: var(--ef-color-surface-primary);
171
+ --ef-tone-action-text: var(--ef-color-text-primary);
172
+ --ef-tone-action-hover: var(--ef-color-surface-secondary);
173
+ --ef-tone-hover: var(--ef-color-surface-inverse-secondary);
174
+ }
175
+
176
+ [data-ef-tone],
177
+ :where(.ef-card:not([data-ef-tone])) {
178
+ background: var(--ef-tone-background);
179
+ color: var(--ef-tone-text);
180
+ }
181
+
182
+ :where(html):has(> body.ef-site) {
183
+ background: var(--ef-color-surface-primary);
184
+ }
185
+
186
+ .ef-site {
187
+ /* Stacking context keeps an optional theme backdrop behind content even
188
+ when the shell is not the body element. */
189
+ isolation: isolate;
190
+ display: flex;
191
+ flex-direction: column;
192
+ margin: 0;
193
+ background: var(--ef-color-surface-primary);
194
+ color: var(--ef-tone-text);
195
+ font-family: var(--ef-type-family-body);
196
+ font-size: var(--ef-type-size-body);
197
+ font-weight: var(--ef-type-weight-body);
198
+ letter-spacing: var(--ef-type-tracking-body);
199
+ line-height: var(--ef-type-leading-body);
200
+ text-rendering: optimizeLegibility;
201
+ }
202
+
203
+ /* The shell fills the viewport only when it is the document body. */
204
+ body.ef-site {
205
+ min-block-size: 100vh;
206
+ min-block-size: 100dvh;
207
+ }
208
+
209
+ /* Optional brand backdrop supplied by a theme; absent means none. */
210
+ .ef-site::before {
211
+ content: "";
212
+ position: fixed;
213
+ inset: 0;
214
+ z-index: -1;
215
+ pointer-events: none;
216
+ background-image: var(--ef-site-backdrop, none);
217
+ background-size: var(--ef-site-backdrop-size, auto);
218
+ opacity: var(--ef-site-backdrop-opacity, 1);
219
+ }
220
+
221
+ .ef-site :where(h1, h2, h3, h4, h5, h6) {
222
+ margin-block: 0;
223
+ color: var(--ef-tone-heading);
224
+ text-wrap: balance;
225
+ }
226
+
227
+ .ef-site :where(h1, h2) {
228
+ font-family: var(--ef-type-family-display);
229
+ font-weight: var(--ef-type-weight-heading);
230
+ letter-spacing: var(--ef-type-tracking-display);
231
+ line-height: var(--ef-type-leading-heading);
232
+ }
233
+
234
+ .ef-site :where(h1) { font-size: var(--ef-type-size-h1); max-inline-size: 14ch; }
235
+ .ef-site :where(h2) { font-size: var(--ef-type-size-h2); max-inline-size: 18ch; }
236
+ .ef-site :where(h3) { font-size: var(--ef-type-size-h3); line-height: var(--ef-type-leading-snug); }
237
+ .ef-site :where(h4, h5, h6) { font-size: var(--ef-type-size-h4); line-height: var(--ef-type-leading-snug); }
238
+
239
+ .ef-site :where(p) {
240
+ margin-block: 0 var(--ef-space-4);
241
+ max-inline-size: var(--ef-layout-measure);
242
+ color: var(--ef-tone-muted);
243
+ }
244
+
245
+ .ef-site :where(p:last-child) { margin-block-end: 0; }
246
+
247
+ /* Links stay underlined everywhere except where a component gives them
248
+ another non-color cue (navigation, buttons, cards). */
249
+ .ef-site :where(a) {
250
+ color: var(--ef-tone-link);
251
+ text-decoration-line: underline;
252
+ text-decoration-thickness: var(--ef-shape-border-width);
253
+ text-underline-offset: 0.2em;
254
+ }
255
+
256
+ .ef-site :where(a:hover) {
257
+ color: var(--ef-tone-link-hover);
258
+ text-decoration-thickness: calc(var(--ef-shape-border-width) * 2);
259
+ }
260
+
261
+ .ef-site :where(ul, ol) { margin-block: 0 var(--ef-space-4); padding-inline-start: var(--ef-space-5); }
262
+ .ef-site :where(li + li) { margin-block-start: 0.45em; }
263
+ /* Only prose lists take the reading measure; classed lists are structure. */
264
+ .ef-site :where(ul:not([class]), ol:not([class])) > li { max-inline-size: var(--ef-layout-measure); }
265
+ .ef-site :where(strong, b) { font-weight: var(--ef-type-weight-strong); }
266
+ .ef-site :where(img, svg, video, canvas, picture) { max-inline-size: 100%; block-size: auto; }
267
+ .ef-site :where(code, kbd, samp, pre) { font-family: var(--ef-type-family-mono); font-size: 0.9em; }
268
+
269
+ .ef-site :where(:not(pre) > code) {
270
+ padding: 0.1em 0.3em;
271
+ background: var(--ef-tone-hover);
272
+ color: var(--ef-tone-text);
273
+ overflow-wrap: anywhere;
274
+ }
275
+
276
+ .ef-site :where(hr) {
277
+ margin-block: var(--ef-layout-section-space);
278
+ border: 0;
279
+ border-block-start: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
280
+ }
281
+
282
+ .ef-site :where(blockquote) {
283
+ margin: var(--ef-layout-block-space) 0;
284
+ padding-inline-start: var(--ef-layout-block-space);
285
+ border-inline-start: var(--ef-shape-border-width-accent) solid var(--ef-color-accent-primary);
286
+ font-family: var(--ef-type-family-display);
287
+ font-size: var(--ef-type-size-lead);
288
+ line-height: var(--ef-type-leading-lead);
289
+ }
290
+
291
+ .ef-site :where(blockquote p) { color: var(--ef-tone-text); }
292
+
293
+ /* One focus treatment for the whole shell: a gap-colored outline inside a
294
+ ring-colored halo remains visible on every tone. */
295
+ .ef-site :where(:focus-visible) {
296
+ outline: var(--ef-focus-ring-width) solid var(--ef-color-focus-gap);
297
+ outline-offset: var(--ef-focus-ring-offset);
298
+ box-shadow: 0 0 0 var(--ef-focus-halo-width) var(--ef-color-focus-ring);
299
+ }
300
+
301
+ @media (prefers-reduced-motion: no-preference) {
302
+ :where(html):has(.ef-site) { scroll-behavior: smooth; }
303
+ }
304
+
305
+ /* Anchored targets must land below a sticky header (see layouts.css). */
306
+ @media (min-width: 40rem) and (min-height: 36rem) {
307
+ :where(html):has(.ef-site-header) { scroll-padding-block-start: var(--ef-layout-header-offset); }
308
+ }
309
+ }
310
+
311
+ /*
312
+ Forma marketing components.
313
+
314
+ Small, composable presentation primitives for public/marketing pages. Each
315
+ component reads role tokens and the surrounding surface tone (--ef-tone-*);
316
+ none embeds a brand value, a product name, or page-specific placement.
317
+ Components own their intrinsic layout only. Where they sit on a page is
318
+ owned by layouts.css or the consuming page composition.
319
+
320
+ Responsive behavior is content-driven: wrapping flex rows and auto-fit grids
321
+ with minimum sizes. The two container queries below exist because a row's
322
+ column structure cannot be expressed as a minimum item size.
323
+ */
324
+ @layer echelon.marketing.components {
325
+ /* Skip link --------------------------------------------------------- */
326
+ .ef-skip-link {
327
+ position: fixed;
328
+ inset-block-start: var(--ef-space-2);
329
+ inset-inline-start: var(--ef-space-2);
330
+ z-index: 100;
331
+ display: inline-flex;
332
+ align-items: center;
333
+ min-block-size: var(--ef-layout-target-min);
334
+ padding-inline: var(--ef-space-4);
335
+ background: var(--ef-color-surface-primary);
336
+ color: var(--ef-color-text-primary);
337
+ font-weight: var(--ef-type-weight-strong);
338
+ transform: translateY(-200%);
339
+ }
340
+
341
+ .ef-skip-link:focus,
342
+ .ef-skip-link:focus-visible {
343
+ transform: none;
344
+ }
345
+
346
+ /* Site header and navigation ---------------------------------------- */
347
+ .ef-site-header {
348
+ container: ef-site-header / inline-size;
349
+ position: relative;
350
+ z-index: 20;
351
+ border-block-end: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
352
+ background: color-mix(in srgb, var(--ef-color-surface-primary) 94%, transparent);
353
+ -webkit-backdrop-filter: blur(14px);
354
+ backdrop-filter: blur(14px);
355
+ }
356
+
357
+ .ef-site-header__inner {
358
+ display: flex;
359
+ flex-wrap: wrap;
360
+ align-items: center;
361
+ justify-content: space-between;
362
+ gap: var(--ef-space-2) var(--ef-space-7);
363
+ inline-size: min(100%, var(--ef-layout-width-max));
364
+ margin-inline: auto;
365
+ padding: var(--ef-space-3) var(--ef-layout-gutter);
366
+ }
367
+
368
+ .ef-site-header__identity {
369
+ display: flex;
370
+ flex-wrap: wrap;
371
+ align-items: center;
372
+ gap: var(--ef-space-1) var(--ef-space-3);
373
+ min-inline-size: 0;
374
+ }
375
+
376
+ .ef-site-header__brand {
377
+ display: inline-flex;
378
+ align-items: center;
379
+ gap: var(--ef-space-3);
380
+ min-block-size: var(--ef-layout-target-min);
381
+ color: var(--ef-tone-text);
382
+ font-weight: var(--ef-type-weight-strong);
383
+ text-decoration: none;
384
+ }
385
+
386
+ .ef-site-header__brand:hover { color: var(--ef-tone-text); text-decoration: underline; }
387
+
388
+ /* The brand name stays visible at every width; it wraps rather than hides,
389
+ because the mark alone is decorative (aria-hidden). */
390
+ .ef-site-header__name { min-inline-size: 0; overflow-wrap: anywhere; }
391
+
392
+ .ef-site-header__mark {
393
+ display: grid;
394
+ place-items: center;
395
+ flex: none;
396
+ inline-size: 2.35rem;
397
+ aspect-ratio: 1;
398
+ border-radius: var(--ef-shape-radius);
399
+ background: var(--ef-color-surface-inverse);
400
+ color: var(--ef-color-text-inverse);
401
+ font-family: var(--ef-type-family-mono);
402
+ font-size: var(--ef-type-size-label);
403
+ }
404
+
405
+ .ef-site-header__tagline {
406
+ margin: 0;
407
+ max-inline-size: 24ch;
408
+ color: var(--ef-tone-muted);
409
+ font-family: var(--ef-type-family-mono);
410
+ font-size: var(--ef-type-size-label);
411
+ line-height: var(--ef-type-leading-snug);
412
+ }
413
+
414
+ /* The tagline repeats identity context; below this width it would push the
415
+ navigation onto a third line, so it yields. The brand name never hides. */
416
+ @container ef-site-header (inline-size < 56rem) {
417
+ .ef-site-header__tagline { display: none; }
418
+ }
419
+
420
+ .ef-site-nav__list {
421
+ display: flex;
422
+ flex-wrap: wrap;
423
+ justify-content: flex-end;
424
+ gap: var(--ef-space-1);
425
+ margin: 0;
426
+ padding: 0;
427
+ list-style: none;
428
+ }
429
+
430
+ .ef-site-nav__list > li { margin: 0; }
431
+
432
+ .ef-site-nav__link {
433
+ display: inline-flex;
434
+ align-items: center;
435
+ min-block-size: var(--ef-layout-target-min);
436
+ padding-inline: var(--ef-space-3);
437
+ border-block-end: calc(var(--ef-shape-border-width) * 2) solid transparent;
438
+ color: var(--ef-tone-text);
439
+ font-size: var(--ef-type-size-small);
440
+ font-weight: var(--ef-type-weight-nav);
441
+ text-decoration: none;
442
+ }
443
+
444
+ .ef-site-nav__link:hover {
445
+ color: var(--ef-tone-text);
446
+ border-block-end-color: var(--ef-color-accent-primary);
447
+ }
448
+
449
+ /* Current location uses a persistent rule plus aria-current, not hue alone. */
450
+ .ef-site-nav__link[aria-current="page"],
451
+ .ef-site-nav__link[aria-current="true"] {
452
+ border-block-end-color: var(--ef-tone-rule-strong);
453
+ }
454
+
455
+ .ef-site-nav__link[data-ef-variant="action"] {
456
+ margin-inline-start: var(--ef-space-2);
457
+ border-radius: var(--ef-shape-radius);
458
+ background: var(--ef-tone-action-background);
459
+ color: var(--ef-tone-action-text);
460
+ }
461
+
462
+ .ef-site-nav__link[data-ef-variant="action"]:hover {
463
+ background: var(--ef-tone-action-hover);
464
+ color: var(--ef-tone-action-text);
465
+ border-block-end-color: var(--ef-tone-action-text);
466
+ }
467
+
468
+ /* Text roles --------------------------------------------------------- */
469
+ .ef-eyebrow {
470
+ margin-block: 0 var(--ef-space-3);
471
+ color: var(--ef-tone-label);
472
+ font-family: var(--ef-type-family-mono);
473
+ font-size: var(--ef-type-size-label);
474
+ font-weight: var(--ef-type-weight-label);
475
+ letter-spacing: var(--ef-type-tracking-label);
476
+ line-height: var(--ef-type-leading-snug);
477
+ text-transform: uppercase;
478
+ }
479
+
480
+ .ef-lead {
481
+ max-inline-size: var(--ef-layout-measure-narrow);
482
+ color: var(--ef-tone-text);
483
+ font-size: var(--ef-type-size-lead);
484
+ line-height: var(--ef-type-leading-lead);
485
+ }
486
+
487
+ .ef-statement {
488
+ margin: 0;
489
+ padding: var(--ef-space-5) 0 0;
490
+ border-inline-start: 0;
491
+ border-block-start: var(--ef-shape-border-width-accent) solid var(--ef-color-accent-primary);
492
+ color: var(--ef-tone-text);
493
+ font-family: var(--ef-type-family-display);
494
+ font-size: var(--ef-type-size-statement);
495
+ line-height: var(--ef-type-leading-snug);
496
+ }
497
+
498
+ .ef-statement :where(p) { color: inherit; max-inline-size: none; }
499
+
500
+ /* Actions and buttons ------------------------------------------------ */
501
+ .ef-actions {
502
+ display: flex;
503
+ flex-wrap: wrap;
504
+ align-items: center;
505
+ gap: var(--ef-space-3);
506
+ }
507
+
508
+ .ef-site .ef-button {
509
+ display: inline-flex;
510
+ align-items: center;
511
+ justify-content: center;
512
+ gap: var(--ef-space-3);
513
+ min-block-size: calc(var(--ef-layout-target-min) + var(--ef-space-2));
514
+ padding: var(--ef-space-3) var(--ef-space-5);
515
+ border: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
516
+ border-radius: var(--ef-shape-radius);
517
+ background: transparent;
518
+ color: var(--ef-tone-text);
519
+ font: inherit;
520
+ font-size: var(--ef-type-size-small);
521
+ font-weight: var(--ef-type-weight-strong);
522
+ line-height: var(--ef-type-leading-snug);
523
+ text-align: center;
524
+ text-decoration: none;
525
+ cursor: pointer;
526
+ /* Hover and press are perceptual emphasis on the surface only: the hit
527
+ target never moves under the pointer (MOT-014). */
528
+ transition:
529
+ background-color var(--ef-effect-transition-duration) var(--ef-effect-transition-easing),
530
+ color var(--ef-effect-transition-duration) var(--ef-effect-transition-easing);
531
+ }
532
+
533
+ .ef-site .ef-button:hover { background: var(--ef-tone-hover); color: var(--ef-tone-text); }
534
+
535
+ .ef-site .ef-button[data-ef-variant="primary"] {
536
+ border-color: var(--ef-tone-action-background);
537
+ background: var(--ef-tone-action-background);
538
+ color: var(--ef-tone-action-text);
539
+ }
540
+
541
+ .ef-site .ef-button[data-ef-variant="primary"]:hover {
542
+ background: var(--ef-tone-action-hover);
543
+ color: var(--ef-tone-action-text);
544
+ }
545
+
546
+ .ef-site .ef-button:is(:disabled, [aria-disabled="true"]) {
547
+ border-style: dashed;
548
+ background: var(--ef-color-surface-secondary);
549
+ color: var(--ef-color-text-primary);
550
+ cursor: not-allowed;
551
+ }
552
+
553
+ /* Badges ------------------------------------------------------------- */
554
+ .ef-badge-list {
555
+ display: flex;
556
+ flex-wrap: wrap;
557
+ gap: var(--ef-space-2);
558
+ margin: var(--ef-space-4) 0;
559
+ padding: 0;
560
+ list-style: none;
561
+ }
562
+
563
+ .ef-badge-list > li { margin: 0; }
564
+
565
+ .ef-badge {
566
+ display: inline-flex;
567
+ align-items: center;
568
+ gap: var(--ef-space-1);
569
+ padding: var(--ef-space-1) var(--ef-space-2);
570
+ border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
571
+ border-radius: var(--ef-shape-radius);
572
+ color: var(--ef-tone-text);
573
+ font-family: var(--ef-type-family-mono);
574
+ font-size: var(--ef-type-size-label);
575
+ line-height: var(--ef-type-leading-snug);
576
+ text-decoration: none;
577
+ }
578
+
579
+ a.ef-badge { min-block-size: 1.5rem; }
580
+ a.ef-badge:hover { border-color: var(--ef-tone-rule-strong); color: var(--ef-tone-text); }
581
+
582
+ /* Status is carried by the badge text; the colored edge is redundant. */
583
+ .ef-badge[data-ef-status] { border-inline-start-width: var(--ef-shape-border-width-accent); }
584
+ .ef-badge[data-ef-status="success"] { border-inline-start-color: var(--ef-color-status-success); }
585
+ .ef-badge[data-ef-status="warning"] { border-inline-start-color: var(--ef-color-status-warning); }
586
+ .ef-badge[data-ef-status="danger"] { border-inline-start-color: var(--ef-color-status-danger); }
587
+ .ef-badge[data-ef-status="info"] { border-inline-start-color: var(--ef-color-status-info); }
588
+
589
+ /* Cards and card grids ----------------------------------------------- */
590
+ .ef-card-grid {
591
+ --ef-card-columns: 12;
592
+ --ef-card-rule: var(--ef-tone-rule);
593
+ display: grid;
594
+ grid-template-columns: repeat(
595
+ auto-fit,
596
+ minmax(min(100%, max(var(--ef-layout-card-min), 100% / var(--ef-card-columns))), 1fr)
597
+ );
598
+ margin: 0;
599
+ padding: 0;
600
+ border-block-start: var(--ef-shape-border-width) solid var(--ef-card-rule);
601
+ border-inline-start: var(--ef-shape-border-width) solid var(--ef-card-rule);
602
+ list-style: none;
603
+ }
604
+
605
+ /* data-ef-columns caps columns; the minimum card size decides when to drop. */
606
+ .ef-card-grid[data-ef-columns="2"] { --ef-card-columns: 2; }
607
+ .ef-card-grid[data-ef-columns="3"] { --ef-card-columns: 3; }
608
+ .ef-card-grid[data-ef-columns="4"] { --ef-card-columns: 4; }
609
+ .ef-card-grid[data-ef-columns="5"] { --ef-card-columns: 5; --ef-layout-card-min: 12rem; }
610
+
611
+ .ef-card-grid + .ef-card-grid { border-block-start: 0; }
612
+ .ef-card-grid > li { margin: 0; }
613
+
614
+ .ef-card {
615
+ position: relative;
616
+ min-inline-size: 0;
617
+ padding: var(--ef-layout-block-space);
618
+ border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
619
+ border-radius: var(--ef-shape-radius);
620
+ }
621
+
622
+ /* Inside a grid, cards share hairline rules instead of doubling borders. */
623
+ .ef-card-grid > .ef-card,
624
+ .ef-card-grid > li > .ef-card {
625
+ block-size: 100%;
626
+ border-width: 0 var(--ef-shape-border-width) var(--ef-shape-border-width) 0;
627
+ border-color: var(--ef-card-rule);
628
+ border-radius: 0;
629
+ }
630
+
631
+ .ef-card[data-ef-tone="elevated"] { box-shadow: var(--ef-effect-shadow-raised); }
632
+
633
+ /* Card titles size by role, not heading level, so the document outline can
634
+ use whatever level the page needs (VE LAW-COMP-004 hierarchy alignment). */
635
+ .ef-card__title {
636
+ max-inline-size: 24ch;
637
+ margin-block-end: var(--ef-space-3);
638
+ font-family: var(--ef-type-family-body);
639
+ font-size: var(--ef-type-size-h3);
640
+ font-weight: var(--ef-type-weight-strong);
641
+ letter-spacing: var(--ef-type-tracking-body);
642
+ line-height: var(--ef-type-leading-snug);
643
+ }
644
+
645
+ /* A card link stretches over the card so the whole card is a target while
646
+ the accessible name stays the title, not the card's full text. */
647
+ .ef-card__link {
648
+ color: inherit;
649
+ text-decoration: none;
650
+ }
651
+
652
+ .ef-card__link::after {
653
+ content: "";
654
+ position: absolute;
655
+ inset: 0;
656
+ }
657
+
658
+ .ef-card:has(.ef-card__link) { transition: background-color var(--ef-effect-transition-duration) var(--ef-effect-transition-easing); }
659
+ .ef-card:has(.ef-card__link:hover) { background: var(--ef-tone-hover); }
660
+ .ef-card__link:hover { color: inherit; text-decoration: underline; }
661
+ .ef-card__link:focus-visible { box-shadow: none; outline: none; }
662
+
663
+ .ef-card:has(.ef-card__link:focus-visible) {
664
+ outline: var(--ef-focus-ring-width) solid var(--ef-color-focus-gap);
665
+ outline-offset: calc(var(--ef-focus-ring-offset) * -1 - var(--ef-focus-ring-width));
666
+ box-shadow: inset 0 0 0 var(--ef-focus-halo-width) var(--ef-color-focus-ring);
667
+ }
668
+
669
+ .ef-card__footer {
670
+ margin-block-start: var(--ef-space-6);
671
+ padding-block-start: var(--ef-space-4);
672
+ border-block-start: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
673
+ color: var(--ef-tone-text);
674
+ font-size: var(--ef-type-size-small);
675
+ font-weight: var(--ef-type-weight-strong);
676
+ }
677
+
678
+ /* Facts: short claims with supporting detail, as a description list. */
679
+ .ef-facts {
680
+ display: grid;
681
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--ef-layout-fact-min)), 1fr));
682
+ margin: var(--ef-layout-block-space) 0 0;
683
+ border-block-start: var(--ef-shape-border-width) solid var(--ef-tone-rule);
684
+ border-inline-start: var(--ef-shape-border-width) solid var(--ef-tone-rule);
685
+ }
686
+
687
+ .ef-facts__item {
688
+ min-inline-size: 0;
689
+ padding: var(--ef-space-4);
690
+ border-inline-end: var(--ef-shape-border-width) solid var(--ef-tone-rule);
691
+ border-block-end: var(--ef-shape-border-width) solid var(--ef-tone-rule);
692
+ }
693
+
694
+ .ef-facts__item > dt { color: var(--ef-tone-text); font-weight: var(--ef-type-weight-strong); }
695
+
696
+ .ef-facts__item > dd {
697
+ margin: var(--ef-space-1) 0 0;
698
+ color: var(--ef-tone-muted);
699
+ font-size: var(--ef-type-size-small);
700
+ }
701
+
702
+ /* Section heading ---------------------------------------------------- */
703
+ .ef-section-heading {
704
+ display: flex;
705
+ flex-wrap: wrap;
706
+ align-items: flex-end;
707
+ justify-content: space-between;
708
+ gap: var(--ef-space-6) var(--ef-layout-grid-gap);
709
+ margin-block-end: calc(var(--ef-layout-block-space) * 1.25);
710
+ padding-block-end: var(--ef-space-6);
711
+ border-block-end: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
712
+ }
713
+
714
+ .ef-section-heading__text { flex: 999 1 32rem; min-inline-size: min(100%, 20rem); }
715
+ .ef-section-heading__text > :where(h2) { margin-block-end: var(--ef-space-4); }
716
+ .ef-section-heading__aside { flex: 1 1 14rem; max-inline-size: 28rem; }
717
+
718
+ .ef-section-heading__note {
719
+ color: var(--ef-tone-muted);
720
+ font-family: var(--ef-type-family-mono);
721
+ font-size: var(--ef-type-size-label);
722
+ }
723
+
724
+ /* Index: an ordered set of linked entries (research areas, products). */
725
+ .ef-index {
726
+ container: ef-index / inline-size;
727
+ display: grid;
728
+ grid-template-columns: 5rem minmax(0, 0.65fr) minmax(0, 1fr) minmax(0, auto);
729
+ column-gap: var(--ef-layout-block-space);
730
+ margin: 0;
731
+ padding: 0;
732
+ border-block-start: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
733
+ list-style: none;
734
+ }
735
+
736
+ /* Rows share the list's columns (subgrid) so entries align down the page. */
737
+ .ef-index__item {
738
+ display: grid;
739
+ grid-column: 1 / -1;
740
+ grid-template-columns: subgrid;
741
+ row-gap: var(--ef-space-2);
742
+ align-items: start;
743
+ margin: 0;
744
+ padding-block: var(--ef-space-7);
745
+ border-block-end: var(--ef-shape-border-width) solid var(--ef-tone-rule);
746
+ }
747
+
748
+ .ef-index__marker {
749
+ margin: 0;
750
+ color: var(--ef-tone-text);
751
+ font-family: var(--ef-type-family-mono);
752
+ font-size: var(--ef-type-size-label);
753
+ }
754
+
755
+ .ef-index__title {
756
+ margin-block-start: var(--ef-space-1);
757
+ font-family: var(--ef-type-family-display);
758
+ font-size: var(--ef-type-size-statement);
759
+ font-weight: var(--ef-type-weight-heading);
760
+ }
761
+
762
+ .ef-index__heading > .ef-eyebrow { margin: 0; }
763
+ .ef-index__summary { margin: 0; }
764
+
765
+ .ef-index__link {
766
+ display: inline-flex;
767
+ align-items: center;
768
+ min-block-size: 1.5rem;
769
+ font-size: var(--ef-type-size-small);
770
+ font-weight: var(--ef-type-weight-strong);
771
+ white-space: nowrap;
772
+ }
773
+
774
+ /* Four disparate columns cannot degrade by a minimum item size, so the
775
+ row regroups by container width: marker + content, then single column. */
776
+ @container ef-index (inline-size < 56rem) {
777
+ .ef-index__item { grid-template-columns: 3.5rem minmax(0, 1fr); column-gap: var(--ef-space-4); }
778
+ .ef-index__item > :not(.ef-index__marker) { grid-column: 2; }
779
+ }
780
+
781
+ @container ef-index (inline-size < 28rem) {
782
+ .ef-index__item { grid-template-columns: minmax(0, 1fr); }
783
+ .ef-index__item > :not(.ef-index__marker) { grid-column: 1; }
784
+ }
785
+
786
+ /* Steps: a numbered method or process. */
787
+ .ef-steps {
788
+ counter-reset: ef-step;
789
+ margin: 0;
790
+ padding: 0;
791
+ border-block-start: var(--ef-shape-border-width) solid var(--ef-tone-rule);
792
+ list-style: none;
793
+ }
794
+
795
+ .ef-steps > li {
796
+ counter-increment: ef-step;
797
+ display: flex;
798
+ gap: var(--ef-space-4);
799
+ margin: 0;
800
+ padding-block: var(--ef-space-4);
801
+ border-block-end: var(--ef-shape-border-width) solid var(--ef-tone-rule);
802
+ color: var(--ef-tone-text);
803
+ }
804
+
805
+ .ef-steps > li::before {
806
+ content: counter(ef-step, decimal-leading-zero);
807
+ flex: none;
808
+ min-inline-size: 2ch;
809
+ font-family: var(--ef-type-family-mono);
810
+ font-weight: var(--ef-type-weight-strong);
811
+ }
812
+
813
+ /* Call to action ----------------------------------------------------- */
814
+ .ef-cta {
815
+ display: flex;
816
+ flex-wrap: wrap;
817
+ align-items: flex-end;
818
+ justify-content: space-between;
819
+ gap: var(--ef-space-6) var(--ef-layout-grid-gap);
820
+ padding-block-start: calc(var(--ef-layout-block-space) * 1.25);
821
+ border-block-start: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
822
+ }
823
+
824
+ .ef-cta__text { flex: 999 1 32rem; min-inline-size: min(100%, 20rem); }
825
+ .ef-cta__text > :where(h2) { margin-block-end: var(--ef-space-4); }
826
+ .ef-cta > .ef-actions { flex: 1 1 auto; justify-content: flex-start; }
827
+
828
+ .ef-cta[data-ef-tone]:not([data-ef-tone="page"]) {
829
+ padding: calc(var(--ef-layout-block-space) * 2);
830
+ border-block-start: 0;
831
+ }
832
+
833
+ /* Prose: long-form content inside the reading measure. */
834
+ .ef-prose {
835
+ --ef-type-size-h2: var(--ef-type-size-statement);
836
+ max-inline-size: var(--ef-layout-measure);
837
+ }
838
+
839
+ .ef-prose > :where(h1) { margin-block-end: var(--ef-space-6); max-inline-size: none; }
840
+ .ef-prose > :where(h2) { margin-block: var(--ef-space-8) var(--ef-space-5); max-inline-size: none; }
841
+ .ef-prose > :where(h3) { margin-block: var(--ef-space-7) var(--ef-space-3); }
842
+ .ef-prose > :where(h2, h3):first-child { margin-block-start: 0; }
843
+ .ef-prose :where(p, li) { color: var(--ef-tone-text); }
844
+ .ef-prose > :where(figure, pre, .ef-code) { margin-block: var(--ef-layout-block-space); }
845
+
846
+ /* Callout: emphasized supporting material inside prose (FORMA-CONTENT-001).
847
+ A visible text label names the kind; the rule pattern repeats it as a
848
+ second, non-color cue. Presentation only: no state and no runtime. */
849
+ .ef-callout {
850
+ --ef-callout-rule: var(--ef-tone-rule-strong);
851
+ margin: var(--ef-layout-block-space) 0;
852
+ padding: var(--ef-space-4) var(--ef-space-5);
853
+ max-inline-size: var(--ef-layout-measure);
854
+ border: var(--ef-shape-border-width) solid var(--ef-tone-rule);
855
+ border-inline-start: var(--ef-shape-border-width-accent) solid var(--ef-callout-rule);
856
+ color: var(--ef-tone-text);
857
+ }
858
+
859
+ .ef-callout > :first-child { margin-block-start: 0; }
860
+ .ef-callout > :last-child { margin-block-end: 0; }
861
+ .ef-callout :where(p, li) { color: var(--ef-tone-text); max-inline-size: none; }
862
+
863
+ .ef-callout__label {
864
+ margin-block: 0 var(--ef-space-2);
865
+ color: var(--ef-tone-label);
866
+ font-family: var(--ef-type-family-mono);
867
+ font-size: var(--ef-type-size-label);
868
+ font-weight: var(--ef-type-weight-strong);
869
+ letter-spacing: var(--ef-type-tracking-label);
870
+ text-transform: uppercase;
871
+ }
872
+
873
+ /* Quoted evidence reads as body text; the callout already marks it. */
874
+ .ef-callout :where(blockquote) {
875
+ margin: 0;
876
+ padding: 0;
877
+ border: 0;
878
+ font-family: inherit;
879
+ font-size: inherit;
880
+ line-height: inherit;
881
+ }
882
+
883
+ .ef-callout :where(figcaption) {
884
+ margin-block-start: var(--ef-space-3);
885
+ color: var(--ef-tone-muted);
886
+ font-size: var(--ef-type-size-small);
887
+ }
888
+
889
+ /* Caution: a heavy block-start rule and a full-strength frame. */
890
+ .ef-callout[data-ef-callout="caution"] {
891
+ --ef-callout-rule: var(--ef-color-status-warning);
892
+ border-color: var(--ef-tone-rule-strong);
893
+ border-block-start-width: var(--ef-shape-border-width-accent);
894
+ border-inline-start-color: var(--ef-callout-rule);
895
+ }
896
+
897
+ /* Evidence: a dashed frame marks quoted or cited source material. */
898
+ .ef-callout[data-ef-callout="evidence"] {
899
+ --ef-callout-rule: var(--ef-color-status-info);
900
+ border-style: dashed;
901
+ border-inline-start-style: solid;
902
+ }
903
+
904
+ /* Decision: a double rule marks a settled outcome. */
905
+ .ef-callout[data-ef-callout="decision"] {
906
+ --ef-callout-rule: var(--ef-color-accent-primary);
907
+ border-inline-start-style: double;
908
+ border-inline-start-width: calc(var(--ef-shape-border-width-accent) * 2);
909
+ }
910
+
911
+ /* Code: technical samples in a bounded, keyboard-scrollable region. */
912
+ .ef-code {
913
+ margin: 0;
914
+ max-inline-size: 100%;
915
+ overflow: auto;
916
+ padding: var(--ef-layout-block-space);
917
+ border-radius: var(--ef-shape-radius);
918
+ background: var(--ef-color-surface-inverse);
919
+ color: var(--ef-color-text-inverse);
920
+ font-family: var(--ef-type-family-mono);
921
+ font-size: var(--ef-type-size-small);
922
+ line-height: var(--ef-type-leading-lead);
923
+ tab-size: 2;
924
+ }
925
+
926
+ .ef-code :where(code) { padding: 0; background: none; color: inherit; font-size: inherit; overflow-wrap: normal; }
927
+
928
+ .ef-code-figure { margin: 0; }
929
+
930
+ .ef-code-figure > figcaption {
931
+ margin-block-end: var(--ef-space-2);
932
+ color: var(--ef-tone-muted);
933
+ font-family: var(--ef-type-family-mono);
934
+ font-size: var(--ef-type-size-label);
935
+ }
936
+
937
+ /* Site footer -------------------------------------------------------- */
938
+ .ef-site-footer {
939
+ margin-block-start: auto;
940
+ }
941
+
942
+ .ef-site-footer__inner {
943
+ inline-size: min(100%, var(--ef-layout-width-max));
944
+ margin-inline: auto;
945
+ padding: calc(var(--ef-layout-block-space) * 2) var(--ef-layout-gutter);
946
+ }
947
+
948
+ .ef-site-footer__main {
949
+ display: flex;
950
+ flex-wrap: wrap;
951
+ justify-content: space-between;
952
+ gap: var(--ef-layout-grid-gap);
953
+ }
954
+
955
+ .ef-site-footer__main > * { flex: 1 1 18rem; }
956
+ .ef-site-footer__main > .ef-actions { flex-grow: 0; }
957
+
958
+ .ef-site-footer__nav ul {
959
+ display: flex;
960
+ flex-wrap: wrap;
961
+ gap: var(--ef-space-1) var(--ef-space-5);
962
+ margin: 0;
963
+ padding: 0;
964
+ list-style: none;
965
+ }
966
+
967
+ .ef-site-footer__nav li { margin: 0; }
968
+ .ef-site-footer__nav a { display: inline-flex; align-items: center; min-block-size: 1.5rem; }
969
+
970
+ .ef-site-footer__note {
971
+ display: flex;
972
+ flex-wrap: wrap;
973
+ justify-content: space-between;
974
+ gap: var(--ef-space-2) var(--ef-space-7);
975
+ max-inline-size: none;
976
+ margin: calc(var(--ef-layout-block-space) * 1.5) 0 0;
977
+ padding-block-start: var(--ef-space-4);
978
+ border-block-start: var(--ef-shape-border-width) solid var(--ef-tone-rule);
979
+ color: var(--ef-tone-muted);
980
+ font-family: var(--ef-type-family-mono);
981
+ font-size: var(--ef-type-size-label);
982
+ }
983
+
984
+ /* Accessibility and output modes ------------------------------------ */
985
+ @media (prefers-reduced-motion: reduce) {
986
+ .ef-site :where(*, *::before, *::after) {
987
+ scroll-behavior: auto !important;
988
+ transition: none !important;
989
+ animation: none !important;
990
+ }
991
+ }
992
+
993
+ @media (forced-colors: active) {
994
+ .ef-site-header__mark,
995
+ .ef-site [data-ef-tone]:not([data-ef-tone="page"]),
996
+ .ef-code { border: 1px solid CanvasText; }
997
+ .ef-site .ef-button,
998
+ .ef-badge,
999
+ .ef-site-nav__link[data-ef-variant="action"] { border: 1px solid ButtonText; }
1000
+ .ef-site-nav__link[aria-current] { border-block-end-color: Highlight; }
1001
+ .ef-statement { border-block-start-color: CanvasText; }
1002
+ .ef-callout { border-color: CanvasText; }
1003
+ }
1004
+
1005
+ @media print {
1006
+ .ef-site::before,
1007
+ .ef-skip-link,
1008
+ .ef-site-nav,
1009
+ .ef-site-footer .ef-actions { display: none; }
1010
+ .ef-site-header { position: static; backdrop-filter: none; background: none; }
1011
+ .ef-site [data-ef-tone] { background: none; color: CanvasText; }
1012
+ .ef-site :where(h1, h2, h3, h4, p, li, dt, dd, a) { color: CanvasText; }
1013
+ .ef-prose a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; }
1014
+ .ef-card, .ef-index__item, .ef-code, .ef-callout { break-inside: avoid; }
1015
+ .ef-callout { border-color: CanvasText; }
1016
+ }
1017
+ }
1018
+
1019
+ /*
1020
+ Forma marketing layouts.
1021
+
1022
+ Page-level structure for public/marketing sites. The MarketingShell
1023
+ (.ef-site) owns the frame: skip link, header, main, footer, and common
1024
+ behavior. Page layouts (Marketing, Product, Documentation) are compositions
1025
+ of the same components inside that frame; only the documentation layout
1026
+ needs structure of its own.
1027
+
1028
+ Breakpoints, and why each exists:
1029
+ - Sticky header needs min-width 40rem and min-height 36rem. A sticky header
1030
+ on a short or narrow viewport (or at 200-400% zoom) permanently consumes
1031
+ reading space and can obscure focused content (WCAG 2.4.11).
1032
+ - The documentation layout regroups at 48rem and 72rem of main-region
1033
+ width (container queries): three regions with different roles cannot
1034
+ share one minimum item size. Below 48rem the navigation list also wraps
1035
+ like a cluster, so a tall vertical nav never pushes the article below
1036
+ the fold.
1037
+ All other recomposition is content-driven (flex-wrap / auto-fit minimums).
1038
+ */
1039
+ @layer echelon.marketing.layouts {
1040
+ @media (min-width: 40rem) and (min-height: 36rem) {
1041
+ .ef-site-header { position: sticky; inset-block-start: 0; }
1042
+ }
1043
+
1044
+ .ef-site__main {
1045
+ flex: 1 0 auto;
1046
+ inline-size: min(100%, var(--ef-layout-width-max));
1047
+ margin-inline: auto;
1048
+ padding-inline: var(--ef-layout-gutter);
1049
+ padding-block-end: var(--ef-layout-section-space);
1050
+ }
1051
+
1052
+ .ef-site__main:focus { outline: none; }
1053
+
1054
+ /* Containers constrain a region inside the page width. */
1055
+ .ef-container {
1056
+ inline-size: min(100%, var(--ef-container-width, var(--ef-layout-width-standard)));
1057
+ margin-inline: auto;
1058
+ }
1059
+
1060
+ .ef-container[data-ef-width="narrow"] { --ef-container-width: var(--ef-layout-width-narrow); }
1061
+ .ef-container[data-ef-width="wide"] { --ef-container-width: var(--ef-layout-width-wide); }
1062
+ .ef-container[data-ef-width="max"] { --ef-container-width: var(--ef-layout-width-max); }
1063
+
1064
+ /* Sections: consistent vertical rhythm between page regions. */
1065
+ .ef-site .ef-section {
1066
+ padding-block: calc(var(--ef-layout-section-space) / 2);
1067
+ }
1068
+
1069
+ .ef-site .ef-section[data-ef-tone]:not([data-ef-tone="page"]) {
1070
+ margin-block: calc(var(--ef-layout-section-space) / 2);
1071
+ padding: calc(var(--ef-layout-block-space) * 2);
1072
+ }
1073
+
1074
+ .ef-section > * + * { margin-block-start: var(--ef-layout-stack-space); }
1075
+ .ef-section > .ef-section-heading + * { margin-block-start: 0; }
1076
+
1077
+ /* Split: primary content with a supporting column that wraps below it. */
1078
+ .ef-split {
1079
+ display: flex;
1080
+ flex-wrap: wrap;
1081
+ align-items: flex-start;
1082
+ gap: calc(var(--ef-layout-section-space) / 2);
1083
+ }
1084
+
1085
+ .ef-split > :first-child { flex: 999 1 0; min-inline-size: min(100%, 55%); }
1086
+ .ef-split > :last-child:not(:first-child) { flex: 1 1 clamp(18rem, 32%, 30rem); min-inline-size: 0; }
1087
+
1088
+ /* Hero: the page's first region and its one primary recognition path. */
1089
+ .ef-hero {
1090
+ display: flex;
1091
+ flex-wrap: wrap;
1092
+ align-items: flex-end;
1093
+ gap: calc(var(--ef-layout-section-space) / 2);
1094
+ padding-block: var(--ef-layout-section-space) calc(var(--ef-layout-block-space) * 2);
1095
+ border-block-end: var(--ef-shape-border-width) solid var(--ef-tone-rule-strong);
1096
+ }
1097
+
1098
+ .ef-hero__content { flex: 999 1 0; min-inline-size: min(100%, 55%); }
1099
+ .ef-hero__aside { flex: 1 1 clamp(18rem, 32%, 30rem); min-inline-size: 0; }
1100
+ .ef-hero__content > * + *,
1101
+ .ef-hero__aside > * + * { margin-block-start: var(--ef-layout-stack-space); }
1102
+ .ef-hero__title { margin-block: var(--ef-space-2) var(--ef-space-6); }
1103
+ .ef-hero__content > .ef-eyebrow + .ef-hero__title { margin-block-start: 0; }
1104
+
1105
+ /* Documentation page: navigation, article, optional page outline.
1106
+ Three regions with different roles cannot share one minimum size, so
1107
+ the grid regroups by the width of the main region (a container):
1108
+ one column, then navigation + article (outline follows the article),
1109
+ then all three. Source order and reading order never change. */
1110
+ .ef-site__main { container: ef-main / inline-size; }
1111
+
1112
+ .ef-doc {
1113
+ container: ef-doc / inline-size;
1114
+ display: grid;
1115
+ grid-template-columns: minmax(0, 1fr);
1116
+ gap: var(--ef-layout-block-space) calc(var(--ef-layout-section-space) / 2);
1117
+ align-items: start;
1118
+ padding-block: calc(var(--ef-layout-section-space) / 2);
1119
+ }
1120
+
1121
+ .ef-doc__nav,
1122
+ .ef-doc__content,
1123
+ .ef-doc__aside { min-inline-size: 0; }
1124
+
1125
+ .ef-doc__content { --ef-type-size-h1: var(--ef-type-size-h2); }
1126
+
1127
+ @container ef-main (inline-size >= 48rem) {
1128
+ .ef-doc { grid-template-columns: minmax(11rem, 15rem) minmax(0, 1fr); }
1129
+ .ef-doc__aside { grid-column: 2; }
1130
+ }
1131
+
1132
+ @container ef-main (inline-size >= 72rem) {
1133
+ .ef-doc { grid-template-columns: minmax(11rem, 15rem) minmax(0, 1fr) minmax(10rem, 14rem); }
1134
+ .ef-doc__aside { grid-column: 3; }
1135
+ }
1136
+
1137
+ .ef-doc__nav :where(ul),
1138
+ .ef-doc__aside :where(ul) {
1139
+ margin: 0;
1140
+ padding: 0;
1141
+ border-inline-start: var(--ef-shape-border-width) solid var(--ef-tone-rule);
1142
+ list-style: none;
1143
+ }
1144
+
1145
+ .ef-doc__nav :where(li + li),
1146
+ .ef-doc__aside :where(li + li) { margin-block-start: 0; }
1147
+
1148
+ .ef-doc__nav :where(a),
1149
+ .ef-doc__aside :where(a) {
1150
+ display: flex;
1151
+ align-items: center;
1152
+ min-block-size: var(--ef-layout-target-min);
1153
+ margin-inline-start: calc(var(--ef-shape-border-width) * -1);
1154
+ padding-inline: var(--ef-space-4);
1155
+ border-inline-start: calc(var(--ef-shape-border-width) * 2) solid transparent;
1156
+ color: var(--ef-tone-text);
1157
+ font-size: var(--ef-type-size-small);
1158
+ text-decoration: none;
1159
+ }
1160
+
1161
+ .ef-doc__nav :where(a:hover),
1162
+ .ef-doc__aside :where(a:hover) { border-inline-start-color: var(--ef-color-accent-primary); }
1163
+
1164
+ .ef-doc__nav :where(a[aria-current]) {
1165
+ border-inline-start-color: var(--ef-tone-rule-strong);
1166
+ font-weight: var(--ef-type-weight-strong);
1167
+ }
1168
+
1169
+ .ef-doc__heading { margin-block-end: var(--ef-space-2); }
1170
+
1171
+ @media (min-width: 64rem) and (min-height: 36rem) {
1172
+ .ef-doc__nav,
1173
+ .ef-doc__aside {
1174
+ position: sticky;
1175
+ inset-block-start: var(--ef-layout-header-offset);
1176
+ max-block-size: calc(100vh - var(--ef-layout-header-offset) - 1rem);
1177
+ overflow: auto;
1178
+ }
1179
+ }
1180
+
1181
+ @container ef-doc (inline-size < 48rem) {
1182
+ .ef-doc__nav :where(ul) {
1183
+ display: flex;
1184
+ flex-wrap: wrap;
1185
+ gap: 0 var(--ef-space-1);
1186
+ border-inline-start: 0;
1187
+ border-block-end: var(--ef-shape-border-width) solid var(--ef-tone-rule);
1188
+ }
1189
+
1190
+ .ef-doc__nav :where(a) {
1191
+ margin-inline-start: 0;
1192
+ border-inline-start: 0;
1193
+ border-block-end: calc(var(--ef-shape-border-width) * 2) solid transparent;
1194
+ }
1195
+
1196
+ .ef-doc__nav :where(a[aria-current]) { border-block-end-color: var(--ef-tone-rule-strong); }
1197
+ }
1198
+ }