@fluid-theme/core 0.4.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/AGENTS.md +6 -29
  2. package/CHANGELOG.md +564 -2
  3. package/CLAUDE.md +201 -14
  4. package/GEMINI.md +6 -45
  5. package/README.md +17 -8
  6. package/assets/js/compare.js +77 -0
  7. package/assets/js/fit-text.js +103 -0
  8. package/assets/js/lightbox.js +153 -0
  9. package/assets/js/scroll-reveal.js +72 -0
  10. package/assets/js/toc-build.mjs +86 -0
  11. package/assets/js/toc.js +384 -0
  12. package/dist/fluid-1x.min.css +4 -0
  13. package/dist/fluid-fonts-archivo.min.css +2 -0
  14. package/dist/fluid-fonts-bodoni.min.css +2 -0
  15. package/dist/fluid-fonts.min.css +2 -0
  16. package/dist/fluid-legacy-1x.css +3 -0
  17. package/dist/fluid-nav.min.css +3 -0
  18. package/dist/fluid-toc.min.css +3 -0
  19. package/dist/fluid-zero.min.css +3 -0
  20. package/dist/fluid.min.css +3 -2
  21. package/dist/fonts/OFL-archivo.txt +93 -0
  22. package/dist/fonts/OFL-bodoni-moda.txt +93 -0
  23. package/dist/fonts/OFL-hanken-grotesk.txt +93 -0
  24. package/dist/fonts/OFL-newsreader.txt +93 -0
  25. package/dist/fonts/OFL-schibsted-grotesk.txt +93 -0
  26. package/dist/fonts/archivo-latin-wdth-italic.woff2 +0 -0
  27. package/dist/fonts/archivo-latin-wdth-normal.woff2 +0 -0
  28. package/dist/fonts/bodoni-moda-latin-opsz-italic.woff2 +0 -0
  29. package/dist/fonts/bodoni-moda-latin-opsz-normal.woff2 +0 -0
  30. package/dist/fonts/hanken-grotesk-latin-wght-italic.woff2 +0 -0
  31. package/dist/fonts/hanken-grotesk-latin-wght-normal.woff2 +0 -0
  32. package/dist/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
  33. package/dist/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  34. package/dist/fonts/schibsted-grotesk-latin-wght-italic.woff2 +0 -0
  35. package/dist/fonts/schibsted-grotesk-latin-wght-normal.woff2 +0 -0
  36. package/fluid-agent-kit/README.md +1 -0
  37. package/fluid-agent-kit/skills/accessibility.md +3 -1
  38. package/fluid-agent-kit/skills/css-conventions.md +22 -5
  39. package/fluid-agent-kit/skills/image-optimization.md +2 -0
  40. package/fluid-agent-kit/skills/preset-composition.md +108 -0
  41. package/package.json +36 -11
  42. package/src/animations/scroll-reveal.css +75 -193
  43. package/src/components/accordion.css +6 -47
  44. package/src/components/alert.css +47 -88
  45. package/src/components/avatar.css +4 -20
  46. package/src/components/badge.css +25 -48
  47. package/src/components/button.css +69 -175
  48. package/src/components/card.css +146 -481
  49. package/src/components/chip.css +46 -12
  50. package/src/components/compare.css +234 -0
  51. package/src/components/dropdown.css +10 -67
  52. package/src/components/empty-state.css +4 -0
  53. package/src/components/form.css +113 -81
  54. package/src/components/gallery.css +252 -0
  55. package/src/components/hero.css +203 -88
  56. package/src/components/indicator.css +23 -117
  57. package/src/components/loading.css +7 -29
  58. package/src/components/modal.css +12 -65
  59. package/src/components/nav-modes.css +261 -0
  60. package/src/components/nav.css +324 -392
  61. package/src/components/search-input.css +4 -2
  62. package/src/components/step-indicator.css +26 -27
  63. package/src/components/table.css +64 -171
  64. package/src/components/toc.css +254 -0
  65. package/src/components/tooltip.css +5 -46
  66. package/src/core/base.css +393 -547
  67. package/src/core/reset.css +17 -5
  68. package/src/core/tokens.css +426 -2276
  69. package/src/fluid.css +258 -17
  70. package/src/fonts/OFL-newsreader.txt +93 -0
  71. package/src/fonts/OFL-schibsted-grotesk.txt +93 -0
  72. package/src/fonts/fluid-fonts.css +55 -0
  73. package/src/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
  74. package/src/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  75. package/src/fonts/schibsted-grotesk-latin-wght-italic.woff2 +0 -0
  76. package/src/fonts/schibsted-grotesk-latin-wght-normal.woff2 +0 -0
  77. package/src/layouts/positioning.css +6 -3
  78. package/src/legacy-1x.css +61 -0
  79. package/src/sections/cta.css +33 -101
  80. package/src/sections/features.css +42 -219
  81. package/src/sections/footer.css +53 -134
  82. package/src/sections/stats.css +40 -153
  83. package/src/sections/team.css +24 -113
  84. package/src/sections/testimonials.css +34 -148
  85. package/src/zero/base.css +166 -0
  86. package/src/zero/tokens.css +64 -0
  87. package/src/zero.css +20 -0
  88. package/starters/workflows/README.md +40 -0
  89. package/starters/workflows/dependabot.yml +21 -0
  90. package/starters/workflows/update-fluid-theme.yml +296 -0
  91. package/themes/fonts/OFL-archivo.txt +93 -0
  92. package/themes/fonts/OFL-bodoni-moda.txt +93 -0
  93. package/themes/fonts/OFL-hanken-grotesk.txt +93 -0
  94. package/themes/fonts/archivo-latin-wdth-italic.woff2 +0 -0
  95. package/themes/fonts/archivo-latin-wdth-normal.woff2 +0 -0
  96. package/themes/fonts/archivo.css +20 -0
  97. package/themes/fonts/bodoni-moda-latin-opsz-italic.woff2 +0 -0
  98. package/themes/fonts/bodoni-moda-latin-opsz-normal.woff2 +0 -0
  99. package/themes/fonts/bodoni.css +35 -0
  100. package/themes/fonts/hanken-grotesk-latin-wght-italic.woff2 +0 -0
  101. package/themes/fonts/hanken-grotesk-latin-wght-normal.woff2 +0 -0
  102. package/themes/gallery.css +105 -0
  103. package/themes/swiss.css +53 -0
  104. package/src/components/scroll-progress.css +0 -104
  105. package/src/components/settings-panel.css +0 -603
  106. package/src/components/themed-images.css +0 -229
  107. package/src/components/version-badge.css +0 -70
