maverick-wave 5.30.0 → 5.32.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 (147) hide show
  1. package/README.md +2 -2
  2. package/maverick-wave.min.css +2 -2
  3. package/package.json +8 -2
  4. package/src/scss/abstracts/_variables.scss +2 -0
  5. package/src/scss/components/_modals.scss +23 -9
  6. package/src/scss/components/_testimonial.scss +2 -0
  7. package/src/scss/layout/_home.scss +46 -0
  8. package/src/scss/layout/_main.scss +20 -0
  9. package/src/scss/layout/_parallax.scss +2 -2
  10. package/src/scss/main-lean.scss +8 -3
  11. package/.claude/settings.local.json +0 -43
  12. package/.claude/skills/mw-maverick-wave/SKILL.md +0 -532
  13. package/.claude/skills/mw-maverick-wave/examples/angular-form.md +0 -279
  14. package/.claude/skills/mw-maverick-wave/examples/angular-list-page.md +0 -309
  15. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +0 -538
  16. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +0 -530
  17. package/.claude/skills/mw-maverick-wave/references/components.md +0 -2170
  18. package/.claude/skills/mw-maverick-wave/references/forms.md +0 -358
  19. package/.claude/skills/mw-maverick-wave/references/javascript.md +0 -164
  20. package/.claude/skills/mw-maverick-wave/references/layout.md +0 -764
  21. package/.claude/skills/mw-maverick-wave/references/theming.md +0 -530
  22. package/.github/workflows/ci.yml +0 -41
  23. package/.idea/codeStyles/Project.xml +0 -59
  24. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  25. package/.idea/maverick-wave.iml +0 -12
  26. package/.idea/modules.xml +0 -8
  27. package/.idea/prettier.xml +0 -6
  28. package/.idea/vcs.xml +0 -6
  29. package/.prettierignore +0 -8
  30. package/.prettierrc.json +0 -7
  31. package/CHANGELOG.md +0 -956
  32. package/CLAUDE.md +0 -60
  33. package/gulpfile.js +0 -167
  34. package/index.html +0 -1730
  35. package/release.sh +0 -31
  36. package/scripts/verify.js +0 -252
  37. package/src/assets/favicon/apple-touch-icon.png +0 -0
  38. package/src/assets/favicon/favicon-96x96.png +0 -0
  39. package/src/assets/favicon/favicon.svg +0 -1
  40. package/src/assets/favicon/site.webmanifest +0 -37
  41. package/src/assets/favicon/web-app-manifest-192x192.png +0 -0
  42. package/src/assets/favicon/web-app-manifest-512x512.png +0 -0
  43. package/src/assets/favicon.ico +0 -0
  44. package/src/assets/header-logo.svg +0 -24
  45. package/src/assets/images/gallery-city.svg +0 -49
  46. package/src/assets/images/gallery-desert.svg +0 -25
  47. package/src/assets/images/gallery-forest.svg +0 -46
  48. package/src/assets/images/gallery-lake.svg +0 -26
  49. package/src/assets/images/gallery-mountain.svg +0 -19
  50. package/src/assets/images/gallery-ocean.svg +0 -26
  51. package/src/assets/images/gallery-oldtown.svg +0 -41
  52. package/src/assets/images/gallery-snow.svg +0 -39
  53. package/src/assets/images/photo-balloon.svg +0 -35
  54. package/src/assets/images/photo-forest-path.svg +0 -31
  55. package/src/assets/images/photo-lighthouse.svg +0 -42
  56. package/src/assets/images/photo-palm.svg +0 -27
  57. package/src/assets/images/photo-peak.svg +0 -27
  58. package/src/assets/images/photo-tower.svg +0 -34
  59. package/src/assets/images/photo-waterfall.svg +0 -32
  60. package/src/assets/images/slider-field.svg +0 -124
  61. package/src/assets/images/slider-forest.svg +0 -33
  62. package/src/assets/images/slider-lake.svg +0 -35
  63. package/src/assets/images/slider-mountains.svg +0 -29
  64. package/src/assets/images/slider-trees.svg +0 -41
  65. package/src/assets/images/story-aurora.svg +0 -36
  66. package/src/assets/images/story-beach.svg +0 -34
  67. package/src/assets/images/story-stars.svg +0 -35
  68. package/src/assets/images/tile-analytics.svg +0 -22
  69. package/src/assets/images/tile-cloud.svg +0 -19
  70. package/src/assets/images/tile-ecommerce.svg +0 -14
  71. package/src/assets/images/tile-enterprise.svg +0 -22
  72. package/src/assets/images/tile-marketing.svg +0 -16
  73. package/src/assets/media/demo-chime.wav +0 -0
  74. package/src/assets/media/story-sunrise.jpg +0 -0
  75. package/src/assets/media/story-sunrise.mp4 +0 -0
  76. package/src/partials/accordions-container.html +0 -179
  77. package/src/partials/alerts-container.html +0 -151
  78. package/src/partials/announcement-container.html +0 -67
  79. package/src/partials/avatars-container.html +0 -229
  80. package/src/partials/badges-container.html +0 -114
  81. package/src/partials/blog-posts-container.html +0 -158
  82. package/src/partials/breadcrumbs-container.html +0 -126
  83. package/src/partials/button-bar-container.html +0 -175
  84. package/src/partials/buttons-container.html +0 -272
  85. package/src/partials/calendar-container.html +0 -203
  86. package/src/partials/cards-container.html +0 -727
  87. package/src/partials/code-container.html +0 -194
  88. package/src/partials/colors-container.html +0 -263
  89. package/src/partials/coming-soon-container.html +0 -10
  90. package/src/partials/contact-container.html +0 -85
  91. package/src/partials/content-slider-container.html +0 -36
  92. package/src/partials/divider-container.html +0 -70
  93. package/src/partials/documentation-container.html +0 -204
  94. package/src/partials/dropdown-container.html +0 -119
  95. package/src/partials/empty-state-container.html +0 -79
  96. package/src/partials/feed-container.html +0 -506
  97. package/src/partials/footer-container.html +0 -113
  98. package/src/partials/form-container.html +0 -262
  99. package/src/partials/form-elements-container.html +0 -616
  100. package/src/partials/form-field-container.html +0 -166
  101. package/src/partials/gallery-container.html +0 -62
  102. package/src/partials/get-started-container.html +0 -423
  103. package/src/partials/grid-container.html +0 -191
  104. package/src/partials/header-container.html +0 -69
  105. package/src/partials/header-utilities-container.html +0 -331
  106. package/src/partials/highlights-container.html +0 -71
  107. package/src/partials/history-container.html +0 -67
  108. package/src/partials/home-container.html +0 -31
  109. package/src/partials/html-lists-container.html +0 -63
  110. package/src/partials/info-container.html +0 -104
  111. package/src/partials/input-group-container.html +0 -299
  112. package/src/partials/item-lists-container.html +0 -368
  113. package/src/partials/kanban-container.html +0 -459
  114. package/src/partials/kbd-container.html +0 -63
  115. package/src/partials/leader-row-container.html +0 -139
  116. package/src/partials/login-container.html +0 -65
  117. package/src/partials/lunch-menu-container.html +0 -341
  118. package/src/partials/media-container.html +0 -27
  119. package/src/partials/meta-info-container.html +0 -21
  120. package/src/partials/modals-container.html +0 -199
  121. package/src/partials/mosaic-container.html +0 -173
  122. package/src/partials/page-header-container.html +0 -78
  123. package/src/partials/pagination-container.html +0 -64
  124. package/src/partials/palette-container.html +0 -480
  125. package/src/partials/panels-container.html +0 -126
  126. package/src/partials/parallax-container.html +0 -147
  127. package/src/partials/portrait-gallery-container.html +0 -175
  128. package/src/partials/preview-container.html +0 -136
  129. package/src/partials/pricing-container.html +0 -523
  130. package/src/partials/progress-container.html +0 -230
  131. package/src/partials/prose-container.html +0 -43
  132. package/src/partials/ratings-container.html +0 -104
  133. package/src/partials/section-head-container.html +0 -77
  134. package/src/partials/segmented-container.html +0 -66
  135. package/src/partials/skeleton-container.html +0 -87
  136. package/src/partials/spinners-container.html +0 -96
  137. package/src/partials/stepper-container.html +0 -111
  138. package/src/partials/stories-container.html +0 -182
  139. package/src/partials/tables-container.html +0 -309
  140. package/src/partials/tabs-container.html +0 -290
  141. package/src/partials/tags-container.html +0 -242
  142. package/src/partials/techstack-bucket-container.html +0 -187
  143. package/src/partials/testimonials-container.html +0 -88
  144. package/src/partials/tiles-container.html +0 -247
  145. package/src/partials/timelines-container.html +0 -196
  146. package/src/partials/typography-container.html +0 -74
  147. package/src/partials/utilities-container.html +0 -553
