maverick-wave 5.12.0 → 5.14.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 (33) hide show
  1. package/.claude/skills/mw-maverick-wave/SKILL.md +63 -8
  2. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +6 -3
  3. package/.claude/skills/mw-maverick-wave/references/components.md +41 -27
  4. package/.claude/skills/mw-maverick-wave/references/javascript.md +27 -26
  5. package/.claude/skills/mw-maverick-wave/references/layout.md +20 -8
  6. package/.claude/skills/mw-maverick-wave/references/theming.md +59 -4
  7. package/CHANGELOG.md +19 -0
  8. package/README.md +45 -5
  9. package/index.html +5 -4
  10. package/maverick-wave.min.css +4 -4
  11. package/maverick-wave.min.js +1 -1
  12. package/package.json +2 -2
  13. package/src/js/main.js +67 -20
  14. package/src/partials/buttons-container.html +2 -1
  15. package/src/partials/get-started-container.html +52 -3
  16. package/src/partials/parallax-container.html +8 -5
  17. package/src/partials/tabs-container.html +1 -1
  18. package/src/partials/typography-container.html +3 -0
  19. package/src/partials/utilities-container.html +29 -0
  20. package/src/scss/abstracts/_functions.scss +50 -0
  21. package/src/scss/abstracts/_mixins.scss +33 -0
  22. package/src/scss/base/_typography.scss +4 -21
  23. package/src/scss/components/_buttons.scss +1 -1
  24. package/src/scss/components/_content-slider.scss +16 -8
  25. package/src/scss/components/_dropdown.scss +1 -1
  26. package/src/scss/components/_gallery.scss +13 -2
  27. package/src/scss/components/_lang-switch.scss +1 -1
  28. package/src/scss/components/_segmented.scss +4 -0
  29. package/src/scss/layout/_header.scss +3 -2
  30. package/src/scss/layout/_main.scss +87 -8
  31. package/src/scss/layout/_parallax.scss +15 -1
  32. package/src/scss/layout/_section.scss +2 -10
  33. package/src/scss/utilities/_touch-targets.scss +22 -16
@@ -9,21 +9,29 @@
9
9
  border-radius: radius('md');
10
10
  overflow: hidden;
11
11
 
12
+ // The slider caps at 600px, so past a tablet the viewport no longer says how
13
+ // much room it has. Named, so a query cannot resolve to a container above.
14
+ container: mw-slider / inline-size;
15
+
12
16
  // Heights variables for consistency
13
17
  $height-lg: 370px;
14
18
  $height-md: 320px;
15
19
  $height-sm: 250px;
16
20
 
