maverick-wave 5.7.0 → 5.10.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 (45) hide show
  1. package/.claude/skills/mw-maverick-wave/SKILL.md +22 -12
  2. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +10 -1
  3. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +4 -3
  4. package/.claude/skills/mw-maverick-wave/references/components.md +23 -9
  5. package/.claude/skills/mw-maverick-wave/references/javascript.md +1 -1
  6. package/.claude/skills/mw-maverick-wave/references/layout.md +8 -3
  7. package/.claude/skills/mw-maverick-wave/references/theming.md +57 -29
  8. package/CHANGELOG.md +19 -0
  9. package/README.md +43 -22
  10. package/index.html +395 -62
  11. package/maverick-wave.min.css +61 -5
  12. package/maverick-wave.min.js +1 -1
  13. package/package.json +2 -2
  14. package/scripts/verify.js +18 -2
  15. package/src/js/main.js +24 -5
  16. package/src/partials/header-container.html +7 -5
  17. package/src/partials/palette-container.html +176 -66
  18. package/src/partials/preview-container.html +136 -0
  19. package/src/partials/utilities-container.html +55 -4
  20. package/src/scss/abstracts/_functions.scss +31 -3
  21. package/src/scss/abstracts/_mixins.scss +62 -12
  22. package/src/scss/abstracts/_variables.scss +47 -21
  23. package/src/scss/base/_base.scss +101 -8
  24. package/src/scss/base/_reset.scss +2 -0
  25. package/src/scss/components/_badge.scss +1 -1
  26. package/src/scss/components/_buttons.scss +1 -1
  27. package/src/scss/components/_cards.scss +23 -14
  28. package/src/scss/components/_dropdown.scss +58 -1
  29. package/src/scss/components/_kanban.scss +10 -9
  30. package/src/scss/components/_lang-switch.scss +1 -1
  31. package/src/scss/components/_localhost-indicator.scss +1 -1
  32. package/src/scss/components/_modals.scss +13 -1
  33. package/src/scss/components/_pricing.scss +1 -1
  34. package/src/scss/components/_progress.scss +9 -5
  35. package/src/scss/components/_skeleton.scss +1 -1
  36. package/src/scss/components/_spinners.scss +5 -5
  37. package/src/scss/components/_stepper.scss +4 -1
  38. package/src/scss/components/_tabs.scss +28 -8
  39. package/src/scss/components/_tiles.scss +41 -16
  40. package/src/scss/components/_timelines.scss +12 -5
  41. package/src/scss/layout/_main.scss +1 -1
  42. package/src/scss/layout/_parallax.scss +10 -5
  43. package/src/scss/utilities/_elevation.scss +23 -0
  44. package/src/scss/utilities/_touch-targets.scss +1 -1
  45. package/src/scss/utilities/_variants.scss +115 -6
