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.
Files changed (77) hide show
  1. package/.claude/skills/maverick-wave/SKILL.md +208 -0
  2. package/.claude/skills/maverick-wave/examples/angular-form.md +275 -0
  3. package/.claude/skills/maverick-wave/examples/angular-list-page.md +304 -0
  4. package/.claude/skills/maverick-wave/examples/angular-services.md +348 -0
  5. package/.claude/skills/maverick-wave/examples/static-landing-page.md +362 -0
  6. package/.claude/skills/maverick-wave/references/components.md +812 -0
  7. package/.claude/skills/maverick-wave/references/forms.md +289 -0
  8. package/.claude/skills/maverick-wave/references/javascript.md +70 -0
  9. package/.claude/skills/maverick-wave/references/layout.md +300 -0
  10. package/.claude/skills/maverick-wave/references/theming.md +192 -0
  11. package/.github/workflows/ci.yml +41 -0
  12. package/CHANGELOG.md +13 -0
  13. package/README.md +115 -21
  14. package/index.html +15 -9
  15. package/maverick-wave.min.css +1 -1
  16. package/maverick-wave.min.js +1 -1
  17. package/package.json +3 -1
  18. package/scripts/verify.js +137 -0
  19. package/src/js/main.js +39 -11
  20. package/src/partials/accordions-container.html +22 -27
  21. package/src/partials/alerts-container.html +26 -0
  22. package/src/partials/avatars-container.html +22 -0
  23. package/src/partials/buttons-container.html +11 -15
  24. package/src/partials/cards-container.html +8 -8
  25. package/src/partials/colors-container.html +33 -20
  26. package/src/partials/documentation-container.html +3 -0
  27. package/src/partials/form-field-container.html +16 -12
  28. package/src/partials/get-started-container.html +93 -35
  29. package/src/partials/header-utilities-container.html +64 -6
  30. package/src/partials/input-group-container.html +5 -3
  31. package/src/partials/page-header-container.html +78 -0
  32. package/src/partials/tables-container.html +130 -0
  33. package/src/partials/tabs-container.html +20 -16
  34. package/src/partials/tags-container.html +55 -1
  35. package/src/scss/abstracts/_index.scss +4 -1
  36. package/src/scss/abstracts/_variables.scss +128 -106
  37. package/src/scss/base/_base.scss +32 -136
  38. package/src/scss/base/_reset.scss +1 -1
  39. package/src/scss/base/_typography.scss +0 -92
  40. package/src/scss/components/_accordions.scss +3 -3
  41. package/src/scss/components/_avatars.scss +28 -1
  42. package/src/scss/components/_blog-post.scss +1 -1
  43. package/src/scss/components/_button-bar.scss +6 -6
  44. package/src/scss/components/_buttons.scss +33 -10
  45. package/src/scss/components/_cards.scss +7 -7
  46. package/src/scss/components/_coming-soon.scss +4 -4
  47. package/src/scss/components/_gallery.scss +2 -2
  48. package/src/scss/components/_index.scss +1 -0
  49. package/src/scss/components/_info.scss +20 -3
  50. package/src/scss/components/_lists.scss +11 -10
  51. package/src/scss/components/_modals.scss +14 -7
  52. package/src/scss/components/_pagination.scss +3 -3
  53. package/src/scss/components/_panels.scss +4 -4
  54. package/src/scss/components/_progress.scss +1 -1
  55. package/src/scss/components/_ratings.scss +1 -1
  56. package/src/scss/components/_spinners.scss +2 -2
  57. package/src/scss/components/_stepper.scss +3 -2
  58. package/src/scss/components/_tables.scss +42 -1
  59. package/src/scss/components/_tabs.scss +5 -5
  60. package/src/scss/components/_tags.scss +43 -27
  61. package/src/scss/components/_tiles.scss +3 -3
  62. package/src/scss/components/_timelines.scss +2 -2
  63. package/src/scss/components/_toasts.scss +92 -0
  64. package/src/scss/form-elements/_form.scss +11 -3
  65. package/src/scss/form-elements/_input.scss +24 -0
  66. package/src/scss/form-elements/_toggle.scss +1 -1
  67. package/src/scss/layout/_footer.scss +4 -4
  68. package/src/scss/layout/_header.scss +219 -59
  69. package/src/scss/layout/_index.scss +1 -0
  70. package/src/scss/layout/_main.scss +1 -1
  71. package/src/scss/layout/_page-header.scss +48 -0
  72. package/src/scss/layout/_section.scss +15 -14
  73. package/src/scss/main.scss +4 -0
  74. package/src/scss/utilities/_flex.scss +4 -0
  75. package/src/scss/utilities/_index.scss +1 -0
  76. package/src/scss/utilities/_text.scss +105 -0
  77. 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">&copy; 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 &middot; 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`.