maverick-wave 5.6.0 → 5.8.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 +5 -5
- 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 +24 -18
- package/CHANGELOG.md +17 -0
- package/README.md +16 -11
- package/index.html +238 -46
- package/maverick-wave.min.css +4 -4
- package/package.json +2 -2
- package/scripts/verify.js +11 -1
- package/src/partials/header-container.html +7 -5
- package/src/partials/history-container.html +1 -1
- package/src/partials/palette-container.html +123 -26
- package/src/partials/typography-container.html +8 -9
- package/src/partials/utilities-container.html +26 -6
- package/src/scss/abstracts/_mixins.scss +6 -1
- package/src/scss/abstracts/_variables.scss +4 -4
- package/src/scss/base/_base.scss +4 -0
- package/src/scss/base/_reset.scss +2 -0
- package/src/scss/components/_avatars.scss +29 -0
- package/src/scss/components/_button-bar.scss +1 -1
- package/src/scss/components/_buttons.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/_main.scss +31 -2
- package/src/scss/utilities/_reveal.scss +9 -2
- package/src/scss/utilities/_touch-targets.scss +1 -1
- package/src/scss/utilities/_variants.scss +89 -17
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
|
|
@@ -121,40 +124,50 @@
|
|
|
121
124
|
|
|
122
125
|
/* Palette switcher, showcase only. The framework derives every tone from
|
|
123
126
|
the root colors, so the panel writes nothing but those. */
|
|
124
|
-
.palette-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
border
|
|
132
|
-
border:
|
|
127
|
+
.palette-toggle {
|
|
128
|
+
display: none;
|
|
129
|
+
align-items: center;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
width: 50px;
|
|
132
|
+
height: 40px;
|
|
133
|
+
flex-shrink: 0;
|
|
134
|
+
border: none;
|
|
135
|
+
border-radius: calc(10px * var(--mw-radius-scale, 1));
|
|
133
136
|
background: var(--mw-primary-color);
|
|
134
137
|
color: var(--mw-primary-accent-text-color);
|
|
135
|
-
|
|
136
|
-
font-size: 1.25rem;
|
|
138
|
+
font-size: 1.1rem;
|
|
137
139
|
cursor: pointer;
|
|
138
140
|
transition: var(--mw-transition);
|
|
139
141
|
}
|
|
140
142
|
|
|
141
|
-
|
|
143
|
+
/* Below sm the panel would cover the very thing it is changing */
|
|
144
|
+
@media (min-width: 576px) {
|
|
145
|
+
.palette-toggle {
|
|
146
|
+
display: flex;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.palette-toggle:hover {
|
|
142
151
|
background: var(--mw-primary-color-hover);
|
|
143
152
|
}
|
|
144
153
|
|
|
145
|
-
.palette-
|
|
146
|
-
transform: scale(0.
|
|
154
|
+
.palette-toggle:active {
|
|
155
|
+
transform: scale(0.94);
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
.palette-panel {
|
|
150
159
|
position: fixed;
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
top: calc(var(--mw-header-height) + 8px);
|
|
161
|
+
/* Flush with the container edge, so it lines up with the button in the
|
|
162
|
+
header rather than sitting further out */
|
|
163
|
+
right: max(18px, calc((100% - var(--mw-container-width)) / 2));
|
|
153
164
|
z-index: 400;
|
|
154
|
-
|
|
165
|
+
/* Wide enough for three palettes in a row; the min() is for the narrow
|
|
166
|
+
end, where the panel would otherwise reach past the viewport */
|
|
167
|
+
width: min(440px, calc(100% - 24px));
|
|
155
168
|
/* The variant list makes the panel taller than a laptop in landscape -
|
|
156
|
-
|
|
157
|
-
max-height: calc(100dvh -
|
|
169
|
+
it takes what is left under the header and scrolls the rest */
|
|
170
|
+
max-height: calc(100dvh - var(--mw-header-height) - 24px);
|
|
158
171
|
overflow-y: auto;
|
|
159
172
|
overscroll-behavior: contain;
|
|
160
173
|
padding: 18px;
|
|
@@ -162,6 +175,26 @@
|
|
|
162
175
|
border: 1px solid var(--mw-border);
|
|
163
176
|
background: var(--mw-card-background);
|
|
164
177
|
box-shadow: var(--mw-elevation-5);
|
|
178
|
+
transform: translateY(0);
|
|
179
|
+
opacity: 1;
|
|
180
|
+
/* display is in the list so allow-discrete keeps the panel mounted
|
|
181
|
+
through the fade out - `hidden` is the state the script writes */
|
|
182
|
+
transition-property: opacity, transform, display;
|
|
183
|
+
transition-duration: var(--mw-duration-base);
|
|
184
|
+
transition-timing-function: var(--mw-ease-out);
|
|
185
|
+
transition-behavior: allow-discrete;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@starting-style {
|
|
189
|
+
.palette-panel {
|
|
190
|
+
opacity: 0;
|
|
191
|
+
transform: translateY(-12px);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.palette-panel[hidden] {
|
|
196
|
+
opacity: 0;
|
|
197
|
+
transform: translateY(-12px);
|
|
165
198
|
}
|
|
166
199
|
|
|
167
200
|
.palette-heading {
|
|
@@ -178,7 +211,7 @@
|
|
|
178
211
|
|
|
179
212
|
.palette-grid {
|
|
180
213
|
display: grid;
|
|
181
|
-
grid-template-columns:
|
|
214
|
+
grid-template-columns: repeat(3, 1fr);
|
|
182
215
|
gap: 6px;
|
|
183
216
|
}
|
|
184
217
|
|
|
@@ -288,7 +321,7 @@
|
|
|
288
321
|
|
|
289
322
|
.variant-row select {
|
|
290
323
|
flex-shrink: 0;
|
|
291
|
-
max-width:
|
|
324
|
+
max-width: 135px;
|
|
292
325
|
padding: 3px 6px;
|
|
293
326
|
font-size: 0.75rem;
|
|
294
327
|
color: var(--mw-text-color);
|
|
@@ -336,8 +369,18 @@
|
|
|
336
369
|
background: var(--mw-primary-accent-text-color);
|
|
337
370
|
}
|
|
338
371
|
|
|
339
|
-
.variant-setup {
|
|
372
|
+
.variant-setup-label {
|
|
373
|
+
display: block;
|
|
340
374
|
margin-top: 14px;
|
|
375
|
+
margin-bottom: 5px;
|
|
376
|
+
font-size: 0.7rem;
|
|
377
|
+
color: var(--mw-text-muted-color);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.variant-setup {
|
|
381
|
+
display: block;
|
|
382
|
+
width: 100%;
|
|
383
|
+
resize: vertical;
|
|
341
384
|
padding: 10px;
|
|
342
385
|
border-radius: 8px;
|
|
343
386
|
border: 1px solid var(--mw-border);
|
|
@@ -349,25 +392,9 @@
|
|
|
349
392
|
white-space: pre-wrap;
|
|
350
393
|
word-break: break-word;
|
|
351
394
|
}
|
|
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
|
-
}
|
|
368
395
|
</style>
|
|
369
396
|
|
|
370
|
-
<script src="maverick-wave.min.js"></script>
|
|
397
|
+
<script src="maverick-wave.min.js?v={{VERSION}}"></script>
|
|
371
398
|
</head>
|
|
372
399
|
|
|
373
400
|
<body>
|
|
@@ -895,15 +922,22 @@
|
|
|
895
922
|
// root element repaints the page - bar, ink, borders and tints included.
|
|
896
923
|
// Nothing is persisted - a palette is for looking at, a reload is the way back.
|
|
897
924
|
document.addEventListener('DOMContentLoaded', () => {
|
|
898
|
-
const
|
|
925
|
+
const toggleBtn = document.getElementById('palette-toggle');
|
|
899
926
|
const panel = document.getElementById('palette-panel');
|
|
900
927
|
const primaryInput = document.getElementById('palette-primary');
|
|
901
928
|
const secondaryInput = document.getElementById('palette-secondary');
|
|
902
929
|
const items = [...document.querySelectorAll('.palette-item')];
|
|
903
|
-
if (!
|
|
930
|
+
if (!toggleBtn || !panel) return;
|
|
904
931
|
|
|
905
932
|
const root = document.documentElement;
|
|
906
933
|
|
|
934
|
+
// The values the markup ships with, so the setup box below can tell a
|
|
935
|
+
// chosen colour from the built-in one and print only the difference
|
|
936
|
+
const BUILT_IN = {
|
|
937
|
+
primary: primaryInput.value,
|
|
938
|
+
secondary: secondaryInput.value,
|
|
939
|
+
};
|
|
940
|
+
|
|
907
941
|
const DARK_INK = '#131925';
|
|
908
942
|
const LIGHT_INK = '#f2f6fc';
|
|
909
943
|
|
|
@@ -939,6 +973,7 @@
|
|
|
939
973
|
void root.offsetHeight;
|
|
940
974
|
root.classList.remove('mw-theme-switching');
|
|
941
975
|
window.mwRefreshColorSwatches?.();
|
|
976
|
+
window.mwRefreshSetup?.();
|
|
942
977
|
};
|
|
943
978
|
|
|
944
979
|
const paint = (name, color) => {
|
|
@@ -980,10 +1015,10 @@
|
|
|
980
1015
|
|
|
981
1016
|
const toggle = (open) => {
|
|
982
1017
|
panel.hidden = !open;
|
|
983
|
-
|
|
1018
|
+
toggleBtn.setAttribute('aria-expanded', String(open));
|
|
984
1019
|
};
|
|
985
1020
|
|
|
986
|
-
|
|
1021
|
+
toggleBtn.addEventListener('click', (event) => {
|
|
987
1022
|
event.stopPropagation();
|
|
988
1023
|
toggle(panel.hidden);
|
|
989
1024
|
});
|
|
@@ -993,6 +1028,79 @@
|
|
|
993
1028
|
document.addEventListener('keydown', (event) => {
|
|
994
1029
|
if (event.key === 'Escape') toggle(false);
|
|
995
1030
|
});
|
|
1031
|
+
|
|
1032
|
+
// The seam to the variants panel below, which prints and reads the whole
|
|
1033
|
+
// setup - the palette is half of it. Only inline values count as chosen:
|
|
1034
|
+
// anything still coming from the stylesheet is the built-in one.
|
|
1035
|
+
const PAGE_GROUNDS = {
|
|
1036
|
+
dark: '--mw-dark-page-background',
|
|
1037
|
+
light: '--mw-light-page-background',
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
window.mwPalette = {
|
|
1041
|
+
read: () => ({
|
|
1042
|
+
primary: root.style.getPropertyValue('--mw-primary-color') || null,
|
|
1043
|
+
secondary:
|
|
1044
|
+
root.style.getPropertyValue('--mw-secondary-color') || null,
|
|
1045
|
+
// The label colour ink() measured for each fill. It has to travel
|
|
1046
|
+
// with the setup: a project that only copies the two brand colours
|
|
1047
|
+
// keeps the stock accent text, and on a light brand tone that is an
|
|
1048
|
+
// unreadable button. write() recomputes it, so pasting it back is
|
|
1049
|
+
// harmless.
|
|
1050
|
+
primaryInk:
|
|
1051
|
+
root.style.getPropertyValue('--mw-primary-accent-text-color') ||
|
|
1052
|
+
null,
|
|
1053
|
+
secondaryInk:
|
|
1054
|
+
root.style.getPropertyValue('--mw-secondary-accent-text-color') ||
|
|
1055
|
+
null,
|
|
1056
|
+
dark: root.style.getPropertyValue(PAGE_GROUNDS.dark) || null,
|
|
1057
|
+
light: root.style.getPropertyValue(PAGE_GROUNDS.light) || null,
|
|
1058
|
+
}),
|
|
1059
|
+
|
|
1060
|
+
write: ({ primary, secondary, dark, light }) => {
|
|
1061
|
+
const current = window.mwPalette.read();
|
|
1062
|
+
const same =
|
|
1063
|
+
current.primary === primary &&
|
|
1064
|
+
current.secondary === secondary &&
|
|
1065
|
+
current.dark === dark &&
|
|
1066
|
+
current.light === light;
|
|
1067
|
+
// Typing into the setup box fires per keystroke, and settle() forces
|
|
1068
|
+
// a reflow - so a run that changes nothing has to cost nothing
|
|
1069
|
+
if (same) return;
|
|
1070
|
+
|
|
1071
|
+
[
|
|
1072
|
+
['primary', primary, primaryInput],
|
|
1073
|
+
['secondary', secondary, secondaryInput],
|
|
1074
|
+
].forEach(([name, value, input]) => {
|
|
1075
|
+
if (value) {
|
|
1076
|
+
paint(name, value);
|
|
1077
|
+
input.value = value;
|
|
1078
|
+
} else {
|
|
1079
|
+
root.style.removeProperty(`--mw-${name}-color`);
|
|
1080
|
+
root.style.removeProperty(`--mw-${name}-accent-text-color`);
|
|
1081
|
+
input.value = BUILT_IN[name];
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
Object.entries(PAGE_GROUNDS).forEach(([key, prop]) => {
|
|
1086
|
+
const value = key === 'dark' ? dark : light;
|
|
1087
|
+
if (value) root.style.setProperty(prop, value);
|
|
1088
|
+
else root.style.removeProperty(prop);
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
const shownPrimary = primary || BUILT_IN.primary;
|
|
1092
|
+
const shownSecondary = secondary || BUILT_IN.secondary;
|
|
1093
|
+
items.forEach((item) =>
|
|
1094
|
+
item.classList.toggle(
|
|
1095
|
+
'mw-active',
|
|
1096
|
+
item.dataset.primary === shownPrimary &&
|
|
1097
|
+
item.dataset.secondary === shownSecondary
|
|
1098
|
+
)
|
|
1099
|
+
);
|
|
1100
|
+
|
|
1101
|
+
settle();
|
|
1102
|
+
},
|
|
1103
|
+
};
|
|
996
1104
|
});
|
|
997
1105
|
|
|
998
1106
|
// just for the showcase: the variant switches. Each one is a class on <html>
|
|
@@ -1014,13 +1122,36 @@
|
|
|
1014
1122
|
: input.checked;
|
|
1015
1123
|
|
|
1016
1124
|
const render = () => {
|
|
1125
|
+
// Never while it is being typed in - the box is an input as well as a
|
|
1126
|
+
// readout, and rewriting it under the cursor would eat the paste
|
|
1127
|
+
if (document.activeElement === setup) return;
|
|
1128
|
+
|
|
1017
1129
|
const classes = [];
|
|
1018
1130
|
const notes = [];
|
|
1019
1131
|
const props = [];
|
|
1020
1132
|
|
|
1133
|
+
// The palette is part of the setup, and it is printed first because a
|
|
1134
|
+
// project sets its brand colours before it retunes anything else
|
|
1135
|
+
const palette = window.mwPalette?.read() ?? {};
|
|
1136
|
+
[
|
|
1137
|
+
['--mw-primary-color', palette.primary],
|
|
1138
|
+
['--mw-primary-accent-text-color', palette.primaryInk],
|
|
1139
|
+
['--mw-secondary-color', palette.secondary],
|
|
1140
|
+
['--mw-secondary-accent-text-color', palette.secondaryInk],
|
|
1141
|
+
['--mw-dark-page-background', palette.dark],
|
|
1142
|
+
['--mw-light-page-background', palette.light],
|
|
1143
|
+
].forEach(([prop, value]) => {
|
|
1144
|
+
if (value) props.push(` ${prop}: ${value};`);
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1021
1147
|
controls.forEach((control) => {
|
|
1022
1148
|
if (control.type !== 'checkbox') {
|
|
1023
|
-
|
|
1149
|
+
// A select whose options are class names picks one of a set the
|
|
1150
|
+
// markup cannot hold at the same time - a shape is one or none
|
|
1151
|
+
if (!control.value) return;
|
|
1152
|
+
if (control.hasAttribute('data-variant-classes')) {
|
|
1153
|
+
classes.push(control.value);
|
|
1154
|
+
} else {
|
|
1024
1155
|
props.push(
|
|
1025
1156
|
` ${control.dataset.variantProp}: ${control.value};`
|
|
1026
1157
|
);
|
|
@@ -1047,16 +1178,22 @@
|
|
|
1047
1178
|
lines.push(...notes);
|
|
1048
1179
|
if (props.length) lines.push(':root {', ...props, '}');
|
|
1049
1180
|
|
|
1050
|
-
setup.
|
|
1181
|
+
setup.value = lines.length
|
|
1051
1182
|
? lines.join('\n')
|
|
1052
1183
|
: 'Default setup - nothing to change.';
|
|
1053
1184
|
};
|
|
1054
1185
|
|
|
1186
|
+
const optionClasses = (control) =>
|
|
1187
|
+
[...control.options].map((option) => option.value).filter(Boolean);
|
|
1188
|
+
|
|
1055
1189
|
const apply = (control) => {
|
|
1056
1190
|
if (control.type === 'checkbox') {
|
|
1057
1191
|
const target = control.dataset.variantTarget;
|
|
1058
1192
|
const el = target ? document.querySelector(target) : root;
|
|
1059
1193
|
el?.classList.toggle(control.dataset.variantClass, isOn(control));
|
|
1194
|
+
} else if (control.hasAttribute('data-variant-classes')) {
|
|
1195
|
+
root.classList.remove(...optionClasses(control));
|
|
1196
|
+
if (control.value) root.classList.add(control.value);
|
|
1060
1197
|
} else if (control.value) {
|
|
1061
1198
|
root.style.setProperty(control.dataset.variantProp, control.value);
|
|
1062
1199
|
} else {
|
|
@@ -1064,6 +1201,61 @@
|
|
|
1064
1201
|
}
|
|
1065
1202
|
};
|
|
1066
1203
|
|
|
1204
|
+
// The box read backwards: someone else's setup pasted in drives the
|
|
1205
|
+
// switches, which drive the page. Anything the text does not mention
|
|
1206
|
+
// falls back to the default, so an empty box is a reset.
|
|
1207
|
+
const load = (text) => {
|
|
1208
|
+
const named = new Set(
|
|
1209
|
+
(text.match(/class\s*=\s*"([^"]*)"/)?.[1] || '')
|
|
1210
|
+
.trim()
|
|
1211
|
+
.split(/\s+/)
|
|
1212
|
+
.filter(Boolean)
|
|
1213
|
+
);
|
|
1214
|
+
// `.mw-header: no mw-header-reveal` - a class the markup ships and the
|
|
1215
|
+
// setup takes away, which is the deviation worth reading
|
|
1216
|
+
const dropped = new Set(
|
|
1217
|
+
[...text.matchAll(/:\s*no\s+([\w-]+)/g)].map((m) => m[1])
|
|
1218
|
+
);
|
|
1219
|
+
const props = new Map(
|
|
1220
|
+
[...text.matchAll(/(--[\w-]+)\s*:\s*([^;\n}]+)/g)].map((m) => [
|
|
1221
|
+
m[1],
|
|
1222
|
+
m[2].trim(),
|
|
1223
|
+
])
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
window.mwPalette?.write({
|
|
1227
|
+
primary: props.get('--mw-primary-color') ?? null,
|
|
1228
|
+
secondary: props.get('--mw-secondary-color') ?? null,
|
|
1229
|
+
dark: props.get('--mw-dark-page-background') ?? null,
|
|
1230
|
+
light: props.get('--mw-light-page-background') ?? null,
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
controls.forEach((control) => {
|
|
1234
|
+
if (control.type === 'checkbox') {
|
|
1235
|
+
const name = control.dataset.variantClass;
|
|
1236
|
+
const on = control.dataset.variantTarget
|
|
1237
|
+
? !dropped.has(name)
|
|
1238
|
+
: named.has(name);
|
|
1239
|
+
control.checked = control.hasAttribute('data-variant-invert')
|
|
1240
|
+
? !on
|
|
1241
|
+
: on;
|
|
1242
|
+
} else if (control.hasAttribute('data-variant-classes')) {
|
|
1243
|
+
control.value =
|
|
1244
|
+
optionClasses(control).find((name) => named.has(name)) ?? '';
|
|
1245
|
+
} else {
|
|
1246
|
+
control.value = props.get(control.dataset.variantProp) ?? '';
|
|
1247
|
+
}
|
|
1248
|
+
apply(control);
|
|
1249
|
+
});
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
// Picking a palette repaints the page, and the box has to say so
|
|
1253
|
+
window.mwRefreshSetup = render;
|
|
1254
|
+
|
|
1255
|
+
setup.addEventListener('input', () => load(setup.value));
|
|
1256
|
+
// Normalises what was pasted back into the shape render() writes
|
|
1257
|
+
setup.addEventListener('blur', render);
|
|
1258
|
+
|
|
1067
1259
|
controls.forEach((control) =>
|
|
1068
1260
|
control.addEventListener('change', () => {
|
|
1069
1261
|
apply(control);
|