maverick-wave 3.3.0 → 3.3.2
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/maverick-wave/SKILL.md +208 -0
- package/.claude/skills/maverick-wave/examples/angular-form.md +275 -0
- package/.claude/skills/maverick-wave/examples/angular-list-page.md +304 -0
- package/.claude/skills/maverick-wave/examples/angular-services.md +348 -0
- package/.claude/skills/maverick-wave/examples/static-landing-page.md +362 -0
- package/.claude/skills/maverick-wave/references/components.md +812 -0
- package/.claude/skills/maverick-wave/references/forms.md +289 -0
- package/.claude/skills/maverick-wave/references/javascript.md +70 -0
- package/.claude/skills/maverick-wave/references/layout.md +300 -0
- package/.claude/skills/maverick-wave/references/theming.md +192 -0
- package/.github/workflows/ci.yml +41 -0
- package/CHANGELOG.md +13 -0
- package/README.md +115 -21
- package/index.html +15 -9
- package/maverick-wave.min.css +1 -1
- package/maverick-wave.min.js +1 -1
- package/package.json +3 -1
- package/scripts/verify.js +137 -0
- package/src/js/main.js +39 -11
- package/src/partials/accordions-container.html +22 -27
- package/src/partials/alerts-container.html +26 -0
- package/src/partials/avatars-container.html +22 -0
- package/src/partials/buttons-container.html +11 -15
- package/src/partials/cards-container.html +8 -8
- package/src/partials/colors-container.html +33 -20
- package/src/partials/documentation-container.html +3 -0
- package/src/partials/form-field-container.html +16 -12
- package/src/partials/get-started-container.html +93 -35
- package/src/partials/header-utilities-container.html +64 -6
- package/src/partials/input-group-container.html +5 -3
- package/src/partials/page-header-container.html +78 -0
- package/src/partials/tables-container.html +130 -0
- package/src/partials/tabs-container.html +20 -16
- package/src/partials/tags-container.html +55 -1
- package/src/scss/abstracts/_index.scss +4 -1
- package/src/scss/abstracts/_variables.scss +128 -106
- package/src/scss/base/_base.scss +32 -136
- package/src/scss/base/_reset.scss +1 -1
- package/src/scss/base/_typography.scss +0 -92
- package/src/scss/components/_accordions.scss +3 -3
- package/src/scss/components/_avatars.scss +28 -1
- package/src/scss/components/_blog-post.scss +1 -1
- package/src/scss/components/_button-bar.scss +6 -6
- package/src/scss/components/_buttons.scss +33 -10
- package/src/scss/components/_cards.scss +7 -7
- package/src/scss/components/_coming-soon.scss +4 -4
- package/src/scss/components/_gallery.scss +2 -2
- package/src/scss/components/_index.scss +1 -0
- package/src/scss/components/_info.scss +20 -3
- package/src/scss/components/_lists.scss +11 -10
- package/src/scss/components/_modals.scss +14 -7
- package/src/scss/components/_pagination.scss +3 -3
- package/src/scss/components/_panels.scss +4 -4
- package/src/scss/components/_progress.scss +1 -1
- package/src/scss/components/_ratings.scss +1 -1
- package/src/scss/components/_spinners.scss +2 -2
- package/src/scss/components/_stepper.scss +3 -2
- package/src/scss/components/_tables.scss +42 -1
- package/src/scss/components/_tabs.scss +5 -5
- package/src/scss/components/_tags.scss +43 -27
- package/src/scss/components/_tiles.scss +3 -3
- package/src/scss/components/_timelines.scss +2 -2
- package/src/scss/components/_toasts.scss +92 -0
- package/src/scss/form-elements/_form.scss +11 -3
- package/src/scss/form-elements/_input.scss +24 -0
- package/src/scss/form-elements/_toggle.scss +1 -1
- package/src/scss/layout/_footer.scss +4 -4
- package/src/scss/layout/_header.scss +219 -59
- package/src/scss/layout/_index.scss +1 -0
- package/src/scss/layout/_main.scss +1 -1
- package/src/scss/layout/_page-header.scss +48 -0
- package/src/scss/layout/_section.scss +15 -14
- package/src/scss/main.scss +4 -0
- package/src/scss/utilities/_flex.scss +4 -0
- package/src/scss/utilities/_index.scss +1 -0
- package/src/scss/utilities/_text.scss +105 -0
- package/.claude/commands/mw.md +0 -595
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Forms
|
|
2
|
+
|
|
3
|
+
## The field pattern
|
|
4
|
+
|
|
5
|
+
`mw-field` groups label, control, hint and error into one unit. It is the
|
|
6
|
+
recommended wrapper for every labelled control and the natural fit for a
|
|
7
|
+
reactive form control.
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<div class="mw-field">
|
|
11
|
+
<label class="mw-field-label mw-required" for="email">Email</label>
|
|
12
|
+
<input id="email" type="email" class="mw-input" />
|
|
13
|
+
<span class="mw-field-hint">Used for login and notifications.</span>
|
|
14
|
+
<span class="mw-field-error">
|
|
15
|
+
<i class="fas fa-exclamation-circle"></i> Please enter a valid email
|
|
16
|
+
address.
|
|
17
|
+
</span>
|
|
18
|
+
</div>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- `mw-required` on the label appends a red asterisk (`data-required="true"`
|
|
22
|
+
works too).
|
|
23
|
+
- `mw-field-hint` is the small muted helper line, `mw-field-error` the small red
|
|
24
|
+
one. Render only one of them at a time.
|
|
25
|
+
- `mw-field-has-error` on the **wrapper** turns the border of the contained
|
|
26
|
+
`mw-input` / `mw-select` / `mw-textarea` red and adds a soft red halo.
|
|
27
|
+
- `mw-form-element-error` does the same for a single control that has no field
|
|
28
|
+
wrapper - it also works on `mw-checkbox-group`, `mw-radio-group` and
|
|
29
|
+
`mw-slider-container`.
|
|
30
|
+
|
|
31
|
+
> **The framework does not style Angular's `ng-invalid` / `ng-touched` classes.**
|
|
32
|
+
> Bind the framework classes to the control state yourself:
|
|
33
|
+
>
|
|
34
|
+
> ```html
|
|
35
|
+
> <div
|
|
36
|
+
> class="mw-field"
|
|
37
|
+
> [class.mw-field-has-error]="email.invalid && email.touched"
|
|
38
|
+
> >
|
|
39
|
+
> <label class="mw-field-label mw-required" for="email">Email</label>
|
|
40
|
+
> <input id="email" type="email" class="mw-input" formControlName="email" />
|
|
41
|
+
> @if (email.hasError('required') && email.touched) {
|
|
42
|
+
> <span class="mw-field-error">
|
|
43
|
+
> <i class="fas fa-exclamation-circle"></i> Email is required.
|
|
44
|
+
> </span>
|
|
45
|
+
> }
|
|
46
|
+
> </div>
|
|
47
|
+
> ```
|
|
48
|
+
>
|
|
49
|
+
> The same applies to any other framework - React: `className={...}`, Vue:
|
|
50
|
+
> `:class`. Nothing reacts to validation on its own.
|
|
51
|
+
|
|
52
|
+
## Form layout
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<form class="mw-form">
|
|
56
|
+
<div class="mw-form-group">
|
|
57
|
+
<h4 class="mw-form-group-title">Personal information</h4>
|
|
58
|
+
<div class="mw-grid-2">
|
|
59
|
+
<div class="mw-field">...</div>
|
|
60
|
+
<div class="mw-field">...</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="mw-form-actions">
|
|
65
|
+
<p class="mw-form-actions-hint">Changes are saved immediately.</p>
|
|
66
|
+
<button type="button" class="mw-btn mw-btn-outline">Cancel</button>
|
|
67
|
+
<button type="submit" class="mw-btn mw-btn-primary">Save</button>
|
|
68
|
+
</div>
|
|
69
|
+
</form>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- `mw-form` is a flex column with a gap and **no padding** - safe to put
|
|
73
|
+
directly on `mw-modal-body` or `mw-panel-body`. It also works on a `<div>`
|
|
74
|
+
when there is no real form element.
|
|
75
|
+
- `mw-form-inline` lays the children out in a row.
|
|
76
|
+
- `mw-form-group` is the bordered block for a titled group of fields.
|
|
77
|
+
`mw-form-group-title` is the heading hook inside it - put it on the `<h3>` /
|
|
78
|
+
`<h4>`, otherwise the heading keeps its full document-level size.
|
|
79
|
+
- `mw-form-actions` is a right-aligned wrapping button row.
|
|
80
|
+
`mw-form-actions-hint` is a full-width note above the buttons.
|
|
81
|
+
Alignment variants: `mw-form-actions-left`, `mw-form-actions-center`,
|
|
82
|
+
`mw-form-actions-full-width` (stacked, buttons at 100% - login forms).
|
|
83
|
+
- Put multi-column layouts inside a group with `mw-grid-2` etc. and let a field
|
|
84
|
+
span everything with `style="grid-column: 1 / -1"`.
|
|
85
|
+
|
|
86
|
+
## Text inputs
|
|
87
|
+
|
|
88
|
+
```html
|
|
89
|
+
<input type="text" class="mw-input" />
|
|
90
|
+
<input type="text" class="mw-input mw-input-sm" />
|
|
91
|
+
<input type="text" class="mw-input mw-input-lg" />
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- Full width by default, `2px` border, focus ring in the primary colour.
|
|
95
|
+
- `readonly` renders on a muted surface, `disabled` additionally as
|
|
96
|
+
`not-allowed`. Both are attribute driven - there is no readonly/disabled
|
|
97
|
+
class.
|
|
98
|
+
- Date/time work as normal inputs (`type="date" | "time" | "datetime-local"`);
|
|
99
|
+
the native picker indicator is styled.
|
|
100
|
+
|
|
101
|
+
### Numbers and money
|
|
102
|
+
|
|
103
|
+
```html
|
|
104
|
+
<div class="mw-field">
|
|
105
|
+
<label class="mw-field-label" for="budget">Budget</label>
|
|
106
|
+
<div class="mw-input-group">
|
|
107
|
+
<span class="mw-input-group-prefix"><i class="fas fa-euro-sign"></i></span>
|
|
108
|
+
<input
|
|
109
|
+
id="budget"
|
|
110
|
+
type="text"
|
|
111
|
+
inputmode="decimal"
|
|
112
|
+
class="mw-input mw-input-numeric"
|
|
113
|
+
placeholder="0,00"
|
|
114
|
+
/>
|
|
115
|
+
<span class="mw-input-group-suffix">EUR</span>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`mw-input-numeric` is the entry counterpart to the `mw-text-numeric` utility:
|
|
121
|
+
right aligned, tabular lining figures, native spinners suppressed. A value looks
|
|
122
|
+
identical while being typed and once it is rendered into a table.
|
|
123
|
+
|
|
124
|
+
> **Use `type="text"` + `inputmode="decimal"`, not `type="number"`.** A focused
|
|
125
|
+
> number input changes its value when the page is scrolled, and in most locales
|
|
126
|
+
> it rejects a comma as decimal separator - the user sees `1234,50` but `.value`
|
|
127
|
+
> comes back empty. `inputmode="decimal"` still brings up the numeric keypad on
|
|
128
|
+
> mobile. Parsing the comma stays the application's job.
|
|
129
|
+
|
|
130
|
+
## Input group
|
|
131
|
+
|
|
132
|
+
Prefix, suffix and buttons glued to the control:
|
|
133
|
+
|
|
134
|
+
```html
|
|
135
|
+
<div class="mw-input-group">
|
|
136
|
+
<span class="mw-input-group-prefix"><i class="fas fa-search"></i></span>
|
|
137
|
+
<input type="text" class="mw-input" placeholder="Search…" />
|
|
138
|
+
<button type="button" class="mw-btn mw-btn-primary">
|
|
139
|
+
<i class="fas fa-search"></i>
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Sizes: `mw-input-group-sm`, `mw-input-group-lg` - combine them with the matching
|
|
145
|
+
`mw-input-sm` / `mw-input-lg` on the control. A prefix or suffix takes an icon,
|
|
146
|
+
a symbol (`@`, `https://`) or a short unit.
|
|
147
|
+
|
|
148
|
+
## Select
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<select class="mw-select">
|
|
152
|
+
<option value="" disabled selected>Choose…</option>
|
|
153
|
+
<option>Option A</option>
|
|
154
|
+
</select>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Sizes: `mw-select-sm`, `mw-select-lg`.
|
|
158
|
+
|
|
159
|
+
## Textarea
|
|
160
|
+
|
|
161
|
+
```html
|
|
162
|
+
<textarea class="mw-textarea" rows="4"></textarea>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Sizes: `mw-textarea-sm`, `mw-textarea-lg`. Resizing is off by default; enable it
|
|
166
|
+
with `mw-textarea-resizable`, `mw-textarea-resizable-vertical` or
|
|
167
|
+
`mw-textarea-resizable-horizontal`.
|
|
168
|
+
|
|
169
|
+
## Checkbox
|
|
170
|
+
|
|
171
|
+
The native input is hidden; `mw-checkbox-box` is the visible control, so the
|
|
172
|
+
order of the three children matters.
|
|
173
|
+
|
|
174
|
+
```html
|
|
175
|
+
<div class="mw-checkbox-group mw-checkbox-group-inline">
|
|
176
|
+
<label class="mw-checkbox mw-checkbox-success">
|
|
177
|
+
<input type="checkbox" />
|
|
178
|
+
<div class="mw-checkbox-box"></div>
|
|
179
|
+
<div class="mw-checkbox-label">Send a copy</div>
|
|
180
|
+
</label>
|
|
181
|
+
</div>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- Sizes: `mw-checkbox-sm`, `mw-checkbox-lg`
|
|
185
|
+
- Colours: `mw-checkbox-primary`, `-secondary`, `-success`, `-warning`,
|
|
186
|
+
`-danger`, `-info`
|
|
187
|
+
- Disabled: `mw-checkbox-disabled` on the label **plus** the `disabled`
|
|
188
|
+
attribute on the input
|
|
189
|
+
- Group: `mw-checkbox-group` (column), `mw-checkbox-group-inline` (row)
|
|
190
|
+
- With a heading and a description use `mw-checkbox-content` containing
|
|
191
|
+
`mw-checkbox-header` + `mw-checkbox-label` - see the checkbox item list in
|
|
192
|
+
`references/components.md`
|
|
193
|
+
|
|
194
|
+
## Radio
|
|
195
|
+
|
|
196
|
+
Same structure, with `mw-radio-button` as the visible control:
|
|
197
|
+
|
|
198
|
+
```html
|
|
199
|
+
<div class="mw-radio-group">
|
|
200
|
+
<label class="mw-radio">
|
|
201
|
+
<input type="radio" name="plan" value="free" />
|
|
202
|
+
<div class="mw-radio-button"></div>
|
|
203
|
+
<div class="mw-radio-label">Free</div>
|
|
204
|
+
</label>
|
|
205
|
+
</div>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Sizes `mw-radio-sm`, `-lg`; colours `mw-radio-primary`, `-secondary`,
|
|
209
|
+
`-success`, `-warning`, `-danger`, `-info`; `mw-radio-disabled`; groups
|
|
210
|
+
`mw-radio-group`, `mw-radio-group-inline`.
|
|
211
|
+
|
|
212
|
+
## Toggle
|
|
213
|
+
|
|
214
|
+
```html
|
|
215
|
+
<div class="mw-toggle-group">
|
|
216
|
+
<label class="mw-toggle mw-toggle-success">
|
|
217
|
+
<input type="checkbox" checked />
|
|
218
|
+
<div class="mw-toggle-track"></div>
|
|
219
|
+
<span class="mw-toggle-label">Notifications</span>
|
|
220
|
+
</label>
|
|
221
|
+
</div>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Sizes `mw-toggle-sm`, `-lg`; colours `mw-toggle-primary`, `-secondary`,
|
|
225
|
+
`-success`, `-warning`, `-danger` (no `info` variant); `mw-toggle-disabled` plus
|
|
226
|
+
the `disabled` attribute; groups `mw-toggle-group`, `mw-toggle-group-inline`.
|
|
227
|
+
|
|
228
|
+
## Slider
|
|
229
|
+
|
|
230
|
+
```html
|
|
231
|
+
<label>
|
|
232
|
+
<span>Experience</span>
|
|
233
|
+
<div class="mw-slider-container">
|
|
234
|
+
<div class="mw-slider-value mw-slider-numeric" data-value="3"></div>
|
|
235
|
+
<input
|
|
236
|
+
type="range"
|
|
237
|
+
class="mw-slider mw-slider-primary"
|
|
238
|
+
min="0"
|
|
239
|
+
max="10"
|
|
240
|
+
value="3"
|
|
241
|
+
/>
|
|
242
|
+
</div>
|
|
243
|
+
</label>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- The badge prints `data-value`: `mw-slider-numeric` appends `/10`,
|
|
247
|
+
`mw-slider-percent` appends `%`.
|
|
248
|
+
- The filled part of the track comes from the custom property `--value` on the
|
|
249
|
+
input (`style="--value: 30%"`).
|
|
250
|
+
- Both `data-value` and `--value` are set by the shipped JS on `input`. In a SPA
|
|
251
|
+
bind them yourself - two bindings, see `examples/angular-form.md`.
|
|
252
|
+
- Colours: `mw-slider-primary`, `-secondary`, `-success`, `-warning`, `-danger`,
|
|
253
|
+
`-info`; sizes `mw-slider-sm`, `-lg`.
|
|
254
|
+
|
|
255
|
+
## Login card
|
|
256
|
+
|
|
257
|
+
A centered, self-contained card for sign-in screens.
|
|
258
|
+
|
|
259
|
+
```html
|
|
260
|
+
<div class="mw-login">
|
|
261
|
+
<div class="mw-login-logo"><img src="logo.svg" alt="" /></div>
|
|
262
|
+
<div class="mw-login-message">Internal area - valid account required.</div>
|
|
263
|
+
<div class="mw-login-message mw-login-message-error">Wrong credentials.</div>
|
|
264
|
+
|
|
265
|
+
<form class="mw-form">
|
|
266
|
+
<div class="mw-grid-1">
|
|
267
|
+
<div class="mw-field">
|
|
268
|
+
<label class="mw-field-label mw-required" for="login-email"
|
|
269
|
+
>Email</label
|
|
270
|
+
>
|
|
271
|
+
<div class="mw-input-group">
|
|
272
|
+
<span class="mw-input-group-prefix"
|
|
273
|
+
><i class="fas fa-envelope"></i
|
|
274
|
+
></span>
|
|
275
|
+
<input id="login-email" type="email" class="mw-input" required />
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="mw-form-actions mw-form-actions-full-width">
|
|
280
|
+
<button type="submit" class="mw-btn mw-btn-primary">
|
|
281
|
+
<i class="fas fa-sign-in-alt"></i> Sign in
|
|
282
|
+
</button>
|
|
283
|
+
</div>
|
|
284
|
+
</form>
|
|
285
|
+
</div>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`mw-login-error` on the card flashes a red border after a failed attempt,
|
|
289
|
+
`mw-login-message-info` / `mw-login-message-error` colour the message line.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# JavaScript & SPA integration
|
|
2
|
+
|
|
3
|
+
## What `maverick-wave.min.js` is
|
|
4
|
+
|
|
5
|
+
One vanilla IIFE, no dependencies, ~8 kB. It queries the DOM **once** on
|
|
6
|
+
`DOMContentLoaded` and attaches listeners. There is no re-init API, no
|
|
7
|
+
`MutationObserver`, no exported module - it is built for a server-rendered or
|
|
8
|
+
static page.
|
|
9
|
+
|
|
10
|
+
## Why a SPA must not load it
|
|
11
|
+
|
|
12
|
+
- It runs once during bootstrap. Anything rendered afterwards - i.e. everything
|
|
13
|
+
in a routed application - never gets initialised.
|
|
14
|
+
- It writes straight into the DOM (class toggles, generated elements, inline
|
|
15
|
+
styles). In Angular that happens outside change detection; in a zoneless app
|
|
16
|
+
the framework never learns about it, and on the next re-render your bindings
|
|
17
|
+
win and the mutation is gone.
|
|
18
|
+
- It reads and writes `localStorage` for the theme, competing with whatever
|
|
19
|
+
service you build for the same job.
|
|
20
|
+
|
|
21
|
+
So: no `"scripts"` entry in `angular.json`, no `import 'maverick-wave.min.js'`
|
|
22
|
+
in a Vite entry point. Load the **CSS only** and rebuild the handful of
|
|
23
|
+
behaviours in components. Each one is a few lines - the framework's state
|
|
24
|
+
classes are the entire contract.
|
|
25
|
+
|
|
26
|
+
## Behaviour inventory
|
|
27
|
+
|
|
28
|
+
| Behaviour | What the shipped JS does | What to do instead |
|
|
29
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| Accordion | Toggles `active` on `mw-accordion-header` and the following `mw-accordion-content` | `[class.active]="isOpen()"` on both elements |
|
|
31
|
+
| Tabs | `data-tab` → panel `id`; sets `active` on nav item and panel | Track the selected index/key, bind `active` on both; drop `data-tab` |
|
|
32
|
+
| Modal | Click on `mw-modal-close` removes `mw-modal-open` from the overlay | `[class.mw-modal-open]="isOpen()"`; backdrop click closes. Opening is not in the script at all (the showcase has its own `openModal`) |
|
|
33
|
+
| Mobile nav | Toggles `open` on `mw-menu-btn` and `mw-navbar`, closes on anchor click | One signal, bound to both; reset it on navigation end |
|
|
34
|
+
| Scroll spy | Sets `active` on `mw-navbar-link` from the scroll position | Router-based: `routerLinkActive="active"` |
|
|
35
|
+
| Theme toggle | `localStorage['mw-theme']`, toggles `mw-theme-light` on `<body>` and `active` on the toggle | A theme service - see `examples/angular-services.md` |
|
|
36
|
+
| Progress bar | `IntersectionObserver` sets `width` from `data-value` | Bind `[style.width.%]="value()"` on `mw-progress-fill` |
|
|
37
|
+
| Slider | On `input`, sets `--value` (track fill) and `data-value` (badge text) | Bind `[style.--value.%]` and `[attr.data-value]` |
|
|
38
|
+
| Alerts | Close button adds `mw-alert-closed` (`display: none`) | Remove the alert from the list/signal |
|
|
39
|
+
| Checkbox lists | Adds `mw-selected` to the `li`, emits a `checkboxToggle` event, exposes `window.toggleCheckbox` | `[class.mw-selected]="item.checked"` |
|
|
40
|
+
| Gallery | Generates the dots, moves the track, swipe handling, writes `mw-gallery-desc` | Render dots in the template, bind the track transform and `mw-active` on the current dot |
|
|
41
|
+
| Image slider | Toggles `active` on the overlay image and the control button with the matching `data-index` | Bind `active` from the selected index |
|
|
42
|
+
| Localhost indicator | On a local hostname, prepends `mw-localhost-indicator-pulse` to the header when it carries `mw-localhost-indicator-activated` | Render the element conditionally |
|
|
43
|
+
| Header login button | Swaps the FontAwesome lock icon | Bind the icon class |
|
|
44
|
+
| Color swatches | Showcase-only (prints computed hex values) | Not needed |
|
|
45
|
+
|
|
46
|
+
## What works without any JavaScript
|
|
47
|
+
|
|
48
|
+
Pure CSS, nothing to wire up: hover and focus states, the card lift, tooltips
|
|
49
|
+
(`data-tooltip`), `mw-rating` (via `data-rating`), the responsive table card
|
|
50
|
+
view (`data-label`), all grids and utilities, the body scroll lock while a modal
|
|
51
|
+
is open (`body:has(.mw-modal-open)`), toast entry animations, the sticky table
|
|
52
|
+
header, `prefers-reduced-motion` handling.
|
|
53
|
+
|
|
54
|
+
## When you do keep the shipped JS
|
|
55
|
+
|
|
56
|
+
For a static page, a landing page or a server-rendered site (Thymeleaf, Twig,
|
|
57
|
+
Jekyll, plain HTML) it is exactly right - load it at the end of `<body>`. The
|
|
58
|
+
only manual part is opening a modal, which the script does not cover:
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<script src="maverick-wave.min.js"></script>
|
|
62
|
+
<script>
|
|
63
|
+
function openModal(id) {
|
|
64
|
+
document.getElementById(id).classList.add('mw-modal-open');
|
|
65
|
+
}
|
|
66
|
+
function closeModal(id) {
|
|
67
|
+
document.getElementById(id).classList.remove('mw-modal-open');
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
70
|
+
```
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Layout & utilities
|
|
2
|
+
|
|
3
|
+
## Page skeleton
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<body>
|
|
7
|
+
<header class="mw-header">
|
|
8
|
+
<div class="mw-container"><!-- logo + actions --></div>
|
|
9
|
+
</header>
|
|
10
|
+
|
|
11
|
+
<main class="mw-main">
|
|
12
|
+
<section id="overview" class="mw-section">
|
|
13
|
+
<div class="mw-container">...</div>
|
|
14
|
+
</section>
|
|
15
|
+
<section id="details" class="mw-section mw-section-alternate">
|
|
16
|
+
<div class="mw-container">...</div>
|
|
17
|
+
</section>
|
|
18
|
+
</main>
|
|
19
|
+
|
|
20
|
+
<footer class="mw-footer">
|
|
21
|
+
<div class="mw-container">...</div>
|
|
22
|
+
</footer>
|
|
23
|
+
</body>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `mw-main` is a full-height flex column - the footer stays at the bottom on
|
|
27
|
+
short pages.
|
|
28
|
+
- `mw-container` is `min(1200px, 89%)`, horizontally centered. Nest it inside
|
|
29
|
+
every full-bleed band (header, section, footer), never around them.
|
|
30
|
+
- `mw-content` (`flex: 1` + top padding) is the alternative to `mw-section` when
|
|
31
|
+
a page has one single content area. `mw-content-centered` centers it
|
|
32
|
+
vertically over the full viewport - login pages, error pages.
|
|
33
|
+
- `<section>` has **no** padding of its own. Always add `mw-section`.
|
|
34
|
+
|
|
35
|
+
In an Angular app the shell above lives in `app.component.html` and the router
|
|
36
|
+
outlet goes inside the `mw-container`:
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<main class="mw-main">
|
|
40
|
+
<section class="mw-section">
|
|
41
|
+
<div class="mw-container"><router-outlet /></div>
|
|
42
|
+
</section>
|
|
43
|
+
</main>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Header & navigation
|
|
47
|
+
|
|
48
|
+
The header is fixed, dark in both themes, and expects exactly this structure -
|
|
49
|
+
its children are styled through descendant selectors:
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<header class="mw-header">
|
|
53
|
+
<div class="mw-container">
|
|
54
|
+
<div class="mw-logo">
|
|
55
|
+
<button type="button"><img src="logo.svg" alt="Logo" /></button>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="mw-header-actions">
|
|
59
|
+
<nav class="mw-navbar mw-navbar-medium">
|
|
60
|
+
<ul class="mw-navbar-list">
|
|
61
|
+
<li class="mw-navbar-item">
|
|
62
|
+
<a href="#start" class="mw-navbar-link active">Start</a>
|
|
63
|
+
</li>
|
|
64
|
+
<li class="mw-navbar-item">
|
|
65
|
+
<a href="#docs" class="mw-navbar-link">Docs</a>
|
|
66
|
+
</li>
|
|
67
|
+
</ul>
|
|
68
|
+
</nav>
|
|
69
|
+
|
|
70
|
+
<div class="mw-theme-toggle mw-ml-5">
|
|
71
|
+
<div class="mw-theme-toggle-slider">
|
|
72
|
+
<div class="mw-theme-toggle-icon"><i class="fas fa-moon"></i></div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<button class="mw-login-btn" type="button">
|
|
77
|
+
<i class="fas fa-lock"></i>
|
|
78
|
+
</button>
|
|
79
|
+
|
|
80
|
+
<div class="mw-menu-btn"><div class="mw-menu-btn-burger"></div></div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</header>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- **Collapse breakpoint follows the item count**: default (1-3 items) collapses
|
|
87
|
+
at `md`, `mw-navbar-medium` (4-5) at `lg`, `mw-navbar-large` (6+) at `xl`.
|
|
88
|
+
Pick the class by how many links you have.
|
|
89
|
+
- Below the breakpoint the list is hidden and `mw-menu-btn` appears. Opening the
|
|
90
|
+
drawer means adding `open` to **both** `mw-menu-btn` and `mw-navbar`.
|
|
91
|
+
- The active link carries `active` (no prefix).
|
|
92
|
+
- `mw-profile-btn` is the signed-in pill, next to or instead of the login button:
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<button class="mw-profile-btn" type="button">
|
|
96
|
+
<span class="mw-avatar mw-avatar-initials mw-avatar-xs">MW</span>
|
|
97
|
+
<span class="mw-profile-btn-name">Michael</span>
|
|
98
|
+
</button>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
An icon (`<i class="fas fa-user-circle"></i>`) works instead of the avatar.
|
|
102
|
+
Below `md` the name hides and the button becomes a round 40px control, the
|
|
103
|
+
same height as the login and burger buttons.
|
|
104
|
+
|
|
105
|
+
- Header colours have their own tokens (`--mw-header-background`,
|
|
106
|
+
`--mw-header-text-color`, `--mw-header-navbar-list-color`,
|
|
107
|
+
`--mw-header-navbar-list-active-color`, `--mw-header-burgerbutton-color`,
|
|
108
|
+
`--mw-header-border`) so the chrome can be retuned without touching the brand
|
|
109
|
+
palette.
|
|
110
|
+
|
|
111
|
+
**Localhost indicator.** Put `mw-localhost-indicator-activated` on the header
|
|
112
|
+
and the shipped JS prepends a pulsing bar when the host is localhost/127.0.0.1/
|
|
113
|
+
192.168.\*. In a SPA, reimplement it: add a `<div class="mw-localhost-indicator-pulse">`
|
|
114
|
+
as the header's first child under the same condition.
|
|
115
|
+
|
|
116
|
+
## Footer
|
|
117
|
+
|
|
118
|
+
```html
|
|
119
|
+
<footer class="mw-footer">
|
|
120
|
+
<div class="mw-container">
|
|
121
|
+
<div class="mw-footer-top">
|
|
122
|
+
<div class="mw-footer-column">
|
|
123
|
+
<h3>Product</h3>
|
|
124
|
+
<p>...</p>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="mw-footer-column">
|
|
127
|
+
<h3>Links</h3>
|
|
128
|
+
<ul>
|
|
129
|
+
<li><a href="#">Docs</a></li>
|
|
130
|
+
</ul>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div class="mw-social-links">
|
|
135
|
+
<a href="#" data-tooltip="GitHub"><i class="fab fa-github"></i></a>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<p class="mw-disclaimer">Long-form text under the columns.</p>
|
|
139
|
+
<p class="mw-copyright">© 2026 Example</p>
|
|
140
|
+
<p class="mw-last-updated">Last updated: 2026-08-04</p>
|
|
141
|
+
</div>
|
|
142
|
+
</footer>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`data-tooltip="..."` is a global attribute hook, not a class - it works on any
|
|
146
|
+
element and shows a tooltip above it on hover.
|
|
147
|
+
|
|
148
|
+
## Sections
|
|
149
|
+
|
|
150
|
+
| Class | Use |
|
|
151
|
+
| ----------------------------------- | -------------------------------------------------------------------------- |
|
|
152
|
+
| `mw-section` | Vertical rhythm (1.75rem top/bottom) for a page band |
|
|
153
|
+
| `mw-section-alternate` | Diagonal pattern background; combine with `mw-section` |
|
|
154
|
+
| `mw-section-title` | Centered `3xl` heading with a decorative primary underline - landing pages |
|
|
155
|
+
| `mw-section-subtitle` | Centered `2xl` heading with a thin secondary underline |
|
|
156
|
+
| `mw-section-nav` + `mw-section-btn` | Centered, wrapping row of outline-style jump links |
|
|
157
|
+
|
|
158
|
+
```html
|
|
159
|
+
<section class="mw-section mw-section-alternate">
|
|
160
|
+
<div class="mw-container">
|
|
161
|
+
<h2 class="mw-section-title">Components</h2>
|
|
162
|
+
<nav class="mw-section-nav">
|
|
163
|
+
<a href="#buttons" class="mw-section-btn"
|
|
164
|
+
><i class="fas fa-hand-pointer"></i> Buttons</a
|
|
165
|
+
>
|
|
166
|
+
<a href="#cards" class="mw-section-btn"
|
|
167
|
+
><i class="fas fa-square"></i> Cards</a
|
|
168
|
+
>
|
|
169
|
+
</nav>
|
|
170
|
+
</div>
|
|
171
|
+
</section>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Page header
|
|
175
|
+
|
|
176
|
+
The application counterpart to `mw-section-title`: title and subtitle left,
|
|
177
|
+
actions right, wrapping when it gets tight. Below `sm` the actions take the full
|
|
178
|
+
width.
|
|
179
|
+
|
|
180
|
+
```html
|
|
181
|
+
<header class="mw-page-header">
|
|
182
|
+
<div>
|
|
183
|
+
<h2>Invoices</h2>
|
|
184
|
+
<p>14 entries · 3 drafts</p>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="mw-page-header-actions">
|
|
187
|
+
<button type="button" class="mw-btn mw-btn-outline mw-btn-sm">
|
|
188
|
+
<i class="fas fa-filter"></i> Filter
|
|
189
|
+
</button>
|
|
190
|
+
<button type="button" class="mw-btn mw-btn-primary mw-btn-sm">
|
|
191
|
+
<i class="fas fa-plus"></i> New
|
|
192
|
+
</button>
|
|
193
|
+
</div>
|
|
194
|
+
</header>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- `h1`-`h3` inside are normalised to `2xl` (`xl` below `sm`), `p` becomes muted
|
|
198
|
+
and small - no extra classes needed.
|
|
199
|
+
- `mw-meta-header` fits under the title instead of the `<p>` (see
|
|
200
|
+
`references/components.md`).
|
|
201
|
+
- `mw-page-header-plain` removes the bottom rule.
|
|
202
|
+
|
|
203
|
+
## Hero
|
|
204
|
+
|
|
205
|
+
```html
|
|
206
|
+
<div class="mw-container">
|
|
207
|
+
<div class="mw-hero">
|
|
208
|
+
<div class="mw-home mw-home-content-fade">
|
|
209
|
+
<div class="mw-home-text">
|
|
210
|
+
<h1>Product<span class="mw-text-primary">Name</span></h1>
|
|
211
|
+
<p>Subline</p>
|
|
212
|
+
</div>
|
|
213
|
+
<div class="mw-d-flex mw-gap-8 mw-justify-center mw-mb-7">
|
|
214
|
+
<button class="mw-btn mw-btn-primary mw-btn-lg">Get started</button>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
A `mw-container` that contains a `mw-hero` switches to full-bleed, full-height
|
|
222
|
+
mode with the background image from `--mw-hero-background` and a blurred overlay
|
|
223
|
+
(`--mw-hero-overlay-background`). `mw-home-content-fade` fades the content in.
|
|
224
|
+
|
|
225
|
+
## Grid
|
|
226
|
+
|
|
227
|
+
All grid classes are `display: grid` with a preset gap (`mw-gap-*` overrides
|
|
228
|
+
it). They collapse to fewer columns on their own - no responsive suffixes to
|
|
229
|
+
manage.
|
|
230
|
+
|
|
231
|
+
| Class | Columns | Collapses |
|
|
232
|
+
| ----------------- | ----------------------------------------------- | ---------------------------- |
|
|
233
|
+
| `mw-grid-1` | 1 | - (children forced to 100%) |
|
|
234
|
+
| `mw-grid-2` | 2 | 1 below `sm` |
|
|
235
|
+
| `mw-grid-3` | 3 | 2 below `md`, 1 below `sm` |
|
|
236
|
+
| `mw-grid-4` | 4 | 2 below `lg`, 1 below `sm` |
|
|
237
|
+
| `mw-grid-5` | 5 | 4 / 3 / 2 / 1 down the scale |
|
|
238
|
+
| `mw-grid-auto` | `auto-fill`, min 300px | automatic |
|
|
239
|
+
| `mw-grid-auto-sm` | min 250px | automatic |
|
|
240
|
+
| `mw-grid-auto-md` | min 350px | automatic |
|
|
241
|
+
| `mw-grid-flex` | 12 columns + `mw-col-span-1` … `mw-col-span-12` | single column below `md` |
|
|
242
|
+
| `mw-grid` | no template, just grid + gap | - |
|
|
243
|
+
|
|
244
|
+
Every one of them has a `-lg` twin with a wider gap (2.5rem instead of 1.35rem):
|
|
245
|
+
`mw-grid-2-lg`, `mw-grid-3-lg`, `mw-grid-4-lg`, `mw-grid-5-lg`, `mw-grid-1-lg`,
|
|
246
|
+
`mw-grid-auto-lg`, `mw-grid-flex-lg`, `mw-grid-lg`.
|
|
247
|
+
|
|
248
|
+
```html
|
|
249
|
+
<div class="mw-grid-flex">
|
|
250
|
+
<aside class="mw-col-span-4">Sidebar</aside>
|
|
251
|
+
<div class="mw-col-span-8">Content</div>
|
|
252
|
+
</div>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Stacks** - single column, differing horizontal alignment of the children:
|
|
256
|
+
|
|
257
|
+
| Class | Children |
|
|
258
|
+
| --------------------------------------------- | --------------------------------------- |
|
|
259
|
+
| `mw-grid-stack` | natural width, left aligned |
|
|
260
|
+
| `mw-grid-stack-center` | centered |
|
|
261
|
+
| `mw-grid-stack-end` | right aligned |
|
|
262
|
+
| `mw-grid-stack-stretch` | full width (same result as `mw-grid-1`) |
|
|
263
|
+
| `mw-grid-stack-lg`, `mw-grid-stack-center-lg` | wide-gap twins |
|
|
264
|
+
|
|
265
|
+
## Utilities
|
|
266
|
+
|
|
267
|
+
**Spacing** - margin `mw-m-*`, `mw-mt-*`, `mw-mb-*`, `mw-ml-*`, `mw-mr-*`,
|
|
268
|
+
`mw-mx-*`, `mw-my-*`; padding `mw-p-*`, `mw-pt-*`, `mw-pb-*`, `mw-pl-*`,
|
|
269
|
+
`mw-pr-*`, `mw-px-*`, `mw-py-*`; `mw-gap-*`. Keys `0`-`14`, plus negative keys
|
|
270
|
+
`1`-`6` for margins only (`mw-mt--3`). Gap shrinks to 75% below `sm`
|
|
271
|
+
automatically.
|
|
272
|
+
|
|
273
|
+
**Display** - `mw-d-flex`, `mw-d-inline-flex`, `mw-d-block`, `mw-d-inline`,
|
|
274
|
+
`mw-d-inline-block`, `mw-d-grid`, `mw-d-none`.
|
|
275
|
+
|
|
276
|
+
There are no responsive display variants. Show/hide per breakpoint is the
|
|
277
|
+
application's job (media query in your own stylesheet, or `@if` in the
|
|
278
|
+
template).
|
|
279
|
+
|
|
280
|
+
**Flex** - `mw-flex-row`, `mw-flex-column`, `mw-flex-wrap`, `mw-flex-nowrap`,
|
|
281
|
+
`mw-flex-1`, `mw-flex-grow-1`, `mw-flex-shrink-0`,
|
|
282
|
+
`mw-justify-start|end|center|between|around|evenly`,
|
|
283
|
+
`mw-items-start|end|center|stretch`.
|
|
284
|
+
|
|
285
|
+
`mw-flex-1` sets `flex: 1` (basis 0, all items equal). `mw-flex-grow-1` only
|
|
286
|
+
grows and keeps the content width as the basis - that is the one you want next
|
|
287
|
+
to an avatar or an icon.
|
|
288
|
+
|
|
289
|
+
**Text** - alignment `mw-text-left|center|right`; colour `mw-text-primary`,
|
|
290
|
+
`-secondary`, `-success`, `-warning`, `-danger`, `-info`, `-muted`,
|
|
291
|
+
`mw-text-color-dark`, `mw-text-color-light`; weight `mw-text-bold`, `-medium`,
|
|
292
|
+
`-normal`, `-light`; `mw-text-italic`; size `mw-text-3xs` … `mw-text-6xl`;
|
|
293
|
+
line height `mw-leading-tight|normal|loose`.
|
|
294
|
+
|
|
295
|
+
`mw-text-numeric` is the one to know: right aligned, tabular lining figures, no
|
|
296
|
+
wrap - for money and figures in tables. It is locale agnostic (alignment comes
|
|
297
|
+
from the right edge), but the number of decimal places has to be constant per
|
|
298
|
+
column.
|
|
299
|
+
|
|
300
|
+
**Radius** - `mw-radius-none|xs|sm|md|lg|xl|2xl|full`.
|