maverick-wave 5.25.0 → 5.27.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 (81) hide show
  1. package/.claude/skills/mw-maverick-wave/SKILL.md +9 -1
  2. package/.claude/skills/mw-maverick-wave/references/components.md +13 -6
  3. package/.claude/skills/mw-maverick-wave/references/layout.md +18 -12
  4. package/.claude/skills/mw-maverick-wave/references/theming.md +15 -10
  5. package/CHANGELOG.md +12 -0
  6. package/README.md +2 -2
  7. package/maverick-wave.min.css +8 -6
  8. package/package.json +2 -2
  9. package/scripts/verify.js +10 -1
  10. package/src/partials/accordions-container.html +1 -1
  11. package/src/partials/alerts-container.html +1 -1
  12. package/src/partials/announcement-container.html +1 -1
  13. package/src/partials/avatars-container.html +1 -1
  14. package/src/partials/badges-container.html +1 -1
  15. package/src/partials/blog-posts-container.html +2 -2
  16. package/src/partials/breadcrumbs-container.html +1 -1
  17. package/src/partials/button-bar-container.html +1 -1
  18. package/src/partials/buttons-container.html +1 -1
  19. package/src/partials/calendar-container.html +1 -1
  20. package/src/partials/cards-container.html +1 -1
  21. package/src/partials/code-container.html +1 -1
  22. package/src/partials/colors-container.html +3 -3
  23. package/src/partials/coming-soon-container.html +1 -1
  24. package/src/partials/contact-container.html +1 -1
  25. package/src/partials/divider-container.html +1 -1
  26. package/src/partials/dropdown-container.html +1 -1
  27. package/src/partials/empty-state-container.html +1 -1
  28. package/src/partials/form-container.html +1 -1
  29. package/src/partials/form-elements-container.html +1 -1
  30. package/src/partials/form-field-container.html +1 -1
  31. package/src/partials/gallery-container.html +1 -1
  32. package/src/partials/grid-container.html +1 -1
  33. package/src/partials/header-utilities-container.html +2 -2
  34. package/src/partials/html-lists-container.html +1 -1
  35. package/src/partials/info-container.html +1 -1
  36. package/src/partials/input-group-container.html +1 -1
  37. package/src/partials/item-lists-container.html +1 -1
  38. package/src/partials/kanban-container.html +2 -2
  39. package/src/partials/kbd-container.html +1 -1
  40. package/src/partials/leader-row-container.html +2 -2
  41. package/src/partials/login-container.html +2 -2
  42. package/src/partials/lunch-menu-container.html +2 -2
  43. package/src/partials/media-container.html +1 -1
  44. package/src/partials/meta-info-container.html +1 -1
  45. package/src/partials/modals-container.html +1 -1
  46. package/src/partials/page-header-container.html +1 -1
  47. package/src/partials/pagination-container.html +2 -2
  48. package/src/partials/palette-container.html +2 -2
  49. package/src/partials/panels-container.html +2 -2
  50. package/src/partials/parallax-container.html +1 -1
  51. package/src/partials/pricing-container.html +2 -2
  52. package/src/partials/progress-container.html +1 -1
  53. package/src/partials/prose-container.html +1 -1
  54. package/src/partials/ratings-container.html +1 -1
  55. package/src/partials/section-head-container.html +6 -3
  56. package/src/partials/segmented-container.html +1 -1
  57. package/src/partials/skeleton-container.html +1 -1
  58. package/src/partials/spinners-container.html +1 -1
  59. package/src/partials/stepper-container.html +1 -1
  60. package/src/partials/tables-container.html +1 -1
  61. package/src/partials/tabs-container.html +1 -1
  62. package/src/partials/tags-container.html +1 -1
  63. package/src/partials/testimonials-container.html +1 -1
  64. package/src/partials/tiles-container.html +39 -39
  65. package/src/partials/typography-container.html +1 -1
  66. package/src/partials/utilities-container.html +1 -1
  67. package/src/scss/base/_base.scss +37 -11
  68. package/src/scss/base/_reset.scss +7 -1
  69. package/src/scss/components/_announcement.scss +1 -3
  70. package/src/scss/components/_blog-post.scss +5 -4
  71. package/src/scss/components/_cards.scss +2 -2
  72. package/src/scss/components/_contact.scss +5 -3
  73. package/src/scss/components/_localhost-indicator.scss +2 -2
  74. package/src/scss/components/_panels.scss +18 -52
  75. package/src/scss/components/_tabs.scss +2 -2
  76. package/src/scss/components/_tiles.scss +43 -49
  77. package/src/scss/layout/_grid.scss +22 -0
  78. package/src/scss/layout/_header.scss +0 -11
  79. package/src/scss/layout/_home.scss +2 -2
  80. package/src/scss/layout/_section.scss +45 -13
  81. package/src/scss/utilities/_touch-targets.scss +4 -0
