maverick-wave 4.28.0 → 5.0.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 (138) hide show
  1. package/.claude/settings.local.json +29 -1
  2. package/.claude/skills/mw-maverick-wave/SKILL.md +39 -24
  3. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +16 -4
  4. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +13 -4
  5. package/.claude/skills/mw-maverick-wave/references/components.md +42 -5
  6. package/.claude/skills/mw-maverick-wave/references/forms.md +14 -2
  7. package/.claude/skills/mw-maverick-wave/references/javascript.md +17 -4
  8. package/.claude/skills/mw-maverick-wave/references/layout.md +53 -19
  9. package/.claude/skills/mw-maverick-wave/references/theming.md +38 -1
  10. package/.impeccable/config.local.json +5 -0
  11. package/.impeccable/hook.cache.json +1 -0
  12. package/CHANGELOG.md +11 -0
  13. package/CLAUDE.md +5 -5
  14. package/README.md +48 -4
  15. package/index.html +19 -38
  16. package/maverick-wave.min.css +14 -12
  17. package/maverick-wave.min.js +1 -1
  18. package/package.json +2 -2
  19. package/scripts/verify.js +27 -2
  20. package/src/js/main.js +223 -83
  21. package/src/partials/accordions-container.html +6 -10
  22. package/src/partials/alerts-container.html +6 -6
  23. package/src/partials/announcement-container.html +5 -5
  24. package/src/partials/avatars-container.html +85 -17
  25. package/src/partials/badges-container.html +6 -6
  26. package/src/partials/blog-posts-container.html +11 -12
  27. package/src/partials/buttons-container.html +3 -3
  28. package/src/partials/cards-container.html +26 -9
  29. package/src/partials/code-container.html +5 -5
  30. package/src/partials/content-slider-container.html +5 -4
  31. package/src/partials/divider-container.html +7 -7
  32. package/src/partials/dropdown-container.html +2 -2
  33. package/src/partials/empty-state-container.html +5 -5
  34. package/src/partials/footer-container.html +21 -18
  35. package/src/partials/form-elements-container.html +14 -14
  36. package/src/partials/gallery-container.html +16 -8
  37. package/src/partials/get-started-container.html +4 -8
  38. package/src/partials/header-container.html +18 -7
  39. package/src/partials/home-container.html +10 -20
  40. package/src/partials/input-group-container.html +11 -2
  41. package/src/partials/kbd-container.html +1 -1
  42. package/src/partials/login-container.html +5 -1
  43. package/src/partials/media-container.html +2 -2
  44. package/src/partials/modals-container.html +22 -32
  45. package/src/partials/pricing-container.html +9 -9
  46. package/src/partials/skeleton-container.html +6 -6
  47. package/src/partials/stepper-container.html +2 -2
  48. package/src/partials/tables-container.html +12 -12
  49. package/src/partials/tabs-container.html +17 -27
  50. package/src/partials/tags-container.html +23 -17
  51. package/src/partials/techstack-bucket-container.html +13 -0
  52. package/src/partials/tiles-container.html +26 -9
  53. package/src/partials/typography-container.html +7 -6
  54. package/src/partials/utilities-container.html +35 -15
  55. package/src/scss/_layers.scss +10 -0
  56. package/src/scss/abstracts/_index.scss +6 -0
  57. package/src/scss/abstracts/_mixins.scss +61 -153
  58. package/src/scss/abstracts/_variables.scss +123 -319
  59. package/src/scss/base/_base.scss +170 -190
  60. package/src/scss/base/_reset.scss +106 -104
  61. package/src/scss/base/_typography.scss +153 -151
  62. package/src/scss/components/_accordions.scss +97 -97
  63. package/src/scss/components/_alerts.scss +71 -70
  64. package/src/scss/components/_announcement.scss +71 -70
  65. package/src/scss/components/_avatars.scss +130 -142
  66. package/src/scss/components/_badge.scss +131 -132
  67. package/src/scss/components/_blog-post.scss +223 -220
  68. package/src/scss/components/_breadcrumbs.scss +93 -92
  69. package/src/scss/components/_button-bar.scss +102 -110
  70. package/src/scss/components/_buttons.scss +319 -324
  71. package/src/scss/components/_calendar.scss +251 -252
  72. package/src/scss/components/_cards.scss +359 -386
  73. package/src/scss/components/_code.scss +190 -194
  74. package/src/scss/components/_coming-soon.scss +77 -75
  75. package/src/scss/components/_content-slider.scss +90 -89
  76. package/src/scss/components/_divider.scss +55 -53
  77. package/src/scss/components/_dropdown.scss +179 -185
  78. package/src/scss/components/_empty-state.scss +57 -57
  79. package/src/scss/components/_flag.scss +294 -278
  80. package/src/scss/components/_gallery.scss +131 -133
  81. package/src/scss/components/_info.scss +151 -167
  82. package/src/scss/components/_kanban.scss +332 -341
  83. package/src/scss/components/_kbd.scss +40 -38
  84. package/src/scss/components/_lang-switch.scss +89 -97
  85. package/src/scss/components/_links.scss +21 -28
  86. package/src/scss/components/_lists.scss +510 -514
  87. package/src/scss/components/_localhost-indicator.scss +26 -24
  88. package/src/scss/components/_media.scss +22 -20
  89. package/src/scss/components/_meta-info.scss +35 -33
  90. package/src/scss/components/_modals.scss +277 -186
  91. package/src/scss/components/_pagination.scss +81 -87
  92. package/src/scss/components/_panels.scss +99 -97
  93. package/src/scss/components/_pricing.scss +183 -241
  94. package/src/scss/components/_progress.scss +112 -89
  95. package/src/scss/components/_prose.scss +35 -33
  96. package/src/scss/components/_ratings.scss +40 -38
  97. package/src/scss/components/_segmented.scss +82 -84
  98. package/src/scss/components/_skeleton.scss +66 -72
  99. package/src/scss/components/_spinners.scss +155 -159
  100. package/src/scss/components/_stepper.scss +116 -116
  101. package/src/scss/components/_tables.scss +155 -148
  102. package/src/scss/components/_tabs.scss +232 -141
  103. package/src/scss/components/_tags.scss +109 -107
  104. package/src/scss/components/_techstack-bucket.scss +123 -122
  105. package/src/scss/components/_testimonial.scss +72 -70
  106. package/src/scss/components/_theme-toggle.scss +53 -51
  107. package/src/scss/components/_tiles.scss +174 -174
  108. package/src/scss/components/_timelines.scss +282 -280
  109. package/src/scss/components/_toasts.scss +66 -72
  110. package/src/scss/form-elements/_checkbox.scss +117 -132
  111. package/src/scss/form-elements/_form.scss +149 -115
  112. package/src/scss/form-elements/_input-group.scss +99 -103
  113. package/src/scss/form-elements/_input.scss +74 -74
  114. package/src/scss/form-elements/_login.scss +48 -48
  115. package/src/scss/form-elements/_prefilled.scss +53 -51
  116. package/src/scss/form-elements/_radio.scss +95 -107
  117. package/src/scss/form-elements/_select.scss +42 -42
  118. package/src/scss/form-elements/_slider.scss +117 -132
  119. package/src/scss/form-elements/_textarea.scss +68 -54
  120. package/src/scss/form-elements/_toggle.scss +127 -140
  121. package/src/scss/layout/_footer.scss +172 -171
  122. package/src/scss/layout/_grid.scss +268 -302
  123. package/src/scss/layout/_header.scss +426 -427
  124. package/src/scss/layout/_home.scss +28 -27
  125. package/src/scss/layout/_main.scss +275 -238
  126. package/src/scss/layout/_page-header.scss +38 -36
  127. package/src/scss/layout/_section.scss +150 -124
  128. package/src/scss/main.scss +1 -1
  129. package/src/scss/utilities/_accessibility.scss +46 -45
  130. package/src/scss/utilities/_aspect.scss +22 -20
  131. package/src/scss/utilities/_corner.scss +23 -5
  132. package/src/scss/utilities/_display.scss +76 -70
  133. package/src/scss/utilities/_elevation.scss +13 -11
  134. package/src/scss/utilities/_flex.scss +83 -85
  135. package/src/scss/utilities/_reveal.scss +40 -63
  136. package/src/scss/utilities/_spacing.scss +63 -64
  137. package/src/scss/utilities/_text.scss +139 -141
  138. package/src/scss/utilities/_touch-targets.scss +130 -147
