mote.css 0.3.0 → 0.5.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,144 @@ 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.5.0] - 2026-10-20
9
+
10
+ ### Added
11
+
12
+ - Semantic buttons: solid buttons take a status class (`.mote-success`,
13
+ `.mote-warning`, `.mote-error`, `.mote-info`, `.mote-muted`) — gated, so
14
+ the class must be on the button itself. Themed sections (error notes,
15
+ toasts, page-level status) never recolor the actions inside them; the
16
+ intent of an action is declared at the action site. Text color on
17
+ semantic solids is `--mote-on-status` (set by the status classes to the
18
+ page color, AA-verified in both themes).
19
+ - Ghost buttons (`.mote-ghost`): transparent fill, status-tinted border
20
+ and text. Gated like solids — the status class on the element itself
21
+ provides the tint, never the surrounding context.
22
+ - `.mote-default` and `.mote-reset`: control status inheritance for
23
+ the non-gated consumers (badges, notes, spinners, …). Default forces the
24
+ accent down the tree; reset clears the inherited status so each
25
+ element's own classes decide.
26
+ - `.mote-row` + `.mote-push`: flex row helper + right-align helper —
27
+ for headers, footers, toolbars, chip lists, form rows.
28
+ - `.mote-display` + `--mote-display` token: opt-in display/hero scale.
29
+ - `--mote-grid-min` variable on `.mote-grid`: density without a class API.
30
+ - `.mote-sr-only`: visually-hidden helper; doubles as a skip link when
31
+ focused.
32
+ - Semantic pagination: `nav[aria-label="pagination"]` — flex row of
33
+ pills with `aria-current` state, no class (mirrors breadcrumbs).
34
+ - Footer baseline: `body > footer` gets a top border and block padding
35
+ (the quiet sibling of the site header).
36
+ - `.mote-card` is now `display: flex; flex-direction: column` —
37
+ CTA-in-bottom-of-card aligns with `margin-block-start: auto` for free.
38
+ - Theme-aware hover deepening for buttons: `color-mix(accent/status,
39
+ fg 14%)` — darkens in light themes, brightens in dark ones.
40
+
41
+ ### Changed
42
+
43
+ - Docs: buttons and statuses section (gating rationale, ghost inheritance),
44
+ `.mote-default`/`.mote-reset` guidance, new component table entries.
45
+
46
+ ### Fixed
47
+
48
+ Layout shell (`mote.components.css`), from the ten-app evaluation:
49
+ - `.mote-doc` mobile: nav/main/aside overlapped in one grid cell — the
50
+ collapse media query now resets `grid-row: auto`.
51
+ - `.mote-doc` blowout on wide content: content tracks are `minmax(0, 1fr)`
52
+ (+ `min-width: 0` on items); a wide table scrolls inside `main`, never the page.
53
+ - Rail nav no longer renders as the horizontal pill row: pill styles scoped
54
+ to `body > header nav`; rails get a quiet vertical list with
55
+ `aria-current` states.
56
+ Sticky rails and anchored content clear the site header via
57
+ `--mote-rail-top` (token) and automatic `scroll-padding` when
58
+ `body > header` exists. `body.mote-doc` gains page gutters.
59
+
60
+ Core (`mote.css`):
61
+ - `:user-invalid` kept its error border through hover and focus
62
+ (`:is(input, select, textarea):user-invalid`, placed to win the cascade) —
63
+ previously the field the user must fix showed the accent focus border.
64
+ - `--mote-accent-deep` now brightens in dark mode (`color-mix(accent,
65
+ white 14%)`); dark hovers, badges and notes no longer dim.
66
+ - `input[type="range"]` stripped of text-input chrome; `select[multiple]`
67
+ restored to native listbox appearance.
68
+ - `[role="status"]` / `[role="alert"]` read `--mote-status` (semantic
69
+ colors as fallback) instead of hard-coding.
70
+ - `.mote-note` gains block margin; `.mote-badge` line-height tamed in
71
+ headings; `blockquote` code no longer italic; `.mote-card` first/last-child
72
+ rules moved into `:where()` so page classes win; dialogs get
73
+ `max-width: min(90vw, 44rem)`; `.mote-avatar` sized by
74
+ `--mote-avatar` token; `.mote-skeleton` gets intrinsic size.
75
+ - Print: the light palette is forced on paper — dark fills (zebra, notes,
76
+ open details) no longer print as near-black bands.
77
+ - Focus rings no longer fade in (box-shadow removed from transitions).
78
+
79
+ Components:
80
+ - Popovers and dialogs now animate on exit, not just entrance
81
+ (closed-state deltas for the `allow-discrete` transitions).
82
+ - `details[name]` content padding fixed for multiple children
83
+ (was padding every child; now only the last).
84
+ - Toasts documented with `popover="manual"` (auto popovers light-dismiss);
85
+ samples corrected.
86
+
87
+ ### Changed
88
+
89
+ - README and docs CDN snippets use `mote.css@0` (range pin) so they never go
90
+ stale; exact-version pinning documented for reproducible builds.
91
+ - Docs: wide-table wrapper recipe, form-width guidance, document-shell
92
+ real-page notes (gutters, rail-top token), print caveats for exclusive
93
+ accordions, toast stacking guidance; section numbering fixed.
94
+
95
+ ## [0.4.0] - 2026-10-20
96
+
97
+ ### Fixed
98
+
99
+ Layout shell (`mote.components.css`), from the ten-app evaluation:
100
+ - `.mote-doc` mobile: nav/main/aside overlapped in one grid cell — the
101
+ collapse media query now resets `grid-row: auto`.
102
+ - `.mote-doc` blowout on wide content: content tracks are `minmax(0, 1fr)`
103
+ (+ `min-width: 0` on items); a wide table scrolls inside `main`, never the page.
104
+ - Rail nav no longer renders as the horizontal pill row: pill styles scoped
105
+ to `body > header nav`; rails get a quiet vertical list with
106
+ `aria-current` states.
107
+ Sticky rails and anchored content clear the site header via
108
+ `--mote-rail-top` (token) and automatic `scroll-padding` when
109
+ `body > header` exists. `body.mote-doc` gains page gutters.
110
+
111
+ Core (`mote.css`):
112
+ - `:user-invalid` kept its error border through hover and focus
113
+ (`:is(input, select, textarea):user-invalid`, placed to win the cascade) —
114
+ previously the field the user must fix showed the accent focus border.
115
+ - `--mote-accent-deep` now brightens in dark mode (`color-mix(accent,
116
+ white 14%)`); dark hovers, badges and notes no longer dim.
117
+ - `input[type="range"]` stripped of text-input chrome; `select[multiple]`
118
+ restored to native listbox appearance.
119
+ - `[role="status"]` / `[role="alert"]` read `--mote-status` (semantic
120
+ colors as fallback) instead of hard-coding.
121
+ - `.mote-note` gains block margin; `.mote-badge` line-height tamed in
122
+ headings; `blockquote` code no longer italic; `.mote-card` first/last-child
123
+ rules moved into `:where()` so page classes win; dialogs get
124
+ `max-width: min(90vw, 44rem)`; `.mote-avatar` sized by
125
+ `--mote-avatar` token; `.mote-skeleton` gets intrinsic size.
126
+ - Print: the light palette is forced on paper — dark fills (zebra, notes,
127
+ open details) no longer print as near-black bands.
128
+ - Focus rings no longer fade in (box-shadow removed from transitions).
129
+
130
+ Components:
131
+ - Popovers and dialogs now animate on exit, not just entrance
132
+ (closed-state deltas for the `allow-discrete` transitions).
133
+ - `details[name]` content padding fixed for multiple children
134
+ (was padding every child; now only the last).
135
+ - Toasts documented with `popover="manual"` (auto popovers light-dismiss);
136
+ samples corrected.
137
+
138
+ ### Changed
139
+
140
+ - README and docs CDN snippets use `mote.css@0` (range pin) so they never go
141
+ stale; exact-version pinning documented for reproducible builds.
142
+ - Docs: wide-table wrapper recipe, form-width guidance, document-shell
143
+ real-page notes (gutters, rail-top token), print caveats for exclusive
144
+ accordions, toast stacking guidance; section numbering fixed.
145
+
8
146
  ## [0.3.0] - 2026-10-19