@@ -1,51 +1,8 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
3
  @layer mw.components {
4
- .mw-tiles {
5
- width: 100%;
6
- margin: spacing('0') auto;
7
- padding: spacing('0');
8
- display: grid;
9
- gap: spacing('4');
10
- grid-template-columns: 1fr;
11
-
12
- @include media-up('sm') {
13
- grid-template-columns: repeat(2, 1fr);
14
- width: 80%;
15
- padding: spacing('3');
16
- gap: spacing('6');
17
- }
18
-
19
- @include media-up('lg') {
20
- grid-template-columns: repeat(3, 1fr);
21
- }
22
-
23
- // The page gutter is the container's job, a second inset only narrows the tile
24
- &-4col {
25
- @include media-up('sm') {
26
- width: 85%;
27
- grid-template-columns: repeat(2, 1fr);
28
- }
29
-
30
- @include media-up('md') {
31
- grid-template-columns: repeat(3, 1fr);
32
- }
33
-
34
- @include media-up('lg') {
35
- grid-template-columns: repeat(4, 1fr);
36
- }
37
- }
38
-
39
- &-2col {
40
- @include media-up('sm') {
41
- width: 75%;
42
- grid-template-columns: repeat(2, 1fr);
43
- }
44
- }
45
- }
46
-
47
4
  // The tile sizes its type off its own width, not the window's. In
48
- // `.mw-tiles-4col` on a wide desktop a tile is a quarter of 85% of the page -
5
+ // `.mw-tiles-4col` on a wide desktop a tile is a quarter of the container -
49
6
  // narrower than the same tile alone on a phone, where it used to be the one
50
7
  // getting the small type.
51
8
  //
@@ -69,7 +26,6 @@
69
26
  overflow: hidden;
70
27
  display: flex;
71
28
  flex-direction: column;
72
- cursor: pointer;
73
29
 
74
30
  // Ahead of the size modifiers below, which take over from 'sm'
75
31
  @include media-up('sm') {
@@ -90,13 +46,22 @@
90
46
 
91
47
  @include hover-move {
92
48
  transform: translateY(-2px);
49
+
50
+ // The whole tile is the target, unlike a card where the image frame
51
+ // owns its own hover - a tile is one face, not a stack of regions.
52
+ .mw-tile-img img {
53
+ transform: scale(1.05);
54
+ }
93
55
  }
94
56
 
95
57
  // A tile is tapped, so it needs a press of its own - the lift above never
96
- // runs on a finger.
97
- &:active {
98
- transform: scale(0.985);
99
- transition-duration: var(--mw-duration-instant);
58
+ // runs on a finger. Guarded like every other travel in the framework; what
59
+ // is left without it is the latched hover, which is feedback enough.
60
+ @media (prefers-reduced-motion: no-preference) {
61
+ &:active {
62
+ transform: scale(0.985);
63
+ transition-duration: var(--mw-duration-instant);
64
+ }
100
65
  }
101
66
 
102
67
  &-img {
@@ -145,6 +110,13 @@
145
110
  font-size: font-size('sm');
146
111
  line-height: 1.3;
147
112
 
113
+ // The ramp the header takes: a tile filling a phone is a wide
114
+ // container and reads at full size, one squeezed into a desktop
115
+ // column stays small.
116
+ @container mw-tile (width >= #{$_mw-tile-narrow}) {
117
+ font-size: font-size('base');
118
+ }
119
+
148
120
  &:last-child {
149
121
  margin-bottom: spacing('0');
150
122
  }
@@ -186,6 +158,10 @@
186
158
 
187
159
  p {
188
160
  font-size: font-size('xs');
161
+
162
+ @container mw-tile (width >= #{$_mw-tile-narrow}) {
163
+ font-size: font-size('sm');
164
+ }
189
165
  }
190
166
  }
191
167
  }
@@ -225,6 +201,24 @@
225
201
  }
226
202
  }
227
203
 