package/index.html DELETED
@@ -1,1730 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta
6
- name="viewport"
7
- content="width=device-width, minimum-scale=1, initial-scale=1, viewport-fit=cover"
8
- />
9
- <meta
10
- name="format-detection"
11
- content="telephone=no,email=no,address=no,date=no"
12
- />
13
- <title>MaverickWave</title>
14
- <!-- Third-party CSS goes into a layer of its own, declared ahead of the
15
- framework's. Unlayered CSS beats every layer, so a plain <link> here
16
- would let Font Awesome's `.fas { display }` win over `.mw-tags-icon`.
17
- Any project that loads a vendor stylesheet next to MaverickWave wants
18
- these two lines. -->
19
- <style>
20
- @layer vendor, mw;
21
- @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css')
22
- layer(vendor);
23
- </style>
24
- <!-- ?v= so a release actually reaches a browser that has the last one
25
- cached: the filename never changes, and without it a stale stylesheet
26
- survives a normal reload -->
27
- <link rel="stylesheet" href="maverick-wave.min.css?v={{VERSION}}" />
28
-
29
- <!-- Applies a stored theme choice before the first paint. Without it a
30
- reader whose choice differs from the OS sees the other theme for a
31
- frame, because main.js only runs at the end of the body. Inline and
32
- synchronous on purpose - an external file would paint first. -->
33
- <script>
34
- (function () {
35
- var stored = localStorage.getItem('mw-theme');
36
- if (stored === 'light' || stored === 'dark') {
37
- document.documentElement.classList.add('mw-theme-' + stored);
38
- }
39
- })();
40
- </script>
41
- <!-- Favicons -->
42
- <link rel="icon" type="image/svg+xml" href="favicon/favicon.svg" />
43
- <link
44
- rel="icon"
45
- type="image/png"
46
- sizes="96x96"
47
- href="favicon/favicon-96x96.png"
48
- />
49
- <link rel="shortcut icon" href="favicon.ico" />
50
- <link
51
- rel="apple-touch-icon"
52
- sizes="180x180"
53
- href="favicon/apple-touch-icon.png"
54
- />
55
- <link rel="manifest" href="favicon/site.webmanifest" />
56
-
57
- <!-- Showcase starts in dark mode unless localStorage says otherwise -->
58
- <meta name="theme-color" content="#171f30" />
59
- <style>
60
- /* Additional styles only for the showcase page */
61
- .position-relative {
62
- position: relative;
63
- }
64
-
65
- /* A version number is digits, so it gets the mono stack - the rest of
66
- the pill is mw-hero-badge */
67
- .version-corner {
68
- font-family: var(--mw-font-family-mono);
69
- }
70
-
71
- .color-card {
72
- padding: 18px;
73
- }
74
-
75
- .color-card p {
76
- margin-bottom: 5px !important;
77
- font-size: 0.9rem;
78
- }
79
-
80
- .color-swatch {
81
- width: 100%;
82
- height: 70px;
83
- border-radius: 6px;
84
- margin-bottom: 9px;
85
- border: 1px solid var(--mw-border);
86
- }
87
-
88
- /* The parallax pattern demo: a gradient has no edge across the travel,
89
- the second layer brings one */
90
- .parallax-demo-ground {
91
- background-image: linear-gradient(
92
- 158deg,
93
- #3c4f78 0%,
94
- #2a3550 45%,
95
- #1b2334 100%
96
- );
97
- }
98
-
99
- .parallax-demo-lines {
100
- --mw-parallax-pattern-image:
101
- repeating-linear-gradient(
102
- to bottom,
103
- rgb(255 255 255 / 24%) 0 1px,
104
- transparent 1px 34px
105
- ),
106
- repeating-linear-gradient(
107
- to right,
108
- rgb(255 255 255 / 14%) 0 1px,
109
- transparent 1px 34px
110
- );
111
- }
112
-
113
- /* The travel has to sit on the animated section, the height on the block
114
- inside it */
115
- .parallax-band-demo {
116
- --mw-parallax-slow-travel: 72vh;
117
- }
118
-
119
- .parallax-band-demo .mw-parallax {
120
- min-height: 88vh;
121
- --mw-parallax-depth: 18vh;
122
- }
123
-
124
- /* Reuses the hero picture through the token, not a second download */
125
- .parallax-band-demo .mw-parallax-media {
126
- background-image: var(--mw-hero-background);
127
- }
128
-
129
- /* A calendar fills its container; across the full page that stretches the
130
- cells to 150px, so the demos get a realistic column instead */
131
- .calendar-demo {
132
- max-width: 520px;
133
- margin-inline: auto;
134
- }
135
-
136
- /* Here, not inline - inline it would outrank the compact value too */
137
- #kanban-demo {
138
- --mw-kanban-column-min-height: 390px;
139
- }
140
-
141
- #kanban-demo.mw-kanban-compact {
142
- --mw-kanban-column-min-height: 210px;
143
- }
144
-
145
- /* A compact calendar has no dots, so its legend has nothing to explain */
146
- .calendar-demo .mw-calendar-compact .mw-calendar-legend {
147
- display: none;
148
- }
149
-
150
- /* Medium screens */
151
- @media (max-width: 992px) {
152
- /* Two calendars side by side squeeze their cells below md, so they
153
- stack one breakpoint earlier than mw-grid-2 would on its own */
154
- .calendar-demos {
155
- grid-template-columns: minmax(0, 1fr);
156
- }
157
- }
158
-
159
- /* Small screens */
160
- @media (max-width: 576px) {
161
- .color-swatch {
162
- margin: 0 auto 7px auto;
163
- width: 92%;
164
- height: 50px;
165
- }
166
- }
167
-
168
- /* Palette switcher, showcase only. The framework derives every tone from
169
- the root colors, so the panel writes nothing but those. */
170
- .palette-toggle {
171
- display: none;
172
- align-items: center;
173
- justify-content: center;
174
- width: 50px;
175
- height: 40px;
176
- flex-shrink: 0;
177
- border: none;
178
- border-radius: calc(10px * var(--mw-radius-scale, 1));
179
- background: var(--mw-primary-color);
180
- color: var(--mw-primary-accent-text-color);
181
- font-size: 1.1rem;
182
- cursor: pointer;
183
- transition: var(--mw-transition);
184
- }
185
-
186
- /* Below sm the panel would cover the very thing it is changing */
187
- @media (min-width: 576px) {
188
- .palette-toggle {
189
- display: flex;
190
- }
191
- }
192
-
193
- .palette-toggle:hover {
194
- background: var(--mw-primary-color-hover);
195
- }
196
-
197
- .palette-toggle:active {
198
- transform: scale(0.94);
199
- }
200
-
201
- .palette-panel {
202
- position: fixed;
203
- top: calc(var(--mw-header-height) + 8px);
204
- /* Flush with the container edge, so it lines up with the button in the
205
- header rather than sitting further out */
206
- right: max(18px, calc((100% - var(--mw-container-width)) / 2));
207
- z-index: 400;
208
- /* Wide enough for three palettes in a row; the min() is for the narrow
209
- end, where the panel would otherwise reach past the viewport */
210
- width: min(440px, calc(100% - 24px));
211
- /* The variant list makes the panel taller than a laptop in landscape -
212
- it takes what is left under the header and scrolls the rest */
213
- max-height: calc(100dvh - var(--mw-header-height) - 24px);
214
- overflow-y: auto;
215
- overscroll-behavior: contain;
216
- padding: 18px;
217
- border-radius: 12px;
218
- border: 1px solid var(--mw-border);
219
- background: var(--mw-card-background);
220
- box-shadow: var(--mw-elevation-5);
221
- transform: translateY(0);
222
- opacity: 1;
223
- /* display is in the list so allow-discrete keeps the panel mounted
224
- through the fade out - `hidden` is the state the script writes */
225
- transition-property: opacity, transform, display;
226
- transition-duration: var(--mw-duration-base);
227
- transition-timing-function: var(--mw-ease-out);
228
- transition-behavior: allow-discrete;
229
- }
230
-
231
- @starting-style {
232
- .palette-panel {
233
- opacity: 0;
234
- transform: translateY(-12px);
235
- }
236
- }
237
-
238
- .palette-panel[hidden] {
239
- opacity: 0;
240
- transform: translateY(-12px);
241
- }
242
-
243
- .palette-heading {
244
- font-size: 1.1rem;
245
- margin-bottom: 4px;
246
- }
247
-
248
- .palette-hint {
249
- font-size: 0.8rem;
250
- line-height: 1.35;
251
- color: var(--mw-text-muted-color);
252
- margin-bottom: 14px;
253
- }
254
-
255
- .palette-grid {
256
- display: grid;
257
- grid-template-columns: repeat(3, 1fr);
258
- gap: 6px;
259
- }
260
-
261
- .palette-item {
262
- display: flex;
263
- align-items: center;
264
- gap: 8px;
265
- padding: 6px 8px;
266
- border-radius: 8px;
267
- border: 1px solid var(--mw-border);
268
- background: transparent;
269
- color: var(--mw-text-color);
270
- font-size: 0.8rem;
271
- text-align: left;
272
- cursor: pointer;
273
- transition: var(--mw-transition);
274
- }
275
-
276
- .palette-item::before {
277
- content: '';
278
- width: 16px;
279
- height: 16px;
280
- flex-shrink: 0;
281
- border-radius: 50%;
282
- background: linear-gradient(
283
- 135deg,
284
- var(--swatch-primary) 50%,
285
- var(--swatch-secondary) 50%
286
- );
287
- }
288
-
289
- .palette-item:hover,
290
- .palette-item.mw-active {
291
- border-color: var(--mw-primary-text-color);
292
- }
293
-
294
- .palette-item.mw-active {
295
- background: var(--mw-primary-background);
296
- }
297
-
298
- .palette-custom {
299
- display: flex;
300
- gap: 10px;
301
- margin-top: 14px;
302
- padding-top: 12px;
303
- border-top: 1px solid var(--mw-border);
304
- }
305
-
306
- .palette-field {
307
- flex: 1;
308
- display: flex;
309
- flex-direction: column;
310
- gap: 4px;
311
- font-size: 0.75rem;
312
- color: var(--mw-text-muted-color);
313
- }
314
-
315
- .palette-field input {
316
- width: 100%;
317
- height: 30px;
318
- padding: 2px;
319
- border: 1px solid var(--mw-border);
320
- border-radius: 6px;
321
- background: var(--mw-card-background);
322
- cursor: pointer;
323
- }
324
-
325
- /* Single accent means the secondary picker writes a token nothing reads any
326
- more - left live it is a knob that does nothing */
327
- :root.mw-accent-single .palette-field:has(#palette-secondary) {
328
- opacity: 0.4;
329
- pointer-events: none;
330
- }
331
-
332
- .palette-heading-split {
333
- margin-top: 18px;
334
- padding-top: 14px;
335
- border-top: 1px solid var(--mw-border);
336
- }
337
-
338
- /* Splits the list by where a switch shows up, because half of them act on
339
- parts of the page the preview does not contain */
340
- .variant-group {
341
- margin: 20px 0 2px;
342
- font-size: 0.78rem;
343
- font-weight: 600;
344
- color: var(--mw-text-color);
345
- }
346
-
347
- .variant-group:first-child {
348
- margin-top: 4px;
349
- }
350
-
351
- .variant-group-hint {
352
- margin: 0 0 4px;
353
- font-size: 0.68rem;
354
- line-height: 1.35;
355
- color: var(--mw-text-muted-color);
356
- }
357
-
358
- .variant-row {
359
- display: flex;
360
- align-items: center;
361
- justify-content: space-between;
362
- gap: 12px;
363
- padding-block: 9px;
364
- font-size: 0.8rem;
365
- color: var(--mw-text-color);
366
- cursor: pointer;
367
- }
368
-
369
- .variant-label {
370
- display: flex;
371
- flex-direction: column;
372
- gap: 2px;
373
- }
374
-
375
- .variant-label small {
376
- font-size: 0.68rem;
377
- line-height: 1.35;
378
- color: var(--mw-text-muted-color);
379
- }
380
-
381
- .variant-row + .variant-row {
382
- border-top: 1px solid var(--mw-border);
383
- }
384
-
385
- /* Width and the two size tokens are taken back from .mw-select for the
386
- same reason the switch below is hand-drawn: the field must not resize
387
- itself while it is the knob for density and radius */
388
- .variant-row .mw-select {
389
- --mw-radius-scale: 1;
390
- --mw-control-height-sm: 1.875rem;
391
- flex-shrink: 0;
392
- width: auto;
393
- max-width: 135px;
394
- cursor: pointer;
395
- }
396
-
397
- /* Drawn by hand rather than reusing mw-toggle: a framework switch would
398
- reshape itself along with the variants it is there to control */
399
- .variant-row input[type='checkbox'] {
400
- appearance: none;
401
- flex-shrink: 0;
402
- position: relative;
403
- width: 34px;
404
- height: 18px;
405
- margin: 0;
406
- border: 1px solid var(--mw-border);
407
- border-radius: 9px;
408
- background: var(--mw-gray-background);
409
- cursor: pointer;
410
- transition: var(--mw-transition);
411
- }
412
-
413
- .variant-row input[type='checkbox']::after {
414
- content: '';
415
- position: absolute;
416
- top: 2px;
417
- left: 2px;
418
- width: 12px;
419
- height: 12px;
420
- border-radius: 50%;
421
- background: var(--mw-text-muted-color);
422
- transition: var(--mw-transition);
423
- }
424
-
425
- .variant-row input[type='checkbox']:checked {
426
- background: var(--mw-primary-color);
427
- border-color: var(--mw-primary-color);
428
- }
429
-
430
- .variant-row input[type='checkbox']:checked::after {
431
- left: 18px;
432
- background: var(--mw-primary-accent-text-color);
433
- }
434
-
435
- /* The sticky bar would otherwise sit on the section title it jumps to */
436
- #preview {
437
- scroll-margin-top: calc(var(--mw-header-height) + 1rem);
438
- }
439
-
440
- .preview-head {
441
- display: flex;
442
- align-items: flex-start;
443
- justify-content: space-between;
444
- gap: 1rem;
445
- margin-bottom: 1.5rem;
446
- }
447
-
448
- .preview-title {
449
- margin: 0 0 0.35rem;
450
- }
451
-
452
- .preview-lede {
453
- margin: 0;
454
- max-inline-size: 60ch;
455
- }
456
-
457
- /* The panel is fixed over the right of the page, flush with the container
458
- edge. Below xl there is no room to dodge it and it simply covers part of
459
- the preview; from there the preview keeps to the strip beside it,
460
- because a switch that moves something hidden behind the panel
461
- demonstrates nothing. */
462
- @media (width >= 1200px) {
463
- body:has(.palette-panel:not([hidden])) #preview > .mw-container {
464
- padding-inline-end: 460px;
465
- }
466
- }
467
-
468
- /* Both wrap rather than squeezing a field to nothing on a phone */
469
- .preview-row,
470
- .preview-actions {
471
- display: flex;
472
- flex-wrap: wrap;
473
- align-items: center;
474
- gap: 0.75rem;
475
- }
476
-
477
- .preview-row .mw-input {
478
- flex: 1 1 12rem;
479
- }
480
-
481
- /* The one thing here that keeps moving without being touched, so Motion
482
- tempo has something to show that a hover state cannot */
483
- .preview-status {
484
- display: flex;
485
- align-items: center;
486
- gap: 14px;
487
- margin: 0 0 1.25rem;
488
- font-size: 0.82rem;
489
- color: var(--mw-text-muted-color);
490
- }
491
-
492
- .preview-status strong {
493
- display: block;
494
- color: var(--mw-text-color);
495
- font-weight: 500;
496
- }
497
-
498
- .preview-status small {
499
- font-size: 0.75rem;
500
- }
501
-
502
- .preview-row .mw-select {
503
- flex: 0 1 14rem;
504
- }
505
-
506
- /* The spacing sits on the row and not on the label: the button is taller
507
- than the caption beside it, so a margin under the label leaves the
508
- button itself resting on the box */
509
- .variant-setup-head {
510
- display: flex;
511
- align-items: baseline;
512
- justify-content: space-between;
513
- gap: 10px;
514
- margin: 14px 0 10px;
515
- }
516
-
517
- .variant-setup-label {
518
- display: block;
519
- margin: 0;
520
- font-size: 0.7rem;
521
- color: var(--mw-text-muted-color);
522
- }
523
-
524
- /* The one control here that leads somewhere - everything else in the
525
- panel only changes what is on screen. Carries the brand colour so it
526
- reads as the way out rather than as one more switch. */
527
- .variant-share {
528
- display: inline-flex;
529
- align-items: center;
530
- gap: 6px;
531
- flex-shrink: 0;
532
- padding: 5px 12px;
533
- border-radius: 999px;
534
- border: 1px solid var(--mw-primary-color);
535
- background: var(--mw-primary-color);
536
- color: var(--mw-primary-accent-text-color);
537
- font: inherit;
538
- font-size: 0.72rem;
539
- font-weight: 500;
540
- cursor: pointer;
541
- transition: var(--mw-transition-fast);
542
- }
543
-
544
- .variant-share:hover {
545
- background: var(--mw-primary-color-hover, var(--mw-primary-color));
546
- transform: translateY(-1px);
547
- }
548
-
549
- .variant-share:active {
550
- transform: translateY(0);
551
- }
552
-
553
- .variant-share:focus-visible {
554
- outline: var(--mw-focus-ring-width) solid var(--mw-focus-ring-color);
555
- outline-offset: var(--mw-focus-ring-offset);
556
- }
557
-
558
- .variant-setup {
559
- display: block;
560
- width: 100%;
561
- resize: vertical;
562
- padding: 10px;
563
- border-radius: 8px;
564
- border: 1px solid var(--mw-border);
565
- background: var(--mw-gray-background);
566
- color: var(--mw-text-muted-color);
567
- font-family: var(--mw-font-family-mono);
568
- font-size: 0.68rem;
569
- line-height: 1.5;
570
- white-space: pre-wrap;
571
- word-break: break-word;
572
- }
573
- </style>
574
-
575
- <script src="maverick-wave.min.js?v={{VERSION}}"></script>
576
- </head>
577
-
578
- <body>
579
- <a class="mw-skip-link" href="#main">Skip to content</a>
580
-
581
- <!-- Header -->
582
- <header class="mw-header mw-localhost-indicator-activated mw-header-reveal">
583
- <div class="mw-container">
584
- @@include('./src/partials/header-container.html')
585
- </div>
586
- </header>
587
-
588
- <main class="mw-main" id="main">
589
- <!-- Home section -->
590
- <section id="home" class="mw-section mw-parallax-sticky mw-parallax-rise">
591
- <div class="mw-container mw-parallax">
592
- @@include('./src/partials/home-container.html')
593
- </div>
594
- </section>
595
-
596
- <!-- Highlights section -->
597
- <section id="highlights" class="mw-section mw-section-alternate">
598
- <div class="mw-container">
599
- @@include('./src/partials/highlights-container.html')
600
- </div>
601
- </section>
602
-
603
- <!-- Get started section -->
604
- <section id="get-started" class="mw-section">
605
- <div class="mw-container">
606
- <div class="mw-section-head">
607
- <h2 class="mw-section-head-title">Get started</h2>
608
- </div>
609
- @@include('./src/partials/get-started-container.html')
610
- </div>
611
- </section>
612
-
613
- <!-- Documentation section -->
614
- <section id="documentation" class="mw-section mw-section-alternate">
615
- <div class="mw-container">
616
- <div class="mw-section-head">
617
- <h2 class="mw-section-head-title">Documentation</h2>
618
- </div>
619
- @@include('./src/partials/documentation-container.html')
620
- </div>
621
- </section>
622
-
623
- <!-- Foundations section -->
624
- <section id="foundations" class="mw-section">
625
- <div class="mw-container">
626
- <div class="mw-section-head">
627
- <h2 class="mw-section-head-title">Foundations</h2>
628
- <p class="mw-section-head-intro">
629
- The raw material - type, color, spacing and the small utilities
630
- everything else is built from.
631
- </p>
632
- </div>
633
- <nav class="mw-section-nav">
634
- <a href="#typography" class="mw-section-btn">Typography</a>
635
- <a href="#colors" class="mw-section-btn">Main Colors</a>
636
- <a href="#utilities" class="mw-section-btn">Utilities</a>
637
- <a href="#header-utilities" class="mw-section-btn"
638
- >Header Utilities</a
639
- >
640
- <a href="#coming-soon" class="mw-section-btn">Coming Soon Banner</a>
641
- <a href="#divider" class="mw-section-btn">Divider</a>
642
- </nav>
643
-
644
- <!-- Typography container -->
645
- <div id="typography">
646
- @@include('./src/partials/typography-container.html')
647
- </div>
648
-
649
- <!-- Colors container -->
650
- <div id="colors">
651
- @@include('./src/partials/colors-container.html')
652
- </div>
653
-
654
- <!-- Utilities container -->
655
- <div id="utilities">
656
- @@include('./src/partials/utilities-container.html')
657
- </div>
658
-
659
- <!-- Header Utilities container -->
660
- <div id="header-utilities">
661
- @@include('./src/partials/header-utilities-container.html')
662
- </div>
663
-
664
- <!-- Coming Soon container -->
665
- <div id="coming-soon">
666
- @@include('./src/partials/coming-soon-container.html')
667
- </div>
668
-
669
- <!-- Divider container -->
670
- <div id="divider">
671
- @@include('./src/partials/divider-container.html')
672
- </div>
673
- </div>
674
- </section>
675
-
676
- <!-- Layout section -->
677
- <section id="layout" class="mw-section mw-section-alternate">
678
- <div class="mw-container">
679
- <div class="mw-section-head">
680
- <h2 class="mw-section-head-title">Layout</h2>
681
- <p class="mw-section-head-intro">
682
- The bones of a page - grids, cards, panels and the containers that
683
- hold content together.
684
- </p>
685
- </div>
686
- <nav class="mw-section-nav">
687
- <a href="#section-head" class="mw-section-btn">Section Head</a>
688
- <a href="#grid" class="mw-section-btn">Grid System</a>
689
- <a href="#cards" class="mw-section-btn">Cards</a>
690
- <a href="#panels" class="mw-section-btn">Panels</a>
691
- <a href="#tabs" class="mw-section-btn">Tabs</a>
692
- <a href="#accordions" class="mw-section-btn">Accordions</a>
693
- <a href="#tiles" class="mw-section-btn">Tiles</a>
694
- <a href="#breadcrumbs" class="mw-section-btn">Breadcrumbs</a>
695
- <a href="#modals" class="mw-section-btn">Modals</a>
696
- <a href="#html-lists" class="mw-section-btn">HTML Lists</a>
697
- <a href="#item-lists" class="mw-section-btn">Item Lists</a>
698
- <a href="#leader-row" class="mw-section-btn">Leader Row</a>
699
- <a href="#stepper" class="mw-section-btn">Stepper</a>
700
- <a href="#parallax" class="mw-section-btn">Parallax</a>
701
- </nav>
702
-
703
- <!-- Section head container -->
704
- <div id="section-head">
705
- @@include('./src/partials/section-head-container.html')
706
- </div>
707
-
708
- <!-- Grid container -->
709
- <div id="grid">@@include('./src/partials/grid-container.html')</div>
710
-
711
- <!-- Cards container -->
712
- <div id="cards">@@include('./src/partials/cards-container.html')</div>
713
-
714
- <!-- Panels container -->
715
- <div id="panels">
716
- @@include('./src/partials/panels-container.html')
717
- </div>
718
-
719
- <!-- Tabs container -->
720
- <div id="tabs">@@include('./src/partials/tabs-container.html')</div>
721
-
722
- <!-- Accordions contaier -->
723
- <div id="accordions">
724
- @@include('./src/partials/accordions-container.html')
725
- </div>
726
-
727
- <!-- Tiles container -->
728
- <div id="tiles">@@include('./src/partials/tiles-container.html')</div>
729
-
730
- <!-- Breadcrumbs container -->
731
- <div id="breadcrumbs">
732
- @@include('./src/partials/breadcrumbs-container.html')
733
- </div>
734
-
735
- <!-- Modals container -->
736
- <div id="modals">
737
- @@include('./src/partials/modals-container.html')
738
- </div>
739
-
740
- <!-- HTML Lists container -->
741
- <div id="html-lists">
742
- @@include('./src/partials/html-lists-container.html')
743
- </div>
744
-
745
- <!-- Item Lists container -->
746
- <div id="item-lists">
747
- @@include('./src/partials/item-lists-container.html')
748
- </div>
749
-
750
- <!-- Leader Row container -->
751
- <div id="leader-row">
752
- @@include('./src/partials/leader-row-container.html')
753
- </div>
754
-
755
- <!-- Stepper container -->
756
- <div id="stepper">
757
- @@include('./src/partials/stepper-container.html')
758
- </div>
759
-
760
- <!-- Parallax container -->
761
- <div id="parallax">
762
- @@include('./src/partials/parallax-container.html')
763
- </div>
764
- </div>
765
- </section>
766
-
767
- <!-- Parallax band -->
768
- <section class="mw-parallax-slow parallax-band-demo">
769
- <div class="mw-parallax mw-parallax-dimmed">
770
- <div class="mw-parallax-media"></div>
771
- <div class="mw-parallax-content mw-text-center">
772
- <div class="mw-container">
773
- <h2>The page rides over the picture</h2>
774
- <p class="mw-mb-0">
775
- This band travels at a fifth of your scrolling. The components
776
- close in from below.
777
- </p>
778
- </div>
779
- </div>
780
- </div>
781
- </section>
782
-
783
- <!-- Components section -->
784
- <section id="components" class="mw-section">
785
- <div class="mw-container">
786
- <div class="mw-section-head">
787
- <h2 class="mw-section-head-title">Components</h2>
788
- <p class="mw-section-head-intro">
789
- The everyday parts - buttons, tags, alerts and their many friends,
790
- ready to drop in.
791
- </p>
792
- </div>
793
- <nav class="mw-section-nav">
794
- <a href="#buttons" class="mw-section-btn">Buttons</a>
795
- <a href="#button-bar" class="mw-section-btn">Button Bar</a>
796
- <a href="#progress" class="mw-section-btn">Progress Bars</a>
797
- <a href="#spinners" class="mw-section-btn">Loading Spinners</a>
798
- <a href="#avatars" class="mw-section-btn">Avatars</a>
799
- <a href="#tags" class="mw-section-btn">Tags</a>
800
- <a href="#badges" class="mw-section-btn">Badges</a>
801
- <a href="#dropdown" class="mw-section-btn">Dropdown</a>
802
- <a href="#kbd" class="mw-section-btn">Keyboard Keys</a>
803
- <a href="#ratings" class="mw-section-btn">Ratings</a>
804
- <a href="#info" class="mw-section-btn">Info</a>
805
- <a href="#alerts" class="mw-section-btn">Alerts</a>
806
- <a href="#announcement" class="mw-section-btn">Announcement</a>
807
- <a href="#code" class="mw-section-btn">Code</a>
808
- <a href="#skeleton" class="mw-section-btn">Skeleton</a>
809
- <a href="#empty-state" class="mw-section-btn">Empty State</a>
810
- <a href="#pricing" class="mw-section-btn">Pricing</a>
811
- </nav>
812
-
813
- <!-- Buttons container -->
814
- <div id="buttons">
815
- @@include('./src/partials/buttons-container.html')
816
- </div>
817
-
818
- <!-- Button Bar container -->
819
- <div id="button-bar">
820
- @@include('./src/partials/button-bar-container.html')
821
- </div>
822
-
823
- <!-- Progress container -->
824
- <div id="progress">
825
- @@include('./src/partials/progress-container.html')
826
- </div>
827
-
828
- <!-- Spinners container -->
829
- <div id="spinners">
830
- @@include('./src/partials/spinners-container.html')
831
- </div>
832
-
833
- <!-- Avatars container -->
834
- <div id="avatars">
835
- @@include('./src/partials/avatars-container.html')
836
- </div>
837
-
838
- <!-- Tags container -->
839
- <div id="tags">@@include('./src/partials/tags-container.html')</div>
840
-
841
- <!-- Badges container -->
842
- <div id="badges">
843
- @@include('./src/partials/badges-container.html')
844
- </div>
845
-
846
- <!-- Dropdown container -->
847
- <div id="dropdown">
848
- @@include('./src/partials/dropdown-container.html')
849
- </div>
850
-
851
- <!-- Keyboard keys container -->
852
- <div id="kbd">@@include('./src/partials/kbd-container.html')</div>
853
-
854
- <!-- Ratings container -->
855
- <div id="ratings">
856
- @@include('./src/partials/ratings-container.html')
857
- </div>
858
-
859
- <!-- Info container -->
860
- <div id="info">@@include('./src/partials/info-container.html')</div>
861
-
862
- <!-- Alerts container -->
863
- <div id="alerts">
864
- @@include('./src/partials/alerts-container.html')
865
- </div>
866
-
867
- <!-- Announcement container -->
868
- <div id="announcement">
869
- @@include('./src/partials/announcement-container.html')
870
- </div>
871
-
872
- <!-- Code container -->
873
- <div id="code">@@include('./src/partials/code-container.html')</div>
874
-
875
- <!-- Skeleton container -->
876
- <div id="skeleton">
877
- @@include('./src/partials/skeleton-container.html')
878
- </div>
879
-
880
- <!-- Empty State container -->
881
- <div id="empty-state">
882
- @@include('./src/partials/empty-state-container.html')
883
- </div>
884
-
885
- <!-- Pricing container -->
886
- <div id="pricing">
887
- @@include('./src/partials/pricing-container.html')
888
- </div>
889
- </div>
890
- </section>
891
-
892
- <!-- Forms section -->
893
- <section id="forms" class="mw-section mw-section-alternate">
894
- <div class="mw-container">
895
- <div class="mw-section-head">
896
- <h2 class="mw-section-head-title">Forms</h2>
897
- <p class="mw-section-head-intro">
898
- Inputs, selects, toggles and validation - fields and buttons share
899
- one height scale by construction.
900
- </p>
901
- </div>
902
- <nav class="mw-section-nav">
903
- <a href="#form-elements" class="mw-section-btn">Form Elements</a>
904
- <a href="#input-group" class="mw-section-btn">Input Group</a>
905
- <a href="#segmented" class="mw-section-btn">Segmented Control</a>
906
- <a href="#form-field" class="mw-section-btn">Field Pattern</a>
907
- <a href="#form" class="mw-section-btn">Form</a>
908
- <a href="#login" class="mw-section-btn">Login</a>
909
- </nav>
910
-
911
- <!-- Form elements container -->
912
- <div id="form-elements">
913
- @@include('./src/partials/form-elements-container.html')
914
- </div>
915
-
916
- <!-- Input Group container -->
917
- <div id="input-group">
918
- @@include('./src/partials/input-group-container.html')
919
- </div>
920
-
921
- <!-- Segmented control container -->
922
- <div id="segmented">
923
- @@include('./src/partials/segmented-container.html')
924
- </div>
925
-
926
- <!-- Form Field pattern container -->
927
- <div id="form-field">
928
- @@include('./src/partials/form-field-container.html')
929
- </div>
930
-
931
- <!-- Form container -->
932
- <div id="form">@@include('./src/partials/form-container.html')</div>
933
-
934
- <!-- Login container -->
935
- <div id="login">@@include('./src/partials/login-container.html')</div>
936
- </div>
937
- </section>
938
-
939
- <!-- Data & display section -->
940
- <section id="data-display" class="mw-section">
941
- <div class="mw-container">
942
- <div class="mw-section-head">
943
- <h2 class="mw-section-head-title">Data & Display</h2>
944
- <p class="mw-section-head-intro">
945
- Tables, boards, timelines and long-form content - everything that
946
- shows real data.
947
- </p>
948
- </div>
949
- <nav class="mw-section-nav">
950
- <a href="#page-header" class="mw-section-btn">Page Header</a>
951
- <a href="#meta-info" class="mw-section-btn">Meta Info</a>
952
- <a href="#tables" class="mw-section-btn">Tables</a>
953
- <a href="#kanban" class="mw-section-btn">Kanban</a>
954
- <a href="#calendar" class="mw-section-btn">Calendar</a>
955
- <a href="#lunch-menu" class="mw-section-btn">Lunch Menu</a>
956
- <a href="#contact" class="mw-section-btn">Contact</a>
957
- <a href="#pagination" class="mw-section-btn">Pagination</a>
958
- <a href="#timelines" class="mw-section-btn">Timelines</a>
959
- <a href="#gallery" class="mw-section-btn">Image Gallery</a>
960
- <a href="#portrait-gallery" class="mw-section-btn"
961
- >Portrait Gallery</a
962
- >
963
- <a href="#mosaic" class="mw-section-btn">Mosaic</a>
964
- <a href="#feed" class="mw-section-btn">Photo Feed</a>
965
- <a href="#stories" class="mw-section-btn">Story Archive</a>
966
- <a href="#techstack-bucket" class="mw-section-btn"
967
- >Technology Stack Bucket</a
968
- >
969
- <a href="#content-slider" class="mw-section-btn">Content Slider</a>
970
- <a href="#testimonials" class="mw-section-btn">Testimonials</a>
971
- <a href="#prose" class="mw-section-btn">Prose</a>
972
- <a href="#media" class="mw-section-btn">Media</a>
973
- <a href="#blog-posts" class="mw-section-btn">Blog Posts</a>
974
- </nav>
975
-
976
- <!-- Page Header container -->
977
- <div id="page-header">
978
- @@include('./src/partials/page-header-container.html')
979
- </div>
980
-
981
- <!-- Meta Info container -->
982
- <div id="meta-info">
983
- @@include('./src/partials/meta-info-container.html')
984
- </div>
985
-
986
- <!-- Tables container -->
987
- <div id="tables">
988
- @@include('./src/partials/tables-container.html')
989
- </div>
990
-
991
- <!-- Kanban container -->
992
- <div id="kanban">
993
- @@include('./src/partials/kanban-container.html')
994
- </div>
995
-
996
- <!-- Calendar container -->
997
- <div id="calendar">
998
- @@include('./src/partials/calendar-container.html')
999
- </div>
1000
-
1001
- <!-- Lunch Menu container -->
1002
- <div id="lunch-menu">
1003
- @@include('./src/partials/lunch-menu-container.html')
1004
- </div>
1005
-
1006
- <!-- Contact container -->
1007
- <div id="contact">
1008
- @@include('./src/partials/contact-container.html')
1009
- </div>
1010
-
1011
- <!-- Pagination container -->
1012
- <div id="pagination">
1013
- @@include('./src/partials/pagination-container.html')
1014
- </div>
1015
-
1016
- <!-- Timeline container -->
1017
- <div id="timelines">
1018
- @@include('./src/partials/timelines-container.html')
1019
- </div>
1020
-
1021
- <!-- Gallery container-->
1022
- <div id="gallery">
1023
- @@include('./src/partials/gallery-container.html')
1024
- </div>
1025
-
1026
- <!-- Portrait gallery container-->
1027
- <div id="portrait-gallery">
1028
- @@include('./src/partials/portrait-gallery-container.html')
1029
- </div>
1030
-
1031
- <!-- Mosaic container-->
1032
- <div id="mosaic">
1033
- @@include('./src/partials/mosaic-container.html')
1034
- </div>
1035
-
1036
- <!-- Feed container-->
1037
- <div id="feed">@@include('./src/partials/feed-container.html')</div>
1038
-
1039
- <!-- Stories container-->
1040
- <div id="stories">
1041
- @@include('./src/partials/stories-container.html')
1042
- </div>
1043
-
1044
- <!-- Tech stack container-->
1045
- <div id="techstack-bucket">
1046
- @@include('./src/partials/techstack-bucket-container.html')
1047
- </div>
1048
-
1049
- <!-- Content slider container-->
1050
- <div id="content-slider">
1051
- @@include('./src/partials/content-slider-container.html')
1052
- </div>
1053
-
1054
- <!-- Testimonials container-->
1055
- <div id="testimonials">
1056
- @@include('./src/partials/testimonials-container.html')
1057
- </div>
1058
-
1059
- <!-- Prose container-->
1060
- <div id="prose">@@include('./src/partials/prose-container.html')</div>
1061
-
1062
- <!-- Media container-->
1063
- <div id="media">@@include('./src/partials/media-container.html')</div>
1064
-
1065
- <!-- Blogposts container-->
1066
- <div id="blog-posts">
1067
- @@include('./src/partials/blog-posts-container.html')
1068
- </div>
1069
- </div>
1070
- </section>
1071
- <!-- History section -->
1072
- <section id="history" class="mw-section mw-section-alternate">
1073
- <div class="mw-container">
1074
- <div class="mw-section-head">
1075
- <h2 class="mw-section-head-title">History</h2>
1076
- <p class="mw-section-head-intro">
1077
- The major releases so far. Everything in between - the minors and
1078
- the patch releases - sits in the changelog.
1079
- </p>
1080
- </div>
1081
- @@include('./src/partials/history-container.html')
1082
- </div>
1083
- </section>
1084
- <!-- Preview section, showcase only - see the partial -->
1085
- <section id="preview" class="mw-section">
1086
- <div class="mw-container">
1087
- <div class="mw-section-head">
1088
- <h2 class="mw-section-head-title">Preview</h2>
1089
- <p class="mw-section-head-intro">
1090
- One page-shaped example, so the variant switches in the palette
1091
- panel have something to act on that is not a component gallery.
1092
- Open the panel and the page lands here.
1093
- </p>
1094
- </div>
1095
- @@include('./src/partials/preview-container.html')
1096
- </div>
1097
- </section>
1098
- </main>
1099
-
1100
- <!-- Footer -->
1101
- <footer class="mw-footer">
1102
- <div class="mw-container">
1103
- @@include('./src/partials/footer-container.html')
1104
- </div>
1105
- </footer>
1106
-
1107
- <!-- Palette switcher -->
1108
- @@include('./src/partials/palette-container.html')
1109
- <script>
1110
- // just for the showcase (login button triggers login error)
1111
- document.addEventListener('DOMContentLoaded', () => {
1112
- const loginCard = document.querySelector('.mw-login');
1113
- const form = document.getElementById('login-form');
1114
- if (form) {
1115
- const btn = form.querySelector('.mw-btn-primary');
1116
- if (btn) {
1117
- btn.addEventListener('click', (e) => {
1118
- e.preventDefault();
1119
- loginCard.classList.remove('mw-login-error');
1120
- void loginCard.offsetWidth;
1121
- loginCard.classList.add('mw-login-error');
1122
- setTimeout(() => {
1123
- loginCard.classList.remove('mw-login-error');
1124
- }, 4000);
1125
- });
1126
- }
1127
- }
1128
- });
1129
-
1130
- // just for the showcase: one segmented switch above the offer grid drives
1131
- // every card in it. The framework ships the look, not the arithmetic - a
1132
- // real application has its own prices and its own state, so this stays
1133
- // here rather than in maverick-wave.js.
1134
- document.addEventListener('DOMContentLoaded', () => {
1135
- const cycleSwitch = document.getElementById('cycle-switch');
1136
- const offers = document.getElementById('cycle-offers');
1137
- if (!cycleSwitch || !offers) return;
1138
-
1139
- // Trailing zero cents are noise on a price - "24 Euro", not "24,00
1140
- // Euro". The tabular figures keep the row from jumping anyway.
1141
- const money = (n) => {
1142
- const fixed = n.toFixed(2);
1143
- return fixed.endsWith('.00')
1144
- ? fixed.slice(0, -3)
1145
- : fixed.replace('.', ',');
1146
- };
1147
-
1148
- const apply = (button) => {
1149
- const months = Number(button.dataset.cycle);
1150
- const off = Number(button.dataset.cycleOff || 0);
1151
-
1152
- cycleSwitch.querySelectorAll('.mw-segmented-item').forEach((item) => {
1153
- const on = item === button;
1154
- item.classList.toggle('mw-active', on);
1155
- item.setAttribute('aria-pressed', String(on));
1156
- });
1157
-
1158
- offers.querySelectorAll('.mw-offer').forEach((card) => {
1159
- const rate = Number(card.dataset.rate);
1160
- const perMonth = rate * (1 - off / 100);
1161
- const [whole, cents] = money(perMonth).split(',');
1162
-
1163
- card.querySelector('[data-price-amount]').innerHTML = cents
1164
- ? whole + '<span class="mw-price-fraction">,' + cents + '</span>'
1165
- : whole;
1166
-
1167
- const original = card.querySelector('.mw-price-original');
1168
- original.hidden = off === 0;
1169
- original.querySelector('[data-price-rate]').textContent =
1170
- money(rate);
1171
-
1172
- const tag = card.querySelector('[data-price-off]');
1173
- tag.hidden = off === 0;
1174
- tag.textContent = '-' + off + '%';
1175
-
1176
- card.querySelector('[data-price-billing]').textContent =
1177
- off === 0
1178
- ? button.dataset.cycleLabel + '.'
1179
- : money(perMonth * months) +
1180
- ' Euro ' +
1181
- button.dataset.cycleLabel +
1182
- '.';
1183
- });
1184
- };
1185
-
1186
- cycleSwitch.querySelectorAll('.mw-segmented-item').forEach((item) => {
1187
- item.addEventListener('click', () => apply(item));
1188
- });
1189
- apply(cycleSwitch.querySelector('.mw-active'));
1190
- });
1191
-
1192
- // just for the showcase: the switches above the photo layouts put one of
1193
- // their modifiers on the demo they name and take the others off
1194
- document.addEventListener('DOMContentLoaded', () => {
1195
- document.querySelectorAll('[data-modifier-demo]').forEach((group) => {
1196
- const target = document.getElementById(group.dataset.modifierDemo);
1197
- const items = group.querySelectorAll('.mw-segmented-item');
1198
- items.forEach((item) => {
1199
- item.setAttribute(
1200
- 'aria-pressed',
1201
- String(item.classList.contains('mw-active'))
1202
- );
1203
- item.addEventListener('click', () => {
1204
- items.forEach((other) => {
1205
- other.classList.toggle('mw-active', other === item);
1206
- other.setAttribute('aria-pressed', String(other === item));
1207
- if (other.dataset.modifier) {
1208
- target.classList.remove(other.dataset.modifier);
1209
- }
1210
- });
1211
- if (item.dataset.modifier) {
1212
- target.classList.add(item.dataset.modifier);
1213
- }
1214
- });
1215
- });
1216
- });
1217
- });
1218
-
1219
- // just for the showcase: the palette switcher. Every tone in the framework
1220
- // is derived from the root colors with color-mix(), so writing those on the
1221
- // root element repaints the page - bar, ink, borders and tints included.
1222
- // Nothing is persisted - a palette is for looking at, a reload is the way back.
1223
- document.addEventListener('DOMContentLoaded', () => {
1224
- const toggleBtn = document.getElementById('palette-toggle');
1225
- const panel = document.getElementById('palette-panel');
1226
- const primaryInput = document.getElementById('palette-primary');
1227
- const secondaryInput = document.getElementById('palette-secondary');
1228
- const items = [...document.querySelectorAll('.palette-item')];
1229
- if (!toggleBtn || !panel) return;
1230
-
1231
- const root = document.documentElement;
1232
-
1233
- // The values the markup ships with, so the setup box below can tell a
1234
- // chosen colour from the built-in one and print only the difference
1235
- const BUILT_IN = {
1236
- primary: primaryInput.value,
1237
- secondary: secondaryInput.value,
1238
- };
1239
-
1240
- const DARK_INK = '#131925';
1241
- const LIGHT_INK = '#f2f6fc';
1242
-
1243
- const luminance = (hex) => {
1244
- const [r, g, b] = [1, 3, 5].map((i) => {
1245
- const channel = parseInt(hex.slice(i, i + 2), 16) / 255;
1246
- return channel <= 0.04045
1247
- ? channel / 12.92
1248
- : ((channel + 0.055) / 1.055) ** 2.4;
1249
- });
1250
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
1251
- };
1252
-
1253
- const contrast = (a, b) => {
1254
- const [high, low] = [luminance(a), luminance(b)].sort(
1255
- (x, y) => y - x
1256
- );
1257
- return (high + 0.05) / (low + 0.05);
1258
- };
1259
-
1260
- // Measured, not thresholded - a lightness cutoff gets a saturated mid
1261
- // tone wrong
1262
- const ink = (fill) =>
1263
- contrast(fill, DARK_INK) > contrast(fill, LIGHT_INK)
1264
- ? DARK_INK
1265
- : LIGHT_INK;
1266
-
1267
- // Cuts the transition the new colours started: the whole page
1268
- // interpolating at once is the expensive part, and the hex labels in the
1269
- // Colors section read computed values, so they need the landed ones
1270
- const settle = () => {
1271
- root.classList.add('mw-theme-switching');
1272
- void root.offsetHeight;
1273
- root.classList.remove('mw-theme-switching');
1274
- window.mwRefreshColorSwatches?.();
1275
- window.mwRefreshSetup?.();
1276
- };
1277
-
1278
- const paint = (name, color) => {
1279
- root.style.setProperty(`--mw-${name}-color`, color);
1280
- root.style.setProperty(`--mw-${name}-accent-text-color`, ink(color));
1281
- };
1282
-
1283
- items.forEach((item) => {
1284
- const { primary, secondary, dark, light } = item.dataset;
1285
- item.style.setProperty('--swatch-primary', primary);
1286
- item.style.setProperty('--swatch-secondary', secondary);
1287
-
1288
- item.addEventListener('click', () => {
1289
- items.forEach((other) =>
1290
- other.classList.toggle('mw-active', other === item)
1291
- );
1292
- paint('primary', primary);
1293
- paint('secondary', secondary);
1294
- root.style.setProperty('--mw-dark-page-background', dark);
1295
- root.style.setProperty('--mw-light-page-background', light);
1296
- primaryInput.value = primary;
1297
- secondaryInput.value = secondary;
1298
- settle();
1299
- });
1300
- });
1301
-
1302
- // A hand-picked color belongs to no preset - the page backgrounds stay put
1303
- [
1304
- ['primary', primaryInput],
1305
- ['secondary', secondaryInput],
1306
- ].forEach(([name, input]) => {
1307
- input.addEventListener('input', () => {
1308
- items.forEach((item) => item.classList.remove('mw-active'));
1309
- paint(name, input.value);
1310
- });
1311
- // Dragging the picker fires `input` per pixel - the rest waits for the end
1312
- input.addEventListener('change', settle);
1313
- });
1314
-
1315
- const preview = document.getElementById('preview');
1316
-
1317
- const toggle = (open) => {
1318
- panel.hidden = !open;
1319
- toggleBtn.setAttribute('aria-expanded', String(open));
1320
-
1321
- // Or the switches act on whatever section the reader is parked at,
1322
- // which is one component at a time and says little about a look.
1323
- //
1324
- // `instant` against the smooth default in the reset: the preview sits
1325
- // at the end of a page some 80 000px long, and over that distance a
1326
- // smooth scroll either crawls through every section on the way or, as
1327
- // here, never starts at all.
1328
- if (open)
1329
- preview?.scrollIntoView({ block: 'start', behavior: 'instant' });
1330
- };
1331
-
1332
- toggleBtn.addEventListener('click', (event) => {
1333
- event.stopPropagation();
1334
- toggle(panel.hidden);
1335
- });
1336
- document.addEventListener('click', (event) => {
1337
- if (!panel.hidden && !panel.contains(event.target)) toggle(false);
1338
- });
1339
- document.addEventListener('keydown', (event) => {
1340
- if (event.key === 'Escape') toggle(false);
1341
- });
1342
-
1343
- // The seam to the variants panel below, which prints and reads the whole
1344
- // setup - the palette is half of it. Only inline values count as chosen:
1345
- // anything still coming from the stylesheet is the built-in one.
1346
- const PAGE_GROUNDS = {
1347
- dark: '--mw-dark-page-background',
1348
- light: '--mw-light-page-background',
1349
- };
1350
-
1351
- window.mwPalette = {
1352
- read: () => ({
1353
- primary: root.style.getPropertyValue('--mw-primary-color') || null,
1354
- secondary:
1355
- root.style.getPropertyValue('--mw-secondary-color') || null,
1356
- // The label colour ink() measured for each fill. It has to travel
1357
- // with the setup: a project that only copies the two brand colours
1358
- // keeps the stock accent text, and on a light brand tone that is an
1359
- // unreadable button. write() recomputes it, so pasting it back is
1360
- // harmless.
1361
- primaryInk:
1362
- root.style.getPropertyValue('--mw-primary-accent-text-color') ||
1363
- null,
1364
- secondaryInk:
1365
- root.style.getPropertyValue('--mw-secondary-accent-text-color') ||
1366
- null,
1367
- dark: root.style.getPropertyValue(PAGE_GROUNDS.dark) || null,
1368
- light: root.style.getPropertyValue(PAGE_GROUNDS.light) || null,
1369
- }),
1370
-
1371
- write: ({ primary, secondary, dark, light }) => {
1372
- const current = window.mwPalette.read();
1373
- const same =
1374
- current.primary === primary &&
1375
- current.secondary === secondary &&
1376
- current.dark === dark &&
1377
- current.light === light;
1378
- // Typing into the setup box fires per keystroke, and settle() forces
1379
- // a reflow - so a run that changes nothing has to cost nothing
1380
- if (same) return;
1381
-
1382
- [
1383
- ['primary', primary, primaryInput],
1384
- ['secondary', secondary, secondaryInput],
1385
- ].forEach(([name, value, input]) => {
1386
- if (value) {
1387
- paint(name, value);
1388
- input.value = value;
1389
- } else {
1390
- root.style.removeProperty(`--mw-${name}-color`);
1391
- root.style.removeProperty(`--mw-${name}-accent-text-color`);
1392
- input.value = BUILT_IN[name];
1393
- }
1394
- });
1395
-
1396
- Object.entries(PAGE_GROUNDS).forEach(([key, prop]) => {
1397
- const value = key === 'dark' ? dark : light;
1398
- if (value) root.style.setProperty(prop, value);
1399
- else root.style.removeProperty(prop);
1400
- });
1401
-
1402
- const shownPrimary = primary || BUILT_IN.primary;
1403
- const shownSecondary = secondary || BUILT_IN.secondary;
1404
- items.forEach((item) =>
1405
- item.classList.toggle(
1406
- 'mw-active',
1407
- item.dataset.primary === shownPrimary &&
1408
- item.dataset.secondary === shownSecondary
1409
- )
1410
- );
1411
-
1412
- settle();
1413
- },
1414
- };
1415
- });
1416
-
1417
- // just for the showcase: the variant switches. Each one is a class on <html>
1418
- // or a single custom property, never a walk over the DOM - markup that
1419
- // arrives later (a modal, a toast, a kanban card) is covered by the same
1420
- // class that was already set. The readout at the bottom of the panel is the
1421
- // whole setup a project would carry to get the look it is currently seeing.
1422
- document.addEventListener('DOMContentLoaded', () => {
1423
- const list = document.getElementById('variant-list');
1424
- const setup = document.getElementById('variant-setup');
1425
- if (!list || !setup) return;
1426
-
1427
- const root = document.documentElement;
1428
- const controls = [...list.querySelectorAll('input, select')];
1429
-
1430
- // Built on every render, put into the address bar by nobody: a setup
1431
- // written to the URL as you go comes back on the next reload, and then
1432
- // the showcase opens in someone else's look for good. Copy link is the
1433
- // one way out of here, and what it hands over is this.
1434
- //
1435
- // URLSearchParams and not a hand-rolled separator: a value like the
1436
- // heading font or the page width is full of commas, quotes and brackets,
1437
- // and escaping those by hand is where this kind of thing breaks.
1438
- let shareUrl = location.origin + location.pathname;
1439
-
1440
- const buildShareUrl = (classes, dropped, pairs) => {
1441
- const params = new URLSearchParams();
1442
- if (classes.length) params.set('c', classes.join(' '));
1443
- if (dropped.length) params.set('d', dropped.join(' '));
1444
- pairs.forEach(([prop, value]) => params.set(prop, value));
1445
-
1446
- const query = params.toString();
1447
- return (
1448
- location.origin + location.pathname + (query ? '#' + query : '')
1449
- );
1450
- };
1451
-
1452
- // Back into the shape load() reads, so the link and a pasted setup go
1453
- // through one parser rather than two that can drift apart. The target in
1454
- // front of `: no` is only there to make the line read like the printed
1455
- // one - load() matches on the class name alone.
1456
- const readHash = () => {
1457
- const params = new URLSearchParams(location.hash.slice(1));
1458
- const lines = [];
1459
-
1460
- const classes = params.get('c');
1461
- if (classes) lines.push('html class="' + classes + '"');
1462
-
1463
- (params.get('d') || '')
1464
- .split(/\s+/)
1465
- .filter(Boolean)
1466
- .forEach((name) => lines.push('markup: no ' + name));
1467
-
1468
- const pairs = [...params.entries()].filter(([key]) =>
1469
- key.startsWith('--')
1470
- );
1471
- if (pairs.length) {
1472
- lines.push(
1473
- ':root {',
1474
- ...pairs.map(([key, value]) => ` ${key}: ${value};`),
1475
- '}'
1476
- );
1477
- }
1478
-
1479
- return lines.length ? lines.join('\n') : null;
1480
- };
1481
-
1482
- const isOn = (input) =>
1483
- input.hasAttribute('data-variant-invert')
1484
- ? !input.checked
1485
- : input.checked;
1486
-
1487
- const render = () => {
1488
- // Never while it is being typed in - the box is an input as well as a
1489
- // readout, and rewriting it under the cursor would eat the paste
1490
- if (document.activeElement === setup) return;
1491
-
1492
- const classes = [];
1493
- // Unformatted, because both the box and the link are written from
1494
- // these and formatting only one of the two ways is a bug waiting
1495
- const notes = [];
1496
- const pairs = [];
1497
-
1498
- // The palette is part of the setup, and it is printed first because a
1499
- // project sets its brand colours before it retunes anything else
1500
- const palette = window.mwPalette?.read() ?? {};
1501
- [
1502
- ['--mw-primary-color', palette.primary],
1503
- ['--mw-primary-accent-text-color', palette.primaryInk],
1504
- ['--mw-secondary-color', palette.secondary],
1505
- ['--mw-secondary-accent-text-color', palette.secondaryInk],
1506
- ['--mw-dark-page-background', palette.dark],
1507
- ['--mw-light-page-background', palette.light],
1508
- ].forEach(([prop, value]) => {
1509
- if (value) pairs.push([prop, value]);
1510
- });
1511
-
1512
- controls.forEach((control) => {
1513
- if (control.type !== 'checkbox') {
1514
- // A select whose options are class names picks one of a set the
1515
- // markup cannot hold at the same time - a shape is one or none
1516
- if (!control.value) return;
1517
- if (control.hasAttribute('data-variant-classes')) {
1518
- classes.push(control.value);
1519
- } else {
1520
- pairs.push([control.dataset.variantProp, control.value]);
1521
- }
1522
- return;
1523
- }
1524
-
1525
- // A targeted class is one the markup already ships, so its absence is
1526
- // the deviation worth naming - the rest are classes to add
1527
- const target = control.dataset.variantTarget;
1528
- if (target) {
1529
- if (!isOn(control)) {
1530
- notes.push([target, control.dataset.variantClass]);
1531
- }
1532
- } else if (isOn(control)) {
1533
- classes.push(control.dataset.variantClass);
1534
- }
1535
- });
1536
-
1537
- const lines = [];
1538
- if (classes.length) {
1539
- lines.push('html class="' + classes.join(' ') + '"');
1540
- }
1541
- lines.push(...notes.map(([target, name]) => `${target}: no ${name}`));
1542
- if (pairs.length) {
1543
- lines.push(
1544
- ':root {',
1545
- ...pairs.map(([prop, value]) => ` ${prop}: ${value};`),
1546
- '}'
1547
- );
1548
- }
1549
-
1550
- setup.value = lines.length
1551
- ? lines.join('\n')
1552
- : 'Default setup - nothing to change.';
1553
-
1554
- shareUrl = buildShareUrl(
1555
- classes,
1556
- notes.map(([, name]) => name),
1557
- pairs
1558
- );
1559
- };
1560
-
1561
- const optionClasses = (control) =>
1562
- [...control.options].map((option) => option.value).filter(Boolean);
1563
-
1564
- const apply = (control) => {
1565
- if (control.type === 'checkbox') {
1566
- const target = control.dataset.variantTarget;
1567
- const el = target ? document.querySelector(target) : root;
1568
- el?.classList.toggle(control.dataset.variantClass, isOn(control));
1569
- } else if (control.hasAttribute('data-variant-classes')) {
1570
- root.classList.remove(...optionClasses(control));
1571
- if (control.value) root.classList.add(control.value);
1572
- } else if (control.value) {
1573
- root.style.setProperty(control.dataset.variantProp, control.value);
1574
- } else {
1575
- root.style.removeProperty(control.dataset.variantProp);
1576
- }
1577
- };
1578
-
1579
- // The box read backwards: someone else's setup pasted in drives the
1580
- // switches, which drive the page. Anything the text does not mention
1581
- // falls back to the default, so an empty box is a reset.
1582
- const load = (text) => {
1583
- const named = new Set(
1584
- (text.match(/class\s*=\s*"([^"]*)"/)?.[1] || '')
1585
- .trim()
1586
- .split(/\s+/)
1587
- .filter(Boolean)
1588
- );
1589
- // `.mw-header: no mw-header-reveal` - a class the markup ships and the
1590
- // setup takes away, which is the deviation worth reading
1591
- const dropped = new Set(
1592
- [...text.matchAll(/:\s*no\s+([\w-]+)/g)].map((m) => m[1])
1593
- );
1594
- const props = new Map(
1595
- [...text.matchAll(/(--[\w-]+)\s*:\s*([^;\n}]+)/g)].map((m) => [
1596
- m[1],
1597
- m[2].trim(),
1598
- ])
1599
- );
1600
-
1601
- window.mwPalette?.write({
1602
- primary: props.get('--mw-primary-color') ?? null,
1603
- secondary: props.get('--mw-secondary-color') ?? null,
1604
- dark: props.get('--mw-dark-page-background') ?? null,
1605
- light: props.get('--mw-light-page-background') ?? null,
1606
- });
1607
-
1608
- controls.forEach((control) => {
1609
- if (control.type === 'checkbox') {
1610
- const name = control.dataset.variantClass;
1611
- const on = control.dataset.variantTarget
1612
- ? !dropped.has(name)
1613
- : named.has(name);
1614
- control.checked = control.hasAttribute('data-variant-invert')
1615
- ? !on
1616
- : on;
1617
- } else if (control.hasAttribute('data-variant-classes')) {
1618
- control.value =
1619
- optionClasses(control).find((name) => named.has(name)) ?? '';
1620
- } else {
1621
- control.value = props.get(control.dataset.variantProp) ?? '';
1622
- }
1623
- apply(control);
1624
- });
1625
- };
1626
-
1627
- // Picking a palette repaints the page, and the box has to say so
1628
- window.mwRefreshSetup = render;
1629
-
1630
- setup.addEventListener('input', () => inPlace(() => load(setup.value)));
1631
- // Normalises what was pasted back into the shape render() writes
1632
- setup.addEventListener('blur', render);
1633
-
1634
- // Type and spacing switches change the height of all 80 000px of page
1635
- // above the preview as well, so the section slides out from under
1636
- // whoever is watching it - Text size alone moves it by 16 000px. Hold
1637
- // its top edge where it was and the change happens in place.
1638
- //
1639
- // Only while the preview is on screen: correcting the scroll for someone
1640
- // parked up in the component sections would move *them* by those 16 000
1641
- // instead. `instant` because the smooth default would animate the
1642
- // correction as a scroll of its own.
1643
- const previewSection = document.getElementById('preview');
1644
-
1645
- const inPlace = (change) => {
1646
- const box = previewSection?.getBoundingClientRect();
1647
- const watching =
1648
- box && box.bottom > 0 && box.top < window.innerHeight;
1649
- if (!watching) return change();
1650
-
1651
- const before = box.top;
1652
- change();
1653
- const shift = previewSection.getBoundingClientRect().top - before;
1654
- if (shift) window.scrollBy({ top: shift, behavior: 'instant' });
1655
- };
1656
-
1657
- controls.forEach((control) =>
1658
- control.addEventListener('change', () =>
1659
- inPlace(() => {
1660
- apply(control);
1661
- render();
1662
- })
1663
- )
1664
- );
1665
-
1666
- const shared = readHash();
1667
- if (shared) {
1668
- // Drives the switches, which drive the page - then render() normalises
1669
- // the box and writes the link back in the order it prints
1670
- load(shared);
1671
- render();
1672
- } else {
1673
- // Without a link the showcase opens in its own look, every time. That
1674
- // has to be spelled out: Firefox restores form state across a reload,
1675
- // so the switches would otherwise come back set the way the last visit
1676
- // left them, and the page with them.
1677
- //
1678
- // The markup is the one source for that starting state - the written
1679
- // defaults for a plain switch, and for a targeted one whether the
1680
- // class it stands for is actually on the element it names. A header
1681
- // shipped without mw-header-reveal keeps it off.
1682
- controls.forEach((control) => {
1683
- const target = control.dataset.variantTarget;
1684
- const el = target && document.querySelector(target);
1685
-
1686
- if (el) {
1687
- const present = el.classList.contains(
1688
- control.dataset.variantClass
1689
- );
1690
- control.checked = control.hasAttribute('data-variant-invert')
1691
- ? !present
1692
- : present;
1693
- } else if (control.type === 'checkbox') {
1694
- control.checked = control.defaultChecked;
1695
- } else {
1696
- const preset = [...control.options].find(
1697
- (o) => o.defaultSelected
1698
- );
1699
- control.value = preset ? preset.value : '';
1700
- }
1701
- });
1702
-
1703
- controls.forEach(apply);
1704
- render();
1705
- }
1706
-
1707
- document
1708
- .getElementById('variant-share')
1709
- ?.addEventListener('click', async (event) => {
1710
- const button = event.currentTarget;
1711
- const label = document.getElementById('variant-share-label');
1712
- try {
1713
- await navigator.clipboard.writeText(shareUrl);
1714
- label.textContent = 'Copied';
1715
- } catch {
1716
- // Denied permission or an insecure origin. The address bar is no
1717
- // help here - it never carries the setup - so hand the link over
1718
- // in the box instead, where it can be selected by hand.
1719
- setup.value = shareUrl;
1720
- setup.select();
1721
- label.textContent = 'Copy from the box';
1722
- }
1723
- setTimeout(() => {
1724
- label.textContent = 'Copy link';
1725
- }, 1600);
1726
- });
1727
- });
1728
- </script>
1729
- </body>
1730
- </html>