maverick-wave 5.4.0 → 5.6.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 (48) hide show
  1. package/.claude/skills/mw-maverick-wave/SKILL.md +13 -10
  2. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +91 -0
  3. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +5 -4
  4. package/.claude/skills/mw-maverick-wave/references/components.md +3 -0
  5. package/.claude/skills/mw-maverick-wave/references/javascript.md +19 -2
  6. package/.claude/skills/mw-maverick-wave/references/layout.md +20 -14
  7. package/.claude/skills/mw-maverick-wave/references/theming.md +44 -0
  8. package/CHANGELOG.md +17 -0
  9. package/CLAUDE.md +1 -1
  10. package/README.md +27 -4
  11. package/index.html +448 -0
  12. package/maverick-wave.min.css +5 -5
  13. package/maverick-wave.min.js +1 -1
  14. package/package.json +2 -2
  15. package/scripts/verify.js +4 -7
  16. package/src/js/main.js +157 -2
  17. package/src/partials/documentation-container.html +4 -0
  18. package/src/partials/footer-container.html +3 -0
  19. package/src/partials/history-container.html +67 -0
  20. package/src/partials/palette-container.html +325 -0
  21. package/src/partials/utilities-container.html +41 -0
  22. package/src/scss/abstracts/_functions.scss +11 -2
  23. package/src/scss/abstracts/_mixins.scss +36 -8
  24. package/src/scss/abstracts/_variables.scss +4 -4
  25. package/src/scss/base/_base.scss +3 -0
  26. package/src/scss/base/_reset.scss +4 -1
  27. package/src/scss/components/_accordions.scss +31 -7
  28. package/src/scss/components/_avatars.scss +4 -1
  29. package/src/scss/components/_blog-post.scss +2 -2
  30. package/src/scss/components/_cards.scss +13 -22
  31. package/src/scss/components/_coming-soon.scss +1 -1
  32. package/src/scss/components/_info.scss +2 -2
  33. package/src/scss/components/_lists.scss +1 -1
  34. package/src/scss/components/_panels.scss +1 -1
  35. package/src/scss/components/_pricing.scss +1 -1
  36. package/src/scss/components/_techstack-bucket.scss +1 -1
  37. package/src/scss/components/_tiles.scss +1 -1
  38. package/src/scss/components/_toasts.scss +1 -1
  39. package/src/scss/form-elements/_slider.scss +5 -23
  40. package/src/scss/layout/_footer.scss +1 -1
  41. package/src/scss/layout/_header-reveal.scss +33 -0
  42. package/src/scss/layout/_header.scss +9 -1
  43. package/src/scss/layout/_parallax.scss +20 -0
  44. package/src/scss/utilities/_index.scss +1 -0
  45. package/src/scss/utilities/_reveal.scss +22 -0
  46. package/src/scss/utilities/_variants.scss +90 -0
  47. package/.impeccable/config.local.json +0 -5
  48. package/.impeccable/hook.cache.json +0 -75