204
+ // A tile is only a control when the markup makes it one. The look above is a
205
+ // surface and stays on a div; everything a finger or a keyboard needs sits
206
+ // here, where the element itself answers for it - a hand cursor on a div
207
+ // promises a click nothing handles, and a ring on it would never be reached.
208
+ a.mw-tile,
209
+ button.mw-tile {
210
+ cursor: pointer;
211
+ // A button brings its own font, alignment and colour, and the tile's own
212
+ // children set none of the three
213
+ font: inherit;
214
+ color: inherit;
215
+ text-align: inherit;
216
+ text-decoration: none;
217
+
218
+ @include focus-ring;
219
+ @include tappable;
220
+ }
221
+
228
222
  // A cramped tile reads better ranged left whatever the window is doing - a
229
223
  // centred line inside 200px is two words and a gap. Every selector here is a
230
224
  // child: a container cannot answer a query about itself, and the tile *is* the
@@ -122,6 +122,28 @@ $_grid-from: ('sm', 'md', 'lg', 'xl');
122
122
  }
123
123
  }
124
124
 
125
+ // The container half of the tile component. Here and not in `_tiles.scss`,
126
+ // because it is one of the ramps above and a component file cannot reach them.
127
+ //
128
+ // The guard is what makes a modifier replace the bare ramp rather than layer
129
+ // on it: both step at the same breakpoints, so which one holds a given width
130
+ // would otherwise come down to media query order.
131
+ .mw-tiles {
132
+ @extend %grid-base;
133
+ }
134
+
135
+ .mw-tiles:not(.mw-tiles-2col):not(.mw-tiles-4col) {
136
+ @include _column-grid(map.get($_column-steps, 3));
137
+ }
138
+
139
+ .mw-tiles-2col {
140
+ @include _column-grid(map.get($_column-steps, 2));
141
+ }
142
+
143
+ .mw-tiles-4col {
144
+ @include _column-grid(map.get($_column-steps, 4));
145
+ }
146
+
125
147
  .mw-grid-flex {
126
148
  @extend %grid-base;
127
149
 
@@ -518,17 +518,6 @@
518
518
  }
519
519
  }
520
520
 
521
- @keyframes fadeIn {
522
- from {
523
- opacity: 0;
524
- transform: translateY(-10px);
525
- }
526
- to {
527
- opacity: 1;
528
- transform: translateY(0);
529
- }
530
- }
531
-
532
521
  // Profile button - a round avatar until there is room for the signed-in name
533
522
  // beside it
534
523
  .mw-profile-btn {
@@ -20,7 +20,7 @@
20
20
 
21
21
  &-fade {
22
22
  @extend .mw-home-content;
23
- animation: fadeIn var(--mw-duration-slower) var(--mw-ease-out);
23
+ animation: mw-home-fade var(--mw-duration-slower) var(--mw-ease-out);
24
24
  }
25
25
  }
26
26
 
@@ -47,7 +47,7 @@
47
47
  }
48
48
  }
49
49
 
