mote.css 0.3.0 → 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,57 @@ 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
+
8
59
  ## [0.3.0] - 2026-10-19
9
60
 
10
61
  ### Added
@@ -114,6 +165,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
114
165
  - Element gallery demo page (`demo.html`).
115
166
 
116
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
117
169
  [0.2.1]: https://gitlab.com/abmach/mote/compare/v0.2.0...v0.2.1
118
170
  [0.2.0]: https://gitlab.com/abmach/mote/compare/v0.1.0...v0.2.0
119
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
 
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * mote.components.css — opt-in components for mote.css.
3
- * v0.3.0 · 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(
@@ -122,7 +124,7 @@
122
124
  border-block-end: var(--mote-border-width) solid var(--mote-border);
123
125
  }
124
126
 
125
- :where(body > header nav, body > nav, .mote-nav) ul {
127
+ :where(body > header nav, .mote-nav) ul {
126
128
  display: flex;
127
129
  flex-wrap: wrap;
128
130
  gap: 0.25rem;
@@ -131,7 +133,7 @@
131
133
  list-style: none;
132
134
  }
133
135
 
134
- :where(body > header nav, body > nav, .mote-nav) a {
136
+ :where(body > header nav, .mote-nav) a {
135
137
  display: inline-block;
136
138
  padding: 0.35rem 0.8rem;
137
139
  border-radius: 999px;
@@ -140,12 +142,12 @@
140
142
  transition: background var(--mote-transition), color var(--mote-transition);
141
143
  }
142
144
 
143
- :where(body > header nav, body > nav, .mote-nav) a:hover {
145
+ :where(body > header nav, .mote-nav) a:hover {
144
146
  background: var(--mote-accent-soft);
145
147
  color: var(--mote-accent-deep);
146
148
  }
147
149
 
148
- :where(body > header nav, body > nav, .mote-nav) a[aria-current="page"] {
150
+ :where(body > header nav, .mote-nav) a[aria-current="page"] {
149
151
  background: var(--mote-accent);
150
152
  color: var(--mote-on-accent);
151
153
  }
@@ -267,7 +269,9 @@ menu[popover] :is(button, [role="menuitem"]):focus-visible {
267
269
  * ------------------------------------------------------------------------- */
268
270
 
269
271
  [popover],
270
- dialog[open] {
272
+ dialog {
273
+ opacity: 0;
274
+ transform: translateY(8px) scale(0.98);
271
275
  transition:
272
276
  opacity 180ms ease,
273
277
  transform 180ms ease,
@@ -328,7 +332,11 @@ details[name][open] summary {
328
332
 
329
333
  details[name] > :not(summary) {
330
334
  margin-block: 0;
331
- 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;
332
340
  }
333
341
 
334
342
  /* ---------------------------------------------------------------------------
@@ -397,31 +405,71 @@ details[name] > :not(summary) {
397
405
  grid-template-columns: 1fr;
398
406
  }
399
407
 
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
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
402
410
  at base specificity; wrappers (demo boxes, embeds) stay natural-size. */
403
411
  :where(body).mote-doc {
404
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;
405
420
  }
406
421
 
407
422
  .mote-doc > header { grid-row: 1; grid-column: 1 / -1; }
408
423
  .mote-doc > footer { grid-row: 3; grid-column: 1 / -1; }
409
424
 
410
- .mote-doc > main { grid-row: 2; grid-column: 1; }
425
+ .mote-doc > main { grid-row: 2; grid-column: 1; min-width: 0; }
411
426
 
412
427
  /* nav = left rail */
413
428
  .mote-doc > nav {
414
429
  grid-row: 2;
415
430
  grid-column: 1;
431
+ min-width: 0;
416
432
  align-self: start;
417
433
  position: sticky;
418
- top: 1rem;
434
+ top: var(--mote-rail-top, 1rem);
419
435
  max-height: calc(100svh - 2rem);
420
436
  overflow: auto;
421
437
  }
422
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
+
423
471
  .mote-doc:where(:has(> nav)) {
424
- grid-template-columns: minmax(12rem, 16rem) 1fr;
472
+ grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr);
425
473
  }
426
474
 
427
475
  .mote-doc:where(:has(> nav)) > main {
@@ -432,6 +480,7 @@ details[name] > :not(summary) {
432
480
  .mote-doc > aside {
433
481
  grid-row: 2;
434
482
  grid-column: 2;
483
+ min-width: 0;
435
484
  align-self: start;
436
485
  }
437
486
 
@@ -440,11 +489,11 @@ details[name] > :not(summary) {
440
489
  }
441
490
 
442
491
  .mote-doc:where(:has(> aside)) {
443
- grid-template-columns: 1fr minmax(10rem, 14rem);
492
+ grid-template-columns: minmax(0, 1fr) minmax(10rem, 14rem);
444
493
  }
445
494
 
446
495
  .mote-doc:where(:has(> nav)):where(:has(> aside)) {
447
- grid-template-columns: minmax(12rem, 16rem) 1fr minmax(10rem, 14rem);
496
+ grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr) minmax(10rem, 14rem);
448
497
  }
449
498
 
450
499
  @media (width < 50rem) {
@@ -456,6 +505,7 @@ details[name] > :not(summary) {
456
505
 
457
506
  .mote-doc > :is(nav, aside, main) {
458
507
  grid-column: 1 / -1;
508
+ grid-row: auto;
459
509
  }
460
510
 
461
511
  .mote-doc > nav {
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.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.3.0",
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": [