maverick-wave 5.8.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.
- package/.claude/skills/mw-maverick-wave/SKILL.md +22 -12
- 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 +1 -1
- package/.claude/skills/mw-maverick-wave/references/layout.md +8 -3
- package/.claude/skills/mw-maverick-wave/references/theming.md +48 -25
- package/CHANGELOG.md +13 -0
- package/README.md +41 -24
- package/index.html +325 -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 +24 -5
- package/src/partials/palette-container.html +105 -65
- 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 +12 -5
- package/src/scss/layout/_main.scss +1 -1
- package/src/scss/layout/_parallax.scss +10 -5
- package/src/scss/utilities/_elevation.scss +23 -0
- package/src/scss/utilities/_touch-targets.scss +1 -1
- package/src/scss/utilities/_variants.scss +74 -7
|
@@ -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:
|
|
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 (
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
265
|
-
|
|
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
|
-
|
|
268
|
-
#000 $
|
|
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
|
|
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
|
-
'
|
|
175
|
+
'oklch(from var(--mw-#{$name}) calc(l * #{$factor}) c h)'
|
|
169
176
|
);
|
|
170
177
|
}
|
|
171
178
|
|
|
172
|
-
//
|
|
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
|
-
'
|
|
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
|
-
//
|
|
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
|
-
//
|
|
227
|
-
|
|
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
|
|
230
|
-
' 0 #{$y-far} #{$blur-far} var(--mw
|
|
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,
|
package/src/scss/base/_base.scss
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
@use 'sass:list';
|
|
2
|
+
@use 'sass:map';
|
|
1
3
|
@use '../abstracts' as *;
|
|
2
4
|
|
|
3
5
|
@layer mw.base {
|
|
6
|
+
// Registered because it is multiplied into all six duration tokens below: a
|
|
7
|
+
// value that does not parse takes every one of them with it, and a page with
|
|
8
|
+
// no transitions at all is a hard failure for a typo in one line.
|
|
9
|
+
@property --mw-motion-scale {
|
|
10
|
+
syntax: '<number>';
|
|
11
|
+
inherits: true;
|
|
12
|
+
initial-value: 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
// Active theme set and its token prefix, derived from $mw-theme-mode
|
|
5
16
|
$_active-theme: $dark-theme-colors;
|
|
6
17
|
$_active-prefix: 'dark';
|
|
@@ -10,6 +21,14 @@
|
|
|
10
21
|
$_active-prefix: 'light';
|
|
11
22
|
}
|
|
12
23
|
|
|
24
|
+
// Everything light-dark() can resolve on its own - see $theme-non-colors
|
|
25
|
+
$_theme-colors: ();
|
|
26
|
+
@each $name, $value in $dark-theme-colors {
|
|
27
|
+
@if not list.index($theme-non-colors, $name) {
|
|
28
|
+
$_theme-colors: map.set($_theme-colors, $name, $value);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
13
32
|
// CSS Variables for theming and overriding
|
|
14
33
|
:root {
|
|
15
34
|
// Brand and neutral colors
|
|
@@ -37,13 +56,19 @@
|
|
|
37
56
|
--mw-light-#{$name}: #{$value};
|
|
38
57
|
}
|
|
39
58
|
|
|
40
|
-
// The scale lives in the variables file - these are the runtime knobs
|
|
41
|
-
--mw-
|
|
42
|
-
--mw-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
--mw-duration-
|
|
46
|
-
|
|
59
|
+
// The scale lives in the variables file - these are the runtime knobs.
|
|
60
|
+
// Multiplied through --mw-motion-scale so one number retunes the page's
|
|
61
|
+
// tempo, the way --mw-radius-scale does for its corners: the six stand in a
|
|
62
|
+
// fixed ratio to each other and have to move together. The reduced-motion
|
|
63
|
+
// block below writes plain 1ms over the top, untouched by any of this.
|
|
64
|
+
--mw-duration-instant: calc(
|
|
65
|
+
#{$duration-instant} * var(--mw-motion-scale, 1)
|
|
66
|
+
);
|
|
67
|
+
--mw-duration-fast: calc(#{$duration-fast} * var(--mw-motion-scale, 1));
|
|
68
|
+
--mw-duration-base: calc(#{$duration-base} * var(--mw-motion-scale, 1));
|
|
69
|
+
--mw-duration-slow: calc(#{$duration-slow} * var(--mw-motion-scale, 1));
|
|
70
|
+
--mw-duration-slower: calc(#{$duration-slower} * var(--mw-motion-scale, 1));
|
|
71
|
+
--mw-duration-zoom: calc(#{$duration-zoom} * var(--mw-motion-scale, 1));
|
|
47
72
|
--mw-ease-out: #{$ease-out};
|
|
48
73
|
--mw-ease-in-out: #{$ease-in-out};
|
|
49
74
|
--mw-ease-spring: #{$ease-spring};
|
|
@@ -67,6 +92,12 @@
|
|
|
67
92
|
--mw-control-font-lg: #{$control-font-lg};
|
|
68
93
|
--mw-control-line-height: #{$control-line-height};
|
|
69
94
|
|
|
95
|
+
// One ramp for both themes - the theme reaches it through the two shadow
|
|
96
|
+
// tokens the levels are built from
|
|
97
|
+
@each $name, $value in $elevations {
|
|
98
|
+
--mw-#{$name}: #{$value};
|
|
99
|
+
}
|
|
100
|
+
|
|
70
101
|
// Focus ring
|
|
71
102
|
--mw-focus-ring-width: #{$focus-ring-width};
|
|
72
103
|
--mw-focus-ring-offset: #{$focus-ring-offset};
|
|
@@ -75,7 +106,9 @@
|
|
|
75
106
|
--mw-focus-halo-opacity: #{$focus-halo-opacity};
|
|
76
107
|
|
|
77
108
|
// The only way the chrome the browser draws itself follows the theme:
|
|
78
|
-
// scrollbars, the select popup, the date picker panel, number spinners
|
|
109
|
+
// scrollbars, the select popup, the date picker panel, number spinners.
|
|
110
|
+
// Under `switchable` this is also what light-dark() reads, so the pair below
|
|
111
|
+
// does double duty and the @supports block widens it to `light dark`.
|
|
79
112
|
@if $mw-theme-mode == 'light' {
|
|
80
113
|
color-scheme: light;
|
|
81
114
|
} @else {
|
|
@@ -139,6 +172,62 @@
|
|
|
139
172
|
|
|
140
173
|
color-scheme: light;
|
|
141
174
|
}
|
|
175
|
+
|
|
176
|
+
// The same two themes, declared once instead of twice. A custom property
|
|
177
|
+
// holding light-dark() is resolved where it is *used*, against that
|
|
178
|
+
// element's color-scheme - so one declaration on :root serves both, and a
|
|
179
|
+
// class further down switches every token by changing nothing but the
|
|
180
|
+
// scheme. The page then also follows the OS out of the box, which the
|
|
181
|
+
// class-only route could never do: before the script had run, a machine set
|
|
182
|
+
// to light still got the dark theme for a frame.
|
|
183
|
+
//
|
|
184
|
+
// The block above stays as the fallback for Chrome 119-122 and Safari
|
|
185
|
+
// 16.4-17.4, which are inside the declared browserslist but predate
|
|
186
|
+
// light-dark(). Drop it, and the two class rules with it, once the support
|
|
187
|
+
// floor moves past Chrome 123 / Safari 17.5.
|
|
188
|
+
@supports (color: light-dark(#000, #fff)) {
|
|
189
|
+
:root,
|
|
190
|
+
.mw-theme-light {
|
|
191
|
+
@each $name, $value in $_theme-colors {
|
|
192
|
+
--mw-#{$name}: light-dark(
|
|
193
|
+
var(--mw-light-#{$name}),
|
|
194
|
+
var(--mw-dark-#{$name})
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
:root {
|
|
200
|
+
color-scheme: light dark;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Not a color, so it cannot ride along above - it follows the OS here and
|
|
204
|
+
// is overridden by whichever class the toggle puts on the body
|
|
205
|
+
@media (prefers-color-scheme: light) {
|
|
206
|
+
:root {
|
|
207
|
+
@each $name in $theme-non-colors {
|
|
208
|
+
--mw-#{$name}: var(--mw-light-#{$name});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.mw-theme-light {
|
|
214
|
+
color-scheme: light;
|
|
215
|
+
|
|
216
|
+
@each $name in $theme-non-colors {
|
|
217
|
+
--mw-#{$name}: var(--mw-light-#{$name});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Only reachable once the OS says light: the explicit counterpart to the
|
|
222
|
+
// class above, so a reader who wants dark on a light machine gets it
|
|
223
|
+
.mw-theme-dark {
|
|
224
|
+
color-scheme: dark;
|
|
225
|
+
|
|
226
|
+
@each $name in $theme-non-colors {
|
|
227
|
+
--mw-#{$name}: var(--mw-dark-#{$name});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
142
231
|
}
|
|
143
232
|
|
|
144
233
|
// Form controls are deliberately light in both themes - see
|
|
@@ -58,14 +58,17 @@
|
|
|
58
58
|
overflow: hidden;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// Named, so the query cannot resolve to a container further up the tree.
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
// Named, so the query cannot resolve to a container further up the tree.
|
|
62
|
+
// Every card, not only the ones with a footer: the title, the padding and
|
|
63
|
+
// the image height all used to step on a *viewport* breakpoint, so a card in
|
|
64
|
+
// a three-column grid on a wide desktop took the same 1.5rem title as the
|
|
65
|
+
// full-width one on a phone while being a third of its width.
|
|
66
|
+
//
|
|
67
|
+
// `width: 100%` is what makes it safe: inline-size containment computes the
|
|
68
|
+
// width as if the card were empty, so a card that sized to its content would
|
|
69
|
+
// otherwise collapse to its border.
|
|
70
|
+
width: 100%;
|
|
71
|
+
container: mw-card / inline-size;
|
|
69
72
|
|
|
70
73
|
// The arc on the two round corners is the one mark that makes a card look
|
|
71
74
|
// like this framework and not like a rounded box, so it is what the hover
|
|
@@ -168,6 +171,9 @@
|
|
|
168
171
|
&-lg {
|
|
169
172
|
--mw-card-img-height: 260px;
|
|
170
173
|
|
|
174
|
+
// On the window and not on the card: this declaration sits on the card,
|
|
175
|
+
// which is the container, and a container cannot query itself. The height
|
|
176
|
+
// of a picture is a question of room on screen anyway.
|
|
171
177
|
@include media-up('sm') {
|
|
172
178
|
--mw-card-img-height: 340px;
|
|
173
179
|
}
|
|
@@ -176,6 +182,9 @@
|
|
|
176
182
|
&-xl {
|
|
177
183
|
--mw-card-img-height: 340px;
|
|
178
184
|
|
|
185
|
+
// On the window and not on the card: this declaration sits on the card,
|
|
186
|
+
// which is the container, and a container cannot query itself. The height
|
|
187
|
+
// of a picture is a question of room on screen anyway.
|
|
179
188
|
@include media-up('sm') {
|
|
180
189
|
--mw-card-img-height: 480px;
|
|
181
190
|
}
|
|
@@ -187,7 +196,7 @@
|
|
|
187
196
|
flex-grow: 1;
|
|
188
197
|
padding: spacing('6') spacing('4') spacing('5');
|
|
189
198
|
|
|
190
|
-
@
|
|
199
|
+
@container mw-card (width >= #{$mw-card-wide}) {
|
|
191
200
|
padding: spacing('7') spacing('5') spacing('6');
|
|
192
201
|
}
|
|
193
202
|
|
|
@@ -204,7 +213,7 @@
|
|
|
204
213
|
&-title {
|
|
205
214
|
margin-bottom: spacing('3');
|
|
206
215
|
font-size: font-size('lg');
|
|
207
|
-
@
|
|
216
|
+
@container mw-card (width >= #{$mw-card-wide}) {
|
|
208
217
|
font-size: font-size('xl');
|
|
209
218
|
}
|
|
210
219
|
|
|
@@ -245,7 +254,7 @@
|
|
|
245
254
|
line-height: 1.2;
|
|
246
255
|
font-style: italic;
|
|
247
256
|
color: var(--mw-text-muted-color);
|
|
248
|
-
@
|
|
257
|
+
@container mw-card (width >= #{$mw-card-wide}) {
|
|
249
258
|
margin-bottom: spacing('3');
|
|
250
259
|
}
|
|
251
260
|
}
|
|
@@ -253,7 +262,7 @@
|
|
|
253
262
|
&-text {
|
|
254
263
|
margin: spacing('2') spacing('0') spacing('4') spacing('0');
|
|
255
264
|
color: var(--mw-text-color);
|
|
256
|
-
@
|
|
265
|
+
@container mw-card (width <= #{$mw-card-narrow}) {
|
|
257
266
|
margin-bottom: spacing('2');
|
|
258
267
|
}
|
|
259
268
|
}
|
|
@@ -285,7 +294,7 @@
|
|
|
285
294
|
// Stacked once the card is narrow, not once the window is - a card in a
|
|
286
295
|
// three-column grid on a wide desktop is as narrow as the same card on a
|
|
287
296
|
// phone and used to keep its actions in a cramped row anyway.
|
|
288
|
-
@container mw-card (
|
|
297
|
+
@container mw-card (width <= #{$mw-card-narrow}) {
|
|
289
298
|
flex-direction: column;
|
|
290
299
|
align-items: stretch;
|
|
291
300
|
gap: spacing('2');
|
|
@@ -353,7 +362,7 @@
|
|
|
353
362
|
justify-content: center;
|
|
354
363
|
margin: spacing('0');
|
|
355
364
|
|
|
356
|
-
@
|
|
365
|
+
@container mw-card (width <= #{$mw-card-narrow}) {
|
|
357
366
|
font-size: font-size('sm');
|
|
358
367
|
}
|
|
359
368
|
}
|
|
@@ -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
|
}
|