@@ -6,9 +6,9 @@
6
6
  * Respects prefers-reduced-motion.
7
7
  *
8
8
  * Spinner Usage:
9
- * <div class="spinner" aria-label="Loading"></div>
10
- * <div class="spinner" data-size="sm"></div>
11
- * <div class="spinner" data-size="lg"></div>
9
+ * <div class="spinner" role="status" aria-label="Loading"></div>
10
+ * <div class="spinner" role="status" data-size="sm" aria-label="Loading"></div>
11
+ * <div class="spinner" role="status" data-size="lg" aria-label="Loading"></div>
12
12
  *
13
13
  * Skeleton Usage:
14
14
  * <div class="skeleton" style="--skeleton-h: 1rem; --skeleton-w: 60%;"></div>
@@ -32,7 +32,7 @@
32
32
  .spinner {
33
33
  --_spinner-size: 2rem;
34
34
  --_spinner-width: 3px;
35
- --_spinner-color: var(--accent);
35
+ --_spinner-color: var(--accent-ink);
36
36
 
37
37
  display: inline-block;
38
38
  inline-size: var(--_spinner-size);
@@ -221,7 +221,7 @@
221
221
  .skeleton[data-variant="button"] {
222
222
  --skeleton-h: 2.75rem;
223
223
  --skeleton-w: 8rem;
224
- --_skeleton-radius: var(--radius-button, var(--radius-md));
224
+ --_skeleton-radius: var(--radius-control);
225
225
  }
226
226
 
227
227
 
@@ -237,38 +237,16 @@
237
237
  padding: var(--space-5);
238
238
  background: var(--color-surface-raised);
239
239
  border: var(--border-width) solid var(--color-border-subtle);
240
- border-radius: var(--radius-xl);
240
+ border-radius: var(--radius-card);
241
241
  }
242
242
 
243
243
  .skeleton-card .skeleton[data-variant="media"] {
244
244
  margin: calc(var(--space-5) * -1);
245
245
  margin-block-end: var(--space-3);
246
246
  inline-size: calc(100% + var(--space-5) * 2);
247
- border-radius: var(--radius-xl) var(--radius-xl) 0 0;
247
+ border-radius: var(--radius-card) var(--radius-card) 0 0;
248
248
  }
249
249
 
250
250
 
