mote.css 0.2.1 → 0.4.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/CHANGELOG.md CHANGED
@@ -5,6 +5,83 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.0] - 2026-10-20
9
+
10
+ ### Fixed
11
+
12
+ Layout shell (`mote.components.css`), from the ten-app evaluation:
13
+ - `.mote-doc` mobile: nav/main/aside overlapped in one grid cell — the
14
+ collapse media query now resets `grid-row: auto`.
15
+ - `.mote-doc` blowout on wide content: content tracks are `minmax(0, 1fr)`
16
+ (+ `min-width: 0` on items); a wide table scrolls inside `main`, never the page.
17
+ - Rail nav no longer renders as the horizontal pill row: pill styles scoped
18
+ to `body > header nav`; rails get a quiet vertical list with
19
+ `aria-current` states.
20
+ Sticky rails and anchored content clear the site header via
21
+ `--mote-rail-top` (token) and automatic `scroll-padding` when
22
+ `body > header` exists. `body.mote-doc` gains page gutters.
23
+
24
+ Core (`mote.css`):
25
+ - `:user-invalid` kept its error border through hover and focus
26
+ (`:is(input, select, textarea):user-invalid`, placed to win the cascade) —
27
+ previously the field the user must fix showed the accent focus border.
28
+ - `--mote-accent-deep` now brightens in dark mode (`color-mix(accent,
29
+ white 14%)`); dark hovers, badges and notes no longer dim.
30
+ - `input[type="range"]` stripped of text-input chrome; `select[multiple]`
31
+ restored to native listbox appearance.
32
+ - `[role="status"]` / `[role="alert"]` read `--mote-status` (semantic
33
+ colors as fallback) instead of hard-coding.
34
+ - `.mote-note` gains block margin; `.mote-badge` line-height tamed in
35
+ headings; `blockquote` code no longer italic; `.mote-card` first/last-child
36
+ rules moved into `:where()` so page classes win; dialogs get
37
+ `max-width: min(90vw, 44rem)`; `.mote-avatar` sized by
38
+ `--mote-avatar` token; `.mote-skeleton` gets intrinsic size.
39
+ - Print: the light palette is forced on paper — dark fills (zebra, notes,
40
+ open details) no longer print as near-black bands.
41
+ - Focus rings no longer fade in (box-shadow removed from transitions).
42
+
43
+ Components:
44
+ - Popovers and dialogs now animate on exit, not just entrance
45
+ (closed-state deltas for the `allow-discrete` transitions).
46
+ - `details[name]` content padding fixed for multiple children
47
+ (was padding every child; now only the last).
48
+ - Toasts documented with `popover="manual"` (auto popovers light-dismiss);
49
+ samples corrected.
50
+
51
+ ### Changed
52
+
53
+ - README and docs CDN snippets use `mote.css@0` (range pin) so they never go
54
+ stale; exact-version pinning documented for reproducible builds.
55
+ - Docs: wide-table wrapper recipe, form-width guidance, document-shell
56
+ real-page notes (gutters, rail-top token), print caveats for exclusive
57
+ accordions, toast stacking guidance; section numbering fixed.
58
+
59
+ ## [0.3.0] - 2026-10-19
60
+
61
+ ### Added
62
+
63
+ - Layout module in `mote.components.css`:
64
+ - `.mote-doc` document shell — opt in with one class on `<body>` and the
65
+ landmarks become the layout via `:has()`: sticky left rail for `nav`,
66
+ right rail for `aside`, `main` flexing between them, footer pinned to the
67
+ viewport bottom on short pages (body-scoped `min-height: 100svh`),
68
+ everything collapsing to a single column under 50rem. No classes on the
69
+ landmarks; `:where()` keeps every rule overridable.
70
+ - `.mote-grid` card grid — auto-fill columns computed from the container;
71
+ no breakpoints, no column utilities.
72
+ - Classless page shell: `body > header` and its nav are styled from landmarks
73
+ alone (sticky translucent bar + nav pills). `.mote-header` / `.mote-nav`
74
+ remain for the one case landmarks cannot express — shells wrapped in a
75
+ `<div>`.
76
+
77
+ ### Changed
78
+
79
+ - Documentation restructured: every element documented exactly once, with all
80
+ its status variants in place; the Status classes section is now the token
81
+ contract plus a consumer map. Complete status coverage (including muted)
82
+ across notes, badges, toasts, blockquotes, progress, meters, spinners and
83
+ summary markers.
84
+
8
85
  ## [0.2.1] - 2026-10-16
