maverick-wave 5.4.0 → 5.6.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 (48) hide show
  1. package/.claude/skills/mw-maverick-wave/SKILL.md +13 -10
  2. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +91 -0
  3. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +5 -4
  4. package/.claude/skills/mw-maverick-wave/references/components.md +3 -0
  5. package/.claude/skills/mw-maverick-wave/references/javascript.md +19 -2
  6. package/.claude/skills/mw-maverick-wave/references/layout.md +20 -14
  7. package/.claude/skills/mw-maverick-wave/references/theming.md +44 -0
  8. package/CHANGELOG.md +17 -0
  9. package/CLAUDE.md +1 -1
  10. package/README.md +27 -4
  11. package/index.html +448 -0
  12. package/maverick-wave.min.css +5 -5
  13. package/maverick-wave.min.js +1 -1
  14. package/package.json +2 -2
  15. package/scripts/verify.js +4 -7
  16. package/src/js/main.js +157 -2
  17. package/src/partials/documentation-container.html +4 -0
  18. package/src/partials/footer-container.html +3 -0
  19. package/src/partials/history-container.html +67 -0
  20. package/src/partials/palette-container.html +325 -0
  21. package/src/partials/utilities-container.html +41 -0
  22. package/src/scss/abstracts/_functions.scss +11 -2
  23. package/src/scss/abstracts/_mixins.scss +36 -8
  24. package/src/scss/abstracts/_variables.scss +4 -4
  25. package/src/scss/base/_base.scss +3 -0
  26. package/src/scss/base/_reset.scss +4 -1
  27. package/src/scss/components/_accordions.scss +31 -7
  28. package/src/scss/components/_avatars.scss +4 -1
  29. package/src/scss/components/_blog-post.scss +2 -2
  30. package/src/scss/components/_cards.scss +13 -22
  31. package/src/scss/components/_coming-soon.scss +1 -1
  32. package/src/scss/components/_info.scss +2 -2
  33. package/src/scss/components/_lists.scss +1 -1
  34. package/src/scss/components/_panels.scss +1 -1
  35. package/src/scss/components/_pricing.scss +1 -1
  36. package/src/scss/components/_techstack-bucket.scss +1 -1
  37. package/src/scss/components/_tiles.scss +1 -1
  38. package/src/scss/components/_toasts.scss +1 -1
  39. package/src/scss/form-elements/_slider.scss +5 -23
  40. package/src/scss/layout/_footer.scss +1 -1
  41. package/src/scss/layout/_header-reveal.scss +33 -0
  42. package/src/scss/layout/_header.scss +9 -1
  43. package/src/scss/layout/_parallax.scss +20 -0
  44. package/src/scss/utilities/_index.scss +1 -0
  45. package/src/scss/utilities/_reveal.scss +22 -0
  46. package/src/scss/utilities/_variants.scss +90 -0
  47. package/.impeccable/config.local.json +0 -5
  48. package/.impeccable/hook.cache.json +0 -75
package/index.html CHANGED
@@ -118,6 +118,253 @@
118
118
  height: 50px;
119
119
  }
120
120
  }