251
- /* ========================================
252
- THEME-SPECIFIC STYLES
253
- ======================================== */
254
251
 
255
- /* Still: no animation */
256
- [data-motion="still"] .spinner {
257
- animation-duration: 800ms;
258
- }
259
-
260
- [data-motion="still"] .skeleton {
261
- animation: none;
262
- background: var(--color-surface);
263
- }
264
-
265
- /* Tsunami: asymmetric skeleton cards */
266
- [data-motion="tsunami"] .skeleton-card {
267
- border-radius: var(--radius-asymmetric, 0 24px 0 0);
268
- }
269
-
270
- /* Rapids: faster spinner */
271
- [data-motion="rapids"] .spinner {
272
- animation-duration: 500ms;
273
- }
274
252
  }
@@ -78,7 +78,6 @@
78
78
  /* Native backdrop */
79
79
  .modal::backdrop {
80
80
  background: oklch(0% 0 0 / 0.5);
81
- backdrop-filter: blur(4px);
82
81
  }
83
82
 
84
83
 
@@ -95,20 +94,11 @@
95
94
  inline-size: var(--_modal-width);
96
95
  max-block-size: min(90vh, 40rem);
97
96
 
98
- background: var(--color-surface-raised);
99
- border: var(--border-width) solid var(--color-border-subtle);
100
- border-radius: var(--radius-2xl);
101
- box-shadow: var(--shadow-xl);
97
+ background: var(--color-background);
98
+ color: var(--color-text);
99
+ border: var(--border-width) solid var(--color-border-strong);
100
+ border-radius: var(--radius-card);
102
101
  overflow: hidden;
103
-
104
- /* Entry animation */
105
- transform: translateY(var(--space-5)) scale(0.97);
106
- transition:
107
- transform var(--duration-normal) var(--ease-settle);
108
- }
109
-
110
- .modal[open] .modal__content {
111
- transform: translateY(0) scale(1);
112
102
  }
113
103
 
114
104
 
@@ -127,6 +117,7 @@
127
117
  }
128
118
 
129
119
  .modal__title {
120
+ font-family: var(--font-heading);
130
121
  font-size: var(--font-size-lg);
131
122
  font-weight: var(--weight-semibold);
132
123
  line-height: var(--leading-tight);
@@ -135,7 +126,7 @@
135
126
 
136
127
  .modal__close {
137
128
  --_btn-bg: transparent;
138
- --_btn-shadow: none;
129
+ --_btn-bg-hover: var(--color-surface);
139
130
 
140
131
  position: relative;
141
132
  flex-shrink: 0;
@@ -145,7 +136,7 @@
145
136
  inline-size: 2.75rem;
146
137
  block-size: 2.75rem;
147
138
  padding: 0;
148
- border-radius: var(--radius-md);
139
+ border-radius: var(--radius-control);
149
140
  font-size: var(--font-size-xl);
150
141
  line-height: 1;
151
142
  color: var(--color-text-muted);
@@ -214,7 +205,7 @@
214
205
  --_modal-width: min(90vw, 28rem);
215
206
  max-block-size: 100vh;
216
207
  block-size: 100vh;
217
- border-radius: var(--radius-2xl) 0 0 var(--radius-2xl);
208
+ border-radius: var(--radius-card) 0 0 var(--radius-card);
218
209
  transform: translateX(100%);
219
210
  }
220
211
 
@@ -232,7 +223,7 @@
232
223
  --_modal-width: min(90vw, 28rem);
233
224
  max-block-size: 100vh;
234
225
  block-size: 100vh;
235
- border-radius: 0 var(--radius-2xl) var(--radius-2xl) 0;
226
+ border-radius: 0 var(--radius-card) var(--radius-card) 0;
236
227
  transform: translateX(-100%);
237
228
  }
238
229
 
@@ -261,43 +252,6 @@
261
252
  }
262
253
 
263
254
 
264
- /* ========================================
265
- THEME-SPECIFIC MODAL STYLES
266
- ======================================== */
267
-
268
- /* Still: sharp, no animation */
269
- [data-motion="still"] .modal__content {
270
- border-radius: var(--radius-md);
271
- transform: none;
272
- transition: none;
273
- }
274
-
275
- [data-motion="still"] .modal {
276
- transition: none;
277
- }
278
-
279
- /* Serene: gentle, pillowy */
280
- [data-motion="serene"] .modal__content {
281
- border-radius: var(--radius-2xl);
282
- box-shadow: var(--shadow-lg);
283
- }
284
-
285
- /* Cascade: bouncy entry */
286
- [data-motion="cascade"] .modal__content {
287
- border-radius: var(--radius-2xl);
288
- }
289
-
290
- /* Rapids: hard offset shadow */
291
- [data-motion="rapids"] .modal__content {
292
- box-shadow: var(--shadow-offset-hover);
293
- }
294
-
295
- /* Tsunami: dramatic, asymmetric corners */
296
- [data-motion="tsunami"] .modal__content {
297
- border-radius: var(--radius-asymmetric-lg, 0 48px 0 0);
298
- box-shadow: var(--shadow-xl);
299
- }
300
-
301
255
 