9
147
 
10
148
  ### Added
@@ -113,11 +251,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
113
251
  - Opt-in helpers: `.mote-container`, `.mote-muted`, `.mote-note` surfaces.
114
252
  - Element gallery demo page (`demo.html`).
115
253
 
254
+ [0.3.0]: https://gitlab.com/abmach/mote/compare/v0.2.1...v0.3.0
255
+ [0.5.0]: https://gitlab.com/abmach/mote/compare/v0.4.0...v0.5.0
256
+ [0.4.0]: https://gitlab.com/abmach/mote/compare/v0.3.0...v0.4.0
116
257
  [0.3.0]: https://gitlab.com/abmach/mote/compare/v0.2.1...v0.3.0
117
258
  [0.2.1]: https://gitlab.com/abmach/mote/compare/v0.2.0...v0.2.1
118
259
  [0.2.0]: https://gitlab.com/abmach/mote/compare/v0.1.0...v0.2.0
119
260
  [0.1.0]: https://gitlab.com/abmach/mote/-/tags/v0.1.0
120
-
121
- ---
122
-
123
- Copyright (c) 2026 Arthur Biazon Machado. All rights reserved.
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
 
@@ -141,6 +142,14 @@ mote is classless by design; these are the only escape hatches, plus status clas
141
142
  |---|---|