@@ -0,0 +1,136 @@
1
+ <!--
2
+ Showcase only: a page-shaped preview for the variant switches. The sections
3
+ above show one component at a time, which is the wrong lens for a switch that
4
+ retunes everything at once - here the parts sit next to each other the way
5
+ they would on a real page, and a change reads as a change of look. One column,
6
+ because the panel is fixed over the right half while any of this is in use.
7
+ -->
8
+ <div class="preview-page">
9
+ <div class="preview-head">
10
+ <div>
11
+ <h3 class="preview-title">Quarterly report</h3>
12
+ <p class="preview-lede">
13
+ Numbers for Q3 are in. The
14
+ <a href="#preview" class="mw-link">full breakdown</a> sits in the sheet,
15
+ the short version is below.
16
+ </p>
17
+ </div>
18
+ <span class="mw-badge mw-badge-info">Draft</span>
19
+ </div>
20
+
21
+ <div class="mw-alert mw-alert-info mw-mb-5">
22
+ <div class="mw-alert-icon">
23
+ <i class="fas fa-circle-info"></i>
24
+ </div>
25
+ <div class="mw-alert-content">
26
+ <span class="mw-alert-title">Two figures are still provisional</span>
27
+ <p>Finance signs off on Friday, the rest is final.</p>
28
+ </div>
29
+ </div>
30
+
31
+ <div class="mw-grid-3 mw-gap-4 mw-mb-5">
32
+ <div class="mw-card">
33
+ <div class="mw-card-badge mw-card-addon-success">Best</div>
34
+ <div class="mw-card-body">
35
+ <h4 class="mw-card-title">
36
+ <span class="mw-card-title-icon">
37
+ <i class="fas fa-arrow-trend-up"></i>
38
+ </span>
39
+ Revenue
40
+ </h4>
41
+ <hr class="mw-card-title-divider" />
42
+ <p class="mw-card-subtitle">EUR 4.2m, up 12 percent</p>
43
+ <p class="mw-card-text">
44
+ Carried by the two enterprise deals that closed in September.
45
+ </p>
46
+ <div class="mw-card-footer">
47
+ <span class="mw-text-muted mw-text-xs">Updated today</span>
48
+ <button type="button" class="mw-btn mw-btn-link">Details</button>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="mw-card">
54
+ <div class="mw-card-body">
55
+ <h4 class="mw-card-title">
56
+ <span class="mw-card-title-icon">
57
+ <i class="fas fa-user-minus"></i>
58
+ </span>
59
+ Churn
60
+ </h4>
61
+ <hr class="mw-card-title-divider" />
62
+ <p class="mw-card-subtitle">1.8 percent, flat</p>
63
+ <p class="mw-card-text">
64
+ Both accounts that left cited the same missing integration.
65
+ </p>
66
+ <div class="mw-card-footer">
67
+ <span class="mw-text-muted mw-text-xs">Updated today</span>
68
+ <button type="button" class="mw-btn mw-btn-link">Details</button>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="mw-card">
74
+ <div class="mw-card-body">
75
+ <h4 class="mw-card-title">
76
+ <span class="mw-card-title-icon">
77
+ <i class="fas fa-users"></i>
78
+ </span>
79
+ Headcount
80
+ </h4>
81
+ <hr class="mw-card-title-divider" />
82
+ <p class="mw-card-subtitle">41 people, four hires</p>
83
+ <p class="mw-card-text">
84
+ Two of them on the platform team. One open role left this quarter.
85
+ </p>
86
+ <div class="mw-card-footer">
87
+ <span class="mw-text-muted mw-text-xs">Updated today</span>
88
+ <button type="button" class="mw-btn mw-btn-link">Details</button>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ <div class="preview-row mw-mb-5">
95
+ <input
96
+ type="text"
97
+ class="mw-input"
98
+ placeholder="Add a note"
99
+ aria-label="Add a note"
100
+ />
101
+ <select class="mw-select" aria-label="Reviewer">
102
+ <option>Send to finance</option>
103
+ <option>Send to the board</option>
104
+ <option>Keep to myself</option>
105
+ </select>
106
+ <label class="mw-toggle">
107
+ <input type="checkbox" checked />
108
+ <div class="mw-toggle-track"></div>
109
+ <span class="mw-toggle-label">Notify the team</span>
110
+ </label>
111
+ </div>
112
+
113
+ <div class="preview-actions mw-mb-5">
114
+ <button type="button" class="mw-btn mw-btn-primary">Publish</button>
115
+ <button type="button" class="mw-btn mw-btn-secondary">Save draft</button>
116
+ <button type="button" class="mw-btn mw-btn-outline">Discard</button>
117
+ <button type="button" class="mw-btn mw-btn-ghost">Cancel</button>
118
+ </div>
119
+
120
+ <div class="preview-status">
121
+ <span class="mw-spinner-border mw-spinner-sm"></span>
122
+ <span>
123
+ <strong>Syncing with the finance sheet</strong>
124
+ <small>The two provisional figures update when it finishes.</small>
125
+ </span>
126
+ </div>
127
+
128
+ <div class="mw-tags">
129
+ <span class="mw-tags-item">
130
+ <i class="fas fa-tag mw-tags-icon"></i>
131
+ Q3
132
+ </span>
133
+ <span class="mw-tags-item">finance</span>
134
+ <span class="mw-tags-item">internal</span>
135
+ </div>
136
+ </div>
@@ -350,6 +350,44 @@
350
350
  </div>
351
351
  </div>
352
352
 