302
256
  /* ========================================
303
257
  RESPONSIVE
@@ -312,7 +266,7 @@
312
266
  .modal__content {
313
267
  --_modal-width: 100%;
314
268
  max-block-size: 85vh;
315
- border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
269
+ border-radius: var(--radius-card) var(--radius-card) 0 0;
316
270
  transform: translateY(100%);
317
271
  }
318
272
 
@@ -332,7 +286,7 @@
332
286
  .modal[data-variant="drawer-left"] .modal__content {
333
287
  block-size: auto;
334
288
  max-block-size: 85vh;
335
- border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
289
+ border-radius: var(--radius-card) var(--radius-card) 0 0;
336
290
  transform: translateY(100%);
337
291
  }
338
292
 
@@ -348,14 +302,7 @@
348
302
  ======================================== */
349
303
 
350
304
  @media (prefers-reduced-motion: reduce) {
351
- .modal {
352
- transition: none;
353
- }
354
-
355
- .modal__content {
356
- transform: none;
357
- transition: none;
358
- }
305
+ .modal__content { transform: none; }
359
306
 
360
307
  .modal[open] .modal__content {
361
308
  transform: none;
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Fluid Framework - Navigation modes (opt-in module)
3
+ * @layer components
4
+ *
5
+ * dist/fluid-nav.min.css (package export "./nav"). Link it after core, as
6
+ * fluid-toc.min.css is. Its own file because core has no room for it under
7
+ * the 24,400 B gzip budget (spec _docs/plans/base-v2.md §11). The markup,
8
+ * the tokens and the default small-screen behaviour (squeeze, then balanced
9
+ * wrap) are in nav.css, in core; this file adds the modes a site opts into:
10
+ *
11
+ * data-nav="icon" the brand shrinks to its mark, the links share its row
12
+ * data-nav="compact" a "+" opens the links in a panel that slides over
13
+ * from the end edge, with a 25% backdrop; no JS
14
+ * data-nav="compact drop" the panel drops under the bar instead
15
+ * data-sticky="links" two rows: the brand row scrolls away, the links stick
16
+ *
17
+ * Without this file each falls back to the plain nav: the links wrap under
18
+ * the brand, and data-sticky="links" keeps the whole bar.
19
+ */
20
+
21
+ @layer components {
22
+ /* ========================================
23
+ ICON: data-nav="icon"
24
+ ======================================== */
25
+
26
+ .nav__brand-mark {
27
+ display: inline-grid;
28
+ place-items: center;
29
+ flex: none;
30
+ inline-size: 2.25rem;
31
+ block-size: 2.25rem;
32
+ font: 600 0.9375rem/1 var(--font-heading);
33
+ letter-spacing: 0.02em;
34
+ color: var(--on-accent-fill);
35
+ background: var(--accent-fill);
36
+ border-radius: var(--radius-control);
37
+ }
38
+
39
+ @container nav (max-width: 40rem) {
40
+ /* data-nav="icon": the name is read but not shown; the links share the
41
+ mark's row and hang at the end edge. */
42
+ .nav[data-nav~="icon"] .nav__brand-name {
43
+ position: absolute;
44
+ inline-size: 1px;
45
+ block-size: 1px;
46
+ overflow: hidden;
47
+ clip-path: inset(50%);
48
+ white-space: nowrap;
49
+ }
50
+
51
+ .nav[data-nav~="icon"]:not(:has(.nav__toggle)) .nav__menu {
52
+ --nav-wrap-align: end;
53
+ flex: 1 1 0;
54
+ order: 0;
55
+ min-inline-size: 0;
56
+ margin-inline-end: calc(-1 * var(--_nav-pad));
57
+ }
58
+ }
59
+
60
+
61
+ /* ========================================
62
+ COMPACT: data-nav="compact" (markup in nav.css). The links move into a
63
+ popover on a narrow bar. The panel wrapper stays invisible to layout
64
+ (nav.css) on wide bars and without popover support, so the links
65
+ behave as usual there.
66
+ ======================================== */
67
+
68
+ /* Outside the container query: a nav cannot style itself from its own query. */
69
+ .nav[data-nav~="drop"] { anchor-name: --fluid-nav; }
70
+
71
+ @supports selector(:popover-open) {
72
+ @container nav (max-width: 40rem) {
73
+ .nav[data-nav~="compact"] .nav__more {
74
+ display: inline-grid;
75
+ place-items: center;
76
+ min-inline-size: 2.75rem;
77
+ min-block-size: 2.75rem;
78
+ padding: 0;
79
+ color: var(--nav-ink, var(--color-text));
80
+ background: none;
81
+ border: 0;
82
+ border-radius: var(--radius-control);
83
+ cursor: pointer;
84
+ }
85
+
86
+ .nav[data-nav~="compact"] > .nav__more {
87
+ order: 2;
88
+ margin-inline: auto calc(-1 * var(--space-2));
89
+ }
90
+
91
+ .nav[data-nav~="compact"]:has(.nav__actions) > .nav__more { margin-inline-start: 0; }
92
+
93
+ /* One row: the brand gives way (and wraps) before the + would. */
94
+ .nav[data-nav~="compact"] > .nav__brand {
95
+ flex: 1 1 0;
96
+ min-inline-size: 0;
97
+ }
98
+
99
+ /* The glyph is a character of the text face, so it matches the links. */
100
+ .nav[data-nav~="compact"] .nav__more::before {
101
+ content: var(--nav-glyph, "+"); /* browsers without alt text for content */
102
+ content: var(--nav-glyph, "+") / "";
103
+ font: 300 2rem/1 var(--font-body);
104
+ transition: rotate var(--duration-normal) var(--ease-fluid);
105
+ }
106
+
107
+ .nav[data-nav~="compact"]:has(.nav__panel:popover-open) .nav__more::before,
108
+ .nav[data-nav~="compact"] .nav__panel .nav__more::before {
109
+ rotate: var(--nav-glyph-turn, 45deg);
110
+ }
111
+
112
+ .nav[data-nav~="compact"] .nav__panel[popover] {
113
+ display: none;
114
+ position: fixed;
115
+ inset: 0 0 0 auto;
116
+ inline-size: var(--nav-panel-size, min(20rem, 85vw));
117
+ block-size: 100dvh;
118
+ max-block-size: none;
119
+ margin: 0;
120
+ padding: var(--space-s) var(--page-edge) var(--space-l);
121
+ flex-direction: column;
122
+ color: var(--nav-ink, var(--color-text));
123
+ background: var(--nav-bg, var(--color-background));
124
+ border: 0;
125
+ border-inline-start: var(--border-width) solid var(--color-border);
126
+ overflow: auto;
127
+ translate: 100% 0;
128
+ transition:
129
+ translate var(--duration-normal) var(--ease-fluid),
130
+ display var(--duration-normal) allow-discrete,
131
+ overlay var(--duration-normal) allow-discrete;
132
+ }
133
+
134
+ .nav[data-nav~="compact"] .nav__panel:popover-open {
135
+ display: flex;
136
+ translate: 0 0;
137
+ }
138
+
139
+ .nav[data-nav~="compact"] .nav__panel::backdrop {
140
+ background: oklch(0% 0 0 / 0);
141
+ transition:
142
+ background var(--duration-normal) var(--ease-fluid),
143
+ display var(--duration-normal) allow-discrete,
144
+ overlay var(--duration-normal) allow-discrete;
145
+ }
146
+
147
+ .nav[data-nav~="compact"] .nav__panel:popover-open::backdrop {
148
+ background: oklch(0% 0 0 / 0.25);
149
+ }
150
+
151
+ @starting-style {
152
+ .nav[data-nav~="compact"] .nav__panel:popover-open { translate: 100% 0; }
153
+ .nav[data-nav~="compact"] .nav__panel:popover-open .nav__more::before { rotate: 0deg; }
154
+ .nav[data-nav~="compact"] .nav__panel:popover-open::backdrop { background: oklch(0% 0 0 / 0); }
155
+ }
156
+
157
+ /* In the panel: the close × sits where the + was, the links stack at
158
+ full size, without the bar's squeeze. */
159
+ .nav[data-nav~="compact"] .nav__panel > .nav__more {
160
+ align-self: flex-end;
161
+ margin-inline-end: calc(-1 * var(--space-2));
162
+ margin-block-end: var(--space-m);
163
+ }
164
+
165
+ .nav[data-nav~="compact"] .nav__panel .nav__menu {
166
+ --_nav-track: initial;
167
+ display: flex;
168
+ flex-direction: column;
169
+ align-items: stretch;
170
+ flex-basis: auto;
171
+ margin: 0;
172
+ }
173
+
174
+ .nav[data-nav~="compact"] .nav__panel .nav__menu > li {
175
+ display: block;
176
+ border-block-start: var(--border-width) solid var(--color-border);
177
+ }
178
+
179
+ .nav[data-nav~="compact"] .nav__panel .nav__link {
180
+ min-block-size: 3.25rem;
181
+ padding-inline: 0;
182
+ font-size: var(--step-1);
183
+ }
184
+
185
+ /* Drop: the panel hangs under the bar and fades in; the bar's + turns
186
+ to × and stays the toggle. Needs anchor positioning; without it the
187
+ panel covers the bar from the top and shows its own ×. */
188
+ .nav[data-nav~="drop"] .nav__panel[popover] {
189
+ inset: 0 0 auto 0;
190
+ inline-size: auto;
191
+ block-size: auto;
192
+ max-block-size: 100dvh;
193
+ padding-block: 0 var(--space-m);
194
+ border: 0;
195
+ border-block-end: var(--border-width) solid var(--color-border);
196
+ translate: 0 0;
197
+ opacity: 0;
198
+ transition:
199
+ opacity var(--duration-normal) var(--ease-fluid),
200
+ translate var(--duration-normal) var(--ease-fluid),
201
+ display var(--duration-normal) allow-discrete,
202
+ overlay var(--duration-normal) allow-discrete;
203
+ }
204
+
205
+ .nav[data-nav~="drop"] .nav__panel:popover-open { opacity: 1; }
206
+
207
+ @starting-style {
208
+ .nav[data-nav~="drop"] .nav__panel:popover-open { opacity: 0; translate: 0 -0.5rem; }
209
+ }
210
+
211
+ @supports (anchor-name: --a) {
212
+ .nav[data-nav~="drop"] .nav__panel[popover] {
213
+ position-anchor: --fluid-nav;
214
+ inset: anchor(bottom) 0 auto 0;
215
+ max-block-size: calc(100dvh - anchor-size(height));
216
+ }
217
+ .nav[data-nav~="drop"] .nav__panel > .nav__more { display: none; }
218
+ }
219
+
220
+ .nav[data-nav~="drop"] .nav__panel:is(*, :popover-open)::backdrop { background: transparent; }
221
+ }
222
+ }
223
+
224
+ /* Reduced motion: no slide or turn (the reset already cuts transition
225
+ durations to near zero). */
226
+ @media (prefers-reduced-motion: reduce) {
227
+ .nav[data-nav~="compact"] .nav__panel[popover] { translate: none; }
228
+ @starting-style {
229
+ .nav[data-nav~="compact"] .nav__panel:popover-open { translate: none; }
230
+ }
231
+ }
232
+
233
+
234
+ /* ========================================
235
+ STICKY LINKS: data-sticky="links". data-sticky keeps the whole bar;
236
+ this pulls the bar up by its brand row once the links have a row of
237
+ their own, so only the links stay. A nav cannot query its own width,
238
+ so this uses the viewport: below 46rem the links always take their own
239
+ row in this mode, so the offset never hides a one-row bar.
240
+ ======================================== */
241
+
242
+ .nav[data-sticky="links"] {
243
+ --nav-stick-offset: 0px;
244
+ inset-block-start: calc(-1 * var(--nav-stick-offset));
245
+ }
246
+
247
+ /* A fixed brand row height, so the offset is exact. */
248
+ .nav[data-sticky="links"] .nav__brand { min-block-size: 2.75rem; }
249
+
250
+ @media (width < 46rem) {
251
+ .nav[data-sticky="links"]:not([data-nav~="icon"], [data-nav~="compact"]) {
252
+ /* top padding + brand row (a 2.75rem control) + the row gap */
253
+ --nav-stick-offset: calc(var(--space-s) + 2.75rem + var(--space-2));
254
+ }
255
+
256
+ .nav[data-sticky="links"]:not([data-nav~="icon"], [data-nav~="compact"]) .nav__menu {
257
+ flex-basis: 100%;
258
+ order: 3;
259
+ }
260
+ }
261
+ }