142
143
  | Document layout (landmark grid) | `.mote-doc` on `<body>` — `:has()` reads your `header`/`nav`/`main`/`aside`/`footer` |
143
144
  | Sticky site header / nav pills | `body > header` — classless; `.mote-header`/`.mote-nav` only for shells wrapped in a `<div>` |
145
+ | Semantic buttons (gated) | `class="mote-success"` etc. on the button itself — themed sections never recolor actions |
146
+ | Ghost buttons | `.mote-ghost` — transparent fill; status tints from the button's own class (gated like solids) |
147
+ | Display text | `.mote-display` — opt-in hero/error-code scale via `--mote-display` |
148
+ | Content rows | `.mote-row` + `.mote-push` — flex row, right-align helper |
149
+ | Pagination | `nav[aria-label="pagination"]` — semantic, no class |
150
+ | Footer baseline | `body > footer` — classless top-border + padding |
151
+ | Screen-reader helper | `.mote-sr-only` (doubles as a skip link when focused) |
152
+ | Status inheritance control | `.mote-default` (force accent) · `.mote-reset` (clear inherited status) |
144
153
  | Breadcrumbs | `nav[aria-label="breadcrumb"]` — semantic, no class |
145
154
  | Card grids | `.mote-grid` — auto-fill columns from the container, no breakpoints |
146
155
  | Cards | `.mote-card` |
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.components.css — opt-in components for mote.css.
3
- * v0.3.0 · MIT
3
+ * v0.5.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).
@@ -15,6 +15,8 @@
15
15
  * ------------------------------------------------------------------------- */
16
16
 
17
17
  .mote-card {
18
+ display: flex;
19
+ flex-direction: column;
18
20
  padding: 1.25rem 1.5rem;
19
21
  border: var(--mote-border-width) solid var(--mote-border);
20
22
  border-radius: var(--mote-radius-lg);
@@ -22,11 +24,11 @@
22
24
  box-shadow: var(--mote-shadow-sm);
23
25
  }
24
26
 
