maverick-wave 5.30.0 → 5.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +2 -2
  2. package/maverick-wave.min.css +2 -2
  3. package/package.json +8 -2
  4. package/src/scss/abstracts/_variables.scss +2 -0
  5. package/src/scss/components/_modals.scss +23 -9
  6. package/src/scss/components/_testimonial.scss +2 -0
  7. package/src/scss/layout/_home.scss +46 -0
  8. package/src/scss/layout/_main.scss +20 -0
  9. package/src/scss/layout/_parallax.scss +2 -2
  10. package/src/scss/main-lean.scss +8 -3
  11. package/.claude/settings.local.json +0 -43
  12. package/.claude/skills/mw-maverick-wave/SKILL.md +0 -532
  13. package/.claude/skills/mw-maverick-wave/examples/angular-form.md +0 -279
  14. package/.claude/skills/mw-maverick-wave/examples/angular-list-page.md +0 -309
  15. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +0 -538
  16. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +0 -530
  17. package/.claude/skills/mw-maverick-wave/references/components.md +0 -2170
  18. package/.claude/skills/mw-maverick-wave/references/forms.md +0 -358
  19. package/.claude/skills/mw-maverick-wave/references/javascript.md +0 -164
  20. package/.claude/skills/mw-maverick-wave/references/layout.md +0 -764
  21. package/.claude/skills/mw-maverick-wave/references/theming.md +0 -530
  22. package/.github/workflows/ci.yml +0 -41
  23. package/.idea/codeStyles/Project.xml +0 -59
  24. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  25. package/.idea/maverick-wave.iml +0 -12
  26. package/.idea/modules.xml +0 -8
  27. package/.idea/prettier.xml +0 -6
  28. package/.idea/vcs.xml +0 -6
  29. package/.prettierignore +0 -8
  30. package/.prettierrc.json +0 -7
  31. package/CHANGELOG.md +0 -956
  32. package/CLAUDE.md +0 -60
  33. package/gulpfile.js +0 -167
  34. package/index.html +0 -1730
  35. package/release.sh +0 -31
  36. package/scripts/verify.js +0 -252
  37. package/src/assets/favicon/apple-touch-icon.png +0 -0
  38. package/src/assets/favicon/favicon-96x96.png +0 -0
  39. package/src/assets/favicon/favicon.svg +0 -1
  40. package/src/assets/favicon/site.webmanifest +0 -37
  41. package/src/assets/favicon/web-app-manifest-192x192.png +0 -0
  42. package/src/assets/favicon/web-app-manifest-512x512.png +0 -0
  43. package/src/assets/favicon.ico +0 -0
  44. package/src/assets/header-logo.svg +0 -24
  45. package/src/assets/images/gallery-city.svg +0 -49
  46. package/src/assets/images/gallery-desert.svg +0 -25
  47. package/src/assets/images/gallery-forest.svg +0 -46
  48. package/src/assets/images/gallery-lake.svg +0 -26
  49. package/src/assets/images/gallery-mountain.svg +0 -19
  50. package/src/assets/images/gallery-ocean.svg +0 -26
  51. package/src/assets/images/gallery-oldtown.svg +0 -41
  52. package/src/assets/images/gallery-snow.svg +0 -39
  53. package/src/assets/images/photo-balloon.svg +0 -35
  54. package/src/assets/images/photo-forest-path.svg +0 -31
  55. package/src/assets/images/photo-lighthouse.svg +0 -42
  56. package/src/assets/images/photo-palm.svg +0 -27
  57. package/src/assets/images/photo-peak.svg +0 -27
  58. package/src/assets/images/photo-tower.svg +0 -34
  59. package/src/assets/images/photo-waterfall.svg +0 -32
  60. package/src/assets/images/slider-field.svg +0 -124
  61. package/src/assets/images/slider-forest.svg +0 -33
  62. package/src/assets/images/slider-lake.svg +0 -35
  63. package/src/assets/images/slider-mountains.svg +0 -29
  64. package/src/assets/images/slider-trees.svg +0 -41
  65. package/src/assets/images/story-aurora.svg +0 -36
  66. package/src/assets/images/story-beach.svg +0 -34
  67. package/src/assets/images/story-stars.svg +0 -35
  68. package/src/assets/images/tile-analytics.svg +0 -22
  69. package/src/assets/images/tile-cloud.svg +0 -19
  70. package/src/assets/images/tile-ecommerce.svg +0 -14
  71. package/src/assets/images/tile-enterprise.svg +0 -22
  72. package/src/assets/images/tile-marketing.svg +0 -16
  73. package/src/assets/media/demo-chime.wav +0 -0
  74. package/src/assets/media/story-sunrise.jpg +0 -0
  75. package/src/assets/media/story-sunrise.mp4 +0 -0
  76. package/src/partials/accordions-container.html +0 -179
  77. package/src/partials/alerts-container.html +0 -151
  78. package/src/partials/announcement-container.html +0 -67
  79. package/src/partials/avatars-container.html +0 -229
  80. package/src/partials/badges-container.html +0 -114
  81. package/src/partials/blog-posts-container.html +0 -158
  82. package/src/partials/breadcrumbs-container.html +0 -126
  83. package/src/partials/button-bar-container.html +0 -175
  84. package/src/partials/buttons-container.html +0 -272
  85. package/src/partials/calendar-container.html +0 -203
  86. package/src/partials/cards-container.html +0 -727
  87. package/src/partials/code-container.html +0 -194
  88. package/src/partials/colors-container.html +0 -263
  89. package/src/partials/coming-soon-container.html +0 -10
  90. package/src/partials/contact-container.html +0 -85
  91. package/src/partials/content-slider-container.html +0 -36
  92. package/src/partials/divider-container.html +0 -70
  93. package/src/partials/documentation-container.html +0 -204
  94. package/src/partials/dropdown-container.html +0 -119
  95. package/src/partials/empty-state-container.html +0 -79
  96. package/src/partials/feed-container.html +0 -506
  97. package/src/partials/footer-container.html +0 -113
  98. package/src/partials/form-container.html +0 -262
  99. package/src/partials/form-elements-container.html +0 -616
  100. package/src/partials/form-field-container.html +0 -166
  101. package/src/partials/gallery-container.html +0 -62
  102. package/src/partials/get-started-container.html +0 -423
  103. package/src/partials/grid-container.html +0 -191
  104. package/src/partials/header-container.html +0 -69
  105. package/src/partials/header-utilities-container.html +0 -331
  106. package/src/partials/highlights-container.html +0 -71
  107. package/src/partials/history-container.html +0 -67
  108. package/src/partials/home-container.html +0 -31
  109. package/src/partials/html-lists-container.html +0 -63
  110. package/src/partials/info-container.html +0 -104
  111. package/src/partials/input-group-container.html +0 -299
  112. package/src/partials/item-lists-container.html +0 -368
  113. package/src/partials/kanban-container.html +0 -459
  114. package/src/partials/kbd-container.html +0 -63
  115. package/src/partials/leader-row-container.html +0 -139
  116. package/src/partials/login-container.html +0 -65
  117. package/src/partials/lunch-menu-container.html +0 -341
  118. package/src/partials/media-container.html +0 -27
  119. package/src/partials/meta-info-container.html +0 -21
  120. package/src/partials/modals-container.html +0 -199
  121. package/src/partials/mosaic-container.html +0 -173
  122. package/src/partials/page-header-container.html +0 -78
  123. package/src/partials/pagination-container.html +0 -64
  124. package/src/partials/palette-container.html +0 -480
  125. package/src/partials/panels-container.html +0 -126
  126. package/src/partials/parallax-container.html +0 -147
  127. package/src/partials/portrait-gallery-container.html +0 -175
  128. package/src/partials/preview-container.html +0 -136
  129. package/src/partials/pricing-container.html +0 -523
  130. package/src/partials/progress-container.html +0 -230
  131. package/src/partials/prose-container.html +0 -43
  132. package/src/partials/ratings-container.html +0 -104
  133. package/src/partials/section-head-container.html +0 -77
  134. package/src/partials/segmented-container.html +0 -66
  135. package/src/partials/skeleton-container.html +0 -87
  136. package/src/partials/spinners-container.html +0 -96
  137. package/src/partials/stepper-container.html +0 -111
  138. package/src/partials/stories-container.html +0 -182
  139. package/src/partials/tables-container.html +0 -309
  140. package/src/partials/tabs-container.html +0 -290
  141. package/src/partials/tags-container.html +0 -242
  142. package/src/partials/techstack-bucket-container.html +0 -187
  143. package/src/partials/testimonials-container.html +0 -88
  144. package/src/partials/tiles-container.html +0 -247
  145. package/src/partials/timelines-container.html +0 -196
  146. package/src/partials/typography-container.html +0 -74
  147. package/src/partials/utilities-container.html +0 -553