9
86
 
10
87
  ### Fixed
@@ -17,8 +94,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
17
94
  - Release CI moved to GitLab CI (`.gitlab-ci.yml`): tag-triggered `npm stage
18
95
  publish` with OIDC trusted publishing — no tokens anywhere; a maintainer
19
96
  still approves every release with 2FA. The GitHub mirror is distribution-only.
20
- - Publishes now carry npm provenance attestations (the source repository is
21
- public).
97
+ - Provenance-ready: trusted publishing is configured and the source repository
98
+ is public; whether attestations survive the staged-approval flow gets proven
99
+ by the pipeline's `--provenance` flag at the next release.
22
100
  - Package metadata points to the canonical GitLab source; GitHub remains the
23
101
  distribution mirror.
24
102
 
@@ -86,6 +164,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
86
164
  - Opt-in helpers: `.mote-container`, `.mote-muted`, `.mote-note` surfaces.
87
165
  - Element gallery demo page (`demo.html`).
88
166
 
167
+ [0.3.0]: https://gitlab.com/abmach/mote/compare/v0.2.1...v0.3.0
168
+ [0.4.0]: https://gitlab.com/abmach/mote/compare/v0.3.0...v0.4.0
89
169
  [0.2.1]: https://gitlab.com/abmach/mote/compare/v0.2.0...v0.2.1
90
170
  [0.2.0]: https://gitlab.com/abmach/mote/compare/v0.1.0...v0.2.0
91
171
  [0.1.0]: https://gitlab.com/abmach/mote/-/tags/v0.1.0
package/README.md CHANGED
@@ -35,15 +35,16 @@ Source on [GitLab](https://gitlab.com/abmach/mote) · mirrored on [GitHub](https
35
35
  **CDN** — jsDelivr, serving the npm package, with auto-minified variants:
36
36
 
37
37
  ```html
38
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mote.css@0.2.0/mote.css">
38
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mote.css@0/mote.css">
39
39
  <!-- minified variant (dotted filenames minify to <name>.min.css) -->
40
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mote.css@0.2.0/mote.min.css">
40
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mote.css@0/mote.min.css">
41
41
 
42
42
  <!-- optional components file -->
43
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mote.css@0.2.0/mote.components.css">
43
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mote.css@0/mote.components.css">
44
44
  ```
45
45
 
46
- Use `mote.css@0` to track the latest 0.x release. CDN URLs go live once the package is published.
46
+ `@0` tracks the latest 0.x release, so these snippets never go stale. Pin an exact
47
+ version (`mote.css@0.3.0`) for reproducible production builds.
47
48
 
48
49
  **npm**:
49
50
 
@@ -139,12 +140,14 @@ mote is classless by design; these are the only escape hatches, plus status clas
139
140
 
140
141
  | Component | Hook |
141
142
  |---|---|
143
+ | Document layout (landmark grid) | `.mote-doc` on `<body>` — `:has()` reads your `header`/`nav`/`main`/`aside`/`footer` |
144
+ | Sticky site header / nav pills | `body > header` — classless; `.mote-header`/`.mote-nav` only for shells wrapped in a `<div>` |
145
+ | Breadcrumbs | `nav[aria-label="breadcrumb"]` — semantic, no class |
146
+ | Card grids | `.mote-grid` — auto-fill columns from the container, no breakpoints |
142
147
  | Cards | `.mote-card` |
143
148
  | Badges | `.mote-badge` + optional status class |
144
149
  | Avatars | `.mote-avatar` |
145
150
  | Skeleton / spinner | `.mote-skeleton`, `.mote-spinner` |
146
- | Sticky site header / nav pills | `.mote-header`, `.mote-nav` |
147
- | Breadcrumbs | `nav[aria-label="breadcrumb"]` — semantic, no class |
148
151
  | Tooltips | `[popover].mote-tooltip` + `popovertarget` button |
149
152
  | Menus | `menu[popover]` + `popovertarget` button |
150
153
  | Toasts | `[popover].mote-toast` + status class |
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.components.css — opt-in components for mote.css.
3
- * v0.2.1 · MIT
3
+ * v0.4.0 · MIT
4
4
  * https://gitlab.com/abmach/mote · mirror: https://github.com/abmach/mote
5
5
  *
6
6
  * Requires mote.css loaded first (provides the --mote-* tokens).
@@ -22,11 +22,11 @@
22
22
  box-shadow: var(--mote-shadow-sm);
23
23
  }