@@ -1,469 +1,442 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
- $_mw-card-addon-colors: (
4
- primary: var(--mw-primary-color),
5
- secondary: var(--mw-secondary-color),
6
- info: var(--mw-info-color),
7
- success: var(--mw-success-color),
8
- warning: var(--mw-warning-color),
9
- danger: var(--mw-danger-color),
10
- );
11
-
12
- // Round icon badge - shared by the title icon and the stack header icon.
13
- // Font size stays with the callers: the stack tile is compact and wants a
14
- // smaller glyph than a full card title.
15
- @mixin _card-icon-circle {
16
- display: flex;
17
- align-items: center;
18
- justify-content: center;
19
- width: 38px;
20
- height: 38px;
21
- flex-shrink: 0;
22
- border-radius: radius('full');
23
- background: var(--mw-card-addon-color, var(--mw-primary-color));
24
- color: var(--mw-card-addon-text-color, var(--mw-primary-accent-text-color));
25
- box-shadow: var(--mw-elevation-2);
26
- transition: var(--mw-transition);
27
- overflow: hidden;
28
-
29
- img {
30
- width: 100%;
31
- height: 100%;
32
- object-fit: cover;
33
- }
34
- }
35
-
36
- // Fading rule used by the title divider and the stack header underline
37
- @mixin _card-divider-gradient {
38
- background-image: linear-gradient(
39
- to right,
40
- var(--mw-secondary-text-color),
41
- var(--mw-secondary-text-color),
42
- var(--mw-gray-background),
43
- transparent
44
- );
45
- opacity: 0.75;
46
- }
47
-
48
- .mw-card {
49
- // flex column so the footer can be pushed to the bottom - no height: 100%,
50
- // grid and flex items already stretch and it would blow up block parents
51
- display: flex;
52
- flex-direction: column;
53
- background: var(--mw-card-background);
54
- @include surface;
55
- box-shadow: var(--mw-elevation-2);
56
- border: 1px solid var(--mw-border);
57
- transition: var(--mw-transition);
58
- // deliberately not `overflow: hidden` - that would also cut off anything a
59
- // child wants to show outside the card, a tooltip above a button being the
60
- // common case. Images round their own corners below, and only the ribbon
61
- // really needs the card to clip.
62
- overflow: visible;
63
-
64
- // Direct child only - a card nested inside another one must not clip its host
65
- &:has(> .mw-card-ribbon) {
66
- overflow: hidden;
67
- }
68
-
69
- // Query container for the footer below. Named, so the query cannot resolve to
70
- // a project's own container further up the tree.
71
- //
72
- // Only cards that have a footer, and they claim their slot: inline-size
73
- // containment computes the card's own width as if it were empty, so a card
74
- // that takes its width from its content - a flex row, `mw-grid-stack`, a
75
- // timeline wrapper - would collapse to its border. Filling the slot keeps the
76
- // width external, which is what makes it queryable in the first place.
77
- &:has(.mw-card-footer) {
78
- width: 100%;
79
- container: mw-card / inline-size;
80
- }
81
-
82
- &:hover {
83
- box-shadow: var(--mw-elevation-3);
84
- border-color: var(--mw-border-accent);
85
- }
86
-
87
- // The lift moves the box, so it is guarded: on touch `:hover` latches after a
88
- // tap and the card stays raised, visibly out of line with the row it sits in.
89
- // The colour and shadow below are not - a latched border reads as "the one you
90
- // last touched", which is no worse than nothing.
91
- @include hover {
92
- transform: translateY(-2px);
93
- }
94
-
95
- // Padded surface for arbitrary content - stays a block container so inline
96
- // children keep flowing instead of turning into flex items
97
- &-simple {
98
- display: block;
99
- padding: spacing('5');
100
- }
101
-
102
- &-badge {
103
- position: absolute;
104
- top: spacing('2');
105
- right: spacing('4');
106
- padding: spacing('1') spacing('4');
107
- font-size: font-size('xs');
108
- color: var(--mw-card-addon-text-color, var(--mw-primary-accent-text-color));
3
+ @layer mw.components {
4
+ // Round icon badge - shared by the title icon and the stack header icon.
5
+ // Font size stays with the callers: the stack tile is compact and wants a
6
+ // smaller glyph than a full card title.
7
+ @mixin _card-icon-circle {
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ width: 38px;
12
+ height: 38px;
13
+ flex-shrink: 0;
14
+ border-radius: radius('full');
109
15
  background: var(--mw-card-addon-color, var(--mw-primary-color));
110
- border-radius: radius('md');
111
- white-space: nowrap;
112
- z-index: 1;
113
- pointer-events: none;
114
- text-align: center;
115
- opacity: 0.9;
116
- font-weight: font-weight('medium');
117
- }
118
-
119
- &-ribbon {
120
- position: absolute;
121
- top: 20px;
122
- right: -35px;
123
- padding: spacing('1') spacing('4');
124
- font-size: font-size('xs');
125
16
  color: var(--mw-card-addon-text-color, var(--mw-primary-accent-text-color));
126
- background: var(--mw-card-addon-color, var(--mw-primary-color));
127
- white-space: nowrap;
128
- width: 220px;
129
- z-index: 1;
130
- pointer-events: none;
131
- transform: rotate(25deg);
132
- text-align: center;
133
- opacity: 0.8;
134
- font-weight: font-weight('medium');
135
- @include media-down('sm') {
136
- font-size: font-size('2xs');
137
- }
138
- }
139
-
140
- &-img,
141
- &-img-contain {
142
- height: var(--mw-card-img-height, 210px);
17
+ box-shadow: var(--mw-elevation-2);
18
+ transition: var(--mw-transition);
143
19
  overflow: hidden;
144
- flex-shrink: 0;
145
- border-radius: (radius('sm') - 1px) (radius('xl') - 1px) 0 0;
146
20
 
147
21
  img {
148
22
  width: 100%;
149
23
  height: 100%;
150
24
  object-fit: cover;
151
- transition: transform var(--mw-duration-zoom) var(--mw-ease-out);
152
- }
153
-
154
- &:hover img {
155
- transform: scale(1.05);
156
25
  }
157
26
  }
158
27
 
159
- &-img-contain img {
160
- object-fit: contain;
161
- object-position: center;
162
- }
163
-
164
- &-lg {
165
- --mw-card-img-height: 340px;
166
-
167
- @include media-down('sm') {
168
- --mw-card-img-height: 260px;
169
- }
170
- }
171
-
172
- &-xl {
173
- --mw-card-img-height: 480px;
174
-
175
- @include media-down('sm') {
176
- --mw-card-img-height: 340px;
177
- }
28
+ // Fading rule used by the title divider and the stack header underline
29
+ @mixin _card-divider-gradient {
30
+ background-image: linear-gradient(
31
+ to right,
32
+ var(--mw-secondary-text-color),
33
+ var(--mw-secondary-text-color),
34
+ var(--mw-gray-background),
35
+ transparent
36
+ );
37
+ opacity: 0.75;
178
38
  }
179
39
 
180
- &-body {
40
+ .mw-card {
41
+ // flex column so the footer can be pushed to the bottom - no height: 100%,
42
+ // grid and flex items already stretch and it would blow up block parents
181
43
  display: flex;
182
44
  flex-direction: column;
183
- flex-grow: 1;
184
- padding: spacing('7') spacing('5') spacing('6');
45
+ background: var(--mw-card-background);
46
+ @include surface;
47
+ box-shadow: var(--mw-elevation-2);
48
+ border: 1px solid var(--mw-border);
49
+ transition: var(--mw-transition);
50
+ // deliberately not `overflow: hidden` - that would also cut off anything a
51
+ // child wants to show outside the card, a tooltip above a button being the
52
+ // common case. Images round their own corners below, and only the ribbon
53
+ // really needs the card to clip.
54
+ overflow: visible;
55
+
56
+ // Direct child only - a card nested inside another one must not clip its host
57
+ &:has(> .mw-card-ribbon) {
58
+ overflow: hidden;
59
+ }
185
60
 
186
- @include media-down('sm') {
187
- padding: spacing('6') spacing('4') spacing('5');
61
+ // Named, so the query cannot resolve to a container further up the tree. Only
62
+ // cards with a footer, and they claim their slot: inline-size containment
63
+ // computes the width as if the card were empty, so one that sizes to its
64
+ // content would collapse to its border.
65
+ &:has(.mw-card-footer) {
66
+ width: 100%;
67
+ container: mw-card / inline-size;
188
68
  }
189
69
 
190
- > *:last-child:not(.mw-card-footer) {
191
- margin-bottom: spacing('0');
70
+ // The arc on the two round corners is the one mark that makes a card look
71
+ // like this framework and not like a rounded box, so it is what the hover
72
+ // pulls on. Recoloured through the token the mixin already reads, which is
73
+ // why the rule sits on the card and not on its ::after.
74
+ &::after {
75
+ transition: var(--mw-transition);
192
76
  }
193
77
 
194
- .mw-card-footer {
195
- margin-top: auto;
196
- padding-top: spacing('5');
78
+ &:hover {
79
+ box-shadow: var(--mw-elevation-3);
80
+ border-color: var(--mw-border-accent);
81
+ --mw-corner-accent: var(--mw-corner-accent-hover);
197
82
  }
198
- }
199
83
 
200
- &-title {
201
- margin-bottom: spacing('3');
202
- font-size: font-size('xl');
203
- @include media-down('md') {
204
- font-size: font-size('lg');
84
+ // The lift moves the box, so it is guarded: on touch `:hover` latches after a
85
+ // tap and the card stays raised, visibly out of line with the row it sits in.
86
+ // The colour and shadow below are not - a latched border reads as "the one you
87
+ // last touched", which is no worse than nothing.
88
+ @include hover {
89
+ transform: translateY(-2px);
205
90
  }
206
91
 
207
- // Only with an icon - a plain title stays a block, so a long one keeps
208
- // wrapping the way a heading is expected to
209
- &:has(> .mw-card-title-icon) {
210
- display: flex;
211
- align-items: center;
212
- gap: spacing('4');
92
+ // Padded surface for arbitrary content - stays a block container so inline
93
+ // children keep flowing instead of turning into flex items
94
+ &-simple {
95
+ display: block;
96
+ padding: spacing('5');
213
97
  }
214
- }
215
98
 
216
- // Icon badge in front of the card title. Takes its colour from
217
- // `mw-card-addon-*` on the card, like the badge and the ribbon do.
218
- //
219
- // <h3 class="mw-card-title">
220
- // <span class="mw-card-title-icon"><i class="fas fa-file-invoice"></i></span>
221
- // Invoices
222
- // </h3>
223
- //
224
- // An <img> works in the same slot - a product logo or a portrait instead of
225
- // an icon. Reach for the -lg step there: a wordmark inside 38px is a smudge.
226
- &-title-icon {
227
- @include _card-icon-circle;
228
- font-size: font-size('lg');
99
+ &-badge {
100
+ position: absolute;
101
+ top: spacing('2');
102
+ right: spacing('4');
103
+ padding: spacing('1') spacing('4');
104
+ font-size: font-size('xs');
105
+ color: var(
106
+ --mw-card-addon-text-color,
107
+ var(--mw-primary-accent-text-color)
108
+ );
109
+ background: var(--mw-card-addon-color, var(--mw-primary-color));
110
+ border-radius: radius('md');
111
+ white-space: nowrap;
112
+ z-index: 1;
113
+ pointer-events: none;
114
+ text-align: center;
115
+ opacity: 0.9;
116
+ font-weight: font-weight('medium');
117
+ }
229
118
 
230
- &-lg {
231
- width: 62px;
232
- height: 62px;
233
- font-size: font-size('2xl');
119
+ &-ribbon {
120
+ position: absolute;
121
+ top: 20px;
122
+ right: -35px;
123
+ padding: spacing('1') spacing('4');
124
+ font-size: font-size('2xs');
125
+ color: var(
126
+ --mw-card-addon-text-color,
127
+ var(--mw-primary-accent-text-color)
128
+ );
129
+ background: var(--mw-card-addon-color, var(--mw-primary-color));
130
+ white-space: nowrap;
131
+ width: 220px;
132
+ z-index: 1;
133
+ pointer-events: none;
134
+ transform: rotate(25deg);
135
+ text-align: center;
136
+ opacity: 0.8;
137
+ font-weight: font-weight('medium');
138
+ @include media-up('sm') {
139
+ font-size: font-size('xs');
140
+ }
234
141
  }
235
- }
236
142
 
237
- &-title-divider {
238
- height: 4px;
239
- margin: spacing('1') spacing('0') spacing('4') spacing('0');
240
- border: 0;
241
- width: 70%;
242
- @include _card-divider-gradient;
243
- }
143
+ &-img,
144
+ &-img-contain {
145
+ height: var(--mw-card-img-height, 210px);
146
+ overflow: hidden;
147
+ flex-shrink: 0;
148
+ border-radius: (radius('sm') - 1px) (radius('xl') - 1px) 0 0;
244
149
 
245
- &-subtitle {
246
- margin-bottom: spacing('3');
247
- font-size: font-size('md');
248
- line-height: 1.2;
249
- font-style: italic;
250
- color: var(--mw-text-muted-color);
251
- @include media-down('md') {
252
- margin-bottom: spacing('2');
150
+ img {
151
+ width: 100%;
152
+ height: 100%;
153
+ object-fit: cover;
154
+ transition: transform var(--mw-duration-zoom) var(--mw-ease-out);
155
+ }
156
+
157
+ &:hover img {
158
+ transform: scale(1.05);
159
+ }
253
160
  }
254
- }
255
161
 
256
- &-text {
257
- margin: spacing('2') spacing('0') spacing('4') spacing('0');
258
- color: var(--mw-text-color);
259
- @include media-down('sm') {
260
- margin-bottom: spacing('2');
162
+ &-img-contain img {
163
+ object-fit: contain;
164
+ object-position: center;
261
165
  }
262
- }
263
166
 
264
- &-footer {
265
- display: flex;
266
- justify-content: flex-start;
267
- // centered, so plain text next to a button sits on the button's centre
268
- // line instead of being stretched to its height and starting at the top
269
- align-items: center;
270
- // Buttons do not shrink, so two that no longer fit have to wrap - without
271
- // this they leave the card sideways
272
- flex-wrap: wrap;
273
- gap: spacing('3');
274
- flex-shrink: 0;
275
- text-wrap: balance;
167
+ &-lg {
168
+ --mw-card-img-height: 260px;
276
169
 
277
- /* push the last element all the way right - a single one included */
278
- > *:last-child {
279
- margin-left: auto;
280
- text-align: right;
170
+ @include media-up('sm') {
171
+ --mw-card-img-height: 340px;
172
+ }
281
173
  }
282
174
 
283
- // Ensure buttons maintain their size
284
- .mw-btn {
285
- flex-shrink: 0;
175
+ &-xl {
176
+ --mw-card-img-height: 340px;
177
+
178
+ @include media-up('sm') {
179
+ --mw-card-img-height: 480px;
180
+ }
286
181
  }
287
182
 
288
- // Stacked once the card is narrow, not once the window is - a card in a
289
- // three-column grid on a wide desktop is as narrow as the same card on a
290
- // phone and used to keep its actions in a cramped row anyway.
291
- @container mw-card (max-width: #{$mw-card-narrow}) {
183
+ &-body {
184
+ display: flex;
292
185
  flex-direction: column;
293
- align-items: stretch;
294
- gap: spacing('2');
295
-
296
- // `flex: 1 0 100%` on the note is 100% of the *height* in a column: it
297
- // fills the column and the buttons wrap out of the card sideways
298
- &:has(.mw-actions-note),
299
- &:has(.mw-form-actions-hint) {
300
- flex-wrap: nowrap;
186
+ flex-grow: 1;
187
+ padding: spacing('6') spacing('4') spacing('5');
188
+
189
+ @include media-up('sm') {
190
+ padding: spacing('7') spacing('5') spacing('6');
301
191
  }
302
192
 
303
- .mw-actions-note,
304
- .mw-form-actions-hint {
305
- flex: 0 0 auto;
306
- text-align: left;
193
+ > *:last-child:not(.mw-card-footer) {
194
+ margin-bottom: spacing('0');
307
195
  }
308
196
 
309
- > * {
310
- width: 100%;
197
+ .mw-card-footer {
198
+ margin-top: auto;
199
+ padding-top: spacing('5');
311
200
  }
201
+ }
312
202
 
313
- .mw-btn {
203
+ &-title {
204
+ margin-bottom: spacing('3');
205
+ font-size: font-size('lg');
206
+ @include media-up('md') {
207
+ font-size: font-size('xl');
208
+ }
209
+
210
+ // Only with an icon - a plain title stays a block, so a long one keeps
211
+ // wrapping the way a heading is expected to
212
+ &:has(> .mw-card-title-icon) {
314
213
  display: flex;
315
214
  align-items: center;
316
- justify-content: center;
215
+ gap: spacing('4');
317
216
  }
318
217
  }
319
- }
320
-
321
- &-stack {
322
- padding: spacing('4') spacing('5');
323
218
 
324
- &-icon {
219
+ // Colour comes from mw-card-addon-* on the card, like the badge and the ribbon.
220
+ // An <img> works in the same slot - reach for -lg there, a wordmark inside
221
+ // 38px is a smudge.
222
+ &-title-icon {
325
223
  @include _card-icon-circle;
326
- margin-right: spacing('2');
327
- font-size: font-size('md');
224
+ font-size: font-size('lg');
225
+
226
+ &-lg {
227
+ width: 62px;
228
+ height: 62px;
229
+ font-size: font-size('2xl');
230
+ }
328
231
  }
329
232
 
330
- &:hover &-icon {
331
- transform: rotate(25deg);
233
+ &-title-divider {
234
+ height: 4px;
235
+ margin: spacing('1') spacing('0') spacing('4') spacing('0');
236
+ border: 0;
237
+ width: 70%;
238
+ @include _card-divider-gradient;
332
239
  }
333
240
 
334
- &-header {
335
- display: flex;
336
- align-items: center;
337
- position: relative;
338
-
339
- &:after {
340
- content: '';
341
- position: absolute;
342
- bottom: spacing('-3');
343
- left: 50%;
344
- transform: translateX(-50%);
345
- width: 90%;
346
- height: 2px;
347
- @include _card-divider-gradient;
241
+ &-subtitle {
242
+ margin-bottom: spacing('2');
243
+ font-size: font-size('md');
244
+ line-height: 1.2;
245
+ font-style: italic;
246
+ color: var(--mw-text-muted-color);
247
+ @include media-up('md') {
248
+ margin-bottom: spacing('3');
348
249
  }
349
250
  }
350
251
 
351
- &-title {
352
- display: flex;
353
- align-items: center;
354
- justify-content: center;
355
- margin: spacing('0');
356
-
252
+ &-text {
253
+ margin: spacing('2') spacing('0') spacing('4') spacing('0');
254
+ color: var(--mw-text-color);
357
255
  @include media-down('sm') {
358
- font-size: font-size('sm');
256
+ margin-bottom: spacing('2');
359
257
  }
360
258
  }
361
259
 
362
- &-body {
260
+ &-footer {
363
261
  display: flex;
364
- flex-direction: column;
365
- padding-top: spacing('5');
262
+ justify-content: flex-start;
263
+ // centered, so plain text next to a button sits on the button's centre
264
+ // line instead of being stretched to its height and starting at the top
265
+ align-items: center;
266
+ // Buttons do not shrink, so two that no longer fit have to wrap - without
267
+ // this they leave the card sideways
268
+ flex-wrap: wrap;
269
+ gap: spacing('3');
270
+ flex-shrink: 0;
271
+ text-wrap: balance;
272
+
273
+ /* push the last element all the way right - a single one included */
274
+ > *:last-child {
275
+ margin-left: auto;
276
+ text-align: right;
277
+ }
278
+
279
+ // Ensure buttons maintain their size
280
+ .mw-btn {
281
+ flex-shrink: 0;
282
+ }
283
+
284
+ // Stacked once the card is narrow, not once the window is - a card in a
285
+ // three-column grid on a wide desktop is as narrow as the same card on a
286
+ // phone and used to keep its actions in a cramped row anyway.
287
+ @container mw-card (max-width: #{$mw-card-narrow}) {
288
+ flex-direction: column;
289
+ align-items: stretch;
290
+ gap: spacing('2');
291
+
292
+ // `flex: 1 0 100%` on the note is 100% of the *height* in a column: it
293
+ // fills the column and the buttons wrap out of the card sideways
294
+ &:has(.mw-actions-note),
295
+ &:has(.mw-form-actions-hint) {
296
+ flex-wrap: nowrap;
297
+ }
298
+
299
+ .mw-actions-note,
300
+ .mw-form-actions-hint {
301
+ flex: 0 0 auto;
302
+ text-align: left;
303
+ }
304
+
305
+ > * {
306
+ width: 100%;
307
+ }
308
+
309
+ .mw-btn {
310
+ display: flex;
311
+ align-items: center;
312
+ justify-content: center;
313
+ }
314
+ }
366
315
  }
367
316
 
368
- &-text {
369
- margin-top: spacing('2');
370
- color: var(--mw-text-color);
371
- font-size: font-size('sm');
317
+ &-stack {
318
+ padding: spacing('4') spacing('5');
319
+
320
+ &-icon {
321
+ @include _card-icon-circle;
322
+ margin-right: spacing('2');
323
+ font-size: font-size('md');
324
+ }
325
+
326
+ &:hover &-icon {
327
+ transform: rotate(25deg);
328
+ }
329
+
330
+ &-header {
331
+ display: flex;
332
+ align-items: center;
333
+ position: relative;
334
+
335
+ &:after {
336
+ content: '';
337
+ position: absolute;
338
+ bottom: spacing('-3');
339
+ left: 50%;
340
+ transform: translateX(-50%);
341
+ width: 90%;
342
+ height: 2px;
343
+ @include _card-divider-gradient;
344
+ }
345
+ }
346
+
347
+ &-title {
348
+ display: flex;
349
+ align-items: center;
350
+ justify-content: center;
351
+ margin: spacing('0');
352
+
353
+ @include media-down('sm') {
354
+ font-size: font-size('sm');
355
+ }
356
+ }
357
+
358
+ &-body {
359
+ display: flex;
360
+ flex-direction: column;
361
+ padding-top: spacing('5');
362
+ }
363
+
364
+ &-text {
365
+ margin-top: spacing('2');
366
+ color: var(--mw-text-color);
367
+ font-size: font-size('sm');
368
+ }
372
369
  }
373
370
  }
374
- }
375
371
 
376
- // Frame with a bar on top, wrapped around a card - for the one variant in a
377
- // grid that should stand out.
378
- //
379
- // `mw-card-badge` sits inside the card body and overlaps the title on narrow
380
- // cards. This one wraps around the card instead, and grows outwards: the card
381
- // keeps the width, height and look of its neighbours, frame and bar reach into
382
- // the grid gap.
383
- //
384
- // <div class="mw-card-feature">
385
- // <p class="mw-card-feature-label">Most booked</p>
386
- // <div class="mw-card">…</div>
387
- // </div>
388
- //
389
- // Primary out of the box, `mw-card-feature-secondary` and
390
- // `mw-card-feature-info` swap the colour.
391
- //
392
- // Geometry: 22px bar, 6px gap between frame and card, 1px border. The bar
393
- // height is fixed and the margins count on it - a two-line label needs the
394
- // margin and the padding raised with it. It is kept low on purpose: every
395
- // pixel of bar is a pixel the grid below has to add to its row gap.
396
- .mw-card-feature {
397
- position: relative;
398
- display: flex;
399
- flex-direction: column;
400
- margin: -29px -7px -7px;
401
- padding: 28px 6px 6px;
402
- border: 1px solid var(--mw-primary-color);
403
- border-radius: 12px 27px 12px 27px;
404
-
405
- &-label {
406
- position: absolute;
407
- inset: 0 0 auto;
372
+ // Frame and bar wrapped *around* a card, growing outwards into the grid gap, so
373
+ // the card keeps the size and look of its neighbours. mw-card-badge sits inside
374
+ // the body instead and overlaps the title on a narrow card.
375
+ //
376
+ // Geometry: 22px bar, 6px gap, 1px border. The bar height is fixed and the
377
+ // margins count on it, so a two-line label needs both raised - and every pixel
378
+ // of bar is one the grid has to add to its row gap.
379
+ .mw-card-feature {
380
+ position: relative;
408
381
  display: flex;
409
- align-items: center;
410
- justify-content: center;
411
- height: 22px;
412
- margin: 0;
413
- padding-inline: spacing('4');
414
- border-radius: 11px 26px 0 0;
415
- background: var(--mw-primary-color);
416
- color: var(--mw-primary-accent-text-color);
417
- font-size: font-size('2xs');
418
- font-weight: font-weight('bold');
419
- letter-spacing: 0.06em;
420
- text-transform: uppercase;
421
- white-space: nowrap;
422
- overflow: hidden;
423
- text-overflow: ellipsis;
424
- }
382
+ flex-direction: column;
383
+ margin: -29px -7px -7px;
384
+ padding: 28px 6px 6px;
385
+ border: 1px solid var(--mw-primary-color);
386
+ border-radius: 12px 27px 12px 27px;
387
+
388
+ &-label {
389
+ position: absolute;
390
+ inset: 0 0 auto;
391
+ display: flex;
392
+ align-items: center;
393
+ justify-content: center;
394
+ height: 22px;
395
+ margin: 0;
396
+ padding-inline: spacing('4');
397
+ border-radius: 11px 26px 0 0;
398
+ background: var(--mw-primary-color);
399
+ color: var(--mw-primary-accent-text-color);
400
+ font-size: font-size('2xs');
401
+ font-weight: font-weight('bold');
402
+ letter-spacing: 0.06em;
403
+ text-transform: uppercase;
404
+ @include truncate;
405
+ }
425
406
 
426
- > .mw-card {
427
- flex: 1;
407
+ > .mw-card {
408
+ flex: 1;
409
+ }
428
410
  }
429
- }
430
-
431
- [class*='mw-grid-']:has(> .mw-card-feature) {
432
- row-gap: 48px;
433
- }
434
411
 
435
- // Colour variants - primary is the default above, these two override it
436
- @each $name in ('secondary', 'info') {
437
- .mw-card-feature-#{$name} {
438
- border-color: var(--mw-#{$name}-color);
412
+ // Colour variants - primary is the default above, these two override it
413
+ @each $name in ('secondary', 'info') {
414
+ .mw-card-feature-#{$name} {
415
+ border-color: var(--mw-#{$name}-color);
439
416
 
440
- > .mw-card-feature-label {
441
- background: var(--mw-#{$name}-color);
442
- color: var(--mw-#{$name}-accent-text-color);
417
+ > .mw-card-feature-label {
418
+ background: var(--mw-#{$name}-color);
419
+ color: var(--mw-#{$name}-accent-text-color);
420
+ }
443
421
  }
444
422
  }
445
- }
446
-
447
- @each $name, $col in $_mw-card-addon-colors {
448
- .mw-card-addon-#{$name} {
449
- --mw-card-addon-color: #{$col};
450
- --mw-card-addon-text-color: var(--mw-#{$name}-accent-text-color);
451
- }
452
- }
453
423
 
454
- @media (prefers-reduced-motion: reduce) {
455
- .mw-card,
456
- .mw-card-img img,
457
- .mw-card-img-contain img,
458
- .mw-card-title-icon,
459
- .mw-card-stack-icon {
460
- transition: none;
424
+ @each $name, $col in $semantic-colors {
425
+ .mw-card-addon-#{$name} {
426
+ --mw-card-addon-color: #{$col};
427
+ --mw-card-addon-text-color: var(--mw-#{$name}-accent-text-color);
428
+ }
461
429
  }
462
430
 
463
- .mw-card:hover,
464
- .mw-card-img:hover img,
465
- .mw-card-img-contain:hover img,
466
- .mw-card-stack:hover .mw-card-stack-icon {
467
- transform: none;
431
+ // Only the movement. The durations are already pinned to 1ms globally in
432
+ // base/_base.scss; a hover that *travels* has to be taken away outright, or it
433
+ // still jumps to its lifted position in one frame.
434
+ @media (prefers-reduced-motion: reduce) {
435
+ .mw-card:hover,
436
+ .mw-card-img:hover img,
437
+ .mw-card-img-contain:hover img,
438
+ .mw-card-stack:hover .mw-card-stack-icon {
439
+ transform: none;
440
+ }
468
441
  }
469
442
  }