maverick-wave 6.6.0 → 6.8.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/README.md +2 -2
- package/maverick-wave.min.css +10 -10
- package/package.json +2 -2
- package/src/scss/abstracts/_functions.scss +26 -3
- package/src/scss/abstracts/_mixins.scss +19 -4
- package/src/scss/abstracts/_variables.scss +26 -5
- package/src/scss/base/_base.scss +1 -0
- package/src/scss/components/_action-bar.scss +29 -0
- package/src/scss/components/_announcement.scss +2 -0
- package/src/scss/components/_code.scss +1 -1
- package/src/scss/components/_localhost-indicator.scss +6 -0
- package/src/scss/components/_techstack-bucket.scss +3 -3
- package/src/scss/layout/_header-reveal.scss +92 -6
- package/src/scss/layout/_header.scss +263 -107
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maverick-wave",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.8.0",
|
|
4
4
|
"config": {
|
|
5
|
-
"version_short": "6.
|
|
5
|
+
"version_short": "6.8"
|
|
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",
|
|
@@ -114,12 +114,21 @@
|
|
|
114
114
|
// rather than off the end.
|
|
115
115
|
$_radius-floor: map.get(v.$radius, 'xs');
|
|
116
116
|
|
|
117
|
+
// The sharp half of the signature on a value of its own, so the scale can round
|
|
118
|
+
// controls up without softening the one corner meant to read as sharp
|
|
119
|
+
$_radius-sharp-base: 5px;
|
|
120
|
+
|
|
117
121
|
// Emitted through --mw-radius-scale, so one number retunes every rounded box in
|
|
118
122
|
// the framework at once - 0 squares them off, above 1 rounds them further, and
|
|
119
123
|
// the corner arc follows. 'none' and 'full' pass through: an explicit 0 stays
|
|
120
|
-
// 0, and a circle has to stay a circle.
|
|
124
|
+
// 0, and a circle has to stay a circle. A length instead of a key takes the
|
|
125
|
+
// same scale and floor, for the few values that sit off the scale.
|
|
121
126
|
@function radius($key) {
|
|
122
|
-
$value:
|
|
127
|
+
$value: $key;
|
|
128
|
+
|
|
129
|
+
@if meta.type-of($key) != 'number' {
|
|
130
|
+
$value: map.get(v.$radius, $key);
|
|
131
|
+
}
|
|
123
132
|
|
|
124
133
|
@if $key == 'none' or $key == 'full' {
|
|
125
134
|
@return $value;
|
|
@@ -137,7 +146,21 @@ $_radius-floor: map.get(v.$radius, 'xs');
|
|
|
137
146
|
|
|
138
147
|
@return max(
|
|
139
148
|
#{$_radius-floor},
|
|
140
|
-
calc(#{
|
|
149
|
+
calc(#{$_radius-sharp-base} * #{$scale} * #{$scale})
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Where a floating island sits: percentages on a fixed box are the viewport's,
|
|
154
|
+
// so this puts the header's content box on the page container,
|
|
155
|
+
// $header-island-reach wider on either side - and the bar at the foot on the
|
|
156
|
+
// same edges. Logo and links stay put while the header docks around them.
|
|
157
|
+
@function island-edge() {
|
|
158
|
+
@return max(
|
|
159
|
+
calc(var(--mw-header-inset) - #{v.$header-island-reach}),
|
|
160
|
+
calc(
|
|
161
|
+
(100% - var(--mw-container-width)) / 2 - var(--mw-header-pad) -
|
|
162
|
+
#{v.$header-island-reach}
|
|
163
|
+
)
|
|
141
164
|
);
|
|
142
165
|
}
|
|
143
166
|
|
|
@@ -276,6 +276,15 @@
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
// The floating chrome, shared by the header island and the action bar island
|
|
280
|
+
@mixin island-surface {
|
|
281
|
+
background: var(--mw-header-glass);
|
|
282
|
+
backdrop-filter: blur(20px) saturate(1.6);
|
|
283
|
+
border: 1px solid var(--mw-header-island-border);
|
|
284
|
+
border-radius: radius('xl');
|
|
285
|
+
box-shadow: var(--mw-elevation-4);
|
|
286
|
+
}
|
|
287
|
+
|
|
279
288
|
// The MaverickWave silhouette: sharp corners on one diagonal, round on the other,
|
|
280
289
|
// with an accent arc on the round pair - the asymmetry alone reads as an accident.
|
|
281
290
|
//
|
|
@@ -352,14 +361,20 @@ $_corner-opaque: 55%;
|
|
|
352
361
|
);
|
|
353
362
|
}
|
|
354
363
|
|
|
355
|
-
// The arc alone, for a variant that redraws it without restating the shape
|
|
356
|
-
|
|
364
|
+
// The arc alone, for a variant that redraws it without restating the shape.
|
|
365
|
+
// The radii are only for a box too small to carry the surface's 20px.
|
|
366
|
+
@mixin corner-accent(
|
|
367
|
+
$border-width: 1px,
|
|
368
|
+
$corners: 'both',
|
|
369
|
+
$sharp-radius: radius-sharp(),
|
|
370
|
+
$round-radius: radius('xl')
|
|
371
|
+
) {
|
|
357
372
|
// max(), not a bare subtraction: --mw-radius-scale can take the radius to 0,
|
|
358
373
|
// and while a negative border-radius is clamped for us, a negative circle in
|
|
359
374
|
// the mask below would throw the whole gradient away and lose the arc.
|
|
360
375
|
$width: _accent-width($border-width);
|
|
361
|
-
$sharp: max(0px, calc(#{radius
|
|
362
|
-
$round: max(0px, calc(#{radius
|
|
376
|
+
$sharp: max(0px, calc(#{$sharp-radius} - #{$width}));
|
|
377
|
+
$round: max(0px, calc(#{$round-radius} - #{$width}));
|
|
363
378
|
// Opaque to where the curve ends, fading from there, so the arc runs into the
|
|
364
379
|
// straight edge instead of stopping at the tangent. 1.8 is bounded by the
|
|
365
380
|
// 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
|
-
//
|
|
431
|
-
//
|
|
432
|
-
|
|
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 63px: a 44px touch control + 2 × spacing('2') +
|
|
438
|
+
// 2px border, with room to spare around the 42px logo.
|
|
439
|
+
$header-height: 77px;
|
|
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.
|
|
@@ -522,14 +537,20 @@ $breakpoints: (
|
|
|
522
537
|
$radius: (
|
|
523
538
|
'none': 0,
|
|
524
539
|
'xs': 2px,
|
|
525
|
-
'sm':
|
|
526
|
-
'md':
|
|
540
|
+
'sm': 7px,
|
|
541
|
+
'md': 12px,
|
|
527
542
|
'lg': 15px,
|
|
528
543
|
'xl': 20px,
|
|
529
544
|
'2xl': 30px,
|
|
530
545
|
'full': 50%,
|
|
531
546
|
);
|
|
532
547
|
|
|
548
|
+
// Off the scale on purpose: a chip next to a tag stays tighter than the buttons
|
|
549
|
+
// and fields it would otherwise be mistaken for. Outside the map, or every key
|
|
550
|
+
// would turn into a mw-radius-* utility.
|
|
551
|
+
$chip-radius: 10px;
|
|
552
|
+
$chip-radius-sm: 5px;
|
|
553
|
+
|
|
533
554
|
// Z-index
|
|
534
555
|
$z-index: (
|
|
535
556
|
'lift': 3,
|
package/src/scss/base/_base.scss
CHANGED
|
@@ -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
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
@use '../abstracts' as *;
|
|
2
2
|
|
|
3
|
+
// Clear of the home indicator where there is one, the header's gap where not
|
|
4
|
+
$_island-bottom: max(var(--mw-header-inset), env(safe-area-inset-bottom, 0px));
|
|
5
|
+
// Lighter than the flat bar, and as low as it goes: the items keep their
|
|
6
|
+
// 2.75rem touch floor, so the rest is only the padding around them
|
|
7
|
+
$_island-height: calc(var(--mw-action-bar-height) - 4px);
|
|
8
|
+
|
|
3
9
|
@layer mw.components {
|
|
4
10
|
:root {
|
|
5
11
|
--mw-action-bar-height: 3.75rem;
|
|
@@ -18,6 +24,10 @@
|
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
26
|
|
|
27
|
+
:root:has(.mw-action-bar-island:not(.mw-action-bar-embedded)) {
|
|
28
|
+
--mw-action-bar-reserve: calc(#{$_island-height} + #{$_island-bottom});
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
// Chrome like the header, so it takes the header's colours in both themes
|
|
22
32
|
.mw-action-bar {
|
|
23
33
|
--mw-focus-ring-color: var(--mw-header-navbar-list-active-color);
|
|
@@ -44,6 +54,19 @@
|
|
|
44
54
|
position: absolute;
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
&-island {
|
|
58
|
+
inset-inline: island-edge();
|
|
59
|
+
bottom: $_island-bottom;
|
|
60
|
+
min-height: $_island-height;
|
|
61
|
+
padding: spacing('1');
|
|
62
|
+
@include island-surface;
|
|
63
|
+
|
|
64
|
+
// Its own frame has no page container to line up with
|
|
65
|
+
&.mw-action-bar-embedded {
|
|
66
|
+
inset-inline: calc(var(--mw-header-inset) - #{$header-island-reach});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
47
70
|
&-item {
|
|
48
71
|
display: flex;
|
|
49
72
|
flex-direction: column;
|
|
@@ -129,6 +152,12 @@
|
|
|
129
152
|
justify-content: center;
|
|
130
153
|
grid-auto-columns: minmax(0, 10rem);
|
|
131
154
|
}
|
|
155
|
+
|
|
156
|
+
// A dock: a container-wide island would float the targets in empty glass
|
|
157
|
+
.mw-action-bar-island {
|
|
158
|
+
width: fit-content;
|
|
159
|
+
margin-inline: auto;
|
|
160
|
+
}
|
|
132
161
|
}
|
|
133
162
|
|
|
134
163
|
@media print {
|
|
@@ -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
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
text-decoration: none;
|
|
14
14
|
background: var(--mw-card-background);
|
|
15
15
|
border: 1px solid var(--mw-border);
|
|
16
|
-
border-radius: radius(
|
|
16
|
+
border-radius: radius($chip-radius);
|
|
17
17
|
padding: spacing('2');
|
|
18
18
|
transition: var(--mw-transition-fast);
|
|
19
19
|
color: var(--mw-text-color);
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
padding: spacing('1') spacing('2');
|
|
56
56
|
min-width: 32px;
|
|
57
57
|
min-height: 32px;
|
|
58
|
-
border-radius: radius(
|
|
58
|
+
border-radius: radius($chip-radius-sm);
|
|
59
59
|
|
|
60
60
|
.mw-techstack-logo {
|
|
61
61
|
width: 18px;
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
|
|
79
79
|
&-logo {
|
|
80
80
|
object-fit: contain;
|
|
81
|
-
border-radius: radius(
|
|
81
|
+
border-radius: radius($chip-radius-sm);
|
|
82
82
|
transition: transform var(--mw-duration-fast) var(--mw-ease-out);
|
|
83
83
|
flex-shrink: 0;
|
|
84
84
|
}
|
|
@@ -1,11 +1,57 @@
|
|
|
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 {
|
|
12
|
+
:root:not(.mw-header-bar) {
|
|
13
|
+
// A header that stays put starts as the full bar and docks into the island over
|
|
14
|
+
// the first stretch of scroll. A revealing one is off screen up there anyway and
|
|
15
|
+
// arrives as the island directly. Without scroll timelines it is the island
|
|
16
|
+
// from the start. The card under the island docks with it, so a menu opened at
|
|
17
|
+
// the top hangs from the bar edge to edge like the bar itself.
|
|
18
|
+
@supports (animation-timeline: scroll()) {
|
|
19
|
+
.mw-header:not(.mw-header-reveal) {
|
|
20
|
+
@include motion-safe {
|
|
21
|
+
animation-name: mw-header-dock;
|
|
22
|
+
animation-timing-function: var(--mw-ease-out);
|
|
23
|
+
animation-fill-mode: both;
|
|
24
|
+
animation-timeline: scroll(root);
|
|
25
|
+
animation-range: 0 $dock-range;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// In the row the nav is static and has no radius, so the same keyframes
|
|
29
|
+
// pass over it without a trace
|
|
30
|
+
.mw-navbar {
|
|
31
|
+
@include motion-safe {
|
|
32
|
+
animation-name: mw-header-dock-sheet;
|
|
33
|
+
animation-timing-function: var(--mw-ease-out);
|
|
34
|
+
animation-fill-mode: both;
|
|
35
|
+
animation-timeline: scroll(root);
|
|
36
|
+
animation-range: 0 $dock-range;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The ribbon hangs from the island, so it docks with it
|
|
42
|
+
body:not(:has(.mw-header-reveal))
|
|
43
|
+
.mw-announcement:not(.mw-announcement-static) {
|
|
44
|
+
@include motion-safe {
|
|
45
|
+
animation-name: mw-announcement-dock;
|
|
46
|
+
animation-timing-function: var(--mw-ease-out);
|
|
47
|
+
animation-fill-mode: both;
|
|
48
|
+
animation-timeline: scroll(root);
|
|
49
|
+
animation-range: 0 $dock-range;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
9
55
|
// The bar starts above the screen and rides in over the first stretch of scroll.
|
|
10
56
|
// Its own file, and not next to .mw-header: that one uses the `animation`
|
|
11
57
|
// shorthand, which a consumer bundler folds together with a timeline into a
|
|
@@ -77,7 +123,7 @@ $float-edge: (0 0 0 1px rgb(255 255 255 / 0.22), 0 4px 14px rgb(0 0 0 / 0.4));
|
|
|
77
123
|
.mw-announcement:focus-within
|
|
78
124
|
) {
|
|
79
125
|
.mw-header-reveal {
|
|
80
|
-
top:
|
|
126
|
+
top: var(--mw-header-inset) !important;
|
|
81
127
|
opacity: 1 !important;
|
|
82
128
|
}
|
|
83
129
|
|
|
@@ -124,11 +170,13 @@ $float-edge: (0 0 0 1px rgb(255 255 255 / 0.22), 0 4px 14px rgb(0 0 0 / 0.4));
|
|
|
124
170
|
// earlier - together the bar would slide away in front of the reader.
|
|
125
171
|
.mw-header-reveal.mw-header-away {
|
|
126
172
|
@include motion-safe {
|
|
127
|
-
top: calc(-
|
|
173
|
+
top: calc(var(--mw-header-inset) - var(--mw-header-reveal-offset));
|
|
128
174
|
opacity: 0;
|
|
129
175
|
|
|
176
|
+
// Visible through the slide, so its shadow would hang into the hero
|
|
130
177
|
&:has(.mw-header-keep) {
|
|
131
178
|
opacity: 1;
|
|
179
|
+
box-shadow: none;
|
|
132
180
|
}
|
|
133
181
|
|
|
134
182
|
.mw-header-keep {
|
|
@@ -177,25 +225,63 @@ $float-edge: (0 0 0 1px rgb(255 255 255 / 0.22), 0 4px 14px rgb(0 0 0 / 0.4));
|
|
|
177
225
|
@keyframes mw-header-reveal {
|
|
178
226
|
0%,
|
|
179
227
|
30% {
|
|
180
|
-
top: calc(-
|
|
228
|
+
top: calc(var(--mw-header-inset) - var(--mw-header-reveal-offset));
|
|
181
229
|
opacity: 0;
|
|
182
230
|
}
|
|
183
231
|
|
|
184
232
|
100% {
|
|
185
|
-
top:
|
|
233
|
+
top: var(--mw-header-inset);
|
|
186
234
|
opacity: 1;
|
|
187
235
|
}
|
|
188
236
|
}
|
|
189
237
|
|
|
190
|
-
// The same ride without the fade, for a bar something stays visible through
|
|
238
|
+
// The same ride without the fade, for a bar something stays visible through -
|
|
239
|
+
// and without the shadow while it is up there, or it hangs into the hero
|
|
191
240
|
@keyframes mw-header-slide {
|
|
192
241
|
0%,
|
|
193
242
|
30% {
|
|
194
|
-
top: calc(-
|
|
243
|
+
top: calc(var(--mw-header-inset) - var(--mw-header-reveal-offset));
|
|
244
|
+
box-shadow: none;
|
|
195
245
|
}
|
|
196
246
|
|
|
197
247
|
100% {
|
|
248
|
+
top: var(--mw-header-inset);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Only the start: the end is the island the header already is
|
|
253
|
+
@keyframes mw-header-dock {
|
|
254
|
+
from {
|
|
198
255
|
top: 0;
|
|
256
|
+
left: 0;
|
|
257
|
+
right: 0;
|
|
258
|
+
min-height: var(--mw-header-height);
|
|
259
|
+
// Percentages of the viewport here, so the content box is the page
|
|
260
|
+
// container in both states and the logo does not move sideways
|
|
261
|
+
padding-inline: calc(
|
|
262
|
+
(100% - var(--mw-container-width)) / 2 - #{$header-island-reach}
|
|
263
|
+
);
|
|
264
|
+
border-radius: 0;
|
|
265
|
+
border-color: transparent transparent var(--mw-header-border);
|
|
266
|
+
background-color: var(--mw-header-background);
|
|
267
|
+
box-shadow: none;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@keyframes mw-header-dock-sheet {
|
|
272
|
+
from {
|
|
273
|
+
top: 100%;
|
|
274
|
+
border-radius: 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@keyframes mw-announcement-dock {
|
|
279
|
+
from {
|
|
280
|
+
left: 0;
|
|
281
|
+
right: 0;
|
|
282
|
+
border-radius: 0;
|
|
283
|
+
border-color: transparent;
|
|
284
|
+
box-shadow: none;
|
|
199
285
|
}
|
|
200
286
|
}
|
|
201
287
|
|