@@ -0,0 +1,325 @@
1
+ <button
2
+ type="button"
3
+ class="palette-fab"
4
+ id="palette-fab"
5
+ aria-expanded="false"
6
+ aria-controls="palette-panel"
7
+ aria-label="Try another palette"
8
+ >
9
+ <i class="fas fa-palette"></i>
10
+ </button>
11
+
12
+ <div class="palette-panel" id="palette-panel" hidden>
13
+ <h3 class="palette-heading">Palette</h3>
14
+ <p class="palette-hint">
15
+ Two colors. Everything else - bar, ink, borders, tints - is derived from
16
+ them at runtime.
17
+ </p>
18
+
19
+ <div class="palette-grid">
20
+ <button
21
+ type="button"
22
+ class="palette-item mw-active"
23
+ data-primary="#1031bb"
24
+ data-secondary="#bb5710"
25
+ data-dark="#171f30"
26
+ data-light="#e7e7f0"
27
+ >
28
+ Maverick
29
+ </button>
30
+ <button
31
+ type="button"
32
+ class="palette-item"
33
+ data-primary="#0f6f8c"
34
+ data-secondary="#e8862a"
35
+ data-dark="#0f1f26"
36
+ data-light="#e2edf1"
37
+ >
38
+ Deep Sea
39
+ </button>
40
+ <button
41
+ type="button"
42
+ class="palette-item"
43
+ data-primary="#c2321f"
44
+ data-secondary="#256f83"
45
+ data-dark="#13161a"
46
+ data-light="#f4e9e6"
47
+ >
48
+ Ember
49
+ </button>
50
+ <button
51
+ type="button"
52
+ class="palette-item"
53
+ data-primary="#2f7a3f"
54
+ data-secondary="#7f4dbd"
55
+ data-dark="#141f16"
56
+ data-light="#e6eee7"
57
+ >
58
+ Moss
59
+ </button>
60
+ <button
61
+ type="button"
62
+ class="palette-item"
63
+ data-primary="#8e3bd4"
64
+ data-secondary="#2fa38a"
65
+ data-dark="#1d1530"
66
+ data-light="#ece8f5"
67
+ >
68
+ Orchid
69
+ </button>
70
+ <button
71
+ type="button"
72
+ class="palette-item"
73
+ data-primary="#46617f"
74
+ data-secondary="#c08a2e"
75
+ data-dark="#1a1f26"
76
+ data-light="#e8ebef"
77
+ >
78
+ Slate
79
+ </button>
80
+ <button
81
+ type="button"
82
+ class="palette-item"
83
+ data-primary="#00b3ff"
84
+ data-secondary="#ff2d95"
85
+ data-dark="#0d1117"
86
+ data-light="#e6f3fb"
87
+ >
88
+ Neon
89
+ </button>
90
+ <button
91
+ type="button"
92
+ class="palette-item"
93
+ data-primary="#9a5f23"
94
+ data-secondary="#3b6ea5"
95
+ data-dark="#241c15"
96
+ data-light="#f2eadd"
97
+ >
98
+ Sand
99
+ </button>
100
+ <button
101
+ type="button"
102
+ class="palette-item"
103
+ data-primary="#12a37a"
104
+ data-secondary="#e2699b"
105
+ data-dark="#101f1b"
106
+ data-light="#e2f1ec"
107
+ >
108
+ Mint
109
+ </button>
110
+ <button
111
+ type="button"
112
+ class="palette-item"
113
+ data-primary="#4b2fc4"
114
+ data-secondary="#d9a72e"
115
+ data-dark="#171331"
116
+ data-light="#eae8f7"
117
+ >
118
+ Royal
119
+ </button>
120
+ <button
121
+ type="button"
122
+ class="palette-item"
123
+ data-primary="#9b1b4a"
124
+ data-secondary="#23a08d"
125
+ data-dark="#1e1220"
126
+ data-light="#f3e7ec"
127
+ >
128
+ Wine
129
+ </button>
130
+ </div>
131
+
132
+ <div class="palette-custom">
133
+ <label class="palette-field">
134
+ <span>Primary</span>
135
+ <input type="color" id="palette-primary" value="#1031bb" />
136
+ </label>
137
+ <label class="palette-field">
138
+ <span>Secondary</span>
139
+ <input type="color" id="palette-secondary" value="#bb5710" />
140
+ </label>
141
+ </div>
142
+
143
+ <h3 class="palette-heading palette-heading-split">Variants</h3>
144
+ <p class="palette-hint">
145
+ Every switch is one class on &lt;html&gt; or one custom property - no build,
146
+ no rebuild. The setup at the bottom is what a project would carry.
147
+ </p>
148
+
149
+ <div id="variant-list">
150
+ <label class="variant-row">
151
+ <span class="variant-label">
152
+ Distinctive card corners
153
+ <small
154
+ >The arc across two corners that gives every surface its shape. Off
155
+ leaves an evenly rounded box.</small
156
+ >
157
+ </span>
158
+ <input
159
+ type="checkbox"
160
+ checked
161
+ data-variant-class="mw-corners-even"
162
+ data-variant-invert
163
+ />
164
+ </label>
165
+
166
+ <label class="variant-row">
167
+ <span class="variant-label">
168
+ Corner radius
169
+ <small
170
+ >How round every box is drawn, from hard edges to fully
171
+ rounded.</small
172
+ >
173
+ </span>
174
+ <select data-variant-prop="--mw-radius-scale">
175
+ <option value="0">Square</option>
176
+ <option value="" selected>Default</option>
177
+ <option value="1.7">Round</option>
178
+ </select>
179
+ </label>
180
+
181
+ <label class="variant-row">
182
+ <span class="variant-label">
183
+ Pill buttons
184
+ <small
185
+ >Buttons rounded all the way. Form fields keep their own shape.</small
186
+ >
187
+ </span>
188
+ <input type="checkbox" data-variant-class="mw-btn-pill" />
189
+ </label>
190
+
191
+ <label class="variant-row">
192
+ <span class="variant-label">
193
+ Single accent color
194
+ <small
195
+ >Drop the second brand colour - hovers, tints and borders all come
196
+ from the first one.</small
197
+ >
198
+ </span>
199
+ <input type="checkbox" data-variant-class="mw-accent-single" />
200
+ </label>
201
+
202
+ <label class="variant-row">
203
+ <span class="variant-label">
204
+ Photos in grey
205
+ <small
206
+ >Pictures stay grey until the pointer reaches them, so only the brand
207
+ carries colour.</small
208
+ >
209
+ </span>
210
+ <input type="checkbox" data-variant-class="mw-media-mono" />
211
+ </label>
212
+
213
+ <label class="variant-row">
214
+ <span class="variant-label">
215
+ Heading font
216
+ <small>The typeface for headlines. Body copy is unaffected.</small>
217
+ </span>
218
+ <select data-variant-prop="--mw-font-family-heading">
219
+ <option value="" selected>System</option>
220
+ <option value="Georgia, 'Times New Roman', serif">Serif</option>
221
+ <option
222
+ value="Optima, Candara, 'Gill Sans', 'Trebuchet MS', sans-serif"
223
+ >
224
+ Humanist
225
+ </option>
226
+ </select>
227
+ </label>
228
+
229
+ <label class="variant-row">
230
+ <span class="variant-label">
231
+ Uppercase headings
232
+ <small>Headlines set in capitals with wider letter spacing.</small>
233
+ </span>
234
+ <input type="checkbox" data-variant-class="mw-headings-caps" />
235
+ </label>
236
+
237
+ <label class="variant-row">
238
+ <span class="variant-label">
239
+ Flat surfaces
240
+ <small
241
+ >Cards and panels lose their shadow and sit flat on the page.
242
+ Dropdowns and dialogs still float.</small
243
+ >
244
+ </span>
245
+ <input type="checkbox" data-variant-class="mw-shadows-flat" />
246
+ </label>
247
+
248
+ <label class="variant-row">
249
+ <span class="variant-label">
250
+ Static on hover
251
+ <small
252
+ >Nothing lifts or zooms under the pointer. Colours and borders still
253
+ respond.</small
254
+ >
255
+ </span>
256
+ <input type="checkbox" data-variant-class="mw-hover-static" />
257
+ </label>
258
+
259
+ <label class="variant-row">
260
+ <span class="variant-label">
261
+ Sticky header
262
+ <small
263
+ >The bar stays put from the top instead of sliding in over the first
264
+ screen.</small
265
+ >
266
+ </span>
267
+ <input
268
+ type="checkbox"
269
+ data-variant-class="mw-header-reveal"
270
+ data-variant-target=".mw-header"
271
+ data-variant-invert
272
+ />
273
+ </label>
274
+
275
+ <label class="variant-row">
276
+ <span class="variant-label">
277
+ Alternating sections
278
+ <small>The fine diagonal hatch behind every second section.</small>
279
+ </span>
280
+ <input
281
+ type="checkbox"
282
+ checked
283
+ data-variant-class="mw-sections-plain"
284
+ data-variant-invert
285
+ />
286
+ </label>
287
+
288
+ <label class="variant-row">
289
+ <span class="variant-label">
290
+ Section spacing
291
+ <small
292
+ >How much air sits above and below each section of the page.</small
293
+ >
294
+ </span>
295
+ <select data-variant-prop="--mw-section-padding-block">
296
+ <option value="2rem">Compact</option>
297
+ <option value="" selected>Default</option>
298
+ <option value="5.5rem">Airy</option>
299
+ </select>
300
+ </label>
301
+
302
+ <label class="variant-row">
303
+ <span class="variant-label">
304
+ Page width
305
+ <small
306
+ >How wide the content runs before it stops growing on a large
307
+ screen.</small
308
+ >
309
+ </span>
310
+ <select data-variant-prop="--mw-container-width">
311
+ <option value="" selected>1200</option>
312
+ <option
313
+ value="min(1440px, calc(100% - 2 * var(--mw-container-gutter)))"
314
+ >
315
+ 1440
316
+ </option>
317
+ <option value="calc(100% - 2 * var(--mw-container-gutter))">
318
+ Full
319
+ </option>
320
+ </select>
321
+ </label>
322
+ </div>
323
+
324
+ <pre class="variant-setup" id="variant-setup"></pre>
325
+ </div>
@@ -423,3 +423,44 @@
423
423
  </p>
