maverick-wave 5.5.0 → 5.7.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.
- package/.claude/skills/mw-maverick-wave/SKILL.md +16 -13
- package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +2 -2
- package/.claude/skills/mw-maverick-wave/references/components.md +5 -2
- package/.claude/skills/mw-maverick-wave/references/forms.md +5 -5
- package/.claude/skills/mw-maverick-wave/references/theming.md +41 -0
- package/CHANGELOG.md +17 -0
- package/README.md +30 -5
- package/index.html +359 -4
- package/maverick-wave.min.css +5 -5
- package/package.json +2 -2
- package/src/partials/history-container.html +1 -1
- package/src/partials/palette-container.html +210 -0
- package/src/partials/typography-container.html +8 -9
- package/src/partials/utilities-container.html +48 -0
- package/src/scss/abstracts/_functions.scss +11 -2
- package/src/scss/abstracts/_mixins.scss +35 -3
- package/src/scss/abstracts/_variables.scss +8 -8
- package/src/scss/base/_base.scss +3 -0
- package/src/scss/components/_avatars.scss +33 -1
- package/src/scss/components/_blog-post.scss +2 -2
- package/src/scss/components/_button-bar.scss +1 -1
- package/src/scss/components/_buttons.scss +1 -1
- package/src/scss/components/_cards.scss +13 -22
- package/src/scss/components/_coming-soon.scss +1 -1
- package/src/scss/components/_info.scss +2 -2
- package/src/scss/components/_lists.scss +1 -1
- package/src/scss/components/_panels.scss +1 -1
- package/src/scss/components/_pricing.scss +1 -1
- package/src/scss/components/_techstack-bucket.scss +1 -1
- package/src/scss/components/_tiles.scss +1 -1
- package/src/scss/form-elements/_input.scss +1 -1
- package/src/scss/form-elements/_select.scss +1 -1
- package/src/scss/layout/_footer.scss +1 -1
- package/src/scss/layout/_header.scss +9 -1
- package/src/scss/layout/_main.scss +31 -2
- package/src/scss/utilities/_index.scss +1 -0
- package/src/scss/utilities/_reveal.scss +9 -2
- package/src/scss/utilities/_touch-targets.scss +1 -1
- package/src/scss/utilities/_variants.scss +120 -0
package/index.html
CHANGED
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css')
|
|
22
22
|
layer(vendor);
|
|
23
23
|
</style>
|
|
24
|
-
|
|
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}}" />
|
|
25
28
|
<!-- Favicons -->
|
|
26
29
|
<link rel="icon" type="image/svg+xml" href="favicon/favicon.svg" />
|
|
27
30
|
<link
|
|
@@ -151,7 +154,12 @@
|
|
|
151
154
|
right: 18px;
|
|
152
155
|
bottom: 82px;
|
|
153
156
|
z-index: 400;
|
|
154
|
-
width:
|
|
157
|
+
width: 360px;
|
|
158
|
+
/* The variant list makes the panel taller than a laptop in landscape -
|
|
159
|
+
the top of it would be behind the browser chrome without this */
|
|
160
|
+
max-height: calc(100dvh - 120px);
|
|
161
|
+
overflow-y: auto;
|
|
162
|
+
overscroll-behavior: contain;
|
|
155
163
|
padding: 18px;
|
|
156
164
|
border-radius: 12px;
|
|
157
165
|
border: 1px solid var(--mw-border);
|
|
@@ -241,7 +249,121 @@
|
|
|
241
249
|
cursor: pointer;
|
|
242
250
|
}
|
|
243
251
|
|
|
244
|
-
|
|
252
|
+
/* Single accent means the secondary picker writes a token nothing reads any
|
|
253
|
+
more - left live it is a knob that does nothing */
|
|
254
|
+
:root.mw-accent-single .palette-field:has(#palette-secondary) {
|
|
255
|
+
opacity: 0.4;
|
|
256
|
+
pointer-events: none;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.palette-heading-split {
|
|
260
|
+
margin-top: 18px;
|
|
261
|
+
padding-top: 14px;
|
|
262
|
+
border-top: 1px solid var(--mw-border);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.variant-row {
|
|
266
|
+
display: flex;
|
|
267
|
+
align-items: center;
|
|
268
|
+
justify-content: space-between;
|
|
269
|
+
gap: 12px;
|
|
270
|
+
padding-block: 9px;
|
|
271
|
+
font-size: 0.8rem;
|
|
272
|
+
color: var(--mw-text-color);
|
|
273
|
+
cursor: pointer;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.variant-label {
|
|
277
|
+
display: flex;
|
|
278
|
+
flex-direction: column;
|
|
279
|
+
gap: 2px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.variant-label small {
|
|
283
|
+
font-size: 0.68rem;
|
|
284
|
+
line-height: 1.35;
|
|
285
|
+
color: var(--mw-text-muted-color);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.variant-row + .variant-row {
|
|
289
|
+
border-top: 1px solid var(--mw-border);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.variant-row select {
|
|
293
|
+
flex-shrink: 0;
|
|
294
|
+
max-width: 110px;
|
|
295
|
+
padding: 3px 6px;
|
|
296
|
+
font-size: 0.75rem;
|
|
297
|
+
color: var(--mw-text-color);
|
|
298
|
+
border: 1px solid var(--mw-border);
|
|
299
|
+
border-radius: 6px;
|
|
300
|
+
background: transparent;
|
|
301
|
+
cursor: pointer;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* Drawn by hand rather than reusing mw-toggle: a framework switch would
|
|
305
|
+
reshape itself along with the variants it is there to control */
|
|
306
|
+
.variant-row input[type='checkbox'] {
|
|
307
|
+
appearance: none;
|
|
308
|
+
flex-shrink: 0;
|
|
309
|
+
position: relative;
|
|
310
|
+
width: 34px;
|
|
311
|
+
height: 18px;
|
|
312
|
+
margin: 0;
|
|
313
|
+
border: 1px solid var(--mw-border);
|
|
314
|
+
border-radius: 9px;
|
|
315
|
+
background: var(--mw-gray-background);
|
|
316
|
+
cursor: pointer;
|
|
317
|
+
transition: var(--mw-transition);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.variant-row input[type='checkbox']::after {
|
|
321
|
+
content: '';
|
|
322
|
+
position: absolute;
|
|
323
|
+
top: 2px;
|
|
324
|
+
left: 2px;
|
|
325
|
+
width: 12px;
|
|
326
|
+
height: 12px;
|
|
327
|
+
border-radius: 50%;
|
|
328
|
+
background: var(--mw-text-muted-color);
|
|
329
|
+
transition: var(--mw-transition);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.variant-row input[type='checkbox']:checked {
|
|
333
|
+
background: var(--mw-primary-color);
|
|
334
|
+
border-color: var(--mw-primary-color);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.variant-row input[type='checkbox']:checked::after {
|
|
338
|
+
left: 18px;
|
|
339
|
+
background: var(--mw-primary-accent-text-color);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.variant-setup-label {
|
|
343
|
+
display: block;
|
|
344
|
+
margin-top: 14px;
|
|
345
|
+
margin-bottom: 5px;
|
|
346
|
+
font-size: 0.7rem;
|
|
347
|
+
color: var(--mw-text-muted-color);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.variant-setup {
|
|
351
|
+
display: block;
|
|
352
|
+
width: 100%;
|
|
353
|
+
resize: vertical;
|
|
354
|
+
padding: 10px;
|
|
355
|
+
border-radius: 8px;
|
|
356
|
+
border: 1px solid var(--mw-border);
|
|
357
|
+
background: var(--mw-gray-background);
|
|
358
|
+
color: var(--mw-text-muted-color);
|
|
359
|
+
font-family: var(--mw-font-family-mono);
|
|
360
|
+
font-size: 0.68rem;
|
|
361
|
+
line-height: 1.5;
|
|
362
|
+
white-space: pre-wrap;
|
|
363
|
+
word-break: break-word;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@media (max-width: 480px) {
|
|
245
367
|
.palette-fab {
|
|
246
368
|
right: 12px;
|
|
247
369
|
bottom: 12px;
|
|
@@ -258,7 +380,7 @@
|
|
|
258
380
|
}
|
|
259
381
|
</style>
|
|
260
382
|
|
|
261
|
-
<script src="maverick-wave.min.js"></script>
|
|
383
|
+
<script src="maverick-wave.min.js?v={{VERSION}}"></script>
|
|
262
384
|
</head>
|
|
263
385
|
|
|
264
386
|
<body>
|
|
@@ -795,6 +917,13 @@
|
|
|
795
917
|
|
|
796
918
|
const root = document.documentElement;
|
|
797
919
|
|
|
920
|
+
// The values the markup ships with, so the setup box below can tell a
|
|
921
|
+
// chosen colour from the built-in one and print only the difference
|
|
922
|
+
const BUILT_IN = {
|
|
923
|
+
primary: primaryInput.value,
|
|
924
|
+
secondary: secondaryInput.value,
|
|
925
|
+
};
|
|
926
|
+
|
|
798
927
|
const DARK_INK = '#131925';
|
|
799
928
|
const LIGHT_INK = '#f2f6fc';
|
|
800
929
|
|
|
@@ -830,6 +959,7 @@
|
|
|
830
959
|
void root.offsetHeight;
|
|
831
960
|
root.classList.remove('mw-theme-switching');
|
|
832
961
|
window.mwRefreshColorSwatches?.();
|
|
962
|
+
window.mwRefreshSetup?.();
|
|
833
963
|
};
|
|
834
964
|
|
|
835
965
|
const paint = (name, color) => {
|
|
@@ -884,6 +1014,231 @@
|
|
|
884
1014
|
document.addEventListener('keydown', (event) => {
|
|
885
1015
|
if (event.key === 'Escape') toggle(false);
|
|
886
1016
|
});
|
|
1017
|
+
|
|
1018
|
+
// The seam to the variants panel below, which prints and reads the whole
|
|
1019
|
+
// setup - the palette is half of it. Only inline values count as chosen:
|
|
1020
|
+
// anything still coming from the stylesheet is the built-in one.
|
|
1021
|
+
const PAGE_GROUNDS = {
|
|
1022
|
+
dark: '--mw-dark-page-background',
|
|
1023
|
+
light: '--mw-light-page-background',
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
window.mwPalette = {
|
|
1027
|
+
read: () => ({
|
|
1028
|
+
primary: root.style.getPropertyValue('--mw-primary-color') || null,
|
|
1029
|
+
secondary:
|
|
1030
|
+
root.style.getPropertyValue('--mw-secondary-color') || null,
|
|
1031
|
+
// The label colour ink() measured for each fill. It has to travel
|
|
1032
|
+
// with the setup: a project that only copies the two brand colours
|
|
1033
|
+
// keeps the stock accent text, and on a light brand tone that is an
|
|
1034
|
+
// unreadable button. write() recomputes it, so pasting it back is
|
|
1035
|
+
// harmless.
|
|
1036
|
+
primaryInk:
|
|
1037
|
+
root.style.getPropertyValue('--mw-primary-accent-text-color') ||
|
|
1038
|
+
null,
|
|
1039
|
+
secondaryInk:
|
|
1040
|
+
root.style.getPropertyValue('--mw-secondary-accent-text-color') ||
|
|
1041
|
+
null,
|
|
1042
|
+
dark: root.style.getPropertyValue(PAGE_GROUNDS.dark) || null,
|
|
1043
|
+
light: root.style.getPropertyValue(PAGE_GROUNDS.light) || null,
|
|
1044
|
+
}),
|
|
1045
|
+
|
|
1046
|
+
write: ({ primary, secondary, dark, light }) => {
|
|
1047
|
+
const current = window.mwPalette.read();
|
|
1048
|
+
const same =
|
|
1049
|
+
current.primary === primary &&
|
|
1050
|
+
current.secondary === secondary &&
|
|
1051
|
+
current.dark === dark &&
|
|
1052
|
+
current.light === light;
|
|
1053
|
+
// Typing into the setup box fires per keystroke, and settle() forces
|
|
1054
|
+
// a reflow - so a run that changes nothing has to cost nothing
|
|
1055
|
+
if (same) return;
|
|
1056
|
+
|
|
1057
|
+
[
|
|
1058
|
+
['primary', primary, primaryInput],
|
|
1059
|
+
['secondary', secondary, secondaryInput],
|
|
1060
|
+
].forEach(([name, value, input]) => {
|
|
1061
|
+
if (value) {
|
|
1062
|
+
paint(name, value);
|
|
1063
|
+
input.value = value;
|
|
1064
|
+
} else {
|
|
1065
|
+
root.style.removeProperty(`--mw-${name}-color`);
|
|
1066
|
+
root.style.removeProperty(`--mw-${name}-accent-text-color`);
|
|
1067
|
+
input.value = BUILT_IN[name];
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
Object.entries(PAGE_GROUNDS).forEach(([key, prop]) => {
|
|
1072
|
+
const value = key === 'dark' ? dark : light;
|
|
1073
|
+
if (value) root.style.setProperty(prop, value);
|
|
1074
|
+
else root.style.removeProperty(prop);
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
const shownPrimary = primary || BUILT_IN.primary;
|
|
1078
|
+
const shownSecondary = secondary || BUILT_IN.secondary;
|
|
1079
|
+
items.forEach((item) =>
|
|
1080
|
+
item.classList.toggle(
|
|
1081
|
+
'mw-active',
|
|
1082
|
+
item.dataset.primary === shownPrimary &&
|
|
1083
|
+
item.dataset.secondary === shownSecondary
|
|
1084
|
+
)
|
|
1085
|
+
);
|
|
1086
|
+
|
|
1087
|
+
settle();
|
|
1088
|
+
},
|
|
1089
|
+
};
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
// just for the showcase: the variant switches. Each one is a class on <html>
|
|
1093
|
+
// or a single custom property, never a walk over the DOM - markup that
|
|
1094
|
+
// arrives later (a modal, a toast, a kanban card) is covered by the same
|
|
1095
|
+
// class that was already set. The readout at the bottom of the panel is the
|
|
1096
|
+
// whole setup a project would carry to get the look it is currently seeing.
|
|
1097
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1098
|
+
const list = document.getElementById('variant-list');
|
|
1099
|
+
const setup = document.getElementById('variant-setup');
|
|
1100
|
+
if (!list || !setup) return;
|
|
1101
|
+
|
|
1102
|
+
const root = document.documentElement;
|
|
1103
|
+
const controls = [...list.querySelectorAll('input, select')];
|
|
1104
|
+
|
|
1105
|
+
const isOn = (input) =>
|
|
1106
|
+
input.hasAttribute('data-variant-invert')
|
|
1107
|
+
? !input.checked
|
|
1108
|
+
: input.checked;
|
|
1109
|
+
|
|
1110
|
+
const render = () => {
|
|
1111
|
+
// Never while it is being typed in - the box is an input as well as a
|
|
1112
|
+
// readout, and rewriting it under the cursor would eat the paste
|
|
1113
|
+
if (document.activeElement === setup) return;
|
|
1114
|
+
|
|
1115
|
+
const classes = [];
|
|
1116
|
+
const notes = [];
|
|
1117
|
+
const props = [];
|
|
1118
|
+
|
|
1119
|
+
// The palette is part of the setup, and it is printed first because a
|
|
1120
|
+
// project sets its brand colours before it retunes anything else
|
|
1121
|
+
const palette = window.mwPalette?.read() ?? {};
|
|
1122
|
+
[
|
|
1123
|
+
['--mw-primary-color', palette.primary],
|
|
1124
|
+
['--mw-primary-accent-text-color', palette.primaryInk],
|
|
1125
|
+
['--mw-secondary-color', palette.secondary],
|
|
1126
|
+
['--mw-secondary-accent-text-color', palette.secondaryInk],
|
|
1127
|
+
['--mw-dark-page-background', palette.dark],
|
|
1128
|
+
['--mw-light-page-background', palette.light],
|
|
1129
|
+
].forEach(([prop, value]) => {
|
|
1130
|
+
if (value) props.push(` ${prop}: ${value};`);
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
controls.forEach((control) => {
|
|
1134
|
+
if (control.type !== 'checkbox') {
|
|
1135
|
+
if (control.value) {
|
|
1136
|
+
props.push(
|
|
1137
|
+
` ${control.dataset.variantProp}: ${control.value};`
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// A targeted class is one the markup already ships, so its absence is
|
|
1144
|
+
// the deviation worth naming - the rest are classes to add
|
|
1145
|
+
const target = control.dataset.variantTarget;
|
|
1146
|
+
if (target) {
|
|
1147
|
+
if (!isOn(control)) {
|
|
1148
|
+
notes.push(target + ': no ' + control.dataset.variantClass);
|
|
1149
|
+
}
|
|
1150
|
+
} else if (isOn(control)) {
|
|
1151
|
+
classes.push(control.dataset.variantClass);
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
const lines = [];
|
|
1156
|
+
if (classes.length) {
|
|
1157
|
+
lines.push('html class="' + classes.join(' ') + '"');
|
|
1158
|
+
}
|
|
1159
|
+
lines.push(...notes);
|
|
1160
|
+
if (props.length) lines.push(':root {', ...props, '}');
|
|
1161
|
+
|
|
1162
|
+
setup.value = lines.length
|
|
1163
|
+
? lines.join('\n')
|
|
1164
|
+
: 'Default setup - nothing to change.';
|
|
1165
|
+
};
|
|
1166
|
+
|
|
1167
|
+
const apply = (control) => {
|
|
1168
|
+
if (control.type === 'checkbox') {
|
|
1169
|
+
const target = control.dataset.variantTarget;
|
|
1170
|
+
const el = target ? document.querySelector(target) : root;
|
|
1171
|
+
el?.classList.toggle(control.dataset.variantClass, isOn(control));
|
|
1172
|
+
} else if (control.value) {
|
|
1173
|
+
root.style.setProperty(control.dataset.variantProp, control.value);
|
|
1174
|
+
} else {
|
|
1175
|
+
root.style.removeProperty(control.dataset.variantProp);
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
// The box read backwards: someone else's setup pasted in drives the
|
|
1180
|
+
// switches, which drive the page. Anything the text does not mention
|
|
1181
|
+
// falls back to the default, so an empty box is a reset.
|
|
1182
|
+
const load = (text) => {
|
|
1183
|
+
const named = new Set(
|
|
1184
|
+
(text.match(/class\s*=\s*"([^"]*)"/)?.[1] || '')
|
|
1185
|
+
.trim()
|
|
1186
|
+
.split(/\s+/)
|
|
1187
|
+
.filter(Boolean)
|
|
1188
|
+
);
|
|
1189
|
+
// `.mw-header: no mw-header-reveal` - a class the markup ships and the
|
|
1190
|
+
// setup takes away, which is the deviation worth reading
|
|
1191
|
+
const dropped = new Set(
|
|
1192
|
+
[...text.matchAll(/:\s*no\s+([\w-]+)/g)].map((m) => m[1])
|
|
1193
|
+
);
|
|
1194
|
+
const props = new Map(
|
|
1195
|
+
[...text.matchAll(/(--[\w-]+)\s*:\s*([^;\n}]+)/g)].map((m) => [
|
|
1196
|
+
m[1],
|
|
1197
|
+
m[2].trim(),
|
|
1198
|
+
])
|
|
1199
|
+
);
|
|
1200
|
+
|
|
1201
|
+
window.mwPalette?.write({
|
|
1202
|
+
primary: props.get('--mw-primary-color') ?? null,
|
|
1203
|
+
secondary: props.get('--mw-secondary-color') ?? null,
|
|
1204
|
+
dark: props.get('--mw-dark-page-background') ?? null,
|
|
1205
|
+
light: props.get('--mw-light-page-background') ?? null,
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
controls.forEach((control) => {
|
|
1209
|
+
if (control.type === 'checkbox') {
|
|
1210
|
+
const name = control.dataset.variantClass;
|
|
1211
|
+
const on = control.dataset.variantTarget
|
|
1212
|
+
? !dropped.has(name)
|
|
1213
|
+
: named.has(name);
|
|
1214
|
+
control.checked = control.hasAttribute('data-variant-invert')
|
|
1215
|
+
? !on
|
|
1216
|
+
: on;
|
|
1217
|
+
} else {
|
|
1218
|
+
control.value = props.get(control.dataset.variantProp) ?? '';
|
|
1219
|
+
}
|
|
1220
|
+
apply(control);
|
|
1221
|
+
});
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
// Picking a palette repaints the page, and the box has to say so
|
|
1225
|
+
window.mwRefreshSetup = render;
|
|
1226
|
+
|
|
1227
|
+
setup.addEventListener('input', () => load(setup.value));
|
|
1228
|
+
// Normalises what was pasted back into the shape render() writes
|
|
1229
|
+
setup.addEventListener('blur', render);
|
|
1230
|
+
|
|
1231
|
+
controls.forEach((control) =>
|
|
1232
|
+
control.addEventListener('change', () => {
|
|
1233
|
+
apply(control);
|
|
1234
|
+
render();
|
|
1235
|
+
})
|
|
1236
|
+
);
|
|
1237
|
+
|
|
1238
|
+
// Applied on load too, so a browser that restored a checked box after a
|
|
1239
|
+
// reload does not leave the page and the panel disagreeing
|
|
1240
|
+
controls.forEach(apply);
|
|
1241
|
+
render();
|
|
887
1242
|
});
|
|
888
1243
|
|
|
889
1244
|
// for the modals - the framework handles both shapes, div and <dialog>
|