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
@@ -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
- @include media-up('sm') {
134
+ @container mw-tile (width >= #{$_mw-tile-narrow}) {
120
135
  font-size: font-size('xl');
121
136
  }
122
137
 
123
- @include media-up('md') {
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
- @include media-up('md') {
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
- @include media-up('sm') {
212
+ @container mw-tile (width >= #{$_mw-tile-narrow}) {
198
213
  font-size: font-size('2xl');
199
214
  }
200
215
 
201
- @include media-up('md') {
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
- @include media-down('sm') {
218
- .mw-tile {
219
- padding: spacing('4');
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
- &-img {
223
- width: 60px;
224
- height: 60px;
225
- margin: spacing('0') auto spacing('2');
226
- }
242
+ .mw-tile-body {
243
+ margin-bottom: spacing('2');
244
+ }
227
245
 
228
- &-body {
229
- margin-bottom: spacing('2');
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
- @include media-down('sm') {
197
- width: 100%;
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;
@@ -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
@@ -151,13 +160,9 @@
151
160
  // is what lets every depth variant above - and an inline
152
161
  // `--mw-parallax-depth` - go on working untouched.
153
162
  //
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.
163
+ // 0 puts the layer at +depth and 1 at -depth, the two ends of the keyframes.
157
164
  @media (prefers-reduced-motion: no-preference) {
158
165
  .mw-parallax-driven {
159
- --mw-parallax-progress: 0.5;
160
-
161
166
  transform: translate3d(
162
167
  0,
163
168
  calc(var(--mw-parallax-depth) * (1 - 2 * var(--mw-parallax-progress))),
@@ -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
  }
@@ -5,7 +5,7 @@
5
5
  // sizes are right under a pointer and too small under a thumb. Coarse *or*
6
6
  // narrow, because a tablet in landscape is wide and still finger-operated.
7
7
  // Loaded last, so these win without extra specificity.
8
- @media (pointer: coarse), (max-width: #{breakpoint('md')}) {
8
+ @media (pointer: coarse), (width <= #{breakpoint('md')}) {
9
9
  // Padding and line-height add up to ~35px, under the 44px a thumb needs. Only
10
10
  // the height grows: the padding sets the label's relationship to the edges.
11
11
  .mw-btn {
@@ -31,17 +31,30 @@
31
31
  // the modal, which genuinely float above content and keep their shadow - in a
32
32
  // flat page they are the only two things that should cast one.
33
33
  //
34
- // The second selector is not optional: elevation is a theme alias, and the
35
- // light theme re-declares it on <body>. A value written on :root alone is
36
- // shadowed for the whole body subtree, so the switch would do nothing at all
37
- // in light mode. Any variant that retunes a theme-bound token needs both.
38
- :root.mw-shadows-flat,
39
- :root.mw-shadows-flat .mw-theme-light {
34
+ // One selector, unlike mw-surfaces-flush below: the ramp is no longer declared
35
+ // per theme, so there is no copy further down the tree to shadow this one. The
36
+ // theme reaches it through --mw-shadow-near/-far instead - see elevation().
37
+ :root.mw-shadows-flat {
40
38
  --mw-elevation-1: none;
41
39
  --mw-elevation-2: none;
42
40
  --mw-elevation-3: none;
43
41
  }
44
42
 
43
+ // Cards, panels and the footer give up their own tone and sit flush on the
44
+ // page, told apart by their line alone. That line then has to carry them:
45
+ // --mw-border is a neighbouring shade of the surface and reads as nothing once
46
+ // the surface *is* the page, so it is redrawn from the ink instead.
47
+ //
48
+ // Theme-bound tokens, and those *are* re-declared on <body> when the toggle puts
49
+ // .mw-theme-light there - a value written on :root alone would be shadowed for
50
+ // the whole body subtree and the switch would do nothing in light mode.
51
+ :root.mw-surfaces-flush,
52
+ :root.mw-surfaces-flush .mw-theme-light {
53
+ --mw-card-background: var(--mw-page-background);
54
+ --mw-footer-background: var(--mw-page-background);
55
+ --mw-border: color-mix(in srgb, var(--mw-text-color) 20%, transparent);
56
+ }
57
+
45
58
  // The diagonal hatch behind an alternating section, dropped. Written on the
46
59
  // section and not as a token on :root: --mw-page-background is a theme alias
47
60
  // that only becomes the light one further down the tree, so a copy taken up
@@ -57,6 +70,35 @@
57
70
  border-radius: 999px;
58
71
  }
59
72
 
73
+ // The other end of the same axis. Not --mw-radius-scale, which squares off the
74
+ // whole page - a page can want its cards round and its buttons cut.
75
+ :root.mw-btn-square .mw-btn {
76
+ border-radius: 0;
77
+ }
78
+
79
+ // A raised edge under the fill that the button sinks onto when pressed. The
80
+ // four solid variants only: outline, ghost and link have no fill to darken.
81
+ //
82
+ // Mixed toward black rather than taken from --mw-*-color-hover, which moves
83
+ // toward the light in one theme and away from it in the other - an edge has to
84
+ // be darker than its face in both.
85
+ :root.mw-btn-tactile {
86
+ @each $name in ('primary', 'secondary', 'danger', 'success') {
87
+ .mw-btn-#{$name} {
88
+ box-shadow: 0
89
+ 3px
90
+ 0
91
+ color-mix(in srgb, var(--mw-#{$name}-color) 72%, #000);
92
+
93
+ // Travels the full height of the edge, where the base button moves 1px
94
+ &:active:not(:disabled) {
95
+ transform: translateY(3px);
96
+ box-shadow: none;
97
+ }
98
+ }
99
+ }
100
+ }
101
+
60
102
  // Tinted glass: a translucent wash with a vertical ramp and a specular line
61
103
  // along the top edge, which is what gives the button its thickness.
62
104
  //
@@ -117,4 +159,71 @@
117
159
  letter-spacing: 0.045em;
118
160
  }
119
161
  }
162
+
163
+ // The other axis to --mw-root-font-size, which zooms the whole page: there
164
+ // the layout is the same layout, larger. This moves the padding inside a
165
+ // surface and leaves the type alone, so a dense screen reads as well as a
166
+ // roomy one and only fits more on.
167
+ //
168
+ // --mw-section-padding-block is deliberately untouched: section spacing is
169
+ // its own switch, and both writing it would have the panel print a setup that
170
+ // contradicts itself. Steps along the spacing scale rather than free values,
171
+ // because that scale is what every other measure is built from - two of them
172
+ // down for compact, where the point is to fit more on screen, one up for
173
+ // roomy, where a second step would only look loose.
174
+ :root.mw-density-compact {
175
+ --mw-control-height-sm: 1.5rem;
176
+ --mw-control-height: 1.75rem;
177
+ --mw-control-height-lg: 2rem;
178
+
179
+ .mw-card-body {
180
+ padding: spacing('4') spacing('2') spacing('3');
181
+
182
+ @container mw-card (width >= #{$mw-card-wide}) {
183
+ padding: spacing('5') spacing('3') spacing('4');
184
+ }
185
+ }
186
+
187
+ .mw-panel-header,
188
+ .mw-panel-body {
189
+ padding: spacing('2') spacing('3');
190
+ }
191
+ }
192
+
193
+ :root.mw-density-roomy {
194
+ --mw-control-height-sm: 2.25rem;
195
+ --mw-control-height: 2.5rem;
196
+ --mw-control-height-lg: 2.75rem;
197
+
198
+ .mw-card-body {
199
+ padding: spacing('7') spacing('5') spacing('6');
200
+
201
+ @container mw-card (width >= #{$mw-card-wide}) {
202
+ padding: spacing('8') spacing('6') spacing('7');
203
+ }
204
+ }
205
+
206
+ .mw-panel-header,
207
+ .mw-panel-body {
208
+ padding: spacing('5') spacing('6');
209
+ }
210
+ }
211
+
212
+ :root.mw-links-underline {
213
+ .mw-link,
214
+ .mw-link-muted,
215
+ .mw-btn-link {
216
+ // The longhand, because the `text-decoration` shorthand resets
217
+ // text-decoration-thickness along with it and would drop the 1.5px
218
+ // link-look() draws the line at.
219
+ text-decoration-line: underline;
220
+
221
+ // The underline was the hover signal and is now the resting state, so the
222
+ // weight takes over: .mw-link carries no colour change of its own and
223
+ // would otherwise go completely dead under the pointer.
224
+ &:hover:not(:disabled) {
225
+ text-decoration-thickness: 3px;
226
+ }
227
+ }
228
+ }
120
229
  }