424
424
  </div>
425
425
  </div>
426
+
427
+ <h3 class="mw-mb-4 mw-mt-6">Site-wide Variants</h3>
428
+ <p class="mw-text-muted mw-text-measure">
429
+ Eight classes on <code>&lt;html&gt;</code> retune the whole look without
430
+ touching markup. They stack, and the palette button in the corner switches all
431
+ of them live - the box at the bottom of that panel prints the setup a project
432
+ would carry.
433
+ </p>
434
+ <ul class="mw-list mw-list-dot mw-text-measure">
435
+ <li>
436
+ <code>mw-corners-even</code> - drops the surface signature; every panel
437
+ becomes an evenly rounded box
438
+ </li>
439
+ <li>
440
+ <code>mw-accent-single</code> - one brand colour instead of two,
441
+ <code>mw-btn-secondary</code> turns outline
442
+ </li>
443
+ <li>
444
+ <code>mw-shadows-flat</code> - elevation 1-3 to <code>none</code>, dropdown
445
+ and modal keep theirs
446
+ </li>
447
+ <li>
448
+ <code>mw-hover-static</code> - no hover travels; colour and border still
449
+ respond
450
+ </li>
451
+ <li>
452
+ <code>mw-sections-plain</code> - the hatch behind
453
+ <code>mw-section-alternate</code> collapses into the page colour
454
+ </li>
455
+ <li><code>mw-headings-caps</code> - h1 to h3 in capitals</li>
456
+ <li>
457
+ <code>mw-btn-pill</code> - fully rounded buttons, form fields keep their
458
+ radius
459
+ </li>
460
+ <li><code>mw-media-mono</code> - photos grey until hovered</li>
461
+ </ul>
462
+ <p class="mw-text-muted mw-text-measure">
463
+ Four properties do the rest: <code>--mw-radius-scale</code> multiplies every
464
+ radius, plus <code>--mw-font-family-heading</code>,
465
+ <code>--mw-container-width</code> and <code>--mw-section-padding-block</code>.
466
+ </p>
@@ -27,9 +27,18 @@
27
27
  @return map.get(v.$breakpoints, $key);