121
+
122
+ /* Palette switcher, showcase only. The framework derives every tone from
123
+ the root colors, so the panel writes nothing but those. */
124
+ .palette-fab {
125
+ position: fixed;
126
+ right: 18px;
127
+ bottom: 18px;
128
+ z-index: 400;
129
+ width: 52px;
130
+ height: 52px;
131
+ border-radius: 50%;
132
+ border: 1px solid var(--mw-border-accent);
133
+ background: var(--mw-primary-color);
134
+ color: var(--mw-primary-accent-text-color);
135
+ box-shadow: var(--mw-elevation-4);
136
+ font-size: 1.25rem;
137
+ cursor: pointer;
138
+ transition: var(--mw-transition);
139
+ }
140
+
141
+ .palette-fab:hover {
142
+ background: var(--mw-primary-color-hover);
143
+ }
144
+
145
+ .palette-fab:active {
146
+ transform: scale(0.92);
147
+ }
148
+
149
+ .palette-panel {
150
+ position: fixed;
151
+ right: 18px;
152
+ bottom: 82px;
153
+ z-index: 400;
154
+ width: 360px;
155
+ /* The variant list makes the panel taller than a laptop in landscape -
156
+ the top of it would be behind the browser chrome without this */
157
+ max-height: calc(100dvh - 120px);
158
+ overflow-y: auto;
159
+ overscroll-behavior: contain;
160
+ padding: 18px;
161
+ border-radius: 12px;
162
+ border: 1px solid var(--mw-border);
163
+ background: var(--mw-card-background);
164
+ box-shadow: var(--mw-elevation-5);
165
+ }
166
+
167
+ .palette-heading {
168
+ font-size: 1.1rem;
169
+ margin-bottom: 4px;
170
+ }
171
+
172
+ .palette-hint {
173
+ font-size: 0.8rem;
174
+ line-height: 1.35;
175
+ color: var(--mw-text-muted-color);
176
+ margin-bottom: 14px;
177
+ }
178
+
179
+ .palette-grid {
180
+ display: grid;
181
+ grid-template-columns: 1fr 1fr;
182
+ gap: 6px;
183
+ }
184
+
185
+ .palette-item {
186
+ display: flex;
187
+ align-items: center;
188
+ gap: 8px;
189
+ padding: 6px 8px;
190
+ border-radius: 8px;
191
+ border: 1px solid var(--mw-border);
192
+ background: transparent;
193
+ color: var(--mw-text-color);
194
+ font-size: 0.8rem;
195
+ text-align: left;
196
+ cursor: pointer;
197
+ transition: var(--mw-transition);
198
+ }
199
+
200
+ .palette-item::before {
201
+ content: '';
202
+ width: 16px;
203
+ height: 16px;
204
+ flex-shrink: 0;
205
+ border-radius: 50%;
206
+ background: linear-gradient(
207
+ 135deg,
208
+ var(--swatch-primary) 50%,
209
+ var(--swatch-secondary) 50%
210
+ );
211
+ }
212
+
213
+ .palette-item:hover,
214
+ .palette-item.mw-active {
215
+ border-color: var(--mw-primary-text-color);
216
+ }
217
+
218
+ .palette-item.mw-active {
219
+ background: var(--mw-primary-background);
220
+ }
221
+
222
+ .palette-custom {
223
+ display: flex;
224
+ gap: 10px;
225
+ margin-top: 14px;
226
+ padding-top: 12px;
227
+ border-top: 1px solid var(--mw-border);
228
+ }
229
+
230
+ .palette-field {
231
+ flex: 1;
232
+ display: flex;
233
+ flex-direction: column;
234
+ gap: 4px;
235
+ font-size: 0.75rem;
236
+ color: var(--mw-text-muted-color);
237
+ }
238
+
239
+ .palette-field input {
240
+ width: 100%;
241
+ height: 30px;
242
+ padding: 2px;
243
+ border: 1px solid var(--mw-border);
244
+ border-radius: 6px;
245
+ background: var(--mw-card-background);
246
+ cursor: pointer;
247
+ }
248
+
249
+ /* Single accent means the secondary picker writes a token nothing reads any
250
+ more - left live it is a knob that does nothing */
251
+ :root.mw-accent-single .palette-field:has(#palette-secondary) {
252
+ opacity: 0.4;
253
+ pointer-events: none;
254
+ }
255
+
256
+ .palette-heading-split {
257
+ margin-top: 18px;
258
+ padding-top: 14px;
259
+ border-top: 1px solid var(--mw-border);
260
+ }
261
+
262
+ .variant-row {
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: space-between;
266
+ gap: 12px;
267
+ padding-block: 9px;
268
+ font-size: 0.8rem;
269
+ color: var(--mw-text-color);
270
+ cursor: pointer;
271
+ }
272
+
273
+ .variant-label {
274
+ display: flex;
275
+ flex-direction: column;
276
+ gap: 2px;
277
+ }
278
+
279
+ .variant-label small {
280
+ font-size: 0.68rem;
281
+ line-height: 1.35;
282
+ color: var(--mw-text-muted-color);
283
+ }
284
+
285
+ .variant-row + .variant-row {
286
+ border-top: 1px solid var(--mw-border);
287
+ }
288
+
289
+ .variant-row select {
290
+ flex-shrink: 0;
291
+ max-width: 110px;
292
+ padding: 3px 6px;
293
+ font-size: 0.75rem;
294
+ color: var(--mw-text-color);
295
+ border: 1px solid var(--mw-border);
296
+ border-radius: 6px;
297
+ background: transparent;
298
+ cursor: pointer;
299
+ }
300
+
301
+ /* Drawn by hand rather than reusing mw-toggle: a framework switch would
302
+ reshape itself along with the variants it is there to control */
303
+ .variant-row input[type='checkbox'] {
304
+ appearance: none;
305
+ flex-shrink: 0;
306
+ position: relative;
307
+ width: 34px;
308
+ height: 18px;
309
+ margin: 0;
310
+ border: 1px solid var(--mw-border);
311
+ border-radius: 9px;
312
+ background: var(--mw-gray-background);
313
+ cursor: pointer;
314
+ transition: var(--mw-transition);
315
+ }
316
+
317
+ .variant-row input[type='checkbox']::after {
318
+ content: '';
319
+ position: absolute;
320
+ top: 2px;
321
+ left: 2px;
322
+ width: 12px;
323
+ height: 12px;
324
+ border-radius: 50%;
325
+ background: var(--mw-text-muted-color);
326
+ transition: var(--mw-transition);
327
+ }
328
+
329
+ .variant-row input[type='checkbox']:checked {
330
+ background: var(--mw-primary-color);
331
+ border-color: var(--mw-primary-color);
332
+ }
333
+
334
+ .variant-row input[type='checkbox']:checked::after {
335
+ left: 18px;
336
+ background: var(--mw-primary-accent-text-color);
337
+ }
338
+
339
+ .variant-setup {
340
+ margin-top: 14px;
341
+ padding: 10px;
342
+ border-radius: 8px;
343
+ border: 1px solid var(--mw-border);
344
+ background: var(--mw-gray-background);
345
+ color: var(--mw-text-muted-color);
346
+ font-family: var(--mw-font-family-mono);
347
+ font-size: 0.68rem;
348
+ line-height: 1.5;
349
+ white-space: pre-wrap;
350
+ word-break: break-word;
351
+ }
352
+
353
+ @media (max-width: 480px) {
354
+ .palette-fab {
355
+ right: 12px;
356
+ bottom: 12px;
357
+ width: 46px;
358
+ height: 46px;
359
+ }
360
+
361
+ .palette-panel {
362
+ right: 12px;
363
+ left: 12px;
364
+ bottom: 68px;
365
+ width: auto;
366
+ }
367
+ }
121
368
  </style>
