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.
- package/README.md +2 -2
- package/maverick-wave.min.css +2 -2
- package/package.json +8 -2
- package/src/scss/abstracts/_variables.scss +2 -0
- package/src/scss/components/_modals.scss +23 -9
- package/src/scss/components/_testimonial.scss +2 -0
- package/src/scss/layout/_home.scss +46 -0
- package/src/scss/layout/_main.scss +20 -0
- package/src/scss/layout/_parallax.scss +2 -2
- package/src/scss/main-lean.scss +8 -3
- package/.claude/settings.local.json +0 -43
- package/.claude/skills/mw-maverick-wave/SKILL.md +0 -532
- package/.claude/skills/mw-maverick-wave/examples/angular-form.md +0 -279
- package/.claude/skills/mw-maverick-wave/examples/angular-list-page.md +0 -309
- package/.claude/skills/mw-maverick-wave/examples/angular-services.md +0 -538
- package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +0 -530
- package/.claude/skills/mw-maverick-wave/references/components.md +0 -2170
- package/.claude/skills/mw-maverick-wave/references/forms.md +0 -358
- package/.claude/skills/mw-maverick-wave/references/javascript.md +0 -164
- package/.claude/skills/mw-maverick-wave/references/layout.md +0 -764
- package/.claude/skills/mw-maverick-wave/references/theming.md +0 -530
- package/.github/workflows/ci.yml +0 -41
- package/.idea/codeStyles/Project.xml +0 -59
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/maverick-wave.iml +0 -12
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -6
- package/.idea/vcs.xml +0 -6
- package/.prettierignore +0 -8
- package/.prettierrc.json +0 -7
- package/CHANGELOG.md +0 -956
- package/CLAUDE.md +0 -60
- package/gulpfile.js +0 -167
- package/index.html +0 -1730
- package/release.sh +0 -31
- package/scripts/verify.js +0 -252
- package/src/assets/favicon/apple-touch-icon.png +0 -0
- package/src/assets/favicon/favicon-96x96.png +0 -0
- package/src/assets/favicon/favicon.svg +0 -1
- package/src/assets/favicon/site.webmanifest +0 -37
- package/src/assets/favicon/web-app-manifest-192x192.png +0 -0
- package/src/assets/favicon/web-app-manifest-512x512.png +0 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/header-logo.svg +0 -24
- package/src/assets/images/gallery-city.svg +0 -49
- package/src/assets/images/gallery-desert.svg +0 -25
- package/src/assets/images/gallery-forest.svg +0 -46
- package/src/assets/images/gallery-lake.svg +0 -26
- package/src/assets/images/gallery-mountain.svg +0 -19
- package/src/assets/images/gallery-ocean.svg +0 -26
- package/src/assets/images/gallery-oldtown.svg +0 -41
- package/src/assets/images/gallery-snow.svg +0 -39
- package/src/assets/images/photo-balloon.svg +0 -35
- package/src/assets/images/photo-forest-path.svg +0 -31
- package/src/assets/images/photo-lighthouse.svg +0 -42
- package/src/assets/images/photo-palm.svg +0 -27
- package/src/assets/images/photo-peak.svg +0 -27
- package/src/assets/images/photo-tower.svg +0 -34
- package/src/assets/images/photo-waterfall.svg +0 -32
- package/src/assets/images/slider-field.svg +0 -124
- package/src/assets/images/slider-forest.svg +0 -33
- package/src/assets/images/slider-lake.svg +0 -35
- package/src/assets/images/slider-mountains.svg +0 -29
- package/src/assets/images/slider-trees.svg +0 -41
- package/src/assets/images/story-aurora.svg +0 -36
- package/src/assets/images/story-beach.svg +0 -34
- package/src/assets/images/story-stars.svg +0 -35
- package/src/assets/images/tile-analytics.svg +0 -22
- package/src/assets/images/tile-cloud.svg +0 -19
- package/src/assets/images/tile-ecommerce.svg +0 -14
- package/src/assets/images/tile-enterprise.svg +0 -22
- package/src/assets/images/tile-marketing.svg +0 -16
- package/src/assets/media/demo-chime.wav +0 -0
- package/src/assets/media/story-sunrise.jpg +0 -0
- package/src/assets/media/story-sunrise.mp4 +0 -0
- package/src/partials/accordions-container.html +0 -179
- package/src/partials/alerts-container.html +0 -151
- package/src/partials/announcement-container.html +0 -67
- package/src/partials/avatars-container.html +0 -229
- package/src/partials/badges-container.html +0 -114
- package/src/partials/blog-posts-container.html +0 -158
- package/src/partials/breadcrumbs-container.html +0 -126
- package/src/partials/button-bar-container.html +0 -175
- package/src/partials/buttons-container.html +0 -272
- package/src/partials/calendar-container.html +0 -203
- package/src/partials/cards-container.html +0 -727
- package/src/partials/code-container.html +0 -194
- package/src/partials/colors-container.html +0 -263
- package/src/partials/coming-soon-container.html +0 -10
- package/src/partials/contact-container.html +0 -85
- package/src/partials/content-slider-container.html +0 -36
- package/src/partials/divider-container.html +0 -70
- package/src/partials/documentation-container.html +0 -204
- package/src/partials/dropdown-container.html +0 -119
- package/src/partials/empty-state-container.html +0 -79
- package/src/partials/feed-container.html +0 -506
- package/src/partials/footer-container.html +0 -113
- package/src/partials/form-container.html +0 -262
- package/src/partials/form-elements-container.html +0 -616
- package/src/partials/form-field-container.html +0 -166
- package/src/partials/gallery-container.html +0 -62
- package/src/partials/get-started-container.html +0 -423
- package/src/partials/grid-container.html +0 -191
- package/src/partials/header-container.html +0 -69
- package/src/partials/header-utilities-container.html +0 -331
- package/src/partials/highlights-container.html +0 -71
- package/src/partials/history-container.html +0 -67
- package/src/partials/home-container.html +0 -31
- package/src/partials/html-lists-container.html +0 -63
- package/src/partials/info-container.html +0 -104
- package/src/partials/input-group-container.html +0 -299
- package/src/partials/item-lists-container.html +0 -368
- package/src/partials/kanban-container.html +0 -459
- package/src/partials/kbd-container.html +0 -63
- package/src/partials/leader-row-container.html +0 -139
- package/src/partials/login-container.html +0 -65
- package/src/partials/lunch-menu-container.html +0 -341
- package/src/partials/media-container.html +0 -27
- package/src/partials/meta-info-container.html +0 -21
- package/src/partials/modals-container.html +0 -199
- package/src/partials/mosaic-container.html +0 -173
- package/src/partials/page-header-container.html +0 -78
- package/src/partials/pagination-container.html +0 -64
- package/src/partials/palette-container.html +0 -480
- package/src/partials/panels-container.html +0 -126
- package/src/partials/parallax-container.html +0 -147
- package/src/partials/portrait-gallery-container.html +0 -175
- package/src/partials/preview-container.html +0 -136
- package/src/partials/pricing-container.html +0 -523
- package/src/partials/progress-container.html +0 -230
- package/src/partials/prose-container.html +0 -43
- package/src/partials/ratings-container.html +0 -104
- package/src/partials/section-head-container.html +0 -77
- package/src/partials/segmented-container.html +0 -66
- package/src/partials/skeleton-container.html +0 -87
- package/src/partials/spinners-container.html +0 -96
- package/src/partials/stepper-container.html +0 -111
- package/src/partials/stories-container.html +0 -182
- package/src/partials/tables-container.html +0 -309
- package/src/partials/tabs-container.html +0 -290
- package/src/partials/tags-container.html +0 -242
- package/src/partials/techstack-bucket-container.html +0 -187
- package/src/partials/testimonials-container.html +0 -88
- package/src/partials/tiles-container.html +0 -247
- package/src/partials/timelines-container.html +0 -196
- package/src/partials/typography-container.html +0 -74
- 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">© 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 · 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`.
|