353
+ <h3 class="mw-mb-4 mw-mt-6">Glow</h3>
354
+ <p class="mw-text-muted mw-text-measure">
355
+ The other half of the lighting model: elevation says how far a surface floats
356
+ above the page, a glow says it is the thing giving off the light. Two layers
357
+ like elevation, and both without an offset - light does not fall downwards,
358
+ which is the whole difference between this and a coloured shadow. For the one
359
+ element on a screen that has to be looked at first, and for nothing else. Bare
360
+ <code>mw-glow</code> is the primary one.
361
+ </p>
362
+ <div class="mw-grid-3 mw-gap-6">
363
+ <div class="mw-card mw-card-simple mw-glow-primary">
364
+ <h4 class="mw-text-medium mw-mb-1">primary</h4>
365
+ <p class="mw-text-muted mw-text-sm mw-mb-0">The brand, and the default.</p>
366
+ </div>
367
+ <div class="mw-card mw-card-simple mw-glow-secondary">
368
+ <h4 class="mw-text-medium mw-mb-1">secondary</h4>
369
+ <p class="mw-text-muted mw-text-sm mw-mb-0">The second brand tone.</p>
370
+ </div>
371
+ <div class="mw-card mw-card-simple mw-glow-info">
372
+ <h4 class="mw-text-medium mw-mb-1">info</h4>
373
+ <p class="mw-text-muted mw-text-sm mw-mb-0">Something worth reading.</p>
374
+ </div>
375
+ <div class="mw-card mw-card-simple mw-glow-success">
376
+ <h4 class="mw-text-medium mw-mb-1">success</h4>
377
+ <p class="mw-text-muted mw-text-sm mw-mb-0">It worked - say so once.</p>
378
+ </div>
379
+ <div class="mw-card mw-card-simple mw-glow-warning">
380
+ <h4 class="mw-text-medium mw-mb-1">warning</h4>
381
+ <p class="mw-text-muted mw-text-sm mw-mb-0">Check this before going on.</p>
382
+ </div>
383
+ <div class="mw-card mw-card-simple mw-glow-danger">
384
+ <h4 class="mw-text-medium mw-mb-1">danger</h4>
385
+ <p class="mw-text-muted mw-text-sm mw-mb-0">
386
+ The one that cannot be undone.
387
+ </p>
388
+ </div>
389
+ </div>
390
+
353
391
  <h3 class="mw-mb-4 mw-mt-6">Responsive Display</h3>
354
392
  <p class="mw-text-muted mw-text-measure">
355
393
  Mobile-first and min-width, like everything else here.
@@ -426,7 +464,7 @@
426
464
 
427
465
  <h3 class="mw-mb-4 mw-mt-6">Site-wide Variants</h3>
428
466
  <p class="mw-text-muted mw-text-measure">
429
- Nine classes on <code>&lt;html&gt;</code> retune the whole look without
467
+ Twelve classes on <code>&lt;html&gt;</code> retune the whole look without
430
468
  touching markup. They stack, and the palette button in the corner switches all
431
469
  of them live - the box at the bottom of that panel prints the setup a project
432
470
  would carry, and reads it back: paste one in to see someone else's.
@@ -444,6 +482,10 @@
444
482
  <code>mw-shadows-flat</code> - elevation 1-3 to <code>none</code>, dropdown
445
483
  and modal keep theirs
446
484
  </li>
485
+ <li>
486
+ <code>mw-surfaces-flush</code> - cards, panels and footer on the page
487
+ colour, held by their border
488
+ </li>
447
489
  <li>
448
490
  <code>mw-hover-static</code> - no hover travels; colour and border still
449
491
  respond
@@ -461,13 +503,22 @@
461
503
  <code>mw-btn-pill</code> - fully rounded buttons, form fields keep their
462
504
  radius
463
505
  </li>
506
+ <li>
507
+ <code>mw-btn-square</code> - the other end of that axis: buttons cut to a
508
+ hard corner while the page keeps its radius
509
+ </li>
510
+ <li>
511
+ <code>mw-btn-tactile</code> - filled buttons stand on a darker edge and sink
512
+ onto it when pressed
513
+ </li>
464
514
  <li>
465
515
  <code>mw-btn-glass</code> - filled buttons become tinted glass with a lit
466
516
  top edge
467
517
  </li>
468
518
  </ul>
469
519
  <p class="mw-text-muted mw-text-measure">