@@ -1,358 +0,0 @@
1
- # Forms
2
-
3
- ## Control sizes
4
-
5
- `mw-input`, `mw-select`, `mw-textarea` and `mw-btn` share one scale:
6
-
7
- | step | height | font |
8
- | ----- | -------- | ------ |
9
- | `-sm` | 1.875rem | 0.8rem |
10
- | base | 2.125rem | 0.9rem |
11
- | `-lg` | 2.375rem | 1rem |
12
-
13
- A field and the button beside it are therefore the same height by construction -
14
- before 4.11.0 each control worked its own height out and an input, a select and
15
- a button in one row measured 32.2, 34.4 and 37.2 pixels. Buttons sit one font
16
- step above the fields: a button carries a label, a field carries what the user
17
- typed.
18
-
19
- Never set a height on a control yourself. Pick the step and leave it alone.
20
-
21
- The minimum only governs while it is the larger of the two numbers. Block
22
- padding above it wins, and the control silently leaves the scale - which is
23
- exactly how a button once ended up 1.2px taller than the field beside it. So if
24
- you do override `padding-block` on a control, keep
25
- `2 x padding + line-height + border` under the height its step allows.
26
- On a coarse pointer or below 768px every control grows to 2.75rem on its own.
27
- Retune the whole scale through `--mw-control-height*` / `--mw-control-font*`.
28
-
29
- ## The field pattern
30
-
31
- `mw-field` groups label, control, hint and error into one unit. It is the
32
- recommended wrapper for every labelled control and the natural fit for a
33
- reactive form control.
34
-
35
- ```html
36
- <div class="mw-field">
37
- <label class="mw-field-label mw-required" for="email">Email</label>
38
- <input id="email" type="email" class="mw-input" />
39
- <span class="mw-field-hint">Used for login and notifications.</span>
40
- <span class="mw-field-error">
41
- <i class="fas fa-exclamation-circle"></i> Please enter a valid email
42
- address.
43
- </span>
44
- </div>
45
- ```
46
-
47
- - `mw-required` on the label appends a red asterisk (`data-required="true"`
48
- works too). A label sitting directly in front of a control with the `required`
49
- attribute gets the asterisk without either class - useful for template-driven
50
- and plain HTML forms, where the marker then cannot drift out of step with the
51
- validation.
52
- - `mw-field-hint` is the small muted helper line, `mw-field-error` the small red
53
- one. Render only one of them at a time.
54
- - `mw-field-has-error` on the **wrapper** turns the border of the contained
55
- `mw-input` / `mw-select` / `mw-textarea` red and adds a soft red halo.
56
- - `mw-form-element-error` does the same for a single control that has no field
57
- wrapper - it also works on `mw-checkbox-group`, `mw-radio-group` and
58
- `mw-slider-container`. On the two groups it adds the inset a field brings with
59
- it, so the rows do not sit flush against the border.
60
- - Native validation is styled too: a control that fails `required`, `type` or
61
- `pattern` gets the same red border and halo through `:user-invalid`, and the
62
- `mw-field-hint` beside it turns red. `:user-invalid` and not `:invalid`, so an
63
- untouched empty field is not red on page load. Nothing has to be bound for
64
- this - which is exactly why the classes still exist for the case below, where
65
- the validator lives in the component rather than on the element.
66
- - `mw-textarea` grows with its content (`field-sizing`) between 3 lines and
67
- 60dvh, where the browser supports it.
68
-
69
- > **The framework does not style Angular's `ng-invalid` / `ng-touched` classes.**
70
- > Bind the framework classes to the control state yourself:
71
- >
72
- > ```html
73
- > <div
74
- > class="mw-field"
75
- > [class.mw-field-has-error]="email.invalid && email.touched"
76
- > >
77
- > <label class="mw-field-label mw-required" for="email">Email</label>
78
- > <input id="email" type="email" class="mw-input" formControlName="email" />
79
- > @if (email.hasError('required') && email.touched) {
80
- > <span class="mw-field-error">
81
- > <i class="fas fa-exclamation-circle"></i> Email is required.
82
- > </span>
83
- > }
84
- > </div>
85
- > ```
86
- >
87
- > The same applies to any other framework - React: `className={...}`, Vue:
88
- > `:class`. Nothing reacts to validation on its own.
89
-
90
- ## Form layout
91
-
92
- ```html
93
- <form class="mw-form">
94
- <div class="mw-form-group">
95
- <h4 class="mw-form-group-title">Personal information</h4>
96
- <div class="mw-grid-2">
97
- <div class="mw-field">...</div>
98
- <div class="mw-field">...</div>
99
- </div>
100
- </div>
101
-
102
- <div class="mw-form-actions">
103
- <p class="mw-actions-note">Changes are saved immediately.</p>
104
- <button type="button" class="mw-btn mw-btn-outline">Cancel</button>
105
- <button type="submit" class="mw-btn mw-btn-primary">Save</button>
106
- </div>
107
- </form>
108
- ```
109
-
110
- - `mw-form` is a flex column with a gap and **no padding** - safe to put
111
- directly on `mw-modal-body` or `mw-panel-body`. It also works on a `<div>`
112
- when there is no real form element.
113
- - `mw-form-inline` lays the children out in a row.
114
- - `mw-form-group` is the bordered block for a titled group of fields.
115
- `mw-form-group-title` is the heading hook inside it - put it on the `<h3>` /
116
- `<h4>`, otherwise the heading keeps its full document-level size.
117
- - `mw-form-actions` is a right-aligned wrapping button row.
118
- `mw-actions-note` is a full-width note above the buttons and works the same
119
- way in a modal, card or panel footer (`references/components.md`). The
120
- alignment variants steer it along: `mw-form-actions-left`,
121
- `mw-form-actions-center`, `mw-form-actions-full-width` (stacked, buttons at
122
- 100% - login forms).
123
- `mw-form-actions-hint` is the old name for the note and still styled, but new
124
- markup should use `mw-actions-note`.
125
- - Put multi-column layouts inside a group with `mw-grid-2` etc. and let a field
126
- span everything with `style="grid-column: 1 / -1"`.
127
-
128
- ## Text inputs
129
-
130
- ```html
131
- <input type="text" class="mw-input" />
132
- <input type="text" class="mw-input mw-input-sm" />
133
- <input type="text" class="mw-input mw-input-lg" />
134
- ```
135
-
136
- - Full width by default, `2px` border, focus ring in the primary colour.
137
- - `readonly` renders on a muted surface, `disabled` additionally as
138
- `not-allowed`. Both are attribute driven - there is no readonly/disabled
139
- class.
140
- - Date/time work as normal inputs (`type="date" | "time" | "datetime-local"`);
141
- the native picker indicator is styled.
142
-
143
- ### Numbers and money
144
-
145
- ```html
146
- <div class="mw-field">
147
- <label class="mw-field-label" for="budget">Budget</label>
148
- <div class="mw-input-group">
149
- <span class="mw-input-group-prefix"><i class="fas fa-euro-sign"></i></span>
150
- <input
151
- id="budget"
152
- type="text"
153
- inputmode="decimal"
154
- class="mw-input mw-input-numeric"
155
- placeholder="0,00"
156
- />
157
- <span class="mw-input-group-suffix">EUR</span>
158
- </div>
159
- </div>
160
- ```
161
-
162
- `mw-input-numeric` is the entry counterpart to the `mw-text-numeric` utility:
163
- right aligned, tabular lining figures, native spinners suppressed. A value looks
164
- identical while being typed and once it is rendered into a table.
165
-
166
- > **Use `type="text"` + `inputmode="decimal"`, not `type="number"`.** A focused
167
- > number input changes its value when the page is scrolled, and in most locales
168
- > it rejects a comma as decimal separator - the user sees `1234,50` but `.value`
169
- > comes back empty. `inputmode="decimal"` still brings up the numeric keypad on
170
- > mobile. Parsing the comma stays the application's job.
171
-
172
- ## Input group
173
-
174
- Prefix, suffix and buttons glued to the control:
175
-
176
- ```html
177
- <div class="mw-input-group">
178
- <span class="mw-input-group-prefix"><i class="fas fa-search"></i></span>
179
- <input type="text" class="mw-input" placeholder="Search…" />
180
- <button type="button" class="mw-btn mw-btn-primary">
181
- <i class="fas fa-search"></i>
182
- </button>
183
- </div>
184
- ```
185
-
186
- Sizes: `mw-input-group-sm`, `mw-input-group-lg` - combine them with the matching
187
- `mw-input-sm` / `mw-input-lg` on the control. A prefix or suffix takes an icon,
188
- a symbol (`@`, `https://`) or a short unit.
189
-
190
- ## Select
191
-
192
- ```html
193
- <select class="mw-select">
194
- <option value="" disabled selected>Choose…</option>
195
- <option>Option A</option>
196
- </select>
197
- ```
198
-
199
- Sizes: `mw-select-sm`, `mw-select-lg`.
200
-
201
- ## Textarea
202
-
203
- ```html
204
- <textarea class="mw-textarea" rows="4"></textarea>
205
- ```
206
-
207
- Sizes: `mw-textarea-sm`, `mw-textarea-lg`. Resizing is off by default; enable it
208
- with `mw-textarea-resizable`, `mw-textarea-resizable-vertical` or
209
- `mw-textarea-resizable-horizontal`.
210
-
211
- ## Prefilled values
212
-
213
- `mw-prefilled` marks a control whose value did not come from the user in this
214
- session - loaded from an existing record, restored from a draft, filled with a
215
- default. It draws a small triangle into the top left corner of the control.
216
-
217
- ```html
218
- <input type="text" class="mw-input mw-prefilled" value="Max Mustermann" />
219
- <select class="mw-select mw-select-sm mw-prefilled">
220
- ...
221
- </select>
222
- <textarea class="mw-textarea mw-prefilled" rows="3"></textarea>
223
- ```
224
-
225
- - Works on `mw-input`, `mw-select` and `mw-textarea`, and goes on the control
226
- itself, not on the `mw-field` wrapper - so it also works in an input group or
227
- on a standalone control.
228
- - The triangle follows the size modifier (`mw-input-sm`, `mw-select-lg`, ...)
229
- and stays visible on `readonly` and `disabled` controls.
230
- - Colour is `--mw-info-color`, deliberately not primary or danger: it is an
231
- information about the value, not a state or an error.
232
- - Decoration only. Screen readers do not see it, so put the same information in
233
- an `mw-field-hint` and reference it with `aria-describedby`.
234
- - It is drawn as a background layer, not a pseudo element (`input` and `select`
235
- never render `::before`/`::after`). A rule that sets the `background`
236
- shorthand on the same control wipes it - use `background-color` there.
237
-
238
- ## Checkbox
239
-
240
- The native input is hidden; `mw-checkbox-box` is the visible control, so the
241
- order of the three children matters.
242
-
243
- ```html
244
- <div class="mw-checkbox-group mw-checkbox-group-inline">
245
- <label class="mw-checkbox mw-checkbox-success">
246
- <input type="checkbox" />
247
- <div class="mw-checkbox-box"></div>
248
- <div class="mw-checkbox-label">Send a copy</div>
249
- </label>
250
- </div>
251
- ```
252
-
253
- - Sizes: `mw-checkbox-sm`, `mw-checkbox-lg`
254
- - Colours: `mw-checkbox-primary`, `-secondary`, `-success`, `-warning`,
255
- `-danger`, `-info`
256
- - Disabled: `mw-checkbox-disabled` on the label **plus** the `disabled`
257
- attribute on the input
258
- - Group: `mw-checkbox-group` (column), `mw-checkbox-group-inline` (row)
259
- - With a heading and a description use `mw-checkbox-content` containing
260
- `mw-checkbox-header` + `mw-checkbox-label` - see the checkbox item list in
261
- `references/components.md`
262
-
263
- ## Radio
264
-
265
- Same structure, with `mw-radio-button` as the visible control:
266
-
267
- ```html
268
- <div class="mw-radio-group">
269
- <label class="mw-radio">
270
- <input type="radio" name="plan" value="free" />
271
- <div class="mw-radio-button"></div>
272
- <div class="mw-radio-label">Free</div>
273
- </label>
274
- </div>
275
- ```
276
-
277
- Sizes `mw-radio-sm`, `-lg`; colours `mw-radio-primary`, `-secondary`,
278
- `-success`, `-warning`, `-danger`, `-info`; `mw-radio-disabled`; groups
279
- `mw-radio-group`, `mw-radio-group-inline`.
280
-
281
- ## Toggle
282
-
283
- ```html
284
- <div class="mw-toggle-group">
285
- <label class="mw-toggle mw-toggle-success">
286
- <input type="checkbox" checked />
287
- <div class="mw-toggle-track"></div>
288
- <span class="mw-toggle-label">Notifications</span>
289
- </label>
290
- </div>
291
- ```
292
-
293
- Sizes `mw-toggle-sm`, `-lg`; colours `mw-toggle-primary`, `-secondary`,
294
- `-success`, `-warning`, `-danger` (no `info` variant); `mw-toggle-disabled` plus
295
- the `disabled` attribute; groups `mw-toggle-group`, `mw-toggle-group-inline`.
296
-
297
- ## Slider
298
-
299
- ```html
300
- <label>
301
- <span>Experience</span>
302
- <div class="mw-slider-container">
303
- <div class="mw-slider-value mw-slider-numeric" data-value="3"></div>
304
- <input
305
- type="range"
306
- class="mw-slider mw-slider-primary"
307
- min="0"
308
- max="10"
309
- value="3"
310
- />
311
- </div>
312
- </label>
313
- ```
314
-
315
- - The badge prints `data-value`: `mw-slider-numeric` appends `/10`,
316
- `mw-slider-percent` appends `%`.
317
- - The filled part of the track comes from the custom property `--value` on the
318
- input (`style="--value: 30%"`).
319
- - Both `data-value` and `--value` are set by the shipped JS on `input`. In a SPA
320
- bind them yourself - two bindings, see `examples/angular-form.md`.
321
- - Colours: `mw-slider-primary`, `-secondary`, `-success`, `-warning`, `-danger`,
322
- `-info`; sizes `mw-slider-sm`, `-lg`.
323
-
324
- ## Login card
325
-
326
- A centered, self-contained card for sign-in screens.
327
-
328
- ```html
329
- <div class="mw-login">
330
- <div class="mw-login-logo"><img src="logo.svg" alt="" /></div>
331
- <div class="mw-login-message">Internal area - valid account required.</div>
332
- <div class="mw-login-message mw-login-message-error">Wrong credentials.</div>
333
-
334
- <form class="mw-form">
335
- <div class="mw-grid-1">
336
- <div class="mw-field">
337
- <label class="mw-field-label mw-required" for="login-email"
338
- >Email</label
339
- >
340
- <div class="mw-input-group">
341
- <span class="mw-input-group-prefix"
342
- ><i class="fas fa-envelope"></i
343
- ></span>
344
- <input id="login-email" type="email" class="mw-input" required />
345
- </div>
346
- </div>
347
- </div>
348
- <div class="mw-form-actions mw-form-actions-full-width">
349
- <button type="submit" class="mw-btn mw-btn-primary">
350
- <i class="fas fa-sign-in-alt"></i> Sign in
351
- </button>
352
- </div>
353
- </form>
354
- </div>
355
- ```
356
-
357
- `mw-login-error` on the card flashes a red border after a failed attempt,
358
- `mw-login-message-info` / `mw-login-message-error` colour the message line.
@@ -1,164 +0,0 @@
1
- # JavaScript & SPA integration
2
-
3
- ## What `maverick-wave.min.js` is
4
-
5
- One vanilla IIFE, no dependencies, ~24 kB. It queries the DOM on
6
- `DOMContentLoaded` and attaches listeners. No `MutationObserver`, no exported
7
- module - it is built for a server-rendered or static page.
8
-
9
- Markup that arrives later gets wired through the one public entry point:
10
-
11
- ```js
12
- window.MaverickWave.init(subtree); // or init() for the whole document
13
- ```
14
-
15
- Call it after an htmx swap, after filling a modal from a fetch, or after a view
16
- transition replaced the body. Every element that already carries listeners is
17
- skipped, so calling it twice over the same markup does nothing - the components
18
- are safe to re-run, the document- and window-level behaviour is wired once and
19
- never again.
20
-
21
- What that does **not** make it is a SPA library - see below.
22
-
23
- ## Why a SPA must not load it
24
-
25
- - Anything rendered after bootstrap needs `MaverickWave.init()` by hand, for
26
- every subtree, on every render - in a routed application that is all of it.
27
- - It writes straight into the DOM (class toggles, generated elements, inline
28
- styles). In Angular that happens outside change detection; in a zoneless app
29
- the framework never learns about it, and on the next re-render your bindings
30
- win and the mutation is gone.
31
- - It reads and writes `localStorage` for the theme, competing with whatever
32
- service you build for the same job.
33
-
34
- So: no `"scripts"` entry in `angular.json`, no `import 'maverick-wave.min.js'`
35
- in a Vite entry point. Load the **CSS only** and rebuild the handful of
36
- behaviours in components. Each one is a few lines - the framework's state
37
- classes are the entire contract.
38
-
39
- ## The theme has to be applied before the first paint
40
-
41
- `main.js` sits at the end of the body, so a stored choice that differs from the
42
- operating system reaches the page one frame too late - the reader sees the other
43
- theme flash. The fix is five lines in the `<head>`, inline and synchronous,
44
- before the stylesheet has painted anything:
45
-
46
- ```html
47
- <script>
48
- (function () {
49
- var stored = localStorage.getItem('mw-theme');
50
- if (stored === 'light' || stored === 'dark') {
51
- document.documentElement.classList.add('mw-theme-' + stored);
52
- }
53
- })();
54
- </script>
55
- ```
56
-
57
- That is also why the toggle writes the class on `<html>` and not on `<body>`:
58
- in the head there is no body yet. An external file would not help - it paints
59
- first. With nothing stored, nothing happens here and the page follows the OS on
60
- its own through `light-dark()`.
61
-
62
- ## Behaviour inventory
63
-
64
- | Behaviour | What the shipped JS does | What to do instead |
65
- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
66
- | Accordion | Toggles `mw-active` on `mw-accordion-header` and the following `mw-accordion-content`, and writes `aria-expanded` when the header is a `<button>` | `[class.mw-active]="isOpen()"` and `[attr.aria-expanded]="isOpen()"` on the header, `mw-active` on the panel |
67
- | Tabs | `data-tab` → panel `id`; sets `mw-active` on nav item and panel, and wires the whole tablist: `role`, `aria-selected`, `aria-controls`, `aria-labelledby`, a roving `tabindex` and arrow/Home/End keys | Track the selected index/key, bind `mw-active` on both; drop `data-tab` |
68
- | Modal | `data-mw-modal="<id>"` on a trigger opens that modal; a click on `mw-modal-close` closes the `<dialog>` it sits in, or clears `mw-modal-open` on an overlay div | A `<dialog>` needs a directive calling `showModal()` - `[open]` only opens it non-modally. See `examples/angular-services.md` |
69
- | Mobile nav | Toggles `open` on `mw-menu-btn` and `mw-navbar` plus `mw-nav-open` on `<body>`, writes `aria-expanded` when the button is a `<button>`, closes on anchor click and on Escape (focus returns to the button) | One signal, bound to all three - the body class carries the scrim, the scroll lock and the pinning of the bar; reset it on navigation end |
70
- | Scroll spy | Sets `mw-active` on `mw-navbar-link` from the scroll position | Router-based: `routerLinkActive="mw-active"` |
71
- | Anchor scrolling | Intercepts `a[href^="#"]` and runs its own eased scroll - duration scales with distance, capped at 1.4s, cancelled by wheel or touch. Lands on `scroll-padding-top`, moves focus to the target, writes the hash with `replaceState`, and measures a sticky target unpinned | The router; for in-page anchors `scrollIntoView({ behavior: 'smooth' })` or your own animation |
72
- | Theme toggle | `localStorage['mw-theme']`, toggles `mw-theme-light` / `mw-theme-dark` on `<html>` and `mw-active` on the toggle, wrapped in `mw-theme-switching` on `<html>` so the flip starts no transitions. With nothing stored it takes `prefers-color-scheme` and keeps following it | A theme service - see `examples/angular-services.md` |
73
- | Progress bar | `IntersectionObserver` sets `width` from `data-value` | Bind `[style.width.%]="value()"` on `mw-progress-fill` |
74
- | Slider | On `input`, sets `--value` (track fill) and `data-value` (badge text) | Bind `[style.--value.%]` and `[attr.data-value]` |
75
- | Alerts | Close button adds `mw-alert-closing` (fade out), then `mw-alert-closed` (`display: none`) after `--mw-duration-base` | Remove the alert from the list/signal |
76
- | Checkbox lists | Adds `mw-selected` to the `li`, emits a `checkboxToggle` event, exposes `window.mwToggleCheckbox` | `[class.mw-selected]="item.checked"` |
77
- | Gallery | Generates the dots, moves the track, swipe handling (single finger, dominant axis, passive listeners), writes `mw-gallery-desc` | Render dots in the template, bind the track transform and `mw-active` on the current dot |
78
- | Image slider | Toggles `mw-active` on the overlay image and the control button with the matching `data-index` | Bind `mw-active` from the selected index |
79
- | Mosaic | Sets `mw-mosaic-wide` / `mw-mosaic-tall` on a tile from the photo's proportions - its `width`/`height` attributes, else the natural size - unless a shape class is already there | Bind the shape class from the image size you already know |
80
- | Lightbox | Delegated: an image link inside `mw-mosaic` or `[data-mw-lightbox]` opens a `<dialog class="mw-lightbox">` built once - slides in reading order, images loaded one either side of the current, swipe, arrows and keys, focus back on the tile of the last photo | A component around a `<dialog>`: `showModal()`, a scroll-snap track, the index read from `scrollLeft` |
81
- | Portrait gallery | Adds arrows and dots and keeps them in step with the scroll position; sets `mw-fit-contain` and a blurred `mw-fit-backdrop` on a slide whose photo is more than 1.4× off its shape, checked again by a `ResizeObserver` | Render arrows and dots, `scrollBy` one slide on click; bind `mw-fit-contain` from the image ratio |
82
- | Story reel | Playback is CSS. Adds the `mw-story-reel-prev` / `-next` tap zones and arrow keys (seeking through `getAnimations()`), starts a video when its frame comes up and stops it with the story, closes the popover at the end, focuses the close button on open | Keep the CSS playback and the popover; add stepping only if you need it, the same way |
83
- | Kanban board | Counts the tickets per lane, moves a card between lanes (`data-kanban-move`) and marks the arrival with `mw-kanban-card-moved-forward` / `-back`, clones `mw-kanban-card-template` on save, derives the next key from `data-kanban-prefix`, toggles `mw-active` on the composer | Keep the tickets in a signal/store and render the lanes from it; `mw-active` on the composer, `mw-kanban-editing` on the ticket it replaces. Neither the `<template>` nor the `mw-kanban-composer-*` hook classes are needed |
84
- | Calendar | Renders the month or week grid from `data-calendar="month | week"`, pages with `data-calendar-nav`, draws the status dots from `data-calendar-markers`, toggles `mw-selected`and emits`mw-calendar-select` | Render the cells from a signal and bind `mw-calendar-adjacent`, `mw-calendar-weekend`, `mw-calendar-today` and `mw-selected` yourself; none of the `data-calendar-*` attributes are needed |
85
- | Viewport check | On a local hostname, warns on the console when the viewport meta has no `viewport-fit=cover` - without it every `env(safe-area-inset-*)` resolves to 0 | Nothing - check the meta tag once |
86
- | 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 |
87
- | Header login button | Swaps the FontAwesome lock icon | Bind the icon class |
88
- | Color swatches | Showcase-only (prints computed hex values); exposes `window.mwRefreshColorSwatches` to read them again after a root colour changed | Not needed |
89
- | Dropdown | Delegated to the document: closes the open `mw-dropdown` on Escape, on a click elsewhere and on a click on a `mw-dropdown-item`, and returns focus to the `summary` | The `<details>` does the opening, the keyboard and the state on its own. Rebuild only the two behaviours markup cannot express - or bind `[attr.open]` and keep them in the component |
90
- | Language switcher | Keeps the trigger's flag and code in step with the chosen item, moves `mw-active` and `aria-current`, and fires `mw-language-change` (`detail: { lang, name }`) on the switcher | Bind the trigger from your locale signal and switch the language in your own i18n service; the menu itself is a `<details>` and needs nothing |
91
- | Scroll reveal | Older browsers only: an `IntersectionObserver` adds `mw-reveal-hidden` to what is still below the fold and swaps it for `mw-reveal-run` on entry, with an `animation-delay` per grid column | A directive per element - see `examples/angular-services.md` |
92
- | Header reveal | Older browsers only: toggles `mw-header-away` and `mw-announcement-away` past 270px of scroll, and adds the transition class one frame later so the bar does not slide away on load | The same two classes bound to a scroll signal, behind the same guard |
93
- | Parallax | Older browsers only: writes `--mw-parallax-progress` (0 to 1) on every `mw-parallax-media` from a `requestAnimationFrame` loop | The same, reading every layer's rect before writing to any of them, and measuring against `document.documentElement.clientHeight` - `innerHeight` grows by up to 100px as the URL bar slides away and steps every layer mid-scroll |
94
-
95
- ## Scroll-driven animations
96
-
97
- Four things ride the browser's own scroll timeline: `mw-reveal`,
98
- `mw-header-reveal`, `mw-parallax` and the `mw-progress-fill` scrub. Chrome and
99
- Edge have had timelines since 115, Firefox since 158 and Safari since 26, and
100
- there they need no script at all. Older versions get the shipped JS instead.
101
- Each one checks `CSS.supports('animation-timeline', ...)` first and does nothing
102
- where the browser has it.
103
-
104
- Without the script an older browser loses the motion and nothing else: cards
105
- stand in place, the picture holds still. The exception is `mw-header-reveal`,
106
- where the bar then sits over the hero from the first paint - a layout
107
- difference, not a missing effect.
108
-
109
- ## Modals and progress bars
110
-
111
- `mwOpenModal(id)` / `mwCloseModal(id)` are on `window` and handle both modal
112
- shapes - a `<dialog class="mw-modal">` and the older `mw-modal-overlay` div.
113
- Close buttons and `data-mw-modal="<id>"` triggers are delegated from the
114
- document, so markup rendered later still works and a static page needs no code
115
- of its own. In a SPA, call `showModal()` and `close()` on the element instead.
116
-
117
- `mw-progress-fill` takes its target width from `data-value="75"` or an inline
118
- `style="width: 75%"`. Where scroll-driven animations are supported the bar fills
119
- with the scroll position and the script only hands the value over; elsewhere it
120
- falls back to setting the width once the bar comes into view.
121
-
122
- ## A note on the state class
123
-
124
- Since 4.0.0 the script writes `mw-active` and clears both `mw-active` and the
125
- deprecated bare `active` when it switches a state off. That is deliberate: HTML
126
- written against an older version marks the first tab with `active`, and if
127
- switching away only removed `mw-active`, that first tab would stay lit next to
128
- the newly chosen one. In your own components bind `mw-active` and forget the
129
- other spelling exists.
130
-
131
- ## What works without any JavaScript
132
-
133
- Pure CSS, nothing to wire up: hover, focus and press states, the card lift,
134
- tooltips (`data-tooltip`), `mw-rating` (via `data-rating`), the responsive table
135
- card view (`data-label`), all grids and utilities, the body scroll lock while a
136
- modal is open (from the `<dialog>` element, or `body:has(.mw-modal-open)` for an
137
- overlay div), the modal turning into a bottom sheet below 576px, toast entry animations, the photo feed and its post cards (`popover`), story
138
- playback with its progress bar and pause, the sticky table header, the scroll
139
- hint on a tab bar (four gradients, no scroll listener), the kanban empty-lane
140
- placeholder (hidden via `:has()` as soon as the lane holds a ticket), touch
141
- target sizing on a coarse pointer, `prefers-reduced-motion` handling.
142
-
143
- `mw-dropdown` is _almost_ in this list: opening, closing, the keyboard and the
144
- open state are all the `<details>` element, so it works with no script at all.
145
- The only two things the shipped JS adds are closing on Escape and closing on a
146
- click somewhere else - worth rebuilding in a SPA, but a menu without them is
147
- still a working menu, not a broken one.
148
-
149
- ## When you do keep the shipped JS
150
-
151
- For a static page, a landing page or a server-rendered site (Thymeleaf, Twig,
152
- Jekyll, plain HTML) it is exactly right - load it at the end of `<body>` and
153
- write no JavaScript at all. A modal opens from its trigger:
154
-
155
- ```html
156
- <button class="mw-btn mw-btn-primary" data-mw-modal="demo">Book a demo</button>
157
-
158
- <dialog id="demo" class="mw-modal" closedby="any">
159
- …
160
- <button class="mw-modal-close mw-btn mw-btn-outline">Cancel</button>
161
- </dialog>
162
-
163
- <script src="maverick-wave.min.js"></script>
164
- ```