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,764 +0,0 @@
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, 100% - 2 * gutter)`, horizontally centered.
29
- Nest it inside every full-bleed band (header, section, footer), never around
30
- them. The gutter is fluid - `clamp(1rem, 4.2vw + 0.5rem, 4rem)`, never smaller
31
- than the safe-area inset (which needs `viewport-fit=cover` on the viewport
32
- meta to be anything but 0) - so it lands on ~24px at 375px, ~32px at 576px,
33
- ~40px on a tablet and tops out at 64px, where the 1200px cap takes over. A
34
- percentage alone keeps too little on a phone and throws away too much on a
35
- desktop; stepping the gutter at a breakpoint instead would make the container
36
- jump backwards at the step. Both values are tokens,
37
- `--mw-container-gutter` and `--mw-container-width`.
38
- - `mw-container-narrow` sits **inside** a `mw-container` and gives the content
39
- 80% of it, back to 90% below `lg` and the full width below `md`. For a column
40
- of full-width cards, where a 1200px line leaves more empty card than content.
41
- It centers itself; put nothing else in it that has to line up with the
42
- headings outside.
43
- - `mw-content` (`flex: 1` + top padding) is the alternative to `mw-section` when
44
- a page has one single content area. `mw-content-centered` centers it
45
- vertically over the full viewport - login pages, error pages.
46
- - `<section>` has **no** padding of its own. Always add `mw-section`.
47
-
48
- In an Angular app the shell above lives in `app.component.html` and the router
49
- outlet goes inside the `mw-container`:
50
-
51
- ```html
52
- <main class="mw-main">
53
- <section class="mw-section">
54
- <div class="mw-container"><router-outlet /></div>
55
- </section>
56
- </main>
57
- ```
58
-
59
- ## Header & navigation
60
-
61
- The header is fixed, dark in both themes, and expects exactly this structure -
62
- its children are styled through descendant selectors:
63
-
64
- ```html
65
- <header class="mw-header">
66
- <div class="mw-container">
67
- <div class="mw-logo">
68
- <a href="#start"><img src="logo.svg" alt="Logo" /></a>
69
- </div>
70
-
71
- <div class="mw-header-actions">
72
- <nav class="mw-navbar mw-navbar-medium" id="main-nav">
73
- <ul class="mw-navbar-list">
74
- <li class="mw-navbar-item">
75
- <a href="#start" class="mw-navbar-link mw-active">Start</a>
76
- </li>
77
- <li class="mw-navbar-item">
78
- <a href="#docs" class="mw-navbar-link">Docs</a>
79
- </li>
80
- </ul>
81
- </nav>
82
-
83
- <button
84
- type="button"
85
- class="mw-theme-toggle mw-ml-5"
86
- aria-label="Toggle light and dark theme"
87
- >
88
- <div class="mw-theme-toggle-slider">
89
- <div class="mw-theme-toggle-icon"><i class="fas fa-moon"></i></div>
90
- </div>
91
- </button>
92
-
93
- <button class="mw-login-btn" type="button" aria-label="Log in">
94
- <i class="fas fa-lock"></i>
95
- </button>
96
-
97
- <button
98
- type="button"
99
- class="mw-menu-btn"
100
- aria-label="Menu"
101
- aria-expanded="false"
102
- aria-controls="main-nav"
103
- >
104
- <span class="mw-menu-btn-burger"></span>
105
- </button>
106
- </div>
107
- </div>
108
- </header>
109
- ```
110
-
111
- - **Collapse breakpoint follows the item count**: default (1-3 items) collapses
112
- at `md`, `mw-navbar-medium` (4-5) at `lg`, `mw-navbar-large` (6+) at `xl`.
113
- Pick the class by how many links you have.
114
- - Below the breakpoint the list becomes a panel under the bar, full width, and
115
- `mw-menu-btn` appears. Opening it means `open` on **both** `mw-menu-btn` and
116
- `mw-navbar`, `mw-nav-open` on the `<body>`, and `aria-expanded` on the button -
117
- below the breakpoint it is the only route to the navigation, so it has to be a
118
- real `<button>`, not a `<div>`.
119
- - `mw-nav-open` is the state the scrim, the scroll lock and the rules that hold
120
- the bar and the burger in place all key off. A class the script sets, not
121
- `:has(.mw-navbar.open)`: the burger is the control that closes the panel, and
122
- it cannot wait for a selector that has to re-match the document from a class
123
- change several levels down. Forget it and the panel opens without a scrim, the
124
- page behind it still scrolls, and on a `mw-header-reveal` bar the burger sits a
125
- header height below the panel it belongs to.
126
- - The rows stack and run the full width of the panel. From `sm` they turn into a
127
- wrapping row of pills, so a wide screen below the collapse breakpoint does not
128
- get a tall band with four links in its left corner.
129
- - The active link carries `mw-active` (bare `active` still works but is
130
- deprecated).
131
- - `mw-profile-btn` is the signed-in pill, next to or instead of the login button:
132
-
133
- ```html
134
- <button class="mw-profile-btn" type="button">
135
- <span class="mw-avatar mw-avatar-initials mw-avatar-xs">MW</span>
136
- <span class="mw-profile-btn-name">Michael</span>
137
- </button>
138
- ```
139
-
140
- An icon (`<i class="fas fa-user-circle"></i>`) works instead of the avatar.
141
- Below `md` the name hides and the button becomes a round 40px control, the
142
- same height as the login and burger buttons.
143
-
144
- - Header colours have their own tokens (`--mw-header-background`,
145
- `--mw-header-text-color`, `--mw-header-navbar-list-color`,
146
- `--mw-header-navbar-list-active-color`, `--mw-header-burgerbutton-color`,
147
- `--mw-header-border`) so the chrome can be retuned without touching the brand
148
- palette. All of them derive from `--mw-primary-color`, not from a theme
149
- colour: the bar is dark in both themes and a light-only project must not have
150
- to configure dark-theme values to change it. The bar itself is the primary
151
- darkened toward black, `$header-surface` (14%) - see `theming.md`.
152
-
153
- **Reveal on scroll.** `mw-header-reveal` keeps the bar above the screen and
154
- rides it in over the first 420px of scroll - for a page that opens on a
155
- full-bleed hero and wants nothing on top of it. It animates `top` and not a
156
- transform, because a transform on the header would make it the containing block
157
- for anything fixed inside it, at every value including the resting one.
158
- A fixed `mw-announcement` rides along without a class of its own - both cover
159
- the height of the pair, so they arrive as one block rather than the ribbon
160
- catching up. Focus inside either brings both back regardless of the scroll
161
- position, so tabbing never lands on a link that is off screen (WCAG 2.4.11).
162
- Under `prefers-reduced-motion` the bar is simply there, and so it is without
163
- scroll timelines unless `maverick-wave.min.js` is on the page - in Firefox that
164
- is what rides it in (`javascript.md`).
165
-
166
- **Something that stays.** `mw-header-keep` on a control inside the bar - the
167
- login button, the burger - holds it in place while the bar rides in behind it:
168
- it travels the bar's own distance the other way, so it waits at the exact
169
- position it will hold in the arrived bar and nothing jumps at the end. On a
170
- narrow screen that is what puts the burger on the hero before the bar exists.
171
- Opening the panel brings the bar in with it and parks the keep controls back on
172
- their own positions - the panel hangs off the bar, and the burger is the X the
173
- moment the panel is out, so it has to sit where the burger sat. One consequence:
174
- the bar loses its fade as soon as one of these is on it, since a group's opacity
175
- cannot be taken back by a child.
176
-
177
- ```html
178
- <button class="mw-login-btn mw-header-keep" type="button">
179
- <i class="fas fa-lock"></i>
180
- </button>
181
- ```
182
-
183
- - Focus on a keep control leaves the bar where it is. The rescue that pulls it
184
- back (WCAG 2.4.11) is for what hides behind it - a control in front of it is
185
- on screen already, and without the exception a click on the theme toggle would
186
- haul in the whole header. Tabbing to anything else in the bar still brings it.
187
- - While it floats it carries a ring and a soft shadow, so a dark control still
188
- reads on a dark picture; both are gone once the bar is there. That occupies
189
- `box-shadow`, so a keep element cannot bring a shadow of its own.
190
-
191
- **Localhost indicator.** Put `mw-localhost-indicator-activated` on the header
192
- and the shipped JS prepends a pulsing bar when the host is localhost/127.0.0.1/
193
- 192.168.\*. In a SPA, reimplement it: add a `<div class="mw-localhost-indicator-pulse">`
194
- as the header's first child under the same condition.
195
-
196
- ## Footer
197
-
198
- ```html
199
- <footer class="mw-footer">
200
- <div class="mw-container">
201
- <div class="mw-footer-top">
202
- <div class="mw-footer-column">
203
- <h3>Product</h3>
204
- <p>...</p>
205
- </div>
206
- <div class="mw-footer-column">
207
- <h3>Links</h3>
208
- <ul>
209
- <li><a href="#">Docs</a></li>
210
- </ul>
211
- </div>
212
- </div>
213
-
214
- <div class="mw-social-links">
215
- <a href="#" data-tooltip="GitHub"><i class="fab fa-github"></i></a>
216
- </div>
217
-
218
- <p class="mw-disclaimer">Long-form text under the columns.</p>
219
- <p class="mw-copyright">&copy; 2026 Example</p>
220
- <p class="mw-last-updated">Last updated: 2026-08-04</p>
221
- </div>
222
- </footer>
223
- ```
224
-
225
- `data-tooltip="..."` is a global attribute hook, not a class - it works on any
226
- element and shows a tooltip above it on hover or keyboard focus. It is pure CSS
227
- (a pseudo element on the trigger), which also means anything that clips its
228
- overflow cuts it off: a scroll container, a tile, or a card carrying a ribbon -
229
- plain cards do not clip.
230
-
231
- Three classes steer where it lands, and they combine: `mw-tooltip-below` puts it
232
- under the trigger instead of over it - for a control in a fixed header, where
233
- above is off the screen - and `mw-tooltip-end` / `mw-tooltip-start` line the
234
- bubble up with that edge of the trigger instead of centring it, which is what
235
- keeps a tooltip on an outermost element inside the viewport. The arrow keeps
236
- pointing at the trigger in every combination.
237
-
238
- ## Sections
239
-
240
- | Class | Use |
241
- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
242
- | `mw-section` | Vertical rhythm for a page band - `--mw-section-padding-block`, 3.3rem (2.5rem below `md`, 1.75rem below `sm`). Point it at `--mw-section-padding-compact` or `--mw-section-padding-airy`, which step down the same way, or at `--mw-section-padding-fluid`, which ramps with the screen instead |
243
- | `mw-section-alternate` | Diagonal pattern background; combine with `mw-section` |
244
- | `mw-section-head` | The opening of a section: hairline rule, a mark on its left end, title and lead - all on one left edge. Variants `-secondary`, `-success`, `-warning`, `-danger`, `-info` recolour the mark |
245
- | `mw-section-head-title` | The heading inside it, `2xl` growing to `4xl` up to 992px, tight tracking |
246
- | `mw-section-head-intro` | The lead under it - muted, 65 characters, and from `md` up at most four fifths of the container |
247
- | `mw-section-head-numbered` | Adds the running number at the right end of the rule; the browser counts, restarting per `mw-main` |
248
- | `mw-section-subtitle` | Left-aligned heading with a thin secondary underline, `xl` growing to `2xl` up to 576px - the level inside a section. A paragraph right under it is treated as its lead and gets the gap to the content from the framework - no margin utility needed |
249
- | `mw-section-nav` + `mw-section-btn` | Sticky single-row strip of outline-style jump links; parks under the header and scrolls sideways when it overflows |
250
-
251
- ```html
252
- <section class="mw-section mw-section-alternate">
253
- <div class="mw-container">
254
- <div class="mw-section-head mw-section-head-numbered">
255
- <h2 class="mw-section-head-title">Components</h2>
256
- <p class="mw-section-head-intro">
257
- The everyday parts - buttons, tags, alerts and their many friends.
258
- </p>
259
- </div>
260
- <nav class="mw-section-nav">
261
- <a href="#buttons" class="mw-section-btn"
262
- ><i class="fas fa-hand-pointer"></i> Buttons</a
263
- >
264
- <a href="#cards" class="mw-section-btn"
265
- ><i class="fas fa-square"></i> Cards</a
266
- >
267
- </nav>
268
- </div>
269
- </section>
270
- ```
271
-
272
- **Why the head is left-aligned and has no centred variant.** Centred running
273
- text moves its own left edge with every line break, so the eye has to find the
274
- start of each line instead of returning to a fixed position - unnoticeable over
275
- two lines, tiring from the third or fourth on. Aligned, the title, the lead and
276
- the content below share one edge, and that shared edge is what makes a band of
277
- elements read as one section. A hero, a single call to action or a caption is
278
- short enough to centre; a section head is not, so the framework does not offer
279
- the choice.
280
-
281
- Its tokens: `--mw-section-head-gap` (space to the content below),
282
- `--mw-section-head-pad` (rule to title), `--mw-section-head-mark-width` (80px)
283
- and `--mw-section-head-mark-color`.
284
-
285
- The first two are multiples of `--mw-section-head-step`, which is derived from
286
- `--mw-section-padding-block` - so a section spacing preset moves the heads with
287
- it, and the fluid preset carries them along without a ramp of its own. A head
288
- with no `mw-section-head-intro` takes a smaller gap: the gap sized to follow a
289
- paragraph reads as a hole under a rule and one line of type.
290
-
291
- The strip sticks at `--mw-header-height` and is `--mw-section-nav-height` tall
292
- (3.75rem). A page carrying one adds that height to `scroll-padding-top` on its
293
- own, so anchors and tab stops both clear it - nothing to add per target.
294
-
295
- ## Page header
296
-
297
- The application counterpart to `mw-section-head`: title and subtitle left,
298
- actions right, wrapping when it gets tight. Below `sm` the actions take the full
299
- width.
300
-
301
- ```html
302
- <header class="mw-page-header">
303
- <div>
304
- <h2>Invoices</h2>
305
- <p>14 entries &middot; 3 drafts</p>
306
- </div>
307
- <div class="mw-page-header-actions">
308
- <button type="button" class="mw-btn mw-btn-outline mw-btn-sm">
309
- <i class="fas fa-filter"></i> Filter
310
- </button>
311
- <button type="button" class="mw-btn mw-btn-primary mw-btn-sm">
312
- <i class="fas fa-plus"></i> New
313
- </button>
314
- </div>
315
- </header>
316
- ```
317
-
318
- - `h1`-`h3` inside are normalised to `2xl` (`xl` below `sm`), `p` becomes muted
319
- and small - no extra classes needed.
320
- - `mw-meta-header` fits under the title instead of the `<p>` (see
321
- `references/components.md`).
322
- - `mw-page-header-plain` removes the bottom rule.
323
-
324
- ## Hero
325
-
326
- ```html
327
- <div class="mw-container">
328
- <div class="mw-hero">
329
- <div class="mw-home mw-home-content-fade">
330
- <div class="mw-home-text">
331
- <h1>Product<span class="mw-text-primary">Name</span></h1>
332
- <p>Subline</p>
333
- </div>
334
- <div class="mw-d-flex mw-gap-8 mw-justify-center mw-mb-7">
335
- <button class="mw-btn mw-btn-primary mw-btn-lg">Get started</button>
336
- </div>
337
- </div>
338
- </div>
339
- </div>
340
- ```
341
-
342
- A `mw-container` that contains a `mw-hero` switches to full-bleed, full-height
343
- mode with the background image from `--mw-hero-background`.
344
- `mw-home-content-fade` fades the content in. `mw-home-start` puts the whole
345
- block against the left container edge instead of centring it - for a hero that
346
- points somewhere rather than sitting in the middle. `mw-home` carries the
347
- container width itself, because the hero container runs the full width of the
348
- screen; without that the hero content would start at a different edge than
349
- every section below it.
350
-
351
- The image is treated per theme through `--mw-hero-image-filter` - a filter, not
352
- a tinted overlay: `$hero-filter-dark` (`brightness(0.7)`) against
353
- `$hero-filter-light` (`brightness(1.1)`). A translucent layer can
354
- only darken toward its own colour, so on an already dark photo both themes look
355
- the same until the alpha is high enough that the tint colour is what you see.
356
- Moving the image's brightness reads as a difference even on a dark photo, and
357
- keeps it dark enough for the fixed light ink either way. Any filter list works.
358
-
359
- It is applied with `backdrop-filter` on a pseudo element, so the content in
360
- front of the image stays unfiltered.
361
-
362
- The ink on that image comes from `--mw-hero-text-color` and does **not** follow
363
- the theme, because the photo does not either - a theme-bound colour would go
364
- dark over an unchanged dark image the moment the light theme is on. It defaults
365
- to the light end of the palette; for a bright photo set it per page:
366
-
367
- ```css
368
- .hero-page {
369
- --mw-hero-text-color: var(--mw-light-text-color);
370
- }
371
- ```
372
-
373
- Components that paint their own surface (`mw-card`, `mw-panel`, `mw-modal`,
374
- `mw-tile`, `mw-calendar`) are exempt and keep the theme's ink - a card in the
375
- hero is still a card.
376
-
377
- `mw-btn-outline` is exempt the other way round: inside the hero it takes the
378
- hero's ink plus a dark scrim and a blur, because its usual border and label are
379
- tuned for the page background and go quiet over a photograph. A second call to
380
- action next to `mw-btn-primary` therefore looks different here than it does
381
- further down the page - that is deliberate, not a stray override.
382
-
383
- **Scroll cue** - `mw-scroll-hint` is the "scroll down" cue on the lower edge of
384
- the hero. An `<a>` that is a sibling of `mw-hero`, directly in the container:
385
-
386
- ```html
387
- <div class="mw-container">
388
- <div class="mw-hero">...</div>
389
- <a class="mw-scroll-hint" href="#features">
390
- More <i class="fas fa-chevron-down"></i>
391
- </a>
392
- </div>
393
- ```
394
-
395
- It is taken out of the flow, and the hero keeps a strip at its bottom free for
396
- it, so buttons under the hero text never land on top of the cue.
397
- `mw-scroll-hint-end` puts it in the bottom right corner instead - for a hero
398
- whose text is not centred either - and gives the hero its full height back.
399
- Below `md` both variants sit in the corner, because the middle under the text
400
- is taken on a phone. The bobbing stops under `prefers-reduced-motion`.
401
-
402
- **Parallax** - `mw-parallax` on the container plus a `mw-parallax-media` child
403
- moves the picture into its own layer. The container drops its own background and
404
- the layer reads `--mw-hero-background`, so the image stays configured in one
405
- place. Every mode runs on the browser's scroll timeline, and on no
406
- `background-attachment: fixed`, which iOS ignores. Older Safari and Firefox have
407
- no timelines; there the shipped JS moves the layers instead, and without it the
408
- picture simply sits still (`javascript.md`).
409
-
410
- ```html
411
- <header class="mw-header mw-header-reveal">...</header>
412
-
413
- <main class="mw-main">
414
- <section class="mw-section mw-parallax-sticky mw-parallax-rise">
415
- <div class="mw-container mw-parallax">
416
- <div class="mw-parallax-media"></div>
417
- <div class="mw-parallax-media mw-parallax-pattern"></div>
418
- <div class="mw-hero">...</div>
419
- </div>
420
- </section>
421
- <section class="mw-section">...</section>
422
- </main>
423
- ```
424
-
425
- On its own, `mw-parallax` drifts the picture against the scroll by
426
- `--mw-parallax-depth` (`10vh`) - the layer overhangs the block by that much top
427
- and bottom, so no edge is ever uncovered.
428
-
429
- `mw-parallax-sticky` pins the block to the top of the screen and lets the page
430
- ride up over it. It goes on the element the page content is a **sibling** of -
431
- the section, not the hero - because everything after it is given
432
- `--mw-page-background` and a layer above, or the pinned picture shows through
433
- the transparent ones. The one section directly after it also gets an upward
434
- shadow, so the overlap reads as a surface sliding over the picture instead of a
435
- cut. Fixed values, not the elevation ramp - the shadow falls on the picture
436
- rather than on a theme surface, and the light theme's near layer is cut for
437
- paper and disappears there. It sits at `:where()` weight, so a plain rule of
438
- your own overrides it. Depth drops to `0` there: a pinned block does not travel
439
- through the viewport, so its `view()` timeline stands still with it.
440
- `mw-parallax-rise` puts it at `12vh` and runs the layer on the document's own
441
- scroll instead - the picture lifts, the text stays put. That works out to the
442
- same travel per scrolled pixel as a drifting band at `20vh`, which is measured
443
- over a much longer range. It is a hero move only: the range is the first screen
444
- of document scroll, so further down a page the picture holds still. Several
445
- pinned blocks on one page are fine, each takes over from the one before.
446
-
447
- A second `mw-parallax-media` carrying `mw-parallax-pattern` puts a pattern over
448
- the picture. It travels a third of the distance the layer below it does, and
449
- that difference between the two is what reads as depth - one layer alone only
450
- slides. It matters most over a gradient: the travel is vertical, so only an edge
451
- across it - a rule, a grid, a hatch - ever shows the movement, and a gradient
452
- has none. In a hero the layer reads `$mw-hero-pattern`, the way the picture
453
- reads `$mw-hero-image`; anywhere else set `--mw-parallax-pattern-image` on it.
454
- `--mw-parallax-pattern-depth` is the dial, `4vh` next to a risen hero.
455
-
456
- Outside a hero the same classes build a standalone band: `mw-parallax`
457
- (`min-height: 60vh`), `mw-parallax-media` as an `<img>` or a div with a
458
- background image, `mw-parallax-content` for what sits on top, and
459
- `mw-parallax-dimmed` to turn the picture down and the text light. Drop the
460
- `mw-container` between section and block and the picture runs full width, with
461
- a `mw-container` inside `mw-parallax-content` around the text instead.
462
-
463
- `mw-parallax-slow` is the third mode and the one for a band mid-page: the block
464
- keeps its place in the flow and travels slower than the page, so picture and
465
- text are still moving while the next section closes in from below. Pinning is
466
- the same move with the tempo at zero. `--mw-parallax-slow-travel` (`45vh`) reads
467
- against the block's own height - half the height is half speed, the full height
468
- stands still, past it the block runs backwards. The dial belongs on the element
469
- carrying `mw-parallax-slow`, the height on the `mw-parallax` block inside it;
470
- put the dial on the inner block and it never reaches the animation. Everything
471
- after it gets the same page background and layer that a pinned block gives its
472
- siblings, and the section directly after it the same edge shadow.
473
-
474
- Without scroll timelines, or under `prefers-reduced-motion`, the picture stands
475
- still and nothing else changes.
476
-
477
- ## Grid
478
-
479
- All grid classes are `display: grid` with a preset gap (`mw-gap-*` overrides
480
- it). They handle their own column count - no responsive suffixes to manage. One
481
- column is the base and each breakpoint adds to it, so a phone renders them
482
- without evaluating a single media query; the table below reads the same either
483
- way.
484
-
485
- | Class | Columns | Collapses |
486
- | ----------------- | ----------------------------------------------- | ---------------------------- |
487
- | `mw-grid-1` | 1 | - (children forced to 100%) |
488
- | `mw-grid-2` | 2 | 1 below `sm` |
489
- | `mw-grid-3` | 3 | 2 below `md`, 1 below `sm` |
490
- | `mw-grid-4` | 4 | 2 below `lg`, 1 below `sm` |
491
- | `mw-grid-5` | 5 | 4 / 3 / 2 / 1 down the scale |
492
- | `mw-grid-auto` | `auto-fill`, min 300px | automatic |
493
- | `mw-grid-auto-sm` | min 250px | automatic |
494
- | `mw-grid-auto-md` | min 350px | automatic |
495
- | `mw-grid-flex` | 12 columns + `mw-col-span-1` … `mw-col-span-12` | single column below `md` |
496
- | `mw-grid` | no template, just grid + gap | - |
497
-
498
- `mw-grid-even` is a modifier, not a grid - add it to one of the above and every
499
- row becomes as tall as the tallest instead of as tall as its own contents. For a
500
- column of cards that should end on one line even when one of them carries an
501
- extra sentence.
502
-
503
- `mw-grid-from-sm|md|lg|xl` is the second modifier: it holds the grid at one
504
- column until that breakpoint, and the grid's own ramp takes over from there.
505
- Every ramp turns two-column at `sm`, and 576px is generous for running text
506
- next to a picture - `mw-grid-2 mw-grid-from-md` is the pair that fixes that.
507
- It also resets the `mw-reveal-stagger` cycle below the breakpoint, so two cards
508
- standing above each other do not rise on different beats.
509
-
510
- Every one of them has a `-lg` twin with a wider gap (2.5rem instead of 1.35rem):
511
- `mw-grid-2-lg`, `mw-grid-3-lg`, `mw-grid-4-lg`, `mw-grid-5-lg`, `mw-grid-1-lg`,
512
- `mw-grid-auto-lg`, `mw-grid-flex-lg`, `mw-grid-lg`.
513
-
514
- ```html
515
- <div class="mw-grid-flex">
516
- <aside class="mw-col-span-4">Sidebar</aside>
517
- <div class="mw-col-span-8">Content</div>
518
- </div>
519
- ```
520
-
521
- **Stacks** - single column, differing horizontal alignment of the children:
522
-
523
- | Class | Children |
524
- | --------------------------------------------- | --------------------------------------- |
525
- | `mw-grid-stack` | natural width, left aligned |
526
- | `mw-grid-stack-center` | centered |
527
- | `mw-grid-stack-end` | right aligned |
528
- | `mw-grid-stack-stretch` | full width (same result as `mw-grid-1`) |
529
- | `mw-grid-stack-lg`, `mw-grid-stack-center-lg` | wide-gap twins |
530
-
531
- **Masonry columns** - `mw-columns-2` and `mw-columns-3`, for cards of unequal
532
- height where a row grid tears a gap under every short one. CSS multi-column:
533
- the children flow into columns and close up, the count steps down with the
534
- viewport (3 → 2 below `lg`, → 1 below `sm`), and every child keeps
535
- `break-inside: avoid` so a break never lands mid-card. Reading order runs top
536
- to bottom, then across - right for testimonials and galleries, wrong for
537
- anything ranked or chronological.
538
-
539
- ```html
540
- <div class="mw-columns-3">
541
- <figure class="mw-testimonial">...</figure>
542
- <figure class="mw-testimonial">...</figure>
543
- </div>
544
- ```
545
-
546
- ## Utilities
547
-
548
- **Spacing** - margin `mw-m-*`, `mw-mt-*`, `mw-mb-*`, `mw-ml-*`, `mw-mr-*`,
549
- `mw-mx-*`, `mw-my-*`; padding `mw-p-*`, `mw-pt-*`, `mw-pb-*`, `mw-pl-*`,
550
- `mw-pr-*`, `mw-px-*`, `mw-py-*`; `mw-gap-*`. Keys `0`-`14`, plus negative keys
551
- `1`-`6` for margins only (`mw-mt--3`). Gap shrinks to 75% below `sm`
552
- automatically.
553
-
554
- **Display** - `mw-d-flex`, `mw-d-inline-flex`, `mw-d-block`, `mw-d-inline`,
555
- `mw-d-inline-block`, `mw-d-grid`, `mw-d-none`, `mw-d-contents`.
556
-
557
- **Responsive display** - `mw-d-{sm|md|lg|xl}-{none|block|flex|inline-flex|grid|inline-block}`.
558
- Mobile-first like everything else here: `mw-d-md-flex` means
559
- "flex from the md breakpoint up", and the unprefixed class beside it is what
560
- applies below that. The pair `mw-d-none mw-d-md-flex` is hidden on a phone and a
561
- row from a tablet on. `mw-hide-mobile` (gone below md) and `mw-hide-desktop`
562
- (gone from md up) spell out the two everyone actually reaches for.
563
-
564
- **Print** - the framework ships a print stylesheet, so a page that is dark on
565
- screen comes out of the printer as black on white: the surface, ink, border and
566
- elevation tokens are redeclared for paper, the fixed chrome (header, ribbon,
567
- section nav, scroll cue, burger, theme toggle, toasts) is dropped, cards keep a
568
- hairline instead of a tone, headings do not end a page and cards, rows and
569
- figures are not torn across one. A link in `mw-prose` or `mw-disclaimer` that
570
- points at an http(s) address prints its target after the text. `mw-hide-print`
571
- takes an element off the paper, `mw-print-only` is the note that exists only
572
- there. Nothing to switch on.
573
-
574
- **Text overflow** - `mw-text-truncate` is one line ending in an ellipsis and
575
- carries `min-width: 0` with it, which is the reason truncation "does not work"
576
- nine times out of ten inside a flex row. `mw-text-clamp-2` through `-5` is that
577
- many lines ending in one. `mw-text-break` is for a string with nothing to break
578
- at - a URL, a hash, an API key - and is what keeps a phone page from scrolling
579
- sideways. `mw-text-nowrap` is the opposite.
580
-
581
- **Wrapping and measure** - `mw-text-balance` evens out the lines of a short
582
- block so a headline never leaves one word alone (headings get it already);
583
- `mw-text-pretty` only prevents the orphan and is the one for body copy
584
- (paragraphs get it already). `mw-text-measure` caps a column at 68 characters -
585
- past roughly 75 the eye loses the start of the next line on the way back.
586
- `mw-text-eyebrow` is the small spaced upper-case kicker above a heading.
587
- `mw-text-uppercase` carries the letter-spacing capitals need to stay legible
588
- with it; `mw-text-capitalize` is the plain transform.
589
-
590
- **Elevation** - `mw-elevation-0` through `-5`, the same ramp every component
591
- uses. See the scale table in `SKILL.md`. Never hand-roll a `box-shadow`.
592
-
593
- **Glow** - `mw-glow` plus
594
- `mw-glow-{primary,secondary,info,success,warning,danger}`. Elevation without the
595
- offset, so the surface gives off the light instead of casting it. Once per
596
- screen at most.
597
-
598
- **Scroll entrance** - `mw-reveal` lets a block rise briefly as it scrolls into
599
- view, driven by the browser's scroll timeline (`animation-timeline: view()`).
600
- The block stays hidden while it is still below the bottom edge and has arrived
601
- three fifths of the way in - measured along its own entry, so it is never still
602
- transparent once it stands in its place, whatever its height. A block taller
603
- than the screen is capped at one viewport by the entry phase itself. Doubly guarded:
604
- `prefers-reduced-motion` turns it off, and a browser without scroll timelines
605
- renders the block in place instead of leaving it invisible. Firefox is that
606
- browser - there the shipped JS runs the entrance off an `IntersectionObserver`,
607
- so a page without the script keeps the block and loses only the motion
608
- (`javascript.md`). Put it on section content, not on the section itself - a
609
- screen-high band finishes its entrance before its content is halfway up.
610
-
611
- A row of cards crosses the viewport edge together, so `mw-reveal` on each of
612
- them rises as one slab. `mw-reveal-stagger` goes on the **grid** instead: every
613
- child reveals, and each one in a row a tenth of that entry after the one
614
- before it - a wave across the row. One class, nothing per card. On `mw-grid-2` to `mw-grid-5` and
615
- their `-lg` variants the wave follows the actual columns at every breakpoint:
616
- four steps in a four-column row, two once it has collapsed to two. Any other
617
- container - `mw-columns-*`, a layout of your own - gets a fixed cycle of three.
618
-
619
- ```html
620
- <div class="mw-grid-3 mw-reveal-stagger">
621
- <div class="mw-card">...</div>
622
- <div class="mw-card">...</div>
623
- <div class="mw-card">...</div>
624
- </div>
625
- ```
626
-
627
- The animation lets go once the entrance is done, so a card keeps its hover
628
- lift and an open dropdown inside it is not trapped under the next card.
629
-
630
- **Corner accent** - `mw-corner-plain` drops the accent arc that sits on the
631
- two round corners of every card, panel, modal, tile, accordion, calendar,
632
- pagination and login box. Everything else stays: the radii, the border, the
633
- shadow, the hover. It is for a box too small to carry the mark - the arc reaches
634
- about 34px in from its corner, so under roughly 90px the two of them take up
635
- most of the outline and the pair reads as a frame instead of as a detail. A
636
- chip-sized `mw-card-simple`, a swatch, a marker in a layout demo. A card with
637
- content in it never needs this, and it is not the way to switch the signature
638
- off across a project - restyle `--mw-corner-accent` for that.
639
-
640
- **Squircle corners** - `mw-squircle` draws the same silhouette with a
641
- superellipse instead of a circular arc: same four radii, fuller curve, and the
642
- corner accent follows. Opt-in, because `corner-shape` only lands in Chromium so
643
- far - everywhere else the box is simply the normal one.
644
-
645
- **Aspect ratio** - `mw-aspect-square|video|wide|portrait|photo`. Reserves the
646
- box before the image inside it has loaded, so the page does not reflow when the
647
- picture arrives. The child fills the box and crops rather than stretching.
648
-
649
- **Overflow and scrolling** - `mw-overflow-auto`, `mw-overflow-x-auto` (which
650
- also contains the overscroll, so a sideways swipe on a wide table does not walk
651
- the whole page), `mw-overflow-hidden`, and `mw-snap-x` to make a horizontal
652
- strip come to rest on an item instead of halfway between two.
653
-
654
- **Numbers** - `mw-text-numeric` is fixed-width digits and nothing else, for a
655
- clock, a counter or an ID that must not jitter while it changes.
656
- `mw-text-currency` adds right alignment and `nowrap` on top, which is what a
657
- money column in a table wants. Do not reach for `mw-text-currency` just to get
658
- the digits - that was the old behaviour of `mw-text-numeric` and the reason
659
- people wrote `font-variant-numeric` out by hand.
660
-
661
- ### `mw-row-split` - the row with two ends
662
-
663
- The most common layout in any application: what it is on the left, the value or
664
- the action on the right, wrapping to two lines when it runs out of room. Card
665
- header, section header, key figure, week row.
666
-
667
- ```html
668
- <div class="mw-row-split">
669
- <strong>Invoice 2026-0042</strong>
670
- <span class="mw-text-currency">1,204.50</span>
671
- </div>
672
- ```
673
-
674
- Five declarations you would otherwise write again in every component -
675
- including the `flex-wrap` everyone forgets, which is what keeps the left half
676
- from being squashed on a phone.
677
-
678
- `mw-row-split` aligns on the baseline, which is right for text against text. Add
679
- `mw-row-split-center` when the two sides differ in height - a heading beside a
680
- button, a label beside an icon.
681
-
682
- ### `mw-sr-only` - text for screen readers only
683
-
684
- ```html
685
- <button type="button" class="mw-btn mw-btn-outline">
686
- <i class="fas fa-trash"></i>
687
- <span class="mw-sr-only">Delete invoice 2026-0042</span>
688
- </button>
689
- ```
690
-
691
- Wherever an icon carries the whole message. Not `display: none` and not
692
- `visibility: hidden` - both drop the element out of the accessibility tree,
693
- which is precisely what must not happen.
694
-
695
- There are no responsive display variants. Show/hide per breakpoint is the
696
- application's job (media query in your own stylesheet, or `@if` in the
697
- template).
698
-
699
- ### `mw-d-contents` - wrapper components inside a layout container
700
-
701
- Every flex or grid container here styles its **children**. Plain HTML puts them
702
- right there, a SPA usually does not:
703
-
704
- ```html
705
- <!-- what the CSS expects -->
706
- <div class="mw-grid-2">
707
- <div class="mw-card">…</div>
708
- <div class="mw-card">…</div>
709
- </div>
710
-
711
- <!-- what a component tree produces - one item, not two -->
712
- <div class="mw-grid-2">
713
- <app-card>…</app-card>
714
- <app-card>…</app-card>
715
- </div>
716
- ```
717
-
718
- Two cards side by side still work, because each host _is_ one item. It breaks
719
- when one host wraps several intended items, or when a host sits between the
720
- container and a single child that brings its own width - the host becomes the
721
- item and shrinks to content width. Nothing errors, the layout is just wrong.
722
-
723
- `mw-d-contents` on the host removes its box, so the children become the items:
724
-
725
- ```typescript
726
- @Component({
727
- selector: 'app-header',
728
- host: { class: 'mw-d-contents' },
729
- …
730
- })
731
- ```
732
-
733
- The trade-off: an element with `display: contents` has no box, so background,
734
- padding, border and transforms on that host stop working. If you need those, keep
735
- the box and give it `width: 100%` (flex row) or `mw-flex-1` instead.
736
-
737
- `mw-header` already absorbs a wrapper on its own, no `mw-d-contents` needed
738
- there. The grid, tag, button-bar, form-action, modal-footer and toast containers
739
- do not.
740
-
741
- **Flex** - `mw-flex-row`, `mw-flex-column`, `mw-flex-wrap`, `mw-flex-nowrap`,
742
- `mw-flex-1`, `mw-flex-grow-1`, `mw-flex-shrink-0`,
743
- `mw-justify-start|end|center|between|around|evenly`,
744
- `mw-items-start|end|center|stretch`,
745
- `mw-self-start|end|center|stretch`.
746
-
747
- `mw-flex-1` sets `flex: 1` (basis 0, all items equal). `mw-flex-grow-1` only
748
- grows and keeps the content width as the basis - that is the one you want next
749
- to an avatar or an icon. `mw-self-*` is the per-item counterpart to
750
- `mw-items-*`: it goes on the child, for the one that sits differently from the
751
- rest of the row.
752
-
753
- **Text** - alignment `mw-text-left|center|right`; colour `mw-text-primary`,
754
- `-secondary`, `-success`, `-warning`, `-danger`, `-info`, `-muted`,
755
- `mw-text-color-dark`, `mw-text-color-light`; weight `mw-text-bold`, `-medium`,
756
- `-normal`, `-light`; `mw-text-italic`; size `mw-text-3xs` … `mw-text-6xl`;
757
- line height `mw-leading-tight|normal|loose`.
758
-
759
- `mw-text-numeric` is the one to know: right aligned, tabular lining figures, no
760
- wrap - for money and figures in tables. It is locale agnostic (alignment comes
761
- from the right edge), but the number of decimal places has to be constant per
762
- column.
763
-
764
- **Radius** - `mw-radius-none|xs|sm|md|lg|xl|2xl|full`.