470
- Four properties do the rest: <code>--mw-radius-scale</code> multiplies every
471
- radius, plus <code>--mw-font-family-heading</code>,
472
- <code>--mw-container-width</code> and <code>--mw-section-padding-block</code>.
520
+ Five properties do the rest: <code>--mw-radius-scale</code> multiplies every
521
+ radius and <code>--mw-root-font-size</code> the whole rem scale, plus
522
+ <code>--mw-font-family-heading</code>, <code>--mw-container-width</code> and
523
+ <code>--mw-section-padding-block</code>.
473
524
  </p>
@@ -27,10 +27,16 @@
27
27
  @return map.get(v.$breakpoints, $key);
28
28
  }
29
29
 
30
+ // What --mw-radius-scale cannot take away, shared by both functions below: a
31
+ // page at scale 0 is meant to read as square, not as cut out of sheet metal.
32
+ // The smallest step the scale already has, so the hardest setting lands on it
33
+ // rather than off the end.
34
+ $_radius-floor: map.get(v.$radius, 'xs');
35
+
30
36
  // Emitted through --mw-radius-scale, so one number retunes every rounded box in
31
37
  // 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.
38
+ // the corner arc follows. 'none' and 'full' pass through: an explicit 0 stays
39
+ // 0, and a circle has to stay a circle.
34
40
  @function radius($key) {
35
41
  $value: map.get(v.$radius, $key);
36
42
 
@@ -38,7 +44,29 @@
38
44
  @return $value;
39
45
  }
40
46
 