28
28
  }
29
29
 
30
- // Function to get a radius value from the radius map
30
+ // Emitted through --mw-radius-scale, so one number retunes every rounded box in
31
+ // the framework at once - 0 squares them off, above 1 rounds them further, and
32
+ // the corner arc follows. 'none' and 'full' pass through: a scaled 0 is still 0,
33
+ // and a circle has to stay a circle.
31
34
  @function radius($key) {
32
- @return map.get(v.$radius, $key);
35
+ $value: map.get(v.$radius, $key);
36
+
37
+ @if $key == 'none' or $key == 'full' {
38
+ @return $value;
39
+ }
40
+
41
+ @return calc(#{$value} * var(--mw-radius-scale, 1));
33
42
  }
34
43
 
35
44
  // Function to get a z-index value from the z-index map
@@ -129,6 +129,18 @@
129
129
  }
130
130
  }
131
131
 
132
+ // A hover that *travels*. Separate from `hover`, because movement has two more
133
+ // opt-outs than a colour change does: reduced motion, where a transform still
134
+ // jumps to its end position in one frame however short the duration is, and the
135
+ // static-hover variant. Colour and shadow belong in `hover` and stay either way.
136
+ @mixin hover-move {
137
+ @media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
138
+ :root:not(.mw-hover-static) &:hover {
139
+ @content;
140
+ }
141
+ }
142
+ }
143
+
132
144
  // No 300ms double-tap wait, no grey flash - the component draws its own :active
