maverick-wave 6.5.0 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "maverick-wave",
3
- "version": "6.5.0",
3
+ "version": "6.7.0",
4
4
  "config": {
5
- "version_short": "6.5"
5
+ "version_short": "6.7"
6
6
  },
7
7
  "description": "A lightweight, modern CSS framework for building responsive websites with elegance and speed.",
8
8
  "main": "src/js/main.js",
@@ -352,14 +352,20 @@ $_corner-opaque: 55%;
352
352
  );
353
353
  }
354
354
 
355
- // The arc alone, for a variant that redraws it without restating the shape
356
- @mixin corner-accent($border-width: 1px, $corners: 'both') {
355
+ // The arc alone, for a variant that redraws it without restating the shape.
356
+ // The radii are only for a box too small to carry the surface's 20px.
357
+ @mixin corner-accent(
358
+ $border-width: 1px,
359
+ $corners: 'both',
360
+ $sharp-radius: radius-sharp(),
361
+ $round-radius: radius('xl')
362
+ ) {
357
363
  // max(), not a bare subtraction: --mw-radius-scale can take the radius to 0,
358
364
  // and while a negative border-radius is clamped for us, a negative circle in
359
365
  // the mask below would throw the whole gradient away and lose the arc.
360
366
  $width: _accent-width($border-width);
361
- $sharp: max(0px, calc(#{radius-sharp()} - #{$width}));
362
- $round: max(0px, calc(#{radius('xl')} - #{$width}));
367
+ $sharp: max(0px, calc(#{$sharp-radius} - #{$width}));
368
+ $round: max(0px, calc(#{$round-radius} - #{$width}));
363
369
  // Opaque to where the curve ends, fading from there, so the arc runs into the
364
370
  // straight edge instead of stopping at the tangent. 1.8 is bounded by the
365
371
  // shortest surface: past about 2 the two arcs meet on a 50px box and read as
@@ -426,10 +426,25 @@ $header-colors: (
426
426
  'burgerbutton-color': token('primary-accent-text-color'),
427
427
  'burgerbutton-open-color': token('secondary-accent-text-color'),
428
428
  'border': fade('header-navbar-list-active-color', 40%),
429
+ // Translucent enough for the blur to show, dense enough that the light ink
430
+ // still reads over a white page
431
+ 'glass': fade('header-background', 88%),
432
+ 'island-border': fade('header-text-color', 13%),
433
+ 'navbar-mark': fade('header-navbar-list-active-color', 16%),
429
434
  );
430
- // Drives bar height, anchor scroll offset and drawer padding. Intrinsic height is
431
- // 59px: 42px logo + 2 × spacing('3') + 1px border.
432
- $header-height: 62px;
435
+ // The room the header takes from the top of the viewport - island plus the gap
436
+ // above it - so every offset that reads it clears the island without knowing it
437
+ // floats. The island itself is 60px: a 44px touch control + 2 × spacing('2') +
438
+ // 2px border, with room to spare around the 42px logo.
439
+ $header-height: 74px;
440
+ // Between the island and the viewport edge
441
+ $header-inset: 14px;
442
+ // How far the island reaches past the page container on either side, and in
443
+ // from the inset where that is closer: room for the links, at the price of the
444
+ // logo standing that much left of the text below it
445
+ $header-island-reach: 5px;
446
+ // The flat bar under mw-header-bar, at the height it had before the island
447
+ $header-bar-height: 62px;
433
448
 
434
449
  // Here and not next to the typography rules, because this is the file
435
450
  // `@use 'main' with (...)` configures. No font is bundled.
@@ -131,6 +131,7 @@
131
131
 
132
132
  // header height
133
133
  --mw-header-height: #{$header-height};
134
+ --mw-header-inset: #{$header-inset};
134
135
 
135
136
  // Read by the anchor offset of anything the sticky section nav can cover.
136
137
  // The strip at its tallest: a 2.75rem button under a thumb plus its two
@@ -148,6 +149,9 @@
148
149
  --mw-announcement-offset: 0px;
149
150
  --mw-section-nav-offset: 0px;
150
151
 
152
+ // On :root, so an item, an accordion or the page can set it; `none` lifts the cap
153
+ --mw-accordion-max-height: 500px;
154
+
151
155
  // Fluid rather than stepped: ~24px at 375px up to 64px, where the 1200px cap
152
156
  // takes over. Stepping at a breakpoint makes the container jump *backwards*
153
157
  // there, because the gutter grows faster than the viewport does.
@@ -136,7 +136,7 @@
136
136
 
137
137
  @include _open-content {
138
138
  grid-template-rows: 1fr;
139
- max-height: 500px;
139
+ max-height: var(--mw-accordion-max-height);
140
140
  overflow-y: auto;
141
141
 
142
142
  > .mw-accordion-content-inner {
@@ -54,6 +54,8 @@
54
54
  .mw-announcement-static {
55
55
  // Relative for the close button, without a z-index lifting it over nearby menus
56
56
  position: relative;
57
+ // The fixed ribbon's offsets would shift a relative one by the header height
58
+ inset: auto;
57
59
  z-index: auto;
58
60
  }
59
61
 
@@ -11,6 +11,12 @@
11
11
  background: var(--mw-warning-color);
12
12
  z-index: z-index('modal')-10;
13
13
  animation: mw-pulse-indicator motion(4s) infinite;
14
+
15
+ // Clear of the island's rounded corners
16
+ .mw-header > & {
17
+ inset-inline: radius('xl');
18
+ width: auto;
19
+ }
14
20
  }
15
21
 
16
22
  // Keyframes for the pulsing indicator
@@ -1,172 +1,222 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
+ // Short enough that the bar is the island by the time the hero's headline is gone
4
+ $dock-range: 160px;
5
+
3
6
  // Carried by the float and not by the class, or the ring would still be there in
4
7
  // the arrived bar. Fixed values and not the elevation ramp: this lands on a
5
8
  // picture, not on a theme surface, and the light theme cuts its shadows for paper.
6
9
  $float-edge: (0 0 0 1px rgb(255 255 255 / 0.22), 0 4px 14px rgb(0 0 0 / 0.4));
7
10
 
8
11
  @layer mw.layout {
9
- // The bar starts above the screen and rides in over the first stretch of scroll.
10
- // Its own file, and not next to .mw-header: that one uses the `animation`
11
- // shorthand, which a consumer bundler folds together with a timeline into a
12
- // declaration no browser accepts.
13
- @supports (animation-timeline: scroll()) {
14
- .mw-header-reveal {
15
- @include motion-safe {
16
- animation-name: mw-header-reveal;
17
- animation-timing-function: var(--mw-ease-out);
18
- animation-fill-mode: both;
19
- animation-timeline: scroll(root);
20
- animation-range: 0 420px;
12
+ // None of it on the flat bar, which stands where it is from the first frame
13
+ :root:not(.mw-header-bar) {
14
+ // A header that stays put starts as the full bar and docks into the island over
15
+ // the first stretch of scroll. A revealing one is off screen up there anyway and
16
+ // arrives as the island directly. Without scroll timelines it is the island
17
+ // from the start. The card under the island docks with it, so a menu opened at
18
+ // the top hangs from the bar edge to edge like the bar itself.
19
+ @supports (animation-timeline: scroll()) {
20
+ .mw-header:not(.mw-header-reveal) {
21
+ @include motion-safe {
22
+ animation-name: mw-header-dock;
23
+ animation-timing-function: var(--mw-ease-out);
24
+ animation-fill-mode: both;
25
+ animation-timeline: scroll(root);
26
+ animation-range: 0 $dock-range;
27
+ }
28
+
29
+ // In the row the nav is static and has no radius, so the same keyframes
30
+ // pass over it without a trace
31
+ .mw-navbar {
32
+ @include motion-safe {
33
+ animation-name: mw-header-dock-sheet;
34
+ animation-timing-function: var(--mw-ease-out);
35
+ animation-fill-mode: both;
36
+ animation-timeline: scroll(root);
37
+ animation-range: 0 $dock-range;
38
+ }
39
+ }
21
40
  }
22
- }
23
41
 
24
- // The bar travels, a keep element travels the same distance back and stays
25
- // put - for a burger that has to be reachable while the hero is untouched.
26
- .mw-header-reveal .mw-header-keep {
27
- @include motion-safe {
28
- animation-name: mw-header-keep;
29
- animation-timing-function: var(--mw-ease-out);
30
- animation-fill-mode: both;
31
- animation-timeline: scroll(root);
32
- animation-range: 0 420px;
42
+ // The ribbon hangs from the island, so it docks with it
43
+ body:not(:has(.mw-header-reveal))
44
+ .mw-announcement:not(.mw-announcement-static) {
45
+ @include motion-safe {
46
+ animation-name: mw-announcement-dock;
47
+ animation-timing-function: var(--mw-ease-out);
48
+ animation-fill-mode: both;
49
+ animation-timeline: scroll(root);
50
+ animation-range: 0 $dock-range;
51
+ }
33
52
  }
34
53
  }
35
54
 
36
- // A group's opacity cannot be taken back by a child, so the fade goes as
37
- // soon as something is meant to stay visible through it.
38
- .mw-header-reveal:has(.mw-header-keep) {
39
- @include motion-safe {
40
- animation-name: mw-header-slide;
55
+ // The bar starts above the screen and rides in over the first stretch of scroll.
56
+ // Its own file, and not next to .mw-header: that one uses the `animation`
57
+ // shorthand, which a consumer bundler folds together with a timeline into a
58
+ // declaration no browser accepts.
59
+ @supports (animation-timeline: scroll()) {
60
+ .mw-header-reveal {
61
+ @include motion-safe {
62
+ animation-name: mw-header-reveal;
63
+ animation-timing-function: var(--mw-ease-out);
64
+ animation-fill-mode: both;
65
+ animation-timeline: scroll(root);
66
+ animation-range: 0 420px;
67
+ }
41
68
  }
42
- }
43
69
 
44
- // Glued under the bar, so it leaves and arrives with it. No class of its
45
- // own: a ribbon under a revealing header has no other sensible behaviour.
46
- body:has(.mw-header-reveal) .mw-announcement:not(.mw-announcement-static) {
47
- @include motion-safe {
48
- animation-name: mw-announcement-reveal;
49
- animation-timing-function: var(--mw-ease-out);
50
- animation-fill-mode: both;
51
- animation-timeline: scroll(root);
52
- animation-range: 0 420px;
70
+ // The bar travels, a keep element travels the same distance back and stays
71
+ // put - for a burger that has to be reachable while the hero is untouched.
72
+ .mw-header-reveal .mw-header-keep {
73
+ @include motion-safe {
74
+ animation-name: mw-header-keep;
75
+ animation-timing-function: var(--mw-ease-out);
76
+ animation-fill-mode: both;
77
+ animation-timeline: scroll(root);
78
+ animation-range: 0 420px;
79
+ }
53
80
  }
54
- }
55
- }
56
81
 
57
- // Both cover the height of the pair, or the shorter traveller arrives first and
58
- // the two come in as two things instead of one block.
59
- .mw-header-reveal {
60
- --mw-header-reveal-offset: var(--mw-header-height);
61
- }
82
+ // A group's opacity cannot be taken back by a child, so the fade goes as
83
+ // soon as something is meant to stay visible through it.
84
+ .mw-header-reveal:has(.mw-header-keep) {
85
+ @include motion-safe {
86
+ animation-name: mw-header-slide;
87
+ }
88
+ }
62
89
 
63
- body:has(.mw-announcement:not(.mw-announcement-static)) .mw-header-reveal {
64
- --mw-header-reveal-offset: calc(
65
- var(--mw-header-height) + var(--mw-announcement-height)
66
- );
67
- }
90
+ // Glued under the bar, so it leaves and arrives with it. No class of its
91
+ // own: a ribbon under a revealing header has no other sensible behaviour.
92
+ body:has(.mw-header-reveal)
93
+ .mw-announcement:not(.mw-announcement-static) {
94
+ @include motion-safe {
95
+ animation-name: mw-announcement-reveal;
96
+ animation-timing-function: var(--mw-ease-out);
97
+ animation-fill-mode: both;
98
+ animation-timeline: scroll(root);
99
+ animation-range: 0 420px;
100
+ }
101
+ }
102
+ }
68
103
 
69
- // A focus ring on something off screen is WCAG 2.4.11, so focus brings the bar
70
- // back. `!important` is the one author declaration that outranks a running
71
- // animation; `animation-name: none` would strand the element on its last value
72
- // until the next scroll. Keeps are left out - they are on screen already, and a
73
- // click on the theme toggle would otherwise haul in the whole bar. `:focus` on a
74
- // descendant and not `:focus-within`: that matches every wrapper too.
75
- body:has(
76
- .mw-header-reveal :focus:not(.mw-header-keep, .mw-header-keep *),
77
- .mw-announcement:focus-within
78
- ) {
104
+ // Both cover the height of the pair, or the shorter traveller arrives first and
105
+ // the two come in as two things instead of one block.
79
106
  .mw-header-reveal {
80
- top: 0 !important;
81
- opacity: 1 !important;
107
+ --mw-header-reveal-offset: var(--mw-header-height);
82
108
  }
83
109
 
84
- .mw-header-keep {
85
- translate: none !important;
86
- box-shadow: none !important;
110
+ body:has(.mw-announcement:not(.mw-announcement-static)) .mw-header-reveal {
111
+ --mw-header-reveal-offset: calc(
112
+ var(--mw-header-height) + var(--mw-announcement-height)
113
+ );
87
114
  }
88
115
 
89
- .mw-announcement:not(.mw-announcement-static) {
90
- transform: none !important;
91
- opacity: 1 !important;
92
- }
93
- }
116
+ // A focus ring on something off screen is WCAG 2.4.11, so focus brings the bar
117
+ // back. `!important` is the one author declaration that outranks a running
118
+ // animation; `animation-name: none` would strand the element on its last value
119
+ // until the next scroll. Keeps are left out - they are on screen already, and a
120
+ // click on the theme toggle would otherwise haul in the whole bar. `:focus` on a
121
+ // descendant and not `:focus-within`: that matches every wrapper too.
122
+ body:has(
123
+ .mw-header-reveal :focus:not(.mw-header-keep, .mw-header-keep *),
124
+ .mw-announcement:focus-within
125
+ ) {
126
+ .mw-header-reveal {
127
+ top: var(--mw-header-inset) !important;
128
+ opacity: 1 !important;
129
+ }
94
130
 
95
- // The panel hangs off the bar, so the bar has to be in place - and the burger,
96
- // which is the X while the panel is out, has to stay on its own position.
97
- //
98
- // A class the script sets, not `:has(.mw-navbar.mw-active)`: the burger is the
99
- // control that closes the panel and cannot wait for a selector re-match, which
100
- // is where WebKit's invalidation has been unreliable.
101
- //
102
- // Zeroing the distance rather than overriding each position that reads it: four
103
- // mechanisms carry the pair - `top` and `translate`, each once as a class and
104
- // once as a keyframe - and all four resolve this one property. Overriding the
105
- // values instead pits `!important` against a running animation, which WebKit
106
- // does not reliably hand to the author.
107
- body.mw-nav-open {
108
- .mw-header-reveal {
109
- // The ribbon variant above raises the same property from a `:has()`, which
110
- // counts its argument and outranks this rule - author against author, which
111
- // every engine resolves the same way, not author against an animation.
112
- --mw-header-reveal-offset: 0px !important;
113
- opacity: 1 !important;
114
- }
131
+ .mw-header-keep {
132
+ translate: none !important;
133
+ box-shadow: none !important;
134
+ }
115
135
 
116
- // Not carried by the offset, and there is no picture behind an open panel.
117
- .mw-header-keep {
118
- box-shadow: none !important;
136
+ .mw-announcement:not(.mw-announcement-static) {
137
+ transform: none !important;
138
+ opacity: 1 !important;
139
+ }
119
140
  }
120
- }
121
-
122
- // No scroll timelines: the script toggles these classes instead (main.js).
123
- // State and transition are separate because the script sets the state a frame
124
- // earlier - together the bar would slide away in front of the reader.
125
- .mw-header-reveal.mw-header-away {
126
- @include motion-safe {
127
- top: calc(-1 * var(--mw-header-reveal-offset));
128
- opacity: 0;
129
141
 
130
- &:has(.mw-header-keep) {
131
- opacity: 1;
142
+ // The panel hangs off the bar, so the bar has to be in place - and the burger,
143
+ // which is the X while the panel is out, has to stay on its own position.
144
+ //
145
+ // A class the script sets, not `:has(.mw-navbar.mw-active)`: the burger is the
146
+ // control that closes the panel and cannot wait for a selector re-match, which
147
+ // is where WebKit's invalidation has been unreliable.
148
+ //
149
+ // Zeroing the distance rather than overriding each position that reads it: four
150
+ // mechanisms carry the pair - `top` and `translate`, each once as a class and
151
+ // once as a keyframe - and all four resolve this one property. Overriding the
152
+ // values instead pits `!important` against a running animation, which WebKit
153
+ // does not reliably hand to the author.
154
+ body.mw-nav-open {
155
+ .mw-header-reveal {
156
+ // The ribbon variant above raises the same property from a `:has()`, which
157
+ // counts its argument and outranks this rule - author against author, which
158
+ // every engine resolves the same way, not author against an animation.
159
+ --mw-header-reveal-offset: 0px !important;
160
+ opacity: 1 !important;
132
161
  }
133
162
 
163
+ // Not carried by the offset, and there is no picture behind an open panel.
134
164
  .mw-header-keep {
135
- translate: 0 var(--mw-header-reveal-offset);
136
- box-shadow: $float-edge;
165
+ box-shadow: none !important;
137
166
  }
138
167
  }
139
- }
140
168
 
141
- .mw-header-driven {
142
- @include motion-safe {
143
- // The shared list plus `top`: writing only `top` drops the bar's own.
144
- transition:
145
- var(--mw-transition),
146
- top var(--mw-duration-slow) var(--mw-ease-out);
169
+ // No scroll timelines: the script toggles these classes instead (main.js).
170
+ // State and transition are separate because the script sets the state a frame
171
+ // earlier - together the bar would slide away in front of the reader.
172
+ .mw-header-reveal.mw-header-away {
173
+ @include motion-safe {
174
+ top: calc(var(--mw-header-inset) - var(--mw-header-reveal-offset));
175
+ opacity: 0;
147
176
 
148
- .mw-header-keep {
177
+ // Visible through the slide, so its shadow would hang into the hero
178
+ &:has(.mw-header-keep) {
179
+ opacity: 1;
180
+ box-shadow: none;
181
+ }
182
+
183
+ .mw-header-keep {
184
+ translate: 0 var(--mw-header-reveal-offset);
185
+ box-shadow: $float-edge;
186
+ }
187
+ }
188
+ }
189
+
190
+ .mw-header-driven {
191
+ @include motion-safe {
192
+ // The shared list plus `top`: writing only `top` drops the bar's own.
149
193
  transition:
150
- translate var(--mw-duration-slow) var(--mw-ease-out),
151
- box-shadow var(--mw-duration-slow) var(--mw-ease-out);
194
+ var(--mw-transition),
195
+ top var(--mw-duration-slow) var(--mw-ease-out);
196
+
197
+ .mw-header-keep {
198
+ transition:
199
+ translate var(--mw-duration-slow) var(--mw-ease-out),
200
+ box-shadow var(--mw-duration-slow) var(--mw-ease-out);
201
+ }
152
202
  }
153
203
  }
154
- }
155
204
 
156
- .mw-announcement.mw-announcement-away {
157
- @include motion-safe {
158
- transform: translateY(
159
- calc(-1 * (var(--mw-header-height) + var(--mw-announcement-height)))
160
- );
161
- opacity: 0;
205
+ .mw-announcement.mw-announcement-away {
206
+ @include motion-safe {
207
+ transform: translateY(
208
+ calc(-1 * (var(--mw-header-height) + var(--mw-announcement-height)))
209
+ );
210
+ opacity: 0;
211
+ }
162
212
  }
163
- }
164
213
 
165
- .mw-announcement-driven {
166
- @include motion-safe {
167
- transition:
168
- transform var(--mw-duration-slow) var(--mw-ease-out),
169
- opacity var(--mw-duration-slow) var(--mw-ease-out);
214
+ .mw-announcement-driven {
215
+ @include motion-safe {
216
+ transition:
217
+ transform var(--mw-duration-slow) var(--mw-ease-out),
218
+ opacity var(--mw-duration-slow) var(--mw-ease-out);
219
+ }
170
220
  }
171
221
  }
172
222
 
@@ -177,25 +227,63 @@ $float-edge: (0 0 0 1px rgb(255 255 255 / 0.22), 0 4px 14px rgb(0 0 0 / 0.4));
177
227
  @keyframes mw-header-reveal {
178
228
  0%,
179
229
  30% {
180
- top: calc(-1 * var(--mw-header-reveal-offset));
230
+ top: calc(var(--mw-header-inset) - var(--mw-header-reveal-offset));
181
231
  opacity: 0;
182
232
  }
183
233
 
184
234
  100% {
185
- top: 0;
235
+ top: var(--mw-header-inset);
186
236
  opacity: 1;
187
237
  }
188
238
  }
189
239
 
190
- // The same ride without the fade, for a bar something stays visible through
240
+ // The same ride without the fade, for a bar something stays visible through -
241
+ // and without the shadow while it is up there, or it hangs into the hero
191
242
  @keyframes mw-header-slide {
192
243
  0%,
193
244
  30% {
194
- top: calc(-1 * var(--mw-header-reveal-offset));
245
+ top: calc(var(--mw-header-inset) - var(--mw-header-reveal-offset));
246
+ box-shadow: none;
195
247
  }
196
248
 
197
249
  100% {
250
+ top: var(--mw-header-inset);
251
+ }
252
+ }
253
+
254
+ // Only the start: the end is the island the header already is
255
+ @keyframes mw-header-dock {
256
+ from {
198
257
  top: 0;
258
+ left: 0;
259
+ right: 0;
260
+ min-height: var(--mw-header-height);
261
+ // Percentages of the viewport here, so the content box is the page
262
+ // container in both states and the logo does not move sideways
263
+ padding-inline: calc(
264
+ (100% - var(--mw-container-width)) / 2 - #{$header-island-reach}
265
+ );
266
+ border-radius: 0;
267
+ border-color: transparent transparent var(--mw-header-border);
268
+ background-color: var(--mw-header-background);
269
+ box-shadow: none;
270
+ }
271
+ }
272
+
273
+ @keyframes mw-header-dock-sheet {
274
+ from {
275
+ top: 100%;
276
+ border-radius: 0;
277
+ }
278
+ }
279
+
280
+ @keyframes mw-announcement-dock {
281
+ from {
282
+ left: 0;
283
+ right: 0;
284
+ border-radius: 0;
285
+ border-color: transparent;
286
+ box-shadow: none;
199
287
  }
200
288
  }
201
289