24
24
 
25
- .mote-card > :first-child {
25
+ :where(.mote-card) > :first-child {
26
26
  margin-block-start: 0;
27
27
  }
28
28
 
29
- .mote-card > :last-child {
29
+ :where(.mote-card) > :last-child {
30
30
  margin-block-end: 0;
31
31
  }
32
32
 
@@ -43,7 +43,7 @@
43
43
  border-radius: 999px;
44
44
  font-size: 0.8em;
45
45
  font-weight: 500;
46
- line-height: 1.8;
46
+ line-height: 1.4;
47
47
  background: var(--mote-status-soft, var(--mote-accent-soft));
48
48
  color: var(--mote-status, var(--mote-accent-deep));
49
49
  user-select: none;
@@ -54,8 +54,8 @@
54
54
  * ------------------------------------------------------------------------- */
55
55
 
56
56
  .mote-avatar {
57
- width: 2.5rem;
58
- height: 2.5rem;
57
+ width: var(--mote-avatar, 2.5rem);
58
+ height: var(--mote-avatar, 2.5rem);
59
59
  border-radius: 999px;
60
60
  object-fit: cover;
61
61
  border: var(--mote-border-width) solid var(--mote-border);
@@ -67,6 +67,8 @@
67
67
  * ------------------------------------------------------------------------- */
68
68
 
69
69
  .mote-skeleton {
70
+ display: inline-block;
71
+ min-height: 1em;
70
72
  border-radius: var(--mote-radius-sm);
71
73
  background:
72
74
  linear-gradient(
@@ -105,9 +107,15 @@
105
107
 
106
108
  /* ---------------------------------------------------------------------------
107
109
  * 6. Site header and nav
110
+ *
111
+ * The page shell is styled classlessly: a header that is a direct
112
+ * child of <body> is the site bar, its nav the pill row. The classes
113
+ * exist for the one case landmarks cannot express — a shell wrapped
114
+ * in a <div> (common with frameworks), where body > header cannot
115
+ * see it. All selectors keep :where() specificity so overrides win.
108
116
  * ------------------------------------------------------------------------- */
109
117
 
110
- .mote-header {
118
+ :where(body > header, .mote-header) {
111
119
  position: sticky;
112
120
  top: 0;
113
121
  z-index: 10;
@@ -116,7 +124,7 @@
116
124
  border-block-end: var(--mote-border-width) solid var(--mote-border);
117
125
  }
118
126
 
119
- .mote-nav ul {
127
+ :where(body > header nav, .mote-nav) ul {
120
128
  display: flex;
121
129
  flex-wrap: wrap;
122
130
  gap: 0.25rem;
@@ -125,7 +133,7 @@
125
133
  list-style: none;
126
134
  }
127
135
 
128
- .mote-nav a {
136
+ :where(body > header nav, .mote-nav) a {
129
137
  display: inline-block;
130
138
  padding: 0.35rem 0.8rem;
131
139
  border-radius: 999px;
@@ -134,12 +142,12 @@
134
142
  transition: background var(--mote-transition), color var(--mote-transition);
135
143
  }
136
144
 
137
- .mote-nav a:hover {
145
+ :where(body > header nav, .mote-nav) a:hover {
138
146
  background: var(--mote-accent-soft);
139
147
  color: var(--mote-accent-deep);
140
148
  }
141
149
 
142
- .mote-nav a[aria-current="page"] {
150
+ :where(body > header nav, .mote-nav) a[aria-current="page"] {
143
151
  background: var(--mote-accent);
144
152
  color: var(--mote-on-accent);
145
153
  }
@@ -261,7 +269,9 @@ menu[popover] :is(button, [role="menuitem"]):focus-visible {
261
269
  * ------------------------------------------------------------------------- */
262
270
 
263
271
  [popover],
264
- dialog[open] {
272
+ dialog {
273
+ opacity: 0;
274
+ transform: translateY(8px) scale(0.98);
265
275
  transition:
266
276
  opacity 180ms ease,
267
277
  transform 180ms ease,
@@ -322,7 +332,11 @@ details[name][open] summary {
322
332
 
323
333
  details[name] > :not(summary) {
324
334
  margin-block: 0;
325
- padding: 0.15rem 1.2rem 0.9rem;
335
+ padding: 0.15rem 1.2rem 0;
336
+ }
337
+
338
+ details[name] > :not(summary):last-child {
339
+ padding-block-end: 0.9rem;
326
340
  }
327
341
 
328
342
  /* ---------------------------------------------------------------------------
@@ -370,3 +384,141 @@ details[name] > :not(summary) {
370
384
  }
371
385
  }
372
386
  }
387
+
388
+ /* ---------------------------------------------------------------------------
389
+ * 12. Layout — document shell and card grids
390
+ *
391
+ * The document layout is opted in with a single class on <body> (or any
392
+ * wrapper): .mote-doc. The landmarks you already write — header, nav,
393
+ * main, aside, footer — become the layout; no classes on the landmarks.
394
+ * A nav becomes a left rail, an aside a right rail, and everything
395
+ * collapses to a single column under 50rem.
396
+ *
397
+ * All :has() guards sit in :where(), so the rules carry zero specificity
398
+ * weight — your own styles on the same landmarks always win.
399
+ * ------------------------------------------------------------------------- */
400
+
401
+ .mote-doc {
402
+ display: grid;
403
+ column-gap: 1.5rem;
404
+ grid-template-rows: auto 1fr auto;
405
+ grid-template-columns: 1fr;
406
+ }
407
+
408
+ /* On <body>, the middle row stretches the page so the footer pins to the
409
+ viewport bottom even when content is short. :where() keeps this
410
+ at base specificity; wrappers (demo boxes, embeds) stay natural-size. */
411
+ :where(body).mote-doc {
412
+ min-height: 100svh;
413
+ padding-inline: 1.5rem;
414
+ }
415
+
416
+ /* Anchored content must clear the sticky shell header. Applies only
417
+ when the header actually exists. */
418
+ html:where(:has(body > header)) {
419
+ scroll-padding-block-start: 5rem;
420
+ }
421
+
422
+ .mote-doc > header { grid-row: 1; grid-column: 1 / -1; }
423
+ .mote-doc > footer { grid-row: 3; grid-column: 1 / -1; }
424
+
425
+ .mote-doc > main { grid-row: 2; grid-column: 1; min-width: 0; }
426
+
427
+ /* nav = left rail */
428
+ .mote-doc > nav {
429
+ grid-row: 2;
430
+ grid-column: 1;
431
+ min-width: 0;
432
+ align-self: start;
433
+ position: sticky;
434
+ top: var(--mote-rail-top, 1rem);
435
+ max-height: calc(100svh - 2rem);
436
+ overflow: auto;
437
+ }
438
+
439
+ /* Rail links: a quiet vertical list, not the horizontal pill row.
440
+ --mote-rail-top should clear the shell header: 1rem by default;
441
+ set it on :root when a sticky body > header is taller, e.g.
442
+ --mote-rail-top: 4.75rem. */
443
+ .mote-doc > nav ul {
444
+ display: flex;
445
+ flex-direction: column;
446
+ gap: 0.15rem;
447
+ padding: 0;
448
+ margin: 0;
449
+ list-style: none;
450
+ }
451
+
452
+ .mote-doc > nav a {
453
+ display: block;
454
+ padding: 0.35rem 0.75rem;
455
+ border-radius: var(--mote-radius-sm);
456
+ text-decoration: none;
457
+ color: var(--mote-fg);
458
+ transition: background var(--mote-transition), color var(--mote-transition);
459
+ }
460
+
461
+ .mote-doc > nav a:hover {
462
+ background: var(--mote-accent-soft);
463
+ color: var(--mote-accent-deep);
464
+ }
465
+
466
+ .mote-doc > nav a[aria-current="page"] {
467
+ background: var(--mote-accent);
468
+ color: var(--mote-on-accent);
469
+ }
470
+
471
+ .mote-doc:where(:has(> nav)) {
472
+ grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr);
473
+ }
474
+
475
+ .mote-doc:where(:has(> nav)) > main {
476
+ grid-column: 2;
477
+ }
478
+
479
+ /* aside = right rail (or the main column when there is no nav) */
480
+ .mote-doc > aside {
481
+ grid-row: 2;
482
+ grid-column: 2;
483
+ min-width: 0;
484
+ align-self: start;
485
+ }
486
+
487
+ .mote-doc:where(:has(> nav)) > aside {
488
+ grid-column: 3;
489
+ }
490
+
491
+ .mote-doc:where(:has(> aside)) {
492
+ grid-template-columns: minmax(0, 1fr) minmax(10rem, 14rem);
493
+ }
494
+
495
+ .mote-doc:where(:has(> nav)):where(:has(> aside)) {
496
+ grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr) minmax(10rem, 14rem);
497
+ }
498
+
499
+ @media (width < 50rem) {
500
+ .mote-doc:where(:has(> nav)),
501
+ .mote-doc:where(:has(> aside)),
502
+ .mote-doc:where(:has(> nav)):where(:has(> aside)) {
503
+ grid-template-columns: 1fr;
504
+ }
505
+
506
+ .mote-doc > :is(nav, aside, main) {
507
+ grid-column: 1 / -1;
508
+ grid-row: auto;
509
+ }
510
+
511
+ .mote-doc > nav {
512
+ position: static;
513
+ max-height: none;
514
+ overflow: visible;
515
+ }
516
+ }
517
+
518
+ /* Card grids: one class, no breakpoints, no column utilities.
519
+ Columns are computed from the container: as many as fit in 16rem. */
520
+ .mote-grid {
521
+ display: grid;
522
+ gap: 1.25rem;
523
+ grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
524
+ }
package/mote.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.css — a mote of styling for plain HTML.
3
- * v0.2.1 · MIT
3
+ * v0.4.0 · MIT
4
4
  * https://gitlab.com/abmach/mote · mirror: https://github.com/abmach/mote
5
5
  *
6
6
  * The smallest thing that can carry style:
@@ -84,6 +84,10 @@
84
84
  --mote-error: #d98a8a;
85
85
  --mote-info: #96b1d1;
86
86
 
87
+ /* "Deep" is the emphasis direction per theme: darker on light
88
+ surfaces, brighter on dark ones. Never darken in dark mode. */
89
+ --mote-accent-deep: color-mix(in oklab, var(--mote-accent), white 14%);
90
+
87
91
  --mote-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 4px 16px rgba(0, 0, 0, 0.3);
88
92
  --mote-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
89
93
  --mote-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23a49c90' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
@@ -105,6 +109,8 @@
105
109
  --mote-error: #d98a8a;
106
110
  --mote-info: #96b1d1;
107
111
 
112
+ --mote-accent-deep: color-mix(in oklab, var(--mote-accent), white 14%);
113
+
108
114
  --mote-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 4px 16px rgba(0, 0, 0, 0.3);
109
115
  --mote-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
110
116
  --mote-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23a49c90' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
@@ -245,6 +251,10 @@ blockquote {
245
251
  font-style: italic;
246
252
  }
247
253
 
254
+ blockquote :where(code, samp, kbd, var) {
255
+ font-style: normal;
256
+ }
257
+
248
258
  blockquote > cite {
249
259
  display: block;
250
260
  margin-block-start: 0.5rem;
@@ -403,7 +413,7 @@ input, select, textarea {
403
413
  background: var(--mote-surface);
404
414
  border: var(--mote-border-width) solid var(--mote-border);
405
415
  border-radius: var(--mote-radius-sm);
406
- transition: border-color var(--mote-transition), box-shadow var(--mote-transition);
416
+ transition: border-color var(--mote-transition);
407
417
  }
408
418
 
409
419
  input:hover, select:hover, textarea:hover {
@@ -428,6 +438,13 @@ select {
428
438
  background-position: right 0.8rem center;
429
439
  }
430
440
 
441
+ /* Multi-selects are listboxes, not dropdowns: restore native chrome */
442
+ select[multiple] {
443
+ appearance: auto;
444
+ padding: 0.5rem;
445
+ background-image: none;
446
+ }
447
+
431
448
  [type="checkbox"], [type="radio"] {
432
449
  width: 1em;
433
450
  height: 1em;
@@ -447,6 +464,14 @@ select {
447
464
  border-style: dashed;
448
465
  }
449
466
 
467
+ /* Range inputs are sliders, not text fields: strip text chrome */
468
+ [type="range"] {
469
+ padding: 0;
470
+ border: none;
471
+ background: none;
472
+ accent-color: var(--mote-status, var(--mote-accent));
473
+ }
474
+
450
475
  ::placeholder {
451
476
  color: var(--mote-muted);
452
477
  opacity: 0.8;
@@ -457,7 +482,7 @@ select {
457
482
  cursor: not-allowed;
458
483
  }
459
484
 
460
- :user-invalid {
485
+ :is(input, select, textarea):user-invalid {
461
486
  border-color: var(--mote-error);
462
487
  }
463
488
 
@@ -471,7 +496,7 @@ button, [type="button"], [type="submit"], [type="reset"] {
471
496
  border-radius: var(--mote-radius-sm);
472
497
  cursor: pointer;
473
498
  transition: background var(--mote-transition), border-color var(--mote-transition),
474
- box-shadow var(--mote-transition), transform var(--mote-transition);
499
+ transform var(--mote-transition);
475
500
  }
476
501
 
477
502
  button:hover, [type="button"]:hover, [type="submit"]:hover, [type="reset"]:hover {
@@ -537,6 +562,7 @@ dialog {
537
562
  background: var(--mote-bg);
538
563
  color: var(--mote-fg);
539
564
  box-shadow: var(--mote-shadow);
565
+ max-width: min(90vw, 44rem);
540
566
  }
541
567
 
542
568
  dialog::backdrop {
@@ -550,8 +576,8 @@ progress, meter {
550
576
  }
551
577
 
552
578
  /* Semantic feedback helpers (pure HTML, works with <p>, <div>, anything) */
553
- [role="alert"] { color: var(--mote-error); }
554
- [role="status"] { color: var(--mote-info); }
579
+ [role="alert"] { color: var(--mote-status, var(--mote-error)); }
580
+ [role="status"] { color: var(--mote-status, var(--mote-info)); }
555
581
 
556
582
  /* ---------------------------------------------------------------------------
557
583
  * 11. Opt-in helpers
@@ -569,7 +595,7 @@ progress, meter {
569
595
  .mote-muted { color: var(--mote-muted); }
570
596
 
571
597
  /* ---------------------------------------------------------------------------
572
- * 11. Status classes
598
+ * 12. Status classes
573
599
  *
574
600
  * Statuses are pure token assignments — they set variables, never visual
575
601
  * properties. Base classes (notes, badges, toasts) consume the tokens and
@@ -597,6 +623,7 @@ progress, meter {
597
623
 
598
624
  /* Themed message surfaces — base shape only; status via the token classes */
599
625
  .mote-note {
626
+ margin-block: 1rem;
600
627
  padding: 0.8rem 1.1rem;
601
628
  border-radius: var(--mote-radius-sm);
602
629
  color: var(--mote-status, var(--mote-accent-deep));
@@ -604,7 +631,7 @@ progress, meter {
604
631
  }
605
632
 
606
633
  /* ---------------------------------------------------------------------------
607
- * 12. Motion preferences & print
634
+ * 13. Motion preferences & print
608
635
  * ------------------------------------------------------------------------- */
609
636
 
610
637
  @media (prefers-reduced-motion: reduce) {
@@ -617,6 +644,26 @@ progress, meter {
617
644
  }
618
645
 
619
646
  @media print {
647
+ /* Force the light palette on paper — dark fills (zebra, notes,
648
+ open details) must not print as near-black bands. Placed last:
649
+ source order ties the dark token blocks' specificity. */
650
+ :root,
651
+ :root[data-theme="dark"],
652
+ :root:not([data-theme="light"]) {
653
+ color-scheme: light;
654
+ --mote-bg: #faf9f7;
655
+ --mote-surface: #f1eee7;
656
+ --mote-fg: #2b2924;
657
+ --mote-muted: #716a60;
658
+ --mote-border: #e6e0d4;
659
+ --mote-accent: #6f5486;
660
+ --mote-accent-deep: color-mix(in oklab, var(--mote-accent), black 14%);
661
+ --mote-success: #4e7d51;
662
+ --mote-warning: #8a6819;
663
+ --mote-error: #b34d4d;
664
+ --mote-info: #527097;
665
+ }
666
+
620
667
  body {
621
668
  background: #fff;
622
669
  color: #000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mote.css",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "A mote of styling for plain HTML — tiny, semantic, classless CSS with zero dependencies and no build step.",
5
5
  "style": "mote.css",
6
6
  "files": [