133
145
  @mixin tappable {
134
146
  touch-action: manipulation;
@@ -219,17 +231,32 @@
219
231
  &::after {
220
232
  @include corner-accent($border-width, $corners);
221
233
  }
234
+
235
+ // Site-wide opt-out for an even rounded box - .mw-corner-plain next door drops
236
+ // the arc on a single element, this drops the whole silhouette on all of them.
237
+ // Generated here and not as a list of the eleven callers, so a new caller is
238
+ // covered the day it is written.
239
+ :root.mw-corners-even & {
240
+ border-radius: radius('md');
241
+
242
+ &::after {
243
+ display: none;
244
+ }
245
+ }
222
246
  }
223
247
 
224
248
  // The arc alone, for a variant that redraws it without restating the shape
225
249
  @mixin corner-accent($border-width: 1px, $corners: 'both') {
226
- $sharp: radius('sm') - $border-width;
227
- $round: radius('xl') - $border-width;
250
+ // max(), not a bare subtraction: --mw-radius-scale can take the radius to 0,
251
+ // and while a negative border-radius is clamped for us, a negative circle in
252
+ // the mask below would throw the whole gradient away and lose the arc.
253
+ $sharp: max(0px, calc(#{radius('sm')} - #{$border-width}));
254
+ $round: max(0px, calc(#{radius('xl')} - #{$border-width}));
228
255
  // Opaque to where the curve ends, fading from there, so the arc runs into the
229
256
  // straight edge instead of stopping at the tangent. 1.8 is bounded by the
230
257
  // shortest surface: past about 2 the two arcs meet on a 50px box and read as
231
258
  // a frame.
232
- $reach: $round * 1.8;
259
+ $reach: calc(#{$round} * 1.8);
233
260
  $top: radial-gradient(circle $reach at 100% 0, #000 $round, transparent 100%);
234
261
  $bottom: radial-gradient(
235
262
  circle $reach at 0 100%,
@@ -253,11 +280,12 @@
253
280
  }
254
281
  }
255
282
 
256
- // The reveal zone as the range of the n-th column. Viewport lengths and not
257
- // percentages of the block, so a short band and a tall card rise through the same
258
- // region of the screen. Each further column starts 4dvh after the one before.
283
+ // The reveal zone as the range of the n-th column, measured along the block's own
284
+ // entry and not in viewport lengths: a zone in dvh is the same length whatever the
285
+ // block measures, so a card shorter than the zone stands fully in place while it is
286
+ // still transparent. Each further column starts a tenth of that entry later.
259
287
  @mixin reveal-range($column: 1) {
260
- $shift: 4dvh * ($column - 1);
288
+ $shift: 10% * ($column - 1);
261
289
 
262
- animation-range: entry #{8dvh + $shift} entry #{34dvh + $shift};
290
+ animation-range: entry #{$shift} entry #{60% + $shift};
263
291
  }
@@ -86,10 +86,10 @@ $header-active-tint: 25% !default;
86
86
  $shadow-cast-dark: 0.3 !default;
87
87
  // Near (contact) and far (ambient) opacity. The dark theme's far layer is a light
88
88
  // rim, so it stays low - a glow carries further than a shadow at the same alpha.
89
- $shadow-near-dark: 55% !default;
90
- $shadow-far-dark: 13% !default;
91
- $shadow-near-light: 14% !default;
92
- $shadow-far-light: 10% !default;
89
+ $shadow-near-dark: 68% !default;
90
+ $shadow-far-dark: 16% !default;
91
+ $shadow-near-light: 21% !default;
92
+ $shadow-far-light: 14% !default;
93
93
 
94
94
  // Control sizes - 32 / 36 / 40px, shared by input, select, textarea and button so
95
95
  // the row lines up. A minimum, not a fixed height, and it is what sets a field's
@@ -88,6 +88,9 @@
88
88
  --mw-hero-pattern: #{$mw-hero-pattern};
89
89
  --mw-hero-text-color: #{$mw-hero-text-color};
90
90
 
91
+ // Read by every radius the framework draws - see the radius() function
92
+ --mw-radius-scale: 1;
93
+
91
94
  // header height
92
95
  --mw-header-height: #{$header-height};
93
96
 
@@ -24,9 +24,12 @@
24
24
  -webkit-font-smoothing: antialiased;
25
25
  -moz-osx-font-smoothing: grayscale;
26
26
 
27
+ // `dvh` and not `vh`: on iOS `100vh` is the tall viewport behind the
28
+ // address bar, so the locked body stays taller than the screen and keeps a
29
+ // strip of rubber-band scroll under the modal.
27
30
  &:has(.mw-modal-open) {
28
31
  overflow: hidden;
29
- height: 100vh;
32
+ height: 100dvh;
30
33
  }
31
34
  }
32
35
 
@@ -83,27 +83,51 @@
83
83
  }
84
84
  }
85
85
 
86
+ // A grid row from `0fr` to `1fr`, and not `height: 0` to `auto`: `auto` is
87
+ // not an animatable value, and `interpolate-size`, which would make it one,
88
+ // is Chrome's alone - Firefox and Safari jumped the panel open instead of
89
+ // running the 900ms. A fraction interpolates in all three.
86
90
  &-content {
87
- height: spacing('0');
91
+ display: grid;
92
+ grid-template-rows: 0fr;
88
93
  overflow: hidden;
89
- transition: height var(--mw-duration-slower) var(--mw-ease-out);
94
+ transition: grid-template-rows var(--mw-duration-slower)
95
+ var(--mw-ease-out);
90
96
  background: var(--mw-card-background);
91
- interpolate-size: allow-keywords;
92
97
 
93
98
  &-inner {
94
- padding: spacing('4');
95
- border-top: 1px solid var(--mw-border-accent);
99
+ // The row is never shorter than what the item contributes, and padding
100
+ // and a border do not collapse with it - so both travel along, or the
101
+ // shut panel keeps a 25px strip of itself on screen. No `overflow` here
102
+ // on purpose: the panel above already clips, and clipping twice would
103
+ // hide the overflow that makes a long panel scrollable.
104
+ min-height: 0;
105
+ padding: spacing('0') spacing('4');
106
+ border-top: 0 solid var(--mw-border-accent);
107
+ transition:
108
+ padding-block var(--mw-duration-slower) var(--mw-ease-out),
109
+ border-top-width var(--mw-duration-slower) var(--mw-ease-out);
110
+
96
111
  @include media-down('sm') {
97
112
  font-size: font-size('sm');
98
113
  }
99
114
  }
100
115
 
101
116
  @include active() {
102
- height: auto;
117
+ grid-template-rows: 1fr;
103
118
  max-height: 500px;
104
119
  overflow-y: auto;
105
120
 
106
- // Scrollbar styling
121
+ > .mw-accordion-content-inner {
122
+ padding-block: spacing('4');
123
+ border-top-width: 1px;
124
+ }
125
+
126
+ // Both spellings: the standard pair is what Firefox reads, the webkit
127
+ // pseudos are for Safari below 18.2.
128
+ scrollbar-width: thin;
129
+ scrollbar-color: var(--mw-primary-color-hover) var(--mw-card-background);
130
+
107
131
  &::-webkit-scrollbar {
108
132
  width: 6px;
109
133
  }
@@ -85,10 +85,13 @@
85
85
  transition: transform var(--mw-duration-base) var(--mw-ease-out);
86
86
 
87
87
  @include hover {
88
- transform: translateY(-3px);
89
88
  z-index: z-index('lift');
90
89
  }
91
90
 
91
+ @include hover-move {
92
+ transform: translateY(-3px);
93
+ }
94
+
92
95
  &:last-child {
93
96
  margin-right: spacing('0');
94
97
  }
@@ -274,14 +274,14 @@
274
274
  transition: var(--mw-transition);
275
275
  color: var(--mw-primary-text-color);
276
276
 
277
- &:hover {
277
+ @include hover-move {
278
278
  transform: translateX(-7px);
279
279
  }
280
280
 
281
281
  &.next {
282
282
  flex-direction: row-reverse;
283
283
 
284
- &:hover {
284
+ @include hover-move {
285
285
  transform: translateX(7px);
286
286
  }
287
287
  }
@@ -81,11 +81,10 @@
81
81
  --mw-corner-accent: var(--mw-corner-accent-hover);
82
82
  }
83
83
 
84
- // The lift moves the box, so it is guarded: on touch `:hover` latches after a
85
- // tap and the card stays raised, visibly out of line with the row it sits in.
86
- // The colour and shadow below are not - a latched border reads as "the one you
87
- // last touched", which is no worse than nothing.
88
- @include hover {
84
+ // Pointer-guarded, unlike the colour and shadow above: on touch `:hover`
85
+ // latches after a tap and the card would stay raised, visibly out of line
86
+ // with the row it sits in. A latched border is no worse than nothing.
87
+ @include hover-move {
89
88
  transform: translateY(-2px);
90
89
  }
91
90
 
@@ -145,7 +144,7 @@
145
144
  height: var(--mw-card-img-height, 210px);
146
145
  overflow: hidden;
147
146
  flex-shrink: 0;
148
- border-radius: (radius('sm') - 1px) (radius('xl') - 1px) 0 0;
147
+ border-radius: calc(#{radius('sm')} - 1px) calc(#{radius('xl')} - 1px) 0 0;
149
148
 
150
149
  img {
151
150
  width: 100%;
@@ -154,8 +153,10 @@
154
153
  transition: transform var(--mw-duration-zoom) var(--mw-ease-out);
155
154
  }
156
155
 
157
- &:hover img {
158
- transform: scale(1.05);
156
+ @include hover-move {
157
+ img {
158
+ transform: scale(1.05);
159
+ }
159
160
  }
160
161
  }
161
162
 
@@ -323,8 +324,10 @@
323
324
  font-size: font-size('md');
324
325
  }
325
326
 
326
- &:hover &-icon {
327
- transform: rotate(25deg);
327
+ @include hover-move {
328
+ .mw-card-stack-icon {
329
+ transform: rotate(25deg);
330
+ }
328
331
  }
329
332
 
330
333
  &-header {
@@ -427,16 +430,4 @@
427
430
  --mw-card-addon-text-color: var(--mw-#{$name}-accent-text-color);
428
431
  }
429
432
  }
430
-
431
- // Only the movement. The durations are already pinned to 1ms globally in
432
- // base/_base.scss; a hover that *travels* has to be taken away outright, or it
433
- // still jumps to its lifted position in one frame.
434
- @media (prefers-reduced-motion: reduce) {
435
- .mw-card:hover,
436
- .mw-card-img:hover img,
437
- .mw-card-img-contain:hover img,
438
- .mw-card-stack:hover .mw-card-stack-icon {
439
- transform: none;
440
- }
441
- }
442
433
  }
@@ -51,7 +51,7 @@
51
51
  transition: opacity var(--mw-duration-slower) var(--mw-ease-out);
52
52
  }
53
53
 
54
- @include hover {
54
+ @include hover-move {
55
55
  transform: translateY(-3px);
56
56
  }
57
57