41
- @return calc(#{$value} * var(--mw-radius-scale, 1));
47
+ @return max(#{$_radius-floor}, calc(#{$value} * var(--mw-radius-scale, 1)));
48
+ }
49
+
50
+ // The sharp pair of the surface signature, squared against the scale so the two
51
+ // halves of the silhouette keep their relationship. Linear, a page turned up to
52
+ // Round holds an 8.5px corner against a 34px arc and the pair stops reading as
53
+ // one shape; squared, the sharp side opens up with it.
54
+ @function radius-sharp() {
55
+ $scale: var(--mw-radius-scale, 1);
56
+
57
+ @return max(
58
+ #{$_radius-floor},
59
+ calc(#{map.get(v.$radius, 'sm')} * #{$scale} * #{$scale})
60
+ );
61
+ }
62
+
63
+ // For the animations that run on their own clock rather than off a state change
64
+ // - spinners, shimmers, the pulses. The duration tokens carry --mw-motion-scale
65
+ // already, but these name their own timing, and a motion setting that leaves
66
+ // the one thing on screen that is permanently moving at its old speed is not a
67
+ // motion setting. prefers-reduced-motion still overrides the result outright.
68
+ @function motion($duration) {
69
+ @return calc(#{$duration} * var(--mw-motion-scale, 1));
42
70
  }
43
71
 
44
72
  // Function to get a z-index value from the z-index map
@@ -2,17 +2,14 @@
2
2
  @use 'variables' as v;
3
3
  @use 'functions' as *;
4
4
 
5
- // `media-down` stops 0.02px short, or both ranges match at exactly 768px - an
6
- // iPad in portrait, where mw-hide-mobile and mw-hide-desktop would both hide.
7
- // 0.02 and not 1, because a viewport can be fractional (zoom, scaled display).
8
5
  @mixin media-up($breakpoint) {
9
- @media (min-width: map.get(v.$breakpoints, $breakpoint)) {
6
+ @media (width >= #{map.get(v.$breakpoints, $breakpoint)}) {
10
7
  @content;
11
8
  }
12
9
  }
13
10
 
14
11
  @mixin media-down($breakpoint) {
15
- @media (max-width: map.get(v.$breakpoints, $breakpoint) - 0.02px) {
12
+ @media (width < #{map.get(v.$breakpoints, $breakpoint)}) {
16
13
  @content;
17
14
  }
18
15
  }
@@ -231,7 +228,7 @@
231
228
  // the top arc where a fill already owns that corner.
232
229
  @mixin surface($border-width: 1px, $corners: 'both') {
233
230
  position: relative;
234
- border-radius: radius('sm') radius('xl') radius('sm') radius('xl');
231
+ border-radius: radius-sharp() radius('xl') radius-sharp() radius('xl');
235
232
 
236
233
  &::after {
237
234
  @include corner-accent($border-width, $corners);
@@ -245,27 +242,80 @@
245
242
  border-radius: radius('md');
246
243
 
247
244
  &::after {
248
- display: none;
245
+ @include corner-mark($border-width);
249
246
  }
250
247
  }
251
248
  }
252
249
 
250
+ // Shared by both corner marks, which are meant to read as the same gesture at
251
+ // two volumes: how far the arc runs along the horizontal against the vertical,
252
+ // and where it stops being opaque. A percentage and not a length because the
253
+ // mask is an ellipse - see corner-accent() for what that costs.
254
+ $_corner-stretch: 1.5;
255
+ $_corner-opaque: 55%;
256
+
257
+ // What is left of the signature once the silhouette is switched off: the same
258
+ // arc, on one corner, thinner and over a shorter run. The two-corner shape is
259
+ // the loud half and mw-corners-even exists to turn it off - but a surface with
260
+ // nothing left of it could have come from any framework, and this is the mark
261
+ // that says otherwise. Deliberately quiet enough to read as a detail.
262
+ //
263
+ // Built from radius('md') because that is what the even box is drawn at, and
264
+ // it follows --mw-radius-scale down to a square, where the arc becomes a
265
+ // bracket in the corner rather than disappearing.
266
+ @mixin corner-mark($border-width: 1px) {
267
+ $r: max(0px, calc(#{radius('md')} - #{$border-width}));
268
+
269
+ content: '';
270
+ position: absolute;
271
+ inset: 0;
272
+ pointer-events: none;
273
+ border: 1.5px solid var(--mw-corner-accent);
274
+ border-radius: $r;
275
+ $reach: max(18px, calc(#{radius('md')} * 2.2));
276
+
277
+ mask-image: radial-gradient(
278
+ ellipse calc(#{$reach} * #{$_corner-stretch}) #{$reach} at 100% 0,
279
+ #000 $_corner-opaque,
280
+ transparent 100%
281
+ );
282
+ }
283
+
253
284
  // The arc alone, for a variant that redraws it without restating the shape
254
285
  @mixin corner-accent($border-width: 1px, $corners: 'both') {
255
286
  // max(), not a bare subtraction: --mw-radius-scale can take the radius to 0,
256
287
  // and while a negative border-radius is clamped for us, a negative circle in
257
288
  // the mask below would throw the whole gradient away and lose the arc.
258
- $sharp: max(0px, calc(#{radius('sm')} - #{$border-width}));
289
+ $sharp: max(0px, calc(#{radius-sharp()} - #{$border-width}));
259
290
  $round: max(0px, calc(#{radius('xl')} - #{$border-width}));
260
291
  // Opaque to where the curve ends, fading from there, so the arc runs into the
261
292
  // straight edge instead of stopping at the tangent. 1.8 is bounded by the
262
293
  // shortest surface: past about 2 the two arcs meet on a 50px box and read as
263
294
  // a frame.
264
- $reach: calc(#{$round} * 1.8);
265
- $top: radial-gradient(circle $reach at 100% 0, #000 $round, transparent 100%);
295
+ //
296
+ // The floor is what keeps the mark alive at --mw-radius-scale: 0. The reach
297
+ // is derived from the radius, so a squared-off page took it to a zero-width
298
+ // circle and the accent vanished on every surface at once - the one setting
299
+ // where the framework has least else left to recognise it by. At zero radius
300
+ // there is no curve to trace and the 22px run reads as a bracket instead.
301
+ $reach: max(22px, calc(#{$round} * 1.8));
302
+ // An ellipse and not a circle: with equal reach on both axes the arc reads as
303
+ // a quarter circle, and the mark looks like a corner radius someone forgot to
304
+ // finish. Half again as far along the horizontal gives it a direction - it
305
+ // sits on the top edge and turns down, rather than cutting the corner.
306
+ //
307
+ // The stop is a percentage here where the circle could take a length: on an
308
+ // ellipse a length would be measured along one axis only and the fade would
309
+ // start at different points on the two. The default sits where $round fell.
310
+ $reach-x: calc(#{$reach} * #{$_corner-stretch});
311
+ $top: radial-gradient(
312
+ ellipse $reach-x $reach at 100% 0,
313
+ #000 $_corner-opaque,
314
+ transparent 100%
315
+ );
266
316
  $bottom: radial-gradient(
267
- circle $reach at 0 100%,
268
- #000 $round,
317
+ ellipse $reach-x $reach at 0 100%,
318
+ #000 $_corner-opaque,
269
319
  transparent 100%
270
320
  );
271
321
 
@@ -1,3 +1,4 @@
1
+ @use 'sass:math';
1
2
  @use 'sass:string';
2
3
 
3
4
  $mw-theme-mode: 'switchable' !default; // 'dark', 'light', or 'switchable'
@@ -162,17 +163,28 @@ $focus-halo-opacity: 28% !default;
162
163
  );
163
164
  }
164
165
 
165
- // Darker, fully opaque variant of a token
166
+ // Darker by a share of the color's own lightness, hue and chroma kept. OKLch and
167
+ // not `color-mix(..., black)`: mixing toward black in HSL takes the saturation
168
+ // down with the lightness, so one $amount reads as a different step on every hue
169
+ // - the yellow goes grey while the blue only dims. Here the six status colors
170
+ // move by the same perceived amount.
166
171
  @function shade($name, $amount) {
172
+ $factor: math.div(100% - $amount, 100%);
173
+
167
174
  @return string.unquote(
168
- 'color-mix(in hsl, var(--mw-#{$name}), black #{$amount})'
175
+ 'oklch(from var(--mw-#{$name}) calc(l * #{$factor}) c h)'
169
176
  );
170
177
  }
171
178
 
172
- // Lighter, fully opaque variant of a token
179
+ // The other direction, toward white. Chroma is pulled back by the same share it
180
+ // is lightened by, because a color lifted at full chroma reads as neon rather
181
+ // than as a tint of itself - which is what the HSL mix did implicitly.
173
182
  @function tint($name, $amount) {
183
+ $lift: math.div($amount, 100%);
184
+ $rest: math.div(100% - $amount, 100%);
185
+
174
186
  @return string.unquote(
175
- 'color-mix(in hsl, var(--mw-#{$name}), white #{$amount})'
187
+ 'oklch(from var(--mw-#{$name}) calc(l + (1 - l) * #{$lift}) calc(c * #{$rest}) h)'
176
188
  );
177
189
  }
178
190
 
@@ -183,8 +195,10 @@ $focus-halo-opacity: 28% !default;
183
195
  );
184
196
  }
185
197
 
186
- // Darkened straight toward black, $amount being how much of the color survives.
187
- // shade() washes out the saturation on the way, which a surface must not do.
198
+ // Darkened straight toward black, $amount being how much of the color *survives*
199
+ // - not how far its lightness moves, which is what shade() above scales. The two
200
+ // part company at the deep end: at 14% shade() lands near L 0.05, where the hue
201
+ // is clipped out of gamut, while this keeps the channel ratios and stays blue.
188
202
  @function deepen($name, $amount) {
189
203
  @return string.unquote(
190
204
  'color-mix(in srgb, var(--mw-#{$name}) #{$amount}, black)'
@@ -223,11 +237,16 @@ $focus-halo-opacity: 28% !default;
223
237
 
224
238
  // Near is the contact shadow that pins the box down, far the ambient spread that
225
239
  // says how high it floats. One layer alone reads as a blur, the pair as light.
226
- // Theme-prefixed tokens, so the ramp is declared once per theme.
227
- @function elevation($theme, $y-near, $blur-near, $y-far, $blur-far) {
240
+ //
241
+ // Built on the theme-resolved shadow tokens rather than a per-theme pair: the two
242
+ // themes only ever differed in the colour of those two layers, never in the
243
+ // geometry, so the ramp is one set of five and the theme reaches it through
244
+ // --mw-shadow-near/-far. That also makes .mw-shadows-flat a single-selector
245
+ // override again - it is no longer re-declared further down the tree.
246
+ @function elevation($y-near, $blur-near, $y-far, $blur-far) {
228
247
  @return string.unquote(
229
- '0 #{$y-near} #{$blur-near} var(--mw-#{$theme}-shadow-near),' +
230
- ' 0 #{$y-far} #{$blur-far} var(--mw-#{$theme}-shadow-far)'
248
+ '0 #{$y-near} #{$blur-near} var(--mw-shadow-near),' +
249
+ ' 0 #{$y-far} #{$blur-far} var(--mw-shadow-far)'
231
250
  );
232
251
  }
233
252
 
@@ -307,6 +326,10 @@ $semantic-colors: (
307
326
  danger: var(--mw-danger-color),
308
327
  );
309
328
 
329
+ // The theme keys that are not colors. light-dark() only takes <color>, so these
330
+ // keep the alias-per-theme route while everything else resolves in one line.
331
+ $theme-non-colors: ('hero-image-filter');
332
+
310
333
  // Dark theme colors. Emitted as --mw-dark-<key>, hence the prefixed references.
311
334
  $dark-theme-colors: (
312
335
  'page-background': $dark-background,
@@ -317,17 +340,11 @@ $dark-theme-colors: (
317
340
  'footer-background': surface('dark-page-background', $footer-surface-dark),
318
341
  'border': surface('dark-page-background', 1.35),
319
342
  'shadow': fade('dark-text-color', 30%),
320
- // A component names a level instead of inventing another `0 5px 9px`
321
343
  'shadow-near': alpha(
322
344
  surface('dark-page-background', $shadow-cast-dark),
323
345
  $shadow-near-dark
324
346
  ),
325
347
  'shadow-far': fade('dark-text-color', $shadow-far-dark),
326
- 'elevation-1': elevation('dark', 1px, 2px, 1px, 4px),
327
- 'elevation-2': elevation('dark', 2px, 4px, 5px, 12px),
328
- 'elevation-3': elevation('dark', 3px, 6px, 10px, 24px),
329
- 'elevation-4': elevation('dark', 6px, 12px, 18px, 40px),
330
- 'elevation-5': elevation('dark', 10px, 20px, 30px, 64px),
331
348
  'text-color': $dark-text-color,
332
349
  'text-muted-color': muted(0.7),
333
350
  // Derived, so overriding a root color yields a readable dark variant for free
@@ -355,11 +372,6 @@ $light-theme-colors: (
355
372
  'shadow': fade('light-text-color', 30%),
356
373
  'shadow-near': fade('light-text-color', $shadow-near-light),
357
374
  'shadow-far': fade('light-text-color', $shadow-far-light),
358
- 'elevation-1': elevation('light', 1px, 2px, 1px, 4px),
359
- 'elevation-2': elevation('light', 2px, 4px, 5px, 12px),
360
- 'elevation-3': elevation('light', 3px, 6px, 10px, 24px),
361
- 'elevation-4': elevation('light', 6px, 12px, 18px, 40px),
362
- 'elevation-5': elevation('light', 10px, 20px, 30px, 64px),
363
375
  'text-color': $light-text-color,
364
376
  'text-muted-color': muted(0.5),
365
377
  // Other direction: a color too light to read on paper is deepened
@@ -377,6 +389,17 @@ $light-theme-colors: (
377
389
  'corner-accent-hover': token('light-primary-text-color'),
378
390
  );
379
391
 
392
+ // The elevation ramp, shared by both themes - see elevation(). A component names
393
+ // a level instead of inventing another `0 5px 9px`.
394
+ // Emitted as --mw-<key>.
395
+ $elevations: (
396
+ 'elevation-1': elevation(1px, 2px, 1px, 4px),
397
+ 'elevation-2': elevation(2px, 4px, 5px, 12px),
398
+ 'elevation-3': elevation(3px, 6px, 10px, 24px),
399
+ 'elevation-4': elevation(6px, 12px, 18px, 40px),
400
+ 'elevation-5': elevation(10px, 20px, 30px, 64px),
401
+ );
402
+
380
403
  // Emitted as --mw-header-<key>. Built from the primary rather than a theme token,
381
404
  // because the bar is dark in both themes - see $header-surface.
382
405
  $header-colors: (
@@ -463,6 +486,9 @@ $spacing: (
463
486
  // Breakpoints
464
487
  // Shared by _cards.scss and _pricing.scss, which have to agree on it
465
488
  $mw-card-narrow: 360px !default;
489
+ // The other end of the same axis: at this width a card has the room for the
490
+ // larger title and the roomier padding it used to get from a `md` viewport
491
+ $mw-card-wide: 420px !default;
466
492
 
467
493
  $breakpoints: (
468
494
  'xs': 375px,