50
- @keyframes fadeIn {
50
+ @keyframes mw-home-fade {
51
51
  from {
52
52
  opacity: 0;
53
53
  transform: translateX(-8px) translateY(-23px);
@@ -17,6 +17,12 @@
17
17
  // every background the framework paints.
18
18
  border-top: 2px solid var(--mw-border);
19
19
 
20
+ // A head without a lead is a rule and one line of type, and the gap sized
21
+ // to follow a paragraph reads as a hole under it
22
+ &:not(:has(.mw-section-head-intro)) {
23
+ --mw-section-head-gap: calc(var(--mw-section-head-step) * 1.3);
24
+ }
25
+
20
26
  // The rule stays quiet so it reads as a boundary and not as a border, and
21
27
  // quiet is still hard to see on a patterned band. Hence the mark:
22
28
  // a short, thicker piece of the accent colour sitting on the line at the
@@ -49,7 +55,7 @@
49
55
  // is left - four fifths of a phone is a column nothing fits in.
50
56
  .mw-section-head-intro {
51
57
  max-width: 75ch;
52
- margin: spacing('4') spacing('0') spacing('0');
58
+ margin: calc(var(--mw-section-head-step) * 0.56) spacing('0') spacing('0');
53
59
  font-size: font-size('md');
54
60
  line-height: 1.6;
55
61
  color: var(--mw-text-muted-color);
@@ -113,9 +119,11 @@
113
119
  // levels read as a hierarchy rather than as two of the same thing.
114
120
  .mw-section-subtitle {
115
121
  font-size: fluid('xl', '2xl', $to: 'sm');
116
- // The section boundary is 56px on a phone, so the component boundary sits
117
- // with it or the two stop being distinguishable
118
- margin: spacing('6') spacing('0') spacing('4') spacing('0');
122
+ // The section boundary steps down on a phone, so the component boundary
123
+ // sits with it or the two stop being distinguishable
124
+ margin-block: var(--mw-section-head-step)
125
+ calc(var(--mw-section-head-step) * 0.56);
126
+ margin-inline: spacing('0');
119
127
  position: relative;
120
128
 
121
129
  &::after {
@@ -130,6 +138,21 @@
130
138
  }
131
139
  }
132
140
 
141
+ // The lead under a subtitle is a plain paragraph, so nothing but this gives it
142
+ // the gap to the content - every page would otherwise spell it out per instance.
143
+ // Two selectors because a lead sometimes runs to a second paragraph, and the
144
+ // gap has to sit under the last one.
145
+ .mw-section-subtitle + p,
146
+ .mw-section-subtitle + p + p {
147
+ margin-bottom: var(--mw-section-head-step);
148
+ }
149
+
150
+ // Nothing between the heading and the content: the heading carries the gap the
151
+ // lead would have carried
152
+ .mw-section-subtitle:not(:has(+ p)) {
153
+ margin-bottom: var(--mw-section-head-step);
154
+ }
155
+
133
156
  // Sticky, because the strip is the only way back: inside a long section the
134
157
  // nav at the entrance can be twenty thousand pixels behind the reader
135
158
  .mw-section-nav {
@@ -153,11 +176,17 @@
153
176
  @include scrollbar;
154
177
  }
155
178
 
156
- // The strip parks under the header, so a jump to a component clears both
179
+ // The strip parks under the header, so everything scrolled to - an anchor and
180
+ // a tab stop alike - has to clear both. The reset reserves the header, this
181
+ // adds the strip on top of it for as long as a page carries one.
182
+ html:has(.mw-section-nav) {
183
+ --mw-section-nav-offset: var(--mw-section-nav-height);
184
+ }
185
+
186
+ // The offset above lands a target flush under the strip; this is the air
187
+ // between the two.
157
188
  .mw-container > div:has(> .mw-section-subtitle) {
158
- scroll-margin-top: calc(
159
- var(--mw-header-height) + var(--mw-section-nav-height) + #{spacing('5')}
160
- );
189
+ scroll-margin-top: spacing('5');
161
190
  }
162
191
 
163
192
  .mw-section-btn {
@@ -192,6 +221,9 @@
192
221
 
193
222
  @include focus-ring;
194
223
  @include tappable;
224
+ // It sits under the floor on its own, in a strip that scrolls sideways
225
+ // under the thumb - a near miss here costs a jump, not just a retry
226
+ @include touch-floor;
195
227
 
196
228
  &:disabled {
197
229
  cursor: not-allowed;
@@ -200,12 +232,12 @@
200
232
  }
201
233
 
202
234
  @include media-up('sm') {
203
- // One step above the demo gap of spacing('6'), and that step is the whole
204
- // margin the boundary between two components has over the one between two
205
- // demos - the heading leans toward what it names, not toward what came
206
- // before it.
235
+ // Half a step more than the gap it leaves below itself, and that difference
236
+ // is the whole margin the boundary between two components has over the one
237
+ // between two demos - the heading leans toward what it names, not toward
238
+ // what came before it.
207
239
  .mw-section-subtitle {
208
- margin-top: spacing('8');
240
+ margin-top: calc(var(--mw-section-head-step) * 1.49);
209
241
  }
210
242
  }
211
243
 
@@ -139,12 +139,16 @@
139
139
  @include media-down('md') {
140
140
  :root {
141
141
  --mw-section-padding-block: #{spacing('9')};
142
+ --mw-section-padding-compact: #{spacing('7')};
143
+ --mw-section-padding-airy: #{spacing('10')};
142
144
  }
143
145
  }
144
146
 
145
147
  @include media-down('sm') {
146
148
  :root {
147
149
  --mw-section-padding-block: #{spacing('7')};
150
+ --mw-section-padding-compact: #{spacing('6')};
151
+ --mw-section-padding-airy: #{spacing('9')};
148
152
  }
149
153
  }
150
154