25
- .mote-card > :first-child {
27
+ :where(.mote-card) > :first-child {
26
28
  margin-block-start: 0;
27
29
  }
28
30
 
29
- .mote-card > :last-child {
31
+ :where(.mote-card) > :last-child {
30
32
  margin-block-end: 0;
31
33
  }
32
34
 
@@ -43,7 +45,7 @@
43
45
  border-radius: 999px;
44
46
  font-size: 0.8em;
45
47
  font-weight: 500;
46
- line-height: 1.8;
48
+ line-height: 1.4;
47
49
  background: var(--mote-status-soft, var(--mote-accent-soft));
48
50
  color: var(--mote-status, var(--mote-accent-deep));
49
51
  user-select: none;
@@ -54,8 +56,8 @@
54
56
  * ------------------------------------------------------------------------- */
55
57
 
56
58
  .mote-avatar {
57
- width: 2.5rem;
58
- height: 2.5rem;
59
+ width: var(--mote-avatar, 2.5rem);
60
+ height: var(--mote-avatar, 2.5rem);
59
61
  border-radius: 999px;
60
62
  object-fit: cover;
61
63
  border: var(--mote-border-width) solid var(--mote-border);
@@ -67,6 +69,8 @@
67
69
  * ------------------------------------------------------------------------- */
68
70
 
69
71
  .mote-skeleton {
72
+ display: inline-block;
73
+ min-height: 1em;
70
74
  border-radius: var(--mote-radius-sm);
71
75
  background:
72
76
  linear-gradient(
@@ -122,7 +126,7 @@
122
126
  border-block-end: var(--mote-border-width) solid var(--mote-border);
123
127
  }
124
128
 
125
- :where(body > header nav, body > nav, .mote-nav) ul {
129
+ :where(body > header nav, .mote-nav) ul {
126
130
  display: flex;
127
131
  flex-wrap: wrap;
128
132
  gap: 0.25rem;
@@ -131,7 +135,7 @@
131
135
  list-style: none;
132
136
  }
133
137
 
134
- :where(body > header nav, body > nav, .mote-nav) a {
138
+ :where(body > header nav, .mote-nav) a {
135
139
  display: inline-block;
136
140
  padding: 0.35rem 0.8rem;
137
141
  border-radius: 999px;
@@ -140,12 +144,12 @@
140
144
  transition: background var(--mote-transition), color var(--mote-transition);
141
145
  }
142
146
 
143
- :where(body > header nav, body > nav, .mote-nav) a:hover {
147
+ :where(body > header nav, .mote-nav) a:hover {
144
148
  background: var(--mote-accent-soft);
145
149
  color: var(--mote-accent-deep);
146
150
  }
147
151
 
148
- :where(body > header nav, body > nav, .mote-nav) a[aria-current="page"] {
152
+ :where(body > header nav, .mote-nav) a[aria-current="page"] {
149
153
  background: var(--mote-accent);
150
154
  color: var(--mote-on-accent);
151
155
  }
@@ -176,6 +180,43 @@ nav[aria-label="breadcrumb"] [aria-current="page"] {
176
180
  font-weight: 500;
177
181
  }
178
182
 
183
+ /* Pagination — semantic selector, no class (mirrors breadcrumbs) */
184
+ nav[aria-label="pagination"] ul {
185
+ display: flex;
186
+ gap: 0.25rem;
187
+ padding: 0;
188
+ margin: 0;
189
+ list-style: none;
190
+ }
191
+
192
+ nav[aria-label="pagination"] a {
193
+ display: inline-block;
194
+ min-width: 2.2rem;
195
+ padding: 0.35rem 0.6rem;
196
+ border-radius: var(--mote-radius-sm);
197
+ text-align: center;
198
+ text-decoration: none;
199
+ color: var(--mote-fg);
200
+ transition: background var(--mote-transition), color var(--mote-transition);
201
+ }
202
+
203
+ nav[aria-label="pagination"] a:hover {
204
+ background: var(--mote-accent-soft);
205
+ color: var(--mote-accent-deep);
206
+ }
207
+
208
+ nav[aria-label="pagination"] [aria-current="page"] {
209
+ background: var(--mote-accent);
210
+ color: var(--mote-on-accent);
211
+ }
212
+
213
+ /* Page footer: the quiet sibling of the site header */
214
+ :where(body > footer, .mote-footer) {
215
+ margin-block-start: 3rem;
216
+ padding-block: 1.5rem;
217
+ border-block-start: var(--mote-border-width) solid var(--mote-border);
218
+ }
219
+
179
220
  /* ---------------------------------------------------------------------------
180
221
  * 8. Popover surfaces — tooltips, menus, toasts
181
222
  *
@@ -267,7 +308,9 @@ menu[popover] :is(button, [role="menuitem"]):focus-visible {
267
308
  * ------------------------------------------------------------------------- */
268
309
 
269
310
  [popover],
270
- dialog[open] {
311
+ dialog {
312
+ opacity: 0;
313
+ transform: translateY(8px) scale(0.98);
271
314
  transition:
272
315
  opacity 180ms ease,
273
316
  transform 180ms ease,
@@ -328,7 +371,11 @@ details[name][open] summary {
328
371
 
329
372
  details[name] > :not(summary) {
330
373
  margin-block: 0;
331
- padding: 0.15rem 1.2rem 0.9rem;
374
+ padding: 0.15rem 1.2rem 0;
375
+ }
376
+
377
+ details[name] > :not(summary):last-child {
378
+ padding-block-end: 0.9rem;
332
379
  }
333
380
 
334
381
  /* ---------------------------------------------------------------------------
@@ -397,31 +444,71 @@ details[name] > :not(summary) {
397
444
  grid-template-columns: 1fr;
398
445
  }
399
446
 
400
- /* On <body>, the middle row stretches the page so the footer pins to
401
- the viewport bottom even when content is short. :where() keeps this
447
+ /* On <body>, the middle row stretches the page so the footer pins to the
448
+ viewport bottom even when content is short. :where() keeps this
402
449
  at base specificity; wrappers (demo boxes, embeds) stay natural-size. */
403
450
  :where(body).mote-doc {
404
451
  min-height: 100svh;
452
+ padding-inline: 1.5rem;
453
+ }
454
+
455
+ /* Anchored content must clear the sticky shell header. Applies only
456
+ when the header actually exists. */
457
+ html:where(:has(body > header)) {
458
+ scroll-padding-block-start: 5rem;
405
459
  }
406
460
 
407
461
  .mote-doc > header { grid-row: 1; grid-column: 1 / -1; }
408
462
  .mote-doc > footer { grid-row: 3; grid-column: 1 / -1; }
409
463
 
410
- .mote-doc > main { grid-row: 2; grid-column: 1; }
464
+ .mote-doc > main { grid-row: 2; grid-column: 1; min-width: 0; }
411
465
 
412
466
  /* nav = left rail */
413
467
  .mote-doc > nav {
414
468
  grid-row: 2;
415
469
  grid-column: 1;
470
+ min-width: 0;
416
471
  align-self: start;
417
472
  position: sticky;
418
- top: 1rem;
473
+ top: var(--mote-rail-top, 1rem);
419
474
  max-height: calc(100svh - 2rem);
420
475
  overflow: auto;
421
476
  }
422
477
 
478
+ /* Rail links: a quiet vertical list, not the horizontal pill row.
479
+ --mote-rail-top should clear the shell header: 1rem by default;
480
+ set it on :root when a sticky body > header is taller, e.g.
481
+ --mote-rail-top: 4.75rem. */
482
+ .mote-doc > nav ul {
483
+ display: flex;
484
+ flex-direction: column;
485
+ gap: 0.15rem;
486
+ padding: 0;
487
+ margin: 0;
488
+ list-style: none;
489
+ }
490
+
491
+ .mote-doc > nav a {
492
+ display: block;
493
+ padding: 0.35rem 0.75rem;
494
+ border-radius: var(--mote-radius-sm);
495
+ text-decoration: none;
496
+ color: var(--mote-fg);
497
+ transition: background var(--mote-transition), color var(--mote-transition);
498
+ }
499
+
500
+ .mote-doc > nav a:hover {
501
+ background: var(--mote-accent-soft);
502
+ color: var(--mote-accent-deep);
503
+ }
504
+
505
+ .mote-doc > nav a[aria-current="page"] {
506
+ background: var(--mote-accent);
507
+ color: var(--mote-on-accent);
508
+ }
509
+
423
510
  .mote-doc:where(:has(> nav)) {
424
- grid-template-columns: minmax(12rem, 16rem) 1fr;
511
+ grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr);
425
512
  }
426
513
 
427
514
  .mote-doc:where(:has(> nav)) > main {
@@ -432,6 +519,7 @@ details[name] > :not(summary) {
432
519
  .mote-doc > aside {
433
520
  grid-row: 2;
434
521
  grid-column: 2;
522
+ min-width: 0;
435
523
  align-self: start;
436
524
  }
437
525
 
@@ -440,11 +528,11 @@ details[name] > :not(summary) {
440
528
  }
441
529
 
442
530
  .mote-doc:where(:has(> aside)) {
443
- grid-template-columns: 1fr minmax(10rem, 14rem);
531
+ grid-template-columns: minmax(0, 1fr) minmax(10rem, 14rem);
444
532
  }
445
533
 
446
534
  .mote-doc:where(:has(> nav)):where(:has(> aside)) {
447
- grid-template-columns: minmax(12rem, 16rem) 1fr minmax(10rem, 14rem);
535
+ grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr) minmax(10rem, 14rem);
448
536
  }
449
537
 
450
538
  @media (width < 50rem) {
@@ -456,6 +544,7 @@ details[name] > :not(summary) {
456
544
 
457
545
  .mote-doc > :is(nav, aside, main) {
458
546
  grid-column: 1 / -1;
547
+ grid-row: auto;
459
548
  }
460
549
 
461
550
  .mote-doc > nav {
@@ -470,5 +559,7 @@ details[name] > :not(summary) {
470
559
  .mote-grid {
471
560
  display: grid;
472
561
  gap: 1.25rem;
473
- grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
562
+ /* Density is a variable, not a class API: --mote-grid-min: 22rem
563
+ gives 3-wide pricing; 26rem gives 2-column content. */
564
+ grid-template-columns: repeat(auto-fill, minmax(min(var(--mote-grid-min, 16rem), 100%), 1fr));
474
565
  }
package/mote.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.css — a mote of styling for plain HTML.
3
- * v0.3.0 · MIT
3
+ * v0.5.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:
@@ -44,6 +44,8 @@
44
44
  "Liberation Mono", monospace;
45
45
  --mote-line-height: 1.65;
46
46
  --mote-tracking: -0.011em;
47
+ /* Display scale — opt-in, for heroes and error pages; body scale untouched */
48
+ --mote-display: clamp(2.75rem, 1.5rem + 4vw, 5rem);
47
49
 
48
50
  /* Shape & depth */
49
51
  --mote-radius: 10px;
@@ -84,6 +86,10 @@
84
86
  --mote-error: #d98a8a;
85
87
  --mote-info: #96b1d1;
86
88
 
89
+ /* "Deep" is the emphasis direction per theme: darker on light
90
+ surfaces, brighter on dark ones. Never darken in dark mode. */
91
+ --mote-accent-deep: color-mix(in oklab, var(--mote-accent), white 14%);
92
+
87
93
  --mote-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 4px 16px rgba(0, 0, 0, 0.3);
88
94
  --mote-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
89
95
  --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 +111,8 @@
105
111
  --mote-error: #d98a8a;
106
112
  --mote-info: #96b1d1;
107
113
 
114
+ --mote-accent-deep: color-mix(in oklab, var(--mote-accent), white 14%);
115
+
108
116
  --mote-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 4px 16px rgba(0, 0, 0, 0.3);
109
117
  --mote-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
110
118
  --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 +253,10 @@ blockquote {
245
253
  font-style: italic;
246
254
  }
247
255
 
256
+ blockquote :where(code, samp, kbd, var) {
257
+ font-style: normal;
258
+ }
259
+
248
260
  blockquote > cite {
249
261
  display: block;
250
262
  margin-block-start: 0.5rem;
@@ -403,7 +415,7 @@ input, select, textarea {
403
415
  background: var(--mote-surface);
404
416
  border: var(--mote-border-width) solid var(--mote-border);
405
417
  border-radius: var(--mote-radius-sm);
406
- transition: border-color var(--mote-transition), box-shadow var(--mote-transition);
418
+ transition: border-color var(--mote-transition);
407
419
  }
408
420
 
409
421
  input:hover, select:hover, textarea:hover {
@@ -428,6 +440,13 @@ select {
428
440
  background-position: right 0.8rem center;
429
441
  }
430
442
 
443
+ /* Multi-selects are listboxes, not dropdowns: restore native chrome */
444
+ select[multiple] {
445
+ appearance: auto;
446
+ padding: 0.5rem;
447
+ background-image: none;
448
+ }
449
+
431
450
  [type="checkbox"], [type="radio"] {
432
451
  width: 1em;
433
452
  height: 1em;
@@ -447,6 +466,14 @@ select {
447
466
  border-style: dashed;
448
467
  }
449
468
 
469
+ /* Range inputs are sliders, not text fields: strip text chrome */
470
+ [type="range"] {
471
+ padding: 0;
472
+ border: none;
473
+ background: none;
474
+ accent-color: var(--mote-status, var(--mote-accent));
475
+ }
476
+
450
477
  ::placeholder {
451
478
  color: var(--mote-muted);
452
479
  opacity: 0.8;
@@ -457,7 +484,7 @@ select {
457
484
  cursor: not-allowed;
458
485
  }
459
486
 
460
- :user-invalid {
487
+ :is(input, select, textarea):user-invalid {
461
488
  border-color: var(--mote-error);
462
489
  }
463
490
 
@@ -471,14 +498,55 @@ button, [type="button"], [type="submit"], [type="reset"] {
471
498
  border-radius: var(--mote-radius-sm);
472
499
  cursor: pointer;
473
500
  transition: background var(--mote-transition), border-color var(--mote-transition),
474
- box-shadow var(--mote-transition), transform var(--mote-transition);
501
+ transform var(--mote-transition);
475
502
  }
476
503
 
477
504
  button:hover, [type="button"]:hover, [type="submit"]:hover, [type="reset"]:hover {
478
- background: var(--mote-accent-deep);
505
+ /* Theme-aware deepening: fg is dark in light themes, light in dark ones. */
506
+ background: color-mix(in oklab, var(--mote-accent), var(--mote-fg) 14%);
479
507
  box-shadow: var(--mote-shadow-sm);
480
508
  }
481
509
 
510
+ /* Semantic solids are gated: the status class must sit on the button
511
+ itself, so a themed section (body.mote-error, a note, a toast) never
512
+ recolors the actions inside it. The token read below is therefore
513
+ the button's own, never an inherited one. */
514
+ :is(button, [type="button"], [type="submit"], [type="reset"]):where(.mote-success, .mote-warning, .mote-error, .mote-info, .mote-muted):where(:not(.mote-ghost)) {
515
+ background: var(--mote-status);
516
+ color: var(--mote-on-status, var(--mote-bg));
517
+ }
518
+
519
+ :is(button, [type="button"], [type="submit"], [type="reset"]):where(.mote-success, .mote-warning, .mote-error, .mote-info, .mote-muted):where(:not(.mote-ghost)):hover {
520
+ background: color-mix(in oklab, var(--mote-status), var(--mote-fg) 14%);
521
+ }
522
+
523
+ /* Ghost buttons: quiet, transparent fill, accent-tinted. Like solids,
524
+ status colors come from the button's own class, never from context. */
525
+ .mote-ghost {
526
+ background: none;
527
+ border: var(--mote-border-width) solid color-mix(in oklab, var(--mote-accent), transparent 55%);
528
+ color: var(--mote-accent-deep);
529
+ }
530
+
531
+ .mote-ghost:hover {
532
+ background: var(--mote-accent-soft);
533
+ color: var(--mote-accent-deep);
534
+ box-shadow: none;
535
+ }
536
+
537
+ /* Gated ghost+status: the status class on the element itself tints the
538
+ border and text (same gating as the solid buttons). Works on buttons
539
+ and anchors alike — .mote-ghost is element-agnostic. */
540
+ .mote-ghost:where(.mote-success, .mote-warning, .mote-error, .mote-info, .mote-muted) {
541
+ border-color: color-mix(in oklab, var(--mote-status), transparent 55%);
542
+ color: var(--mote-status);
543
+ }
544
+
545
+ .mote-ghost:where(.mote-success, .mote-warning, .mote-error, .mote-info, .mote-muted):hover {
546
+ background: var(--mote-status-soft);
547
+ color: var(--mote-status);
548
+ }
549
+
482
550
  button:active, [type="button"]:active, [type="submit"]:active, [type="reset"]:active {
483
551
  transform: translateY(1px);
484
552
  }
@@ -537,6 +605,7 @@ dialog {
537
605
  background: var(--mote-bg);
538
606
  color: var(--mote-fg);
539
607
  box-shadow: var(--mote-shadow);
608
+ max-width: min(90vw, 44rem);
540
609
  }
541
610
 
542
611
  dialog::backdrop {
@@ -550,8 +619,8 @@ progress, meter {
550
619
  }
551
620
 
552
621
  /* Semantic feedback helpers (pure HTML, works with <p>, <div>, anything) */
553
- [role="alert"] { color: var(--mote-error); }
554
- [role="status"] { color: var(--mote-info); }
622
+ [role="alert"] { color: var(--mote-status, var(--mote-error)); }
623
+ [role="status"] { color: var(--mote-status, var(--mote-info)); }
555
624
 
556
625
  /* ---------------------------------------------------------------------------
557
626
  * 11. Opt-in helpers
@@ -568,8 +637,49 @@ progress, meter {
568
637
  /* Dimmed text: <small class="mote-muted"> */
569
638
  .mote-muted { color: var(--mote-muted); }
570
639
 
640
+ /* Row of things: headers, footers, toolbars, chip lists.
641
+ .mote-push on a child right-aligns it (and everything after). */
642
+ .mote-row {
643
+ display: flex;
644
+ align-items: center;
645
+ gap: 1rem;
646
+ flex-wrap: wrap;
647
+ }
648
+
649
+ .mote-push { margin-inline-start: auto; }
650
+
651
+ /* Visually hidden, screen-reader available */
652
+ .mote-sr-only {
653
+ position: absolute;
654
+ width: 1px;
655
+ height: 1px;
656
+ padding: 0;
657
+ margin: -1px;
658
+ overflow: hidden;
659
+ clip-path: inset(50%);
660
+ white-space: nowrap;
661
+ border: 0;
662
+ }
663
+
664
+ /* Skip link: sr-only until focused, then a fixed pill */
665
+ .mote-sr-only:focus-visible {
666
+ position: fixed;
667
+ top: 1rem;
668
+ left: 1rem;
669
+ width: auto;
670
+ height: auto;
671
+ margin: 0;
672
+ padding: 0.5rem 1rem;
673
+ clip-path: none;
674
+ z-index: 20;
675
+ background: var(--mote-accent);
676
+ color: var(--mote-on-accent);
677
+ border-radius: var(--mote-radius-sm);
678
+ text-decoration: none;
679
+ }
680
+
571
681
  /* ---------------------------------------------------------------------------
572
- * 11. Status classes
682
+ * 12. Status classes
573
683
  *
574
684
  * Statuses are pure token assignments — they set variables, never visual
575
685
  * properties. Base classes (notes, badges, toasts) consume the tokens and
@@ -582,10 +692,10 @@ progress, meter {
582
692
  * inline: style="--mote-status: rebeccapurple".
583
693
  * ------------------------------------------------------------------------- */
584
694
 
585
- .mote-success { --mote-status: var(--mote-success); --mote-status-soft: var(--mote-success-soft); }
586
- .mote-warning { --mote-status: var(--mote-warning); --mote-status-soft: var(--mote-warning-soft); }
587
- .mote-error { --mote-status: var(--mote-error); --mote-status-soft: var(--mote-error-soft); }
588
- .mote-info { --mote-status: var(--mote-info); --mote-status-soft: var(--mote-info-soft); }
695
+ .mote-success { --mote-status: var(--mote-success); --mote-status-soft: var(--mote-success-soft); --mote-on-status: var(--mote-bg); }
696
+ .mote-warning { --mote-status: var(--mote-warning); --mote-status-soft: var(--mote-warning-soft); --mote-on-status: var(--mote-bg); }
697
+ .mote-error { --mote-status: var(--mote-error); --mote-status-soft: var(--mote-error-soft); --mote-on-status: var(--mote-bg); }
698
+ .mote-info { --mote-status: var(--mote-info); --mote-status-soft: var(--mote-info-soft); --mote-on-status: var(--mote-bg); }
589
699
 
590
700
  /* .mote-muted does double duty: dimmed text where nothing consumes the
591
701
  tokens, and a neutral status where something does. */
@@ -593,18 +703,47 @@ progress, meter {
593
703
  color: var(--mote-muted);
594
704
  --mote-status: var(--mote-muted);
595
705
  --mote-status-soft: color-mix(in oklab, var(--mote-fg) 7%, transparent);
706
+ --mote-on-status: var(--mote-bg);
707
+ }
708
+
709
+ /* .mote-default: the accent as an explicit status — "keep this (and its
710
+ descendants) plum" inside a themed section. Unlike the semantics, this
711
+ IS meant to be applied contextually. */
712
+ .mote-default {
713
+ --mote-status: var(--mote-accent);
714
+ --mote-status-soft: var(--mote-accent-soft);
715
+ }
716
+
717
+ /* .mote-reset: clear an inherited status for a subtree — consumers inside
718
+ fall back to their own defaults; their own classes re-apply locally.
719
+ Where .mote-default forces the accent down the tree, .mote-reset
720
+ removes the theme and lets each element speak for itself. */
721
+ .mote-reset {
722
+ --mote-status: initial;
723
+ --mote-status-soft: initial;
724
+ --mote-on-status: initial;
596
725
  }
597
726
 
598
727
  /* Themed message surfaces — base shape only; status via the token classes */
599
728
  .mote-note {
729
+ margin-block: 1rem;
600
730
  padding: 0.8rem 1.1rem;
601
731
  border-radius: var(--mote-radius-sm);
602
732
  color: var(--mote-status, var(--mote-accent-deep));
603
733
  background: var(--mote-status-soft, var(--mote-accent-soft));
604
734
  }
605
735
 
736
+ /* Display text: opt-in hero / error-code scale. Widen per use with
737
+ style="--mote-display: clamp(6rem, 24vw, 12rem)". */
738
+ .mote-display {
739
+ font-size: var(--mote-display);
740
+ line-height: 1.1;
741
+ letter-spacing: var(--mote-tracking);
742
+ text-wrap: balance;
743
+ }
744
+
606
745
  /* ---------------------------------------------------------------------------
607
- * 12. Motion preferences & print
746
+ * 13. Motion preferences & print
608
747
  * ------------------------------------------------------------------------- */
609
748
 
610
749
  @media (prefers-reduced-motion: reduce) {
@@ -617,6 +756,26 @@ progress, meter {
617
756
  }
618
757
 
619
758
  @media print {
759
+ /* Force the light palette on paper — dark fills (zebra, notes,
760
+ open details) must not print as near-black bands. Placed last:
761
+ source order ties the dark token blocks' specificity. */
762
+ :root,
763
+ :root[data-theme="dark"],
764
+ :root:not([data-theme="light"]) {
765
+ color-scheme: light;
766
+ --mote-bg: #faf9f7;
767
+ --mote-surface: #f1eee7;
768
+ --mote-fg: #2b2924;
769
+ --mote-muted: #716a60;
770
+ --mote-border: #e6e0d4;
771
+ --mote-accent: #6f5486;
772
+ --mote-accent-deep: color-mix(in oklab, var(--mote-accent), black 14%);
773
+ --mote-success: #4e7d51;
774
+ --mote-warning: #8a6819;
775
+ --mote-error: #b34d4d;
776
+ --mote-info: #527097;
777
+ }
778
+
620
779
  body {
621
780
  background: #fff;
622
781
  color: #000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mote.css",
3
- "version": "0.3.0",
3
+ "version": "0.5.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": [