21
+ // The slider widths the old viewport steps landed on, not the breakpoints
22
+ $wide: 560px;
23
+ $mid: 420px;
24
+
17
25
  &-base {
18
26
  position: relative;
19
27
  width: 100%;
20
28
  height: $height-sm;
21
29
 
22
- @include media-up('sm') {
30
+ @container mw-slider (width >= #{$mid}) {
23
31
  height: $height-md;
24
32
  }
25
33
 
26
- @include media-up('md') {
34
+ @container mw-slider (width >= #{$wide}) {
27
35
  height: $height-lg;
28
36
  }
29
37
  }
@@ -43,11 +51,11 @@
43
51
  height: $height-sm;
44
52
  pointer-events: none;
45
53
 
46
- @include media-up('sm') {
54
+ @container mw-slider (width >= #{$mid}) {
47
55
  height: $height-md;
48
56
  }
49
57
 
50
- @include media-up('md') {
58
+ @container mw-slider (width >= #{$wide}) {
51
59
  height: $height-lg;
52
60
  }
53
61
  }
@@ -86,11 +94,11 @@
86
94
  z-index: z-index('menu');
87
95
  width: 100%;
88
96
 
89
- @include media-up('sm') {
97
+ @container mw-slider (width >= #{$mid}) {
90
98
  grid-template-columns: repeat(3, 1fr);
91
99
  }
92
100
 
93
- @include media-up('md') {
101
+ @container mw-slider (width >= #{$wide}) {
94
102
  gap: spacing('3');
95
103
  padding: spacing('3');
96
104
  }
@@ -106,11 +114,11 @@
106
114
  z-index: z-index('menu');
107
115
  width: 100%;
108
116
 
109
- @include media-up('sm') {
117
+ @container mw-slider (width >= #{$mid}) {
110
118
  grid-template-columns: repeat(2, 1fr);
111
119
  }
112
120
 
113
- @include media-up('md') {
121
+ @container mw-slider (width >= #{$wide}) {
114
122
  gap: spacing('3');
115
123
  padding: spacing('3');
116
124
  }
@@ -257,7 +257,7 @@
257
257
  // A menu opened by a thumb needs rows a thumb can hit, and the room to show
258
258
  // them - so it takes the trigger's full width rather than a 200px column
259
259
  // floating beside it.
260
- @media (pointer: coarse), (width <= #{breakpoint('sm')}) {
260
+ @include touch-context('sm') {
261
261
  .mw-dropdown-menu {
262
262
  min-width: max(100%, 240px);
263
263
  }
@@ -1,9 +1,20 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
3
  @layer mw.components {
4
+ // Thresholds are the wrapper widths the old viewport steps landed on once the
5
+ // container gutter is taken off, not the breakpoints themselves.
6
+ $wide: 688px;
7
+ $narrow: 512px;
8
+
4
9
  .mw-gallery-container {
5
10
  max-width: 1100px;
11
+ // Inline-size containment computes the width as if the element were empty,
12
+ // so one dropped into a flex or grid slot would shrink to nothing without it
13
+ width: 100%;
6
14
  margin: spacing('1') auto;
15
+ // The wrapper and not .mw-gallery: the description and the dots are its
16
+ // siblings, and a container query only reaches descendants
17
+ container: mw-gallery / inline-size;
7
18
  }
8
19
 
9
20
  .mw-gallery {
@@ -119,7 +130,7 @@
119
130
  }
120
131
  }
121
132
 
122
- @include media-down('md') {
133
+ @container mw-gallery (width < #{$wide}) {
123
134
  .mw-gallery {
124
135
  &-desc {
125
136
  font-size: font-size('sm');
@@ -134,7 +145,7 @@
134
145
  }
135
146
  }
136
147
 
137
- @include media-down('sm') {
148
+ @container mw-gallery (width < #{$narrow}) {
138
149
  .mw-gallery {
139
150
  &-slide {
140
151
  aspect-ratio: 4 / 3;
@@ -52,7 +52,7 @@
52
52
  .mw-lang-switch .mw-dropdown-menu {
53
53
  min-width: 170px;
54
54
 
55
- @media (pointer: coarse), (width <= #{breakpoint('sm')}) {
55
+ @include touch-context('sm') {
56
56
  min-width: max(100%, 240px);
57
57
  }
58
58
  }
@@ -92,6 +92,10 @@
92
92
  // the width of the shortest, so the items follow their own content instead.
93
93
  .mw-segmented-auto {
94
94
  width: auto;
95
+ // Shrink-to-fit sizes this off the labels, which on a 320px screen is wider
96
+ // than the screen. The items already carry `min-width: 0` and truncate, so
97
+ // the cap only ever bites where the row genuinely has no room.
98
+ max-width: 100%;
95
99
 
96
100
  .mw-segmented-item {
97
101
  flex: 0 1 auto;
@@ -231,9 +231,10 @@
231
231
  // Keeps a rubber-band scroll inside the panel from moving the page
232
232
  overscroll-behavior: contain;
233
233
  padding: calc(
234
- var(--mw-header-height) + #{spacing('6')} + env(safe-area-inset-top)
234
+ var(--mw-header-height) + #{spacing('6')} +
235
+ env(safe-area-inset-top, 0px)
235
236
  )
236
- spacing('4') calc(#{spacing('6')} + env(safe-area-inset-bottom));
237
+ spacing('4') calc(#{spacing('6')} + env(safe-area-inset-bottom, 0px));
237
238
  }
238
239
 
239
240
  .mw-navbar-item {
@@ -1,3 +1,4 @@
1
+ @use 'sass:math';
1
2
  @use '../abstracts' as *;
2
3
 
3
4
  @layer mw.layout {
@@ -268,10 +269,40 @@
268
269
  // scroll on a phone. Hence `allow-discrete` and the starting style.
269
270
  $_mw-tooltip-arrow: 6px;
270
271
  $_mw-tooltip-gap: 8px;
272
+ // A square turned 45deg rather than a border triangle: it reads the same from
273
+ // above and from below, so the second placement costs no second arrow and a
274
+ // flip never has to repaint anything. The diagonal carries the visible width,
275
+ // so the side is the visible height doubled over root two.
276
+ $_mw-tooltip-arrow-side: math.div($_mw-tooltip-arrow * 2, math.sqrt(2));
277
+ // Half a side short of where the triangle's edge sat, so the turned square is
278
+ // centred on the bubble's edge and only its outer half shows. The inner half
279
+ // lands on the bubble in the same colour, which is what makes it read as one
280
+ // shape rather than a diamond stuck to a box.
281
+ $_mw-tooltip-arrow-inset: $_mw-tooltip-gap +
282
+ $_mw-tooltip-arrow - math.div($_mw-tooltip-arrow-side, 2);
271
283
 
272
284
  [data-tooltip] {
273
285
  position: relative;
274
286
 
287
+ // The placement API: the text is the attribute, the side is a class. Both
288
+ // axes are custom properties rather than rules, so `below` and `end` compose
289
+ // without a rule per pairing - and the arrow reads only the block axis,
290
+ // because it points at the trigger whichever way the bubble is aligned.
291
+ --_mw-tooltip-block: block-start;
292
+ --_mw-tooltip-inline: center;
293
+
294
+ &.mw-tooltip-below {
295
+ --_mw-tooltip-block: block-end;
296
+ }
297
+
298
+ &.mw-tooltip-end {
299
+ --_mw-tooltip-inline: span-inline-start;
300
+ }
301
+
302
+ &.mw-tooltip-start {
303
+ --_mw-tooltip-inline: span-inline-end;
304
+ }
305
+
275
306
  &::before {
276
307
  content: attr(data-tooltip);
277
308
  position: absolute;
@@ -301,11 +332,11 @@
301
332
  position: absolute;
302
333
  bottom: 100%;
303
334
  left: 50%;
304
- transform: translateX(-50%);
305
- margin-bottom: $_mw-tooltip-gap;
306
- border: $_mw-tooltip-arrow solid transparent;
307
- border-bottom-width: 0;
308
- border-top-color: var(--mw-gray-color);
335
+ transform: translateX(-50%) rotate(45deg);
336
+ margin-bottom: $_mw-tooltip-arrow-inset;
337
+ width: $_mw-tooltip-arrow-side;
338
+ height: $_mw-tooltip-arrow-side;
339
+ background: var(--mw-gray-color);
309
340
  opacity: 0;
310
341
  display: none;
311
342
  transition:
@@ -314,6 +345,42 @@
314
345
  z-index: z-index('tooltip');
315
346
  }
316
347
 
348
+ // What the modifiers mean without anchor positioning. Kept in its own
349
+ // `@supports not` rather than reset inside the anchored path: a modifier
350
+ // carries a class more weight than such a reset, so the two would fight
351
+ // instead of replace - which is exactly how the absolute placement used to
352
+ // survive into the anchored path and land the bubble on its own trigger.
353
+ @supports not (position-area: block-start) {
354
+ &.mw-tooltip-below {
355
+ &::before {
356
+ top: 100%;
357
+ bottom: auto;
358
+ margin-top: $_mw-tooltip-gap + $_mw-tooltip-arrow;
359
+ margin-bottom: 0;
360
+ }
361
+
362
+ &::after {
363
+ top: 100%;
364
+ bottom: auto;
365
+ margin-top: $_mw-tooltip-arrow-inset;
366
+ margin-bottom: 0;
367
+ }
368
+ }
369
+
370
+ // Only the bubble moves - the arrow stays on the trigger it points at.
371
+ &.mw-tooltip-end::before {
372
+ left: auto;
373
+ right: 0;
374
+ transform: none;
375
+ }
376
+
377
+ &.mw-tooltip-start::before {
378
+ left: 0;
379
+ right: auto;
380
+ transform: none;
381
+ }
382
+ }
383
+
317
384
  &:hover::before,
318
385
  &:hover::after,
319
386
  &:focus-visible::before,
@@ -345,19 +412,31 @@
345
412
  // it attached. No `anchor-name` needed - a pseudo element is implicitly
346
413
  // anchored to the element it comes from.
347
414
  //
348
- // No `position-try-fallbacks: flip-block` on purpose: the arrow is a border
349
- // triangle and would still point up, into a bubble that flipped below.
415
+ // Still no `position-try-fallbacks: flip-block`, and no longer because of the
416
+ // arrow - the turned square survives a flip. Bubble and arrow are two
417
+ // separately anchored boxes, so each would decide on its own whether it fits,
418
+ // and the tiny one nearly always does: the label flips, the arrow stays.
350
419
  @supports (position-area: block-start) {
351
420
  &::before,
352
421
  &::after {
353
422
  position: fixed;
354
- position-area: block-start;
355
423
  // The absolute placement these replace - left as-is they fight the
356
424
  // position-area, which resolves the inset properties itself.
357
425
  bottom: auto;
358
426
  left: auto;
427
+ top: auto;
428
+ }
429
+
430
+ &::before {
431
+ position-area: var(--_mw-tooltip-block) var(--_mw-tooltip-inline);
359
432
  transform: none;
360
433
  }
434
+
435
+ // The turn stays - it is the arrow's shape, not its placement.
436
+ &::after {
437
+ position-area: var(--_mw-tooltip-block) center;
438
+ transform: rotate(45deg);
439
+ }
361
440
  }
362
441
  }
363
442
  }
@@ -24,7 +24,7 @@
24
24
  position: relative;
25
25
  display: flex;
26
26
  align-items: center;
27
- min-height: 42vh;
27
+ min-height: 60vh;
28
28
  // `clip` and never `hidden`: hidden makes this a scroll container, and the
29
29
  // view() timeline below would then resolve against this box instead of the
30
30
  // viewport - the layer keeps its overhang and never moves
@@ -148,6 +148,20 @@
148
148
  background: var(--mw-page-background);
149
149
  }
150
150
 
151
+ // `+` and not `~` like the two rules above: only the section that does the
152
+ // overtaking carries the edge, on every one after it the shadow would read as
153
+ // a seam across the page. Fixed values and not elevation(): this shadow lands
154
+ // on the picture rather than on a theme surface, and the light theme's 21%
155
+ // near layer disappears there - same reason mw-parallax-dimmed below
156
+ // hardcodes its brightness and its text-shadow.
157
+ :where(
158
+ :is(.mw-parallax-sticky, .mw-parallax-slow) + *:not(.mw-parallax-sticky)
159
+ ) {
160
+ box-shadow:
161
+ 0 -6px 12px rgb(0 0 0 / 45%),
162
+ 0 -20px 44px rgb(0 0 0 / 26%);
163
+ }
164
+
151
165
  // Ink on a photograph, the same pair the hero uses: the picture goes down, the
152
166
  // text goes light. A band without content on it needs neither.
153
167
  .mw-parallax-dimmed {
@@ -24,16 +24,12 @@
24
24
  }
25
25
 
26
26
  .mw-section-title {
27
- font-size: font-size('2xl');
27
+ font-size: fluid('2xl', '3xl', $to: 'sm');
28
28
  margin-bottom: var(--mw-section-title-gap);
29
29
  text-align: center;
30
30
  position: relative;
31
31
 
32
32
  @include _section-rule(56px, 3px, spacing('-3'));
33
-
34
- @include media-up('sm') {
35
- font-size: font-size('3xl');
36
- }
37
33
  }
38
34
 
39
35
  // The lead line under a section title. Every landing page writes this
@@ -51,7 +47,7 @@
51
47
  }
52
48
 
53
49
  .mw-section-subtitle {
54
- font-size: font-size('xl');
50
+ font-size: fluid('xl', '2xl', $to: 'sm');
55
51
  // The step above a demo gap of spacing('6') - at spacing('8') the boundary
56
52
  // between two components read like the one between two demos
57
53
  margin: spacing('10') spacing('0') spacing('6') spacing('0');
@@ -64,10 +60,6 @@
64
60
  spacing('-2'),
65
61
  var(--mw-secondary-text-color)
66
62
  );
67
-
68
- @include media-up('sm') {
69
- font-size: font-size('2xl');
70
- }
71
63
  }
72
64
 
73
65
  // Sticky, because the strip is the only way back: inside a long section the
@@ -2,10 +2,9 @@
2
2
 
3
3
  @layer mw.utilities {
4
4
  // WCAG 2.5.8 puts the floor at 24x24px, and a floor is not a goal - the compact
5
- // sizes are right under a pointer and too small under a thumb. Coarse *or*
6
- // narrow, because a tablet in landscape is wide and still finger-operated.
5
+ // sizes are right under a pointer and too small under a thumb.
7
6
  // Loaded last, so these win without extra specificity.
8
- @media (pointer: coarse), (width <= #{breakpoint('md')}) {
7
+ @include touch-context {
9
8
  // Padding and line-height add up to ~35px, under the 44px a thumb needs. Only
10
9
  // the height grows: the padding sets the label's relationship to the edges.
11
10
  .mw-btn {
@@ -85,13 +84,7 @@
85
84
  }
86
85
 
87
86
  .mw-gallery-dot {
88
- position: relative;
89
-
90
- &::after {
91
- content: '';
92
- position: absolute;
93
- inset: -7.5px;
94
- }
87
+ @include hit-area(7.5px);
95
88
  }
96
89
 
97
90
  // A range input is as tall as its track, so the whole hit area is 8px and you
@@ -109,14 +102,27 @@
109
102
  // area grows, to 28px - clear of the 24px floor. The extra margin keeps two
110
103
  // neighbouring hit areas from overlapping.
111
104
  .mw-btn-mini {
112
- position: relative;
113
105
  margin-left: spacing('4');
106
+ @include hit-area(5px);
107
+ }
108
+
109
+ // Edit and delete on a ticket. The 32px box stays - a 44px one would push
110
+ // the assignee off the card footer - and the row carries the gap the two
111
+ // grown hit areas need, or they overlap and a tap meant for edit deletes.
112
+ .mw-kanban-card-actions {
113
+ gap: spacing('4');
114
+ }
114
115
 
115
- &::after {
116
- content: '';
117
- position: absolute;
118
- inset: -5px;
119
- }
116
+ .mw-kanban-action {
117
+ @include hit-area(6px);
118
+ }
119
+
120
+ // The box itself and not a hit area over it: half of these carry a
121
+ // `data-tooltip`, which owns both pseudo-elements, so the trick above lands
122
+ // on some of the logos and silently skips the rest.
123
+ .mw-techstack-item-sm {
124
+ min-width: 2.5rem;
125
+ min-height: 2.5rem;
120
126
  }
121
127
  }
122
128