122
369
 
123
370
  <script src="maverick-wave.min.js"></script>
@@ -538,6 +785,17 @@
538
785
  </div>
539
786
  </div>
540
787
  </section>
788
+ <!-- History section -->
789
+ <section id="history" class="mw-section mw-section-alternate">
790
+ <div class="mw-container">
791
+ <h2 class="mw-section-title">History</h2>
792
+ <p class="mw-section-intro">
793
+ The major releases so far. Everything in between - the minors and
794
+ the patch releases - sits in the changelog.
795
+ </p>
796
+ @@include('./src/partials/history-container.html')
797
+ </div>
798
+ </section>
541
799
  </main>
542
800
 
543
801
  <!-- Footer -->
@@ -546,6 +804,9 @@
546
804
  @@include('./src/partials/footer-container.html')
547
805
  </div>
548
806
  </footer>
807
+
808
+ <!-- Palette switcher -->
809
+ @@include('./src/partials/palette-container.html')
549
810
  <script>
550
811
  // just for the showcase (login button triggers login error)
551
812
  document.addEventListener('DOMContentLoaded', () => {
@@ -629,6 +890,193 @@
629
890
  apply(cycleSwitch.querySelector('.mw-active'));
630
891
  });
631
892
 
893
+ // just for the showcase: the palette switcher. Every tone in the framework
894
+ // is derived from the root colors with color-mix(), so writing those on the
895
+ // root element repaints the page - bar, ink, borders and tints included.
896
+ // Nothing is persisted - a palette is for looking at, a reload is the way back.
897
+ document.addEventListener('DOMContentLoaded', () => {
898
+ const fab = document.getElementById('palette-fab');
899
+ const panel = document.getElementById('palette-panel');
900
+ const primaryInput = document.getElementById('palette-primary');
901
+ const secondaryInput = document.getElementById('palette-secondary');
902
+ const items = [...document.querySelectorAll('.palette-item')];
903
+ if (!fab || !panel) return;
904
+
905
+ const root = document.documentElement;
906
+
907
+ const DARK_INK = '#131925';
908
+ const LIGHT_INK = '#f2f6fc';
909
+
910
+ const luminance = (hex) => {
911
+ const [r, g, b] = [1, 3, 5].map((i) => {
912
+ const channel = parseInt(hex.slice(i, i + 2), 16) / 255;
913
+ return channel <= 0.04045
914
+ ? channel / 12.92
915
+ : ((channel + 0.055) / 1.055) ** 2.4;
916
+ });
917
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
918
+ };
919
+
920
+ const contrast = (a, b) => {
921
+ const [high, low] = [luminance(a), luminance(b)].sort(
922
+ (x, y) => y - x
923
+ );
924
+ return (high + 0.05) / (low + 0.05);
925
+ };
926
+
927
+ // Measured, not thresholded - a lightness cutoff gets a saturated mid
928
+ // tone wrong
929
+ const ink = (fill) =>
930
+ contrast(fill, DARK_INK) > contrast(fill, LIGHT_INK)
931
+ ? DARK_INK
932
+ : LIGHT_INK;
933
+
934
+ // Cuts the transition the new colours started: the whole page
935
+ // interpolating at once is the expensive part, and the hex labels in the
936
+ // Colors section read computed values, so they need the landed ones
937
+ const settle = () => {
938
+ root.classList.add('mw-theme-switching');
939
+ void root.offsetHeight;
940
+ root.classList.remove('mw-theme-switching');
941
+ window.mwRefreshColorSwatches?.();
942
+ };
943
+
944
+ const paint = (name, color) => {
945
+ root.style.setProperty(`--mw-${name}-color`, color);
946
+ root.style.setProperty(`--mw-${name}-accent-text-color`, ink(color));
947
+ };
948
+
949
+ items.forEach((item) => {
950
+ const { primary, secondary, dark, light } = item.dataset;
951
+ item.style.setProperty('--swatch-primary', primary);
952
+ item.style.setProperty('--swatch-secondary', secondary);
953
+
954
+ item.addEventListener('click', () => {
955
+ items.forEach((other) =>
956
+ other.classList.toggle('mw-active', other === item)
957
+ );
958
+ paint('primary', primary);
959
+ paint('secondary', secondary);
960
+ root.style.setProperty('--mw-dark-page-background', dark);
961
+ root.style.setProperty('--mw-light-page-background', light);
962
+ primaryInput.value = primary;
963
+ secondaryInput.value = secondary;
964
+ settle();
965
+ });
966
+ });
967
+
968
+ // A hand-picked color belongs to no preset - the page backgrounds stay put
969
+ [
970
+ ['primary', primaryInput],
971
+ ['secondary', secondaryInput],
972
+ ].forEach(([name, input]) => {
973
+ input.addEventListener('input', () => {
974
+ items.forEach((item) => item.classList.remove('mw-active'));
975
+ paint(name, input.value);
976
+ });
977
+ // Dragging the picker fires `input` per pixel - the rest waits for the end
978
+ input.addEventListener('change', settle);
979
+ });
980
+
981
+ const toggle = (open) => {
982
+ panel.hidden = !open;
983
+ fab.setAttribute('aria-expanded', String(open));
984
+ };
985
+
986
+ fab.addEventListener('click', (event) => {
987
+ event.stopPropagation();
988
+ toggle(panel.hidden);
989
+ });
990
+ document.addEventListener('click', (event) => {
991
+ if (!panel.hidden && !panel.contains(event.target)) toggle(false);
992
+ });
993
+ document.addEventListener('keydown', (event) => {
994
+ if (event.key === 'Escape') toggle(false);
995
+ });
996
+ });
997
+
998
+ // just for the showcase: the variant switches. Each one is a class on <html>
999
+ // or a single custom property, never a walk over the DOM - markup that
1000
+ // arrives later (a modal, a toast, a kanban card) is covered by the same
1001
+ // class that was already set. The readout at the bottom of the panel is the
1002
+ // whole setup a project would carry to get the look it is currently seeing.
1003
+ document.addEventListener('DOMContentLoaded', () => {
1004
+ const list = document.getElementById('variant-list');
1005
+ const setup = document.getElementById('variant-setup');
1006
+ if (!list || !setup) return;
1007
+
1008
+ const root = document.documentElement;
1009
+ const controls = [...list.querySelectorAll('input, select')];
1010
+
1011
+ const isOn = (input) =>
1012
+ input.hasAttribute('data-variant-invert')
1013
+ ? !input.checked
1014
+ : input.checked;
1015
+
1016
+ const render = () => {
1017
+ const classes = [];
1018
+ const notes = [];
1019
+ const props = [];
1020
+
1021
+ controls.forEach((control) => {
1022
+ if (control.type !== 'checkbox') {
1023
+ if (control.value) {
1024
+ props.push(
1025
+ ` ${control.dataset.variantProp}: ${control.value};`
1026
+ );
1027
+ }
1028
+ return;
1029
+ }
1030
+
1031
+ // A targeted class is one the markup already ships, so its absence is
1032
+ // the deviation worth naming - the rest are classes to add
1033
+ const target = control.dataset.variantTarget;
1034
+ if (target) {
1035
+ if (!isOn(control)) {
1036
+ notes.push(target + ': no ' + control.dataset.variantClass);
1037
+ }
1038
+ } else if (isOn(control)) {
1039
+ classes.push(control.dataset.variantClass);
1040
+ }
1041
+ });
1042
+
1043
+ const lines = [];
1044
+ if (classes.length) {
1045
+ lines.push('html class="' + classes.join(' ') + '"');
1046
+ }
1047
+ lines.push(...notes);
1048
+ if (props.length) lines.push(':root {', ...props, '}');
1049
+
1050
+ setup.textContent = lines.length
1051
+ ? lines.join('\n')
1052
+ : 'Default setup - nothing to change.';
1053
+ };
1054
+
1055
+ const apply = (control) => {
1056
+ if (control.type === 'checkbox') {
1057
+ const target = control.dataset.variantTarget;
1058
+ const el = target ? document.querySelector(target) : root;
1059
+ el?.classList.toggle(control.dataset.variantClass, isOn(control));
1060
+ } else if (control.value) {
1061
+ root.style.setProperty(control.dataset.variantProp, control.value);
1062
+ } else {
1063
+ root.style.removeProperty(control.dataset.variantProp);
1064
+ }
1065
+ };
1066
+
1067
+ controls.forEach((control) =>
1068
+ control.addEventListener('change', () => {
1069
+ apply(control);
1070
+ render();
1071
+ })
1072
+ );
1073
+
1074
+ // Applied on load too, so a browser that restored a checked box after a
1075
+ // reload does not leave the page and the panel disagreeing
1076
+ controls.forEach(apply);
1077
+ render();
1078
+ });
1079
+
632
1080
  // for the modals - the framework handles both shapes, div and <dialog>
633
1081
  const openModal = window.mwOpenModal;
634
1082
  const closeModal = window.mwCloseModal;