maverick-wave 5.8.0 → 5.11.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/.claude/skills/mw-maverick-wave/SKILL.md +26 -14
- package/.claude/skills/mw-maverick-wave/examples/angular-services.md +10 -1
- package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +4 -3
- package/.claude/skills/mw-maverick-wave/references/components.md +23 -9
- package/.claude/skills/mw-maverick-wave/references/javascript.md +14 -14
- package/.claude/skills/mw-maverick-wave/references/layout.md +29 -9
- package/.claude/skills/mw-maverick-wave/references/theming.md +48 -25
- package/CHANGELOG.md +23 -0
- package/README.md +41 -24
- package/index.html +356 -20
- package/maverick-wave.min.css +61 -5
- package/maverick-wave.min.js +1 -1
- package/package.json +2 -2
- package/scripts/verify.js +7 -1
- package/src/js/main.js +27 -7
- package/src/partials/palette-container.html +105 -65
- package/src/partials/parallax-container.html +20 -1
- package/src/partials/preview-container.html +136 -0
- package/src/partials/utilities-container.html +38 -0
- package/src/scss/abstracts/_functions.scss +31 -3
- package/src/scss/abstracts/_mixins.scss +62 -12
- package/src/scss/abstracts/_variables.scss +47 -21
- package/src/scss/base/_base.scss +97 -8
- package/src/scss/components/_badge.scss +1 -1
- package/src/scss/components/_buttons.scss +1 -1
- package/src/scss/components/_cards.scss +23 -14
- package/src/scss/components/_dropdown.scss +58 -1
- package/src/scss/components/_kanban.scss +10 -9
- package/src/scss/components/_lang-switch.scss +1 -1
- package/src/scss/components/_localhost-indicator.scss +1 -1
- package/src/scss/components/_modals.scss +13 -1
- package/src/scss/components/_pricing.scss +1 -1
- package/src/scss/components/_progress.scss +9 -5
- package/src/scss/components/_skeleton.scss +1 -1
- package/src/scss/components/_spinners.scss +5 -5
- package/src/scss/components/_stepper.scss +4 -1
- package/src/scss/components/_tabs.scss +28 -8
- package/src/scss/components/_tiles.scss +41 -16
- package/src/scss/components/_timelines.scss +13 -5
- package/src/scss/layout/_header-reveal.scss +5 -5
- package/src/scss/layout/_main.scss +1 -1
- package/src/scss/layout/_parallax.scss +65 -14
- package/src/scss/utilities/_elevation.scss +23 -0
- package/src/scss/utilities/_reveal.scss +5 -5
- package/src/scss/utilities/_touch-targets.scss +1 -1
- package/src/scss/utilities/_variants.scss +74 -7
|
@@ -197,10 +197,67 @@
|
|
|
197
197
|
overflow: visible;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
// The clipping caveat at the top of this file, lifted where the browser can do
|
|
201
|
+
// it. `fixed` takes the menu out of every ancestor's containing block and
|
|
202
|
+
// anchor positioning keeps it attached to the trigger, so an actions menu in a
|
|
203
|
+
// scrolling table or a clipping card is no longer cut in half - and the
|
|
204
|
+
// `:has()` list above becomes belt and braces rather than the only thing
|
|
205
|
+
// holding it together.
|
|
206
|
+
//
|
|
207
|
+
// `anchor-scope` is what makes this safe and why it is in the @supports test:
|
|
208
|
+
// an anchor name is global, so without it every menu on the page would resolve
|
|
209
|
+
// to whichever trigger came last in the DOM. Scoped to the dropdown, each menu
|
|
210
|
+
// sees its own.
|
|
211
|
+
@supports (anchor-scope: all) and (position-area: block-end) {
|
|
212
|
+
.mw-dropdown {
|
|
213
|
+
anchor-scope: --mw-dropdown-trigger;
|
|
214
|
+
|
|
215
|
+
> summary {
|
|
216
|
+
anchor-name: --mw-dropdown-trigger;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.mw-dropdown-menu {
|
|
221
|
+
position: fixed;
|
|
222
|
+
position-anchor: --mw-dropdown-trigger;
|
|
223
|
+
// Below the trigger, left edges aligned - the same place the absolute
|
|
224
|
+
// version sits. The inset properties it used are cleared, or they fight
|
|
225
|
+
// the position-area, which resolves them itself.
|
|
226
|
+
position-area: block-end span-inline-end;
|
|
227
|
+
// Flip up when the trigger sits low, in when it sits near the right edge.
|
|
228
|
+
// The menu has no arrow to contradict a flip, so unlike the tooltip it can
|
|
229
|
+
// take all of them.
|
|
230
|
+
position-try-fallbacks:
|
|
231
|
+
flip-block,
|
|
232
|
+
flip-inline,
|
|
233
|
+
flip-block flip-inline;
|
|
234
|
+
// Never taller than the room the fallback landed in
|
|
235
|
+
position-try-order: most-block-size;
|
|
236
|
+
top: auto;
|
|
237
|
+
right: auto;
|
|
238
|
+
bottom: auto;
|
|
239
|
+
left: auto;
|
|
240
|
+
margin-block: spacing('2');
|
|
241
|
+
// The viewport cap no longer applies - what matters is the slot the anchor
|
|
242
|
+
// left, and 60dvh was measured against the window anyway
|
|
243
|
+
max-height: min(60dvh, 420px);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Both variants are what position-try-fallbacks now does on its own, so they
|
|
247
|
+
// only have to stop pushing the menu around with insets
|
|
248
|
+
.mw-dropdown-end .mw-dropdown-menu {
|
|
249
|
+
position-area: block-end span-inline-start;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.mw-dropdown-up .mw-dropdown-menu {
|
|
253
|
+
position-area: block-start span-inline-end;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
200
257
|
// A menu opened by a thumb needs rows a thumb can hit, and the room to show
|
|
201
258
|
// them - so it takes the trigger's full width rather than a 200px column
|
|
202
259
|
// floating beside it.
|
|
203
|
-
@media (pointer: coarse), (
|
|
260
|
+
@media (pointer: coarse), (width <= #{breakpoint('sm')}) {
|
|
204
261
|
.mw-dropdown-menu {
|
|
205
262
|
min-width: max(100%, 240px);
|
|
206
263
|
}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// Room the ribbon needs in the top right corner of a ticket
|
|
8
8
|
$_mw-kanban-ribbon-clearance: 66px;
|
|
9
9
|
$_mw-kanban-ribbon-clearance-compact: 58px;
|
|
10
|
+
// Narrowest a lane may get before the board wraps it to the next row
|
|
11
|
+
$_mw-kanban-lane-min: 260px;
|
|
10
12
|
|
|
11
13
|
@keyframes mw-kanban-card-in {
|
|
12
14
|
from {
|
|
@@ -58,21 +60,20 @@
|
|
|
58
60
|
--mw-kanban-column-min-height: 120px;
|
|
59
61
|
|
|
60
62
|
display: grid;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
// As many lanes as fit at a readable width, the rest wrap. The board asks
|
|
64
|
+
// its own width, so one inside a panel wraps on a desktop too. `min()`
|
|
65
|
+
// against 100% keeps a lane out of a container narrower than the minimum.
|
|
66
|
+
grid-template-columns: repeat(
|
|
67
|
+
auto-fit,
|
|
68
|
+
minmax(min(#{$_mw-kanban-lane-min}, 100%), 1fr)
|
|
69
|
+
);
|
|
63
70
|
gap: spacing('4');
|
|
64
71
|
padding: spacing('4');
|
|
65
72
|
background: var(--mw-kanban-background);
|
|
66
73
|
border-radius: radius('md');
|
|
67
74
|
|
|
68
|
-
//
|
|
69
|
-
@include media-down('lg') {
|
|
70
|
-
grid-auto-flow: row;
|
|
71
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
72
|
-
}
|
|
73
|
-
|
|
75
|
+
// Frame only - how tight the board sits on a phone is a viewport call
|
|
74
76
|
@include media-down('sm') {
|
|
75
|
-
grid-template-columns: minmax(0, 1fr);
|
|
76
77
|
gap: spacing('3');
|
|
77
78
|
padding: spacing('3');
|
|
78
79
|
}
|
|
@@ -16,12 +16,24 @@
|
|
|
16
16
|
justify-content: center;
|
|
17
17
|
padding: spacing('7');
|
|
18
18
|
opacity: 0;
|
|
19
|
-
|
|
19
|
+
// `display` in the list and `allow-discrete` behind it, or the overlay is
|
|
20
|
+
// taken off the page the frame the class comes off and the fade out never
|
|
21
|
+
// renders - the modal inside it would slide down over nothing. The dialog
|
|
22
|
+
// version below has had this; the div version was landing hard.
|
|
23
|
+
transition:
|
|
24
|
+
opacity var(--mw-duration-base) var(--mw-ease-out),
|
|
25
|
+
display var(--mw-duration-base) allow-discrete;
|
|
20
26
|
|
|
21
27
|
// Show modal when class is added
|
|
22
28
|
&.mw-modal-open {
|
|
23
29
|
display: flex;
|
|
24
30
|
opacity: 1;
|
|
31
|
+
|
|
32
|
+
// A box that goes from `display: none` starts at its end state otherwise,
|
|
33
|
+
// so there is nothing to fade from
|
|
34
|
+
@starting-style {
|
|
35
|
+
opacity: 0;
|
|
36
|
+
}
|
|
25
37
|
}
|
|
26
38
|
}
|
|
27
39
|
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
// row survives the narrow-card stack and the action takes half of it. Here and
|
|
201
201
|
// not in _cards.scss because the price is the guest - the footer's own rule is
|
|
202
202
|
// right for everything that is not one.
|
|
203
|
-
@container mw-card (
|
|
203
|
+
@container mw-card (width <= #{$mw-card-narrow}) {
|
|
204
204
|
.mw-card-footer:has(> .mw-price) {
|
|
205
205
|
flex-direction: row;
|
|
206
206
|
align-items: center;
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
@use '../abstracts' as *;
|
|
2
2
|
|
|
3
3
|
@layer mw.components {
|
|
4
|
+
// How far the bar is filled - written by the script, read by the keyframe at
|
|
5
|
+
// the bottom. Registered and not declared on the element: a `data-value` the
|
|
6
|
+
// script cannot parse lands on 0% instead of dropping `width` altogether.
|
|
7
|
+
@property --mw-progress-value {
|
|
8
|
+
syntax: '<length-percentage>';
|
|
9
|
+
inherits: true;
|
|
10
|
+
initial-value: 0%;
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
.mw-progress {
|
|
5
14
|
&-container {
|
|
6
15
|
margin: spacing('1');
|
|
@@ -61,11 +70,6 @@
|
|
|
61
70
|
// the variants at the bottom of the file
|
|
62
71
|
--mw-progress-ink: var(--mw-secondary-accent-text-color);
|
|
63
72
|
|
|
64
|
-
// How far the bar is filled. Written by the script from data-value or from
|
|
65
|
-
// an inline width, and read by the keyframe below. Declared here so it has
|
|
66
|
-
// a definition rather than only a fallback - an unset bar is an empty one.
|
|
67
|
-
--mw-progress-value: 0;
|
|
68
|
-
|
|
69
73
|
height: 100%;
|
|
70
74
|
background: var(--mw-secondary-color);
|
|
71
75
|
border-radius: radius('sm');
|
|
@@ -16,20 +16,20 @@
|
|
|
16
16
|
border: 0.5rem solid var(--mw-border);
|
|
17
17
|
border-radius: radius('full');
|
|
18
18
|
border-top-color: var(--mw-primary-text-color);
|
|
19
|
-
animation: mw-spin 1.2s linear infinite;
|
|
19
|
+
animation: mw-spin motion(1.2s) linear infinite;
|
|
20
20
|
|
|
21
21
|
&.mw-spinner-sm {
|
|
22
22
|
width: $_size-sm;
|
|
23
23
|
height: $_size-sm;
|
|
24
24
|
border-width: 0.3rem;
|
|
25
|
-
animation: mw-spin 1.4s linear infinite;
|
|
25
|
+
animation: mw-spin motion(1.4s) linear infinite;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
&.mw-spinner-lg {
|
|
29
29
|
width: $_size-lg;
|
|
30
30
|
height: $_size-lg;
|
|
31
31
|
border-width: 0.7rem;
|
|
32
|
-
animation: mw-spin 1s linear infinite;
|
|
32
|
+
animation: mw-spin motion(1s) linear infinite;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
&.mw-spinner-primary {
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
border-radius: radius('full');
|
|
55
55
|
display: inline-block;
|
|
56
56
|
margin: spacing('0') spacing('2');
|
|
57
|
-
animation: mw-bounce 1.4s infinite ease-in-out both;
|
|
57
|
+
animation: mw-bounce motion(1.4s) infinite ease-in-out both;
|
|
58
58
|
|
|
59
59
|
&:nth-child(1) {
|
|
60
60
|
animation-delay: -0.32s;
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
border: 0.5rem solid var(--mw-primary-text-color);
|
|
111
111
|
border-color: var(--mw-primary-text-color) transparent
|
|
112
112
|
var(--mw-primary-text-color) transparent;
|
|
113
|
-
animation: mw-spin 1.5s linear infinite;
|
|
113
|
+
animation: mw-spin motion(1.5s) linear infinite;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
&.mw-spinner-sm {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
display: flex;
|
|
7
7
|
align-items: flex-start;
|
|
8
8
|
width: 100%;
|
|
9
|
+
container: mw-stepper / inline-size;
|
|
9
10
|
|
|
10
11
|
&-step {
|
|
11
12
|
display: flex;
|
|
@@ -79,7 +80,9 @@
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
// The step labels follow the strip's own width, not the window's - a
|
|
84
|
+
// stepper in a narrow card keeps the small label on a wide desktop.
|
|
85
|
+
@container mw-stepper (width >= #{breakpoint('sm')}) {
|
|
83
86
|
&-label {
|
|
84
87
|
font-size: font-size('xs');
|
|
85
88
|
}
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
@use '../abstracts' as *;
|
|
2
2
|
|
|
3
3
|
@layer mw.components {
|
|
4
|
+
// Registered, so a half-written value from the script cannot invalidate the
|
|
5
|
+
// whole `transform` and leave the marker somewhere other than at rest.
|
|
6
|
+
// `inherits: true` because the bar is the nav's ::after, and a pseudo element
|
|
7
|
+
// only ever sees these through inheritance.
|
|
8
|
+
@property --mw-tabs-marker-x {
|
|
9
|
+
syntax: '<length>';
|
|
10
|
+
inherits: true;
|
|
11
|
+
initial-value: 0px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@property --mw-tabs-marker-y {
|
|
15
|
+
syntax: '<length>';
|
|
16
|
+
inherits: true;
|
|
17
|
+
initial-value: 0px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@property --mw-tabs-marker-w {
|
|
21
|
+
syntax: '<number>';
|
|
22
|
+
inherits: true;
|
|
23
|
+
initial-value: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@property --mw-tabs-marker-h {
|
|
27
|
+
syntax: '<number>';
|
|
28
|
+
inherits: true;
|
|
29
|
+
initial-value: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
4
32
|
.mw-tabs {
|
|
5
33
|
display: flex;
|
|
6
34
|
flex-direction: column;
|
|
@@ -71,14 +99,6 @@
|
|
|
71
99
|
// A 1px bar scaled to width, not a bar whose width is set: scaleX runs on the
|
|
72
100
|
// compositor, `width` relayouts the strip on every frame of the slide.
|
|
73
101
|
&-nav[data-mw-tabs-marker] {
|
|
74
|
-
// Declared with a zero length so the four are real tokens with a resting
|
|
75
|
-
// value rather than four names the stylesheet only ever reads. The script
|
|
76
|
-
// sets them on the element, which outranks this either way.
|
|
77
|
-
--mw-tabs-marker-x: 0;
|
|
78
|
-
--mw-tabs-marker-w: 0;
|
|
79
|
-
--mw-tabs-marker-y: 0;
|
|
80
|
-
--mw-tabs-marker-h: 0;
|
|
81
|
-
|
|
82
102
|
.mw-tabs-nav-item.mw-active,
|
|
83
103
|
.mw-tabs-nav-item.active {
|
|
84
104
|
border-bottom-color: transparent;
|
|
@@ -57,7 +57,22 @@
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// The tile sizes its type off its own width, not the window's. In
|
|
61
|
+
// `.mw-tiles-4col` on a wide desktop a tile is a quarter of 85% of the page -
|
|
62
|
+
// narrower than the same tile alone on a phone, where it used to be the one
|
|
63
|
+
// getting the small type.
|
|
64
|
+
//
|
|
65
|
+
// The cost: `container-type` makes the tile a containing block for its own
|
|
66
|
+
// fixed-position descendants, and unlike a card the tile clips - so a
|
|
67
|
+
// [data-tooltip] inside one is now cut off at its edge. Taken knowingly, there
|
|
68
|
+
// being nothing in a tile's anatomy that wants a tooltip. Drop the two lines
|
|
69
|
+
// below and the queries fall back to the window.
|
|
70
|
+
$_mw-tile-wide: 300px;
|
|
71
|
+
$_mw-tile-narrow: 220px;
|
|
72
|
+
|
|
60
73
|
.mw-tile {
|
|
74
|
+
width: 100%;
|
|
75
|
+
container: mw-tile / inline-size;
|
|
61
76
|
background: var(--mw-card-background);
|
|
62
77
|
@include surface;
|
|
63
78
|
padding: spacing('5');
|
|
@@ -116,11 +131,11 @@
|
|
|
116
131
|
line-height: 1.2;
|
|
117
132
|
text-align: center;
|
|
118
133
|
|
|
119
|
-
@
|
|
134
|
+
@container mw-tile (width >= #{$_mw-tile-narrow}) {
|
|
120
135
|
font-size: font-size('xl');
|
|
121
136
|
}
|
|
122
137
|
|
|
123
|
-
@
|
|
138
|
+
@container mw-tile (width >= #{$_mw-tile-wide}) {
|
|
124
139
|
font-size: font-size('2xl');
|
|
125
140
|
}
|
|
126
141
|
}
|
|
@@ -166,7 +181,7 @@
|
|
|
166
181
|
font-size: font-size('md');
|
|
167
182
|
margin-bottom: spacing('2');
|
|
168
183
|
|
|
169
|
-
@
|
|
184
|
+
@container mw-tile (width >= #{$_mw-tile-wide}) {
|
|
170
185
|
font-size: font-size('lg');
|
|
171
186
|
}
|
|
172
187
|
}
|
|
@@ -194,11 +209,11 @@
|
|
|
194
209
|
font-size: font-size('xl');
|
|
195
210
|
margin-bottom: spacing('4');
|
|
196
211
|
|
|
197
|
-
@
|
|
212
|
+
@container mw-tile (width >= #{$_mw-tile-narrow}) {
|
|
198
213
|
font-size: font-size('2xl');
|
|
199
214
|
}
|
|
200
215
|
|
|
201
|
-
@
|
|
216
|
+
@container mw-tile (width >= #{$_mw-tile-wide}) {
|
|
202
217
|
font-size: font-size('3xl');
|
|
203
218
|
}
|
|
204
219
|
}
|
|
@@ -214,20 +229,30 @@
|
|
|
214
229
|
}
|
|
215
230
|
}
|
|
216
231
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
// A cramped tile reads better ranged left whatever the window is doing - a
|
|
233
|
+
// centred line inside 200px is two words and a gap. Every selector here is a
|
|
234
|
+
// child: a container cannot answer a query about itself, and the tile *is* the
|
|
235
|
+
// container - which is also why the padding below stays on the window.
|
|
236
|
+
@container mw-tile (width < #{$_mw-tile-narrow}) {
|
|
237
|
+
.mw-tile-header,
|
|
238
|
+
.mw-tile-body {
|
|
220
239
|
text-align: left;
|
|
240
|
+
}
|
|
221
241
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
margin: spacing('0') auto spacing('2');
|
|
226
|
-
}
|
|
242
|
+
.mw-tile-body {
|
|
243
|
+
margin-bottom: spacing('2');
|
|
244
|
+
}
|
|
227
245
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
246
|
+
.mw-tile-img {
|
|
247
|
+
width: 60px;
|
|
248
|
+
height: 60px;
|
|
249
|
+
margin: spacing('0') auto spacing('2');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@include media-down('sm') {
|
|
254
|
+
.mw-tile {
|
|
255
|
+
padding: spacing('4');
|
|
231
256
|
}
|
|
232
257
|
}
|
|
233
258
|
}
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
margin: spacing('1') auto;
|
|
58
58
|
padding: spacing('5') 0;
|
|
59
59
|
width: min(100%, 900px);
|
|
60
|
+
container: mw-timeline / inline-size;
|
|
60
61
|
|
|
61
62
|
// Center vertical line
|
|
62
63
|
&::before {
|
|
@@ -185,7 +186,7 @@
|
|
|
185
186
|
background: var(--mw-primary-color);
|
|
186
187
|
border-color: var(--mw-primary-text-color);
|
|
187
188
|
transform: scale(1.1);
|
|
188
|
-
animation: mw-pulse-ring-big 2s infinite;
|
|
189
|
+
animation: mw-pulse-ring-big motion(2s) infinite;
|
|
189
190
|
|
|
190
191
|
.mw-timeline-big-date-main,
|
|
191
192
|
.mw-timeline-big-date-sub {
|
|
@@ -193,9 +194,10 @@
|
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
// The two-sided layout collapses when the timeline itself runs out of room,
|
|
198
|
+
// not when the window does - the same timeline in a half-width column had
|
|
199
|
+
// its content overlapping the centre line on a wide desktop.
|
|
200
|
+
@container mw-timeline (width < #{breakpoint('sm')}) {
|
|
199
201
|
// Line moves to the left
|
|
200
202
|
&::before {
|
|
201
203
|
left: 0;
|
|
@@ -258,6 +260,7 @@
|
|
|
258
260
|
margin: spacing('1') auto;
|
|
259
261
|
padding: spacing('3') 0;
|
|
260
262
|
width: min(96%, 930px);
|
|
263
|
+
container: mw-timeline / inline-size;
|
|
261
264
|
|
|
262
265
|
// Vertical line centered over the date column
|
|
263
266
|
&::before {
|
|
@@ -319,12 +322,16 @@
|
|
|
319
322
|
background: var(--mw-primary-color);
|
|
320
323
|
color: var(--mw-primary-accent-text-color);
|
|
321
324
|
transform: scale(1.1);
|
|
322
|
-
animation: mw-pulse-ring-simple 2s infinite;
|
|
325
|
+
animation: mw-pulse-ring-simple motion(2s) infinite;
|
|
323
326
|
}
|
|
324
327
|
|
|
328
|
+
// The inset is a page-level call: on a phone the timeline takes the full
|
|
329
|
+
// width, however wide its column happens to be.
|
|
325
330
|
@include media-down('sm') {
|
|
326
331
|
width: 100%;
|
|
332
|
+
}
|
|
327
333
|
|
|
334
|
+
@container mw-timeline (width < #{breakpoint('sm')}) {
|
|
328
335
|
&::before {
|
|
329
336
|
left: 0;
|
|
330
337
|
transform: none;
|
|
@@ -341,6 +348,7 @@
|
|
|
341
348
|
}
|
|
342
349
|
|
|
343
350
|
&-content {
|
|
351
|
+
align-self: stretch;
|
|
344
352
|
padding-left: spacing('3');
|
|
345
353
|
}
|
|
346
354
|
}
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
// Firefox
|
|
67
|
-
// classes instead (main.js) - the same two positions,
|
|
68
|
-
// scrubbed. State and transition are separate because
|
|
69
|
-
// state a frame earlier: together they would send the
|
|
70
|
-
// front of the reader instead of having it gone already.
|
|
66
|
+
// Older Safari and Firefox ship no scroll timelines, so there the script
|
|
67
|
+
// toggles these classes instead (main.js) - the same two positions,
|
|
68
|
+
// triggered instead of scrubbed. State and transition are separate because
|
|
69
|
+
// the script sets the state a frame earlier: together they would send the
|
|
70
|
+
// bar sliding away in front of the reader instead of having it gone already.
|
|
71
71
|
@media (prefers-reduced-motion: no-preference) {
|
|
72
72
|
.mw-header-reveal.mw-header-away {
|
|
73
73
|
top: calc(-1 * var(--mw-header-reveal-offset));
|
|
@@ -208,7 +208,7 @@
|
|
|
208
208
|
letter-spacing: 0.08em;
|
|
209
209
|
text-transform: uppercase;
|
|
210
210
|
text-decoration: none;
|
|
211
|
-
animation: mw-scroll-hint-float 2.2s var(--mw-ease-in-out) infinite;
|
|
211
|
+
animation: mw-scroll-hint-float motion(2.2s) var(--mw-ease-in-out) infinite;
|
|
212
212
|
text-shadow: 0 1px 4px rgb(0 0 0 / 65%);
|
|
213
213
|
|
|
214
214
|
// The arrow carries the meaning, so it gets the size and the label stays quiet
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
@use '../abstracts' as *;
|
|
2
2
|
|
|
3
3
|
@layer mw.layout {
|
|
4
|
+
// Registered so a bad value cannot invalidate the calc() and drop the layer
|
|
5
|
+
// somewhere else. 0.5 and not 0 is the resting point - see the transform
|
|
6
|
+
// at the bottom of this file.
|
|
7
|
+
@property --mw-parallax-progress {
|
|
8
|
+
syntax: '<number>';
|
|
9
|
+
inherits: true;
|
|
10
|
+
initial-value: 0.5;
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
// The image gets its own layer instead of `background-attachment: fixed`: that
|
|
5
14
|
// one is ignored on iOS and sizes against the viewport rather than the block.
|
|
6
15
|
// Here a transform runs on the browser's own scroll timeline - no listener, no
|
|
@@ -77,6 +86,35 @@
|
|
|
77
86
|
--mw-parallax-pattern-depth: 4vh;
|
|
78
87
|
}
|
|
79
88
|
|
|
89
|
+
// A third way to use the scroll, between the two above: the block keeps its
|
|
90
|
+
// place in the flow but travels slower than the page, so the section after it
|
|
91
|
+
// closes in from below while picture and text are still moving.
|
|
92
|
+
//
|
|
93
|
+
// `exit` and never `cover`: the block leaves a gap where it used to sit, and
|
|
94
|
+
// the exit phase is the one that starts once its top edge is already past the
|
|
95
|
+
// top of the screen - which is where that gap then stays.
|
|
96
|
+
//
|
|
97
|
+
// Travel over the block's own height is what sets the tempo: half the height
|
|
98
|
+
// is half speed, the full height stands still and is mw-parallax-sticky.
|
|
99
|
+
.mw-parallax-slow {
|
|
100
|
+
--mw-parallax-slow-travel: 45vh;
|
|
101
|
+
|
|
102
|
+
position: relative;
|
|
103
|
+
z-index: 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@supports (animation-timeline: view()) {
|
|
107
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
108
|
+
.mw-parallax-slow {
|
|
109
|
+
animation-name: mw-parallax-band;
|
|
110
|
+
animation-timing-function: linear;
|
|
111
|
+
animation-fill-mode: both;
|
|
112
|
+
animation-timeline: view();
|
|
113
|
+
animation-range: exit 0% exit 100%;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
80
118
|
// A second layer on top of the first, added to the same media element. It
|
|
81
119
|
// travels a third of the distance, and it is that difference between the two
|
|
82
120
|
// that reads as depth - one layer alone only slides. What it carries is a
|
|
@@ -88,10 +126,15 @@
|
|
|
88
126
|
background-image: var(--mw-parallax-pattern-image, none);
|
|
89
127
|
}
|
|
90
128
|
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
// `position: relative`
|
|
94
|
-
|
|
129
|
+
// Both variants above are overtaken rather than scrolled away, so everything
|
|
130
|
+
// after them has to cover them or the picture shows through the gaps. The
|
|
131
|
+
// z-index needs the weight of a class to beat the plain `position: relative`
|
|
132
|
+
// those sections carry.
|
|
133
|
+
//
|
|
134
|
+
// Except a pinned block: `position: relative` here and `sticky` above carry
|
|
135
|
+
// the same weight, and this rule is the later one - it would take the pinning
|
|
136
|
+
// away from every band after the first.
|
|
137
|
+
:is(.mw-parallax-sticky, .mw-parallax-slow) ~ *:not(.mw-parallax-sticky) {
|
|
95
138
|
position: relative;
|
|
96
139
|
z-index: 1;
|
|
97
140
|
}
|
|
@@ -99,7 +142,9 @@
|
|
|
99
142
|
// Zero specificity on purpose: it is a floor, not a rule. A section that
|
|
100
143
|
// paints its own surface - mw-section-alternate, a project's own band - has
|
|
101
144
|
// to win here, and this file is forwarded last.
|
|
102
|
-
:where(
|
|
145
|
+
:where(
|
|
146
|
+
:is(.mw-parallax-sticky, .mw-parallax-slow) ~ *:not(.mw-parallax-sticky)
|
|
147
|
+
) {
|
|
103
148
|
background: var(--mw-page-background);
|
|
104
149
|
}
|
|
105
150
|
|
|
@@ -146,18 +191,14 @@
|
|
|
146
191
|
}
|
|
147
192
|
}
|
|
148
193
|
|
|
149
|
-
// Firefox
|
|
150
|
-
// and the travel is worked out here (main.js). Keeping
|
|
151
|
-
// is what lets every depth variant above - and an
|
|
152
|
-
// `--mw-parallax-depth` - go on working untouched.
|
|
194
|
+
// Older Safari and Firefox ship no scroll timelines, so there the script
|
|
195
|
+
// writes the progress and the travel is worked out here (main.js). Keeping
|
|
196
|
+
// the transform in CSS is what lets every depth variant above - and an
|
|
197
|
+
// inline `--mw-parallax-depth` - go on working untouched.
|
|
153
198
|
//
|
|
154
|
-
// 0 puts the layer at +depth and 1 at -depth, the two ends of the keyframes
|
|
155
|
-
// below. Declared and not only used, so the frame before the script has
|
|
156
|
-
// written anything sits at rest instead of nowhere.
|
|
199
|
+
// 0 puts the layer at +depth and 1 at -depth, the two ends of the keyframes.
|
|
157
200
|
@media (prefers-reduced-motion: no-preference) {
|
|
158
201
|
.mw-parallax-driven {
|
|
159
|
-
--mw-parallax-progress: 0.5;
|
|
160
|
-
|
|
161
202
|
transform: translate3d(
|
|
162
203
|
0,
|
|
163
204
|
calc(var(--mw-parallax-depth) * (1 - 2 * var(--mw-parallax-progress))),
|
|
@@ -166,6 +207,16 @@
|
|
|
166
207
|
}
|
|
167
208
|
}
|
|
168
209
|
|
|
210
|
+
@keyframes mw-parallax-band {
|
|
211
|
+
from {
|
|
212
|
+
transform: translateY(0);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
to {
|
|
216
|
+
transform: translateY(var(--mw-parallax-slow-travel));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
169
220
|
@keyframes mw-parallax {
|
|
170
221
|
from {
|
|
171
222
|
transform: translate3d(0, var(--mw-parallax-depth), 0);
|
|
@@ -15,4 +15,27 @@
|
|
|
15
15
|
box-shadow: var(--mw-elevation-#{$level});
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
// Light instead of shadow. Elevation says how far a surface floats above the
|
|
20
|
+
// page; this says it is the thing giving off the light - for the one element on
|
|
21
|
+
// a screen that has to be looked at first, and for nothing else.
|
|
22
|
+
//
|
|
23
|
+
// Two layers like elevation, and for the same reason: the tight one reads as
|
|
24
|
+
// the edge of the source, the wide one as the air around it. Both are
|
|
25
|
+
// offsetless, which is what separates a glow from a shadow - light does not
|
|
26
|
+
// fall downwards. Bare `.mw-glow` rides along on primary, because the brand
|
|
27
|
+
// colour is what a glow means unless it says otherwise.
|
|
28
|
+
@each $name, $col in $semantic-colors {
|
|
29
|
+
$selector: '.mw-glow-#{$name}';
|
|
30
|
+
|
|
31
|
+
@if $name == 'primary' {
|
|
32
|
+
$selector: '.mw-glow, #{$selector}';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#{$selector} {
|
|
36
|
+
box-shadow:
|
|
37
|
+
0 0 6px color-mix(in srgb, #{$col} 45%, transparent),
|
|
38
|
+
0 0 26px color-mix(in srgb, #{$col} 28%, transparent);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
18
41
|
}
|