@usableapp/cardds 0.7.95 → 0.7.97

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/bin/cardds.mjs CHANGED
@@ -78,13 +78,13 @@ In the app's entry, once:
78
78
  import './theme.css'; // after cardds.css
79
79
 
80
80
  Start a screen from a template (Walkthrough · Home · CallSheet · Splash) or compose the components —
81
- import them, never copy their markup: the behaviour lives in the component. Gallery: see README.`);
81
+ import them, never copy their markup: the behaviour lives in the component. Gallery: see docs/reference.md.`);
82
82
  return 0;
83
83
  }
84
84
 
85
85
  // the exports beside the components that are not components (docs/roles.md rows `a function, not a component`…): shape `—` and nothing to tell them from a structural part, so they are named.
86
86
  // A new one joins here and in the list test of tests/catalog.spec.js, or it is listed as structure
87
- const HELPERS = new Set(['applyMask', 'slideTo', 'slideScreen', 'installScreenSlide', 'useLayer', 'useLayers', 'useOptionalLayer', 'registerIcons', 'LayerHistory', 'setWords', 'word', 'setTextScale', 'controlsFor']);
87
+ const HELPERS = new Set(['applyMask', 'cardGrounds', 'inkGrounds', 'slideTo', 'slideScreen', 'installScreenSlide', 'useLayer', 'useLayers', 'useOptionalLayer', 'registerIcons', 'LayerHistory', 'setWords', 'word', 'setTextScale', 'controlsFor']);
88
88
  const one = (text) => text.replace(/\s+/g, ' ').trim();
89
89
  const indent = (text, by = ' ') => text.replace(/^(?=.)/gm, by);
90
90
 
package/css/actions.css CHANGED
@@ -178,7 +178,8 @@
178
178
  toggles, tiles and the dropdown already use. */
179
179
  border: var(--border-w) solid var(--chip-border, var(--outline));
180
180
  background: var(--chip-bg, var(--action-bg));
181
- color: var(--chip-ink, var(--action-ink));
181
+ --dark: var(--action-dark); /* a chip is a surface of the paper's side: a signal dot on it reads onlight, on any card */
182
+ color: var(--chip-ink, var(--card-ink));
182
183
  padding: var(--chip-pad-y, var(--sp-2)) var(--chip-pad-x, var(--sp-3));
183
184
  font: var(--type-label);
184
185
  font-size: var(--chip-font-size, var(--fs-sm));
@@ -197,10 +198,9 @@
197
198
  .dot--info { background: var(--signal-info); }
198
199
  /* --sm: a small LABEL (a name on a note, a tag in a tight head) — low and tight; never a tap target */
199
200
  .chip--sm { min-height: 0; gap: var(--chip-sm-gap, var(--sp-1)); padding: var(--chip-sm-pad-y, var(--sp-0)) var(--chip-sm-pad-x, var(--sp-2)); }
200
- .chip--on { background: var(--chip-on-bg, var(--accent)); color: var(--chip-on-ink, var(--on-accent)); border-color: transparent; }
201
- /* muted: an untouched default — the words wash out, the edge stays; emphasis is the difference from the neighbours (README 13) */
202
- .chip--muted { color: var(--chip-muted-ink, var(--card-muted)); }
203
- /* dark-slot inversion lives in card.css (.card--3 :is(...)) */
201
+ .chip--on { background: var(--chip-on-bg, var(--accent)); --dark: var(--accent-dark); color: var(--chip-on-ink, var(--card-ink)); border-color: transparent; }
202
+ /* muted: an untouched default — the words wash out, the edge stays; emphasis is the difference from the neighbours (docs/reference.md 13) */
203
+ .chip--muted { color: var(--chip-muted-ink, var(--card-muted)); } /* the chip's own fill's muted words: a muted chip on the dark card is still a light chip */
204
204
 
205
205
  .chip-row { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
206
206
  .chip-row.bleed { flex-wrap: nowrap; } /* actions.css loads after card.css — the strip stays one line */
@@ -229,8 +229,8 @@
229
229
  font-size: var(--fs-body);
230
230
  text-align: left;
231
231
  }
232
- /* muted: the value is still the default (README 13) */
233
- .dropdown--muted .dropdown__btn { color: var(--dropdown-muted-ink, var(--card-muted)); }
232
+ /* muted: the value is still the default (docs/reference.md 13) */
233
+ .dropdown--muted .dropdown__btn { color: var(--dropdown-muted-ink, var(--action-muted)); }
234
234
  .dropdown__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
235
235
  .dropdown__btn > .icon { transition: rotate var(--motion-quick) ease; }
236
236
  .dropdown:has(.menu:popover-open) .dropdown__btn > .icon { rotate: 180deg; }
@@ -242,7 +242,7 @@
242
242
  /* ---- segment (day picker / tab strip) — items share width, shrink on small screens ---- */
243
243
  .segment { display: flex; gap: var(--sp-1); }
244
244
  .segment__item {
245
- opacity: var(--rest);
245
+ color: var(--segment-rest-ink, var(--card-muted)); /* unpicked: the ground's muted words */
246
246
  border-radius: var(--r-chip);
247
247
  padding: var(--sp-2) var(--sp-1);
248
248
  font: var(--type-label);
@@ -254,7 +254,6 @@
254
254
  .segment__item[aria-selected="true"] {
255
255
  background: var(--accent);
256
256
  color: var(--on-accent);
257
- opacity: 1;
258
257
  }
259
258
 
260
259
  /* ---- bottom CTA pack: the screen's bottom (Screen `bottom`) ----
@@ -325,10 +324,9 @@
325
324
  width: var(--dot);
326
325
  height: var(--dot);
327
326
  border-radius: 50%;
328
- background: currentColor;
329
- opacity: var(--faint);
327
+ background: var(--card-muted); /* a glyph: the dots at rest are the ground's muted ink (an opacity of 0.3 was 1.5:1), the one where you are its full ink */
330
328
  }
331
- .pager__at > .is-at { background: var(--accent); opacity: 1; }
329
+ .pager__at > .is-at { background: var(--card-ink); }
332
330
  /* ---- tiers: a control row above the buttons ----
333
331
  A screen whose one move comes with a control — the introduction's text-size
334
332
  slider above its pager. Each tier is a row; the bar stacks them, and any
@@ -378,11 +376,10 @@
378
376
  gap: var(--action-note-gap, var(--sp-1));
379
377
  padding-inline: var(--sp-2);
380
378
  font: var(--action-note-font, var(--type-body));
381
- color: var(--action-note-ink, var(--on-surface-muted));
379
+ color: var(--action-note-ink, var(--card-muted));
382
380
  text-align: center;
383
381
  cursor: default;
384
382
  }
385
- .card .action-note { color: var(--action-note-ink, var(--card-muted)); }
386
383
  .action-note__words { display: block; } /* ONE run of words: an icon inside sits in the line, the spaces around it kept */
387
384
  .action-note__words > .icon { display: inline-block; vertical-align: -0.2em; width: 1.25em; height: 1.25em; margin-inline: 0.15em; color: var(--action-note-icon-ink, inherit); } /* a project may colour the icon apart from the words — e.g. the system blue of the control it points at (Lh 2026-09-24) */
388
385
 
package/css/base.css CHANGED
@@ -27,7 +27,7 @@ html { font-size: 100%; }
27
27
  body {
28
28
  margin: 0;
29
29
  background: var(--surface);
30
- color: var(--on-surface);
30
+ color: var(--card-ink);
31
31
  font: var(--type-body);
32
32
  -webkit-font-smoothing: antialiased;
33
33
  }
@@ -103,7 +103,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
103
103
  .screen > * { min-width: 0; } /* content never widens the column past the viewport */
104
104
  /* a screen that carries its own palette (data-palette on the element, not on <html>)
105
105
  paints its own surface, so a design tool can show one screen per palette */
106
- .screen[data-palette] { background: var(--surface); color: var(--on-surface); }
106
+ .screen[data-palette] { background: var(--surface); color: var(--card-ink); }
107
107
 
108
108
  /* app screen: exactly one phone height — a height, not a minimum, so that a
109
109
  .step (step.css) has a bound to fill and its card and sheet scroll INSIDE
@@ -140,9 +140,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
140
140
  .t-stat--mono { font-family: var(--font-mono); letter-spacing: 0; } /* clocks, ETAs, codes */
141
141
  .t-stat--xl { font-size: var(--fs-hero); } /* the one number on a sheet */
142
142
 
143
- .t-muted { color: var(--on-surface-muted); }
144
- .card .t-muted { color: var(--card-muted); } /* muted follows the card's ink, not the surface's */
145
- /* .card--3 .t-muted (dim instead of --card-muted on the dark slot) is declared once in card.css, with the rest of that family */
143
+ .t-muted { color: var(--card-muted); } /* the muted words of whatever it stands on: the screen, a card of any tone, a drawer */
146
144
  .t-caps { text-transform: uppercase; }
147
145
  /* ---- mark: a highlighter stroke over a word INSIDE running text (a sender's name in a greeting) ----
148
146
  <p class="t-body"><mark class="mark">ธนาคารเวลา</mark> ส่งแสตมป์ดวงแรกให้คุณ</p>
@@ -211,4 +209,3 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
211
209
  .empty-state__action { margin-top: var(--sp-2); }
212
210
  /* inside a card (a card's own "nothing yet"): the card's ink */
213
211
  .card .empty-state { color: var(--card-ink); padding-block: var(--sp-6); }
214
- .card--3 .empty-state { color: inherit; }
package/css/card.css CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  .card {
8
8
  --card-bg: var(--card-1-bg); /* the slot, readable by children (avatar rings, fold) */
9
- --card-ink: var(--card-1-ink);
9
+ --dark: var(--card-1-dark); /* its side: the inks follow (tokens.css) */
10
10
  --card-pad: var(--card-pad-rest); /* published so .bleed can cancel it exactly */
11
11
  --card-gap: var(--card-gap-rest); /* published so a sheet's peek strip (sheet.css) is exact */
12
12
  background: var(--card-bg);
@@ -34,19 +34,13 @@
34
34
  .card--away { translate: 0 var(--_y); }
35
35
  @media (prefers-reduced-motion: reduce) { :is(.card--leaver, .post-desk__card) { transition: none; } }
36
36
 
37
- .card--2 { --card-bg: var(--card-2-bg); --card-ink: var(--card-2-ink); --card-muted: var(--card-2-muted); }
38
- /* dark/accent slot: accent-toned controls would vanish — the slot re-points
39
- the accent tokens, so every accent consumer (btn--primary, chip--on,
40
- check, toggle, ring, day…) inverts without per-component rules */
41
- .card--3 { --card-bg: var(--card-3-bg); --card-ink: var(--card-3-ink); --accent: var(--card-3-ink); --on-accent: var(--card-3-bg); }
42
- .card--4 { --card-bg: var(--card-4-bg); --card-ink: var(--card-4-ink); --card-muted: var(--card-4-muted); }
43
- .card--5 { --card-bg: var(--card-5-bg); --card-ink: var(--card-5-ink); --card-muted: var(--card-5-muted); }
44
- /* the dark slot's captions/hints/footnotes: --card-muted is tuned for a light card, so these dim the full ink
45
- instead (one rule, matching specificity to what it replaces — .card--3 <space> <one class>) — merged from
46
- base.css/choice.css/media.css/numbers.css/journey.css/people.css (2026-09-27 review). lists.css's .legend
47
- differs (opacity 0.8, not --dim) and stays there. */
48
- .card--3 :is(.t-muted, .calendar__wd, .composer__hint, .quote__by, .ring__label, .band__foot, .picker__unit,
49
- .route__place, .note-row, .avatar-pick__role) { color: inherit; opacity: var(--dim); }
37
+ /* each tone is a fill and a side; its words and glyphs follow from the side (tokens.css) */
38
+ .card--2 { --card-bg: var(--card-2-bg); --dark: var(--card-2-dark); }
39
+ /* the brand slot: accent-toned controls would vanish on it — the slot re-points the accent to its own ink, and that fill's side is
40
+ the other one, so every accent consumer (btn--primary, chip--on, check, toggle, ring, day…) inverts without per-component rules */
41
+ .card--3 { --card-bg: var(--card-3-bg); --dark: var(--card-3-dark); --accent: var(--card-ink); --accent-dark: calc(1 - var(--dark)); }
42
+ .card--4 { --card-bg: var(--card-4-bg); --dark: var(--card-4-dark); }
43
+ .card--5 { --card-bg: var(--card-5-bg); --dark: var(--card-5-dark); }
50
44
 
51
45
  /* header row: title left, floating action right — centered, since the
52
46
  trailing control (chip/badge/icon-btn/link) is almost always taller than
@@ -317,7 +311,6 @@
317
311
  display: grid;
318
312
  place-items: center;
319
313
  font: var(--card-thumb-font, var(--type-label));
320
- opacity: 0.85;
321
314
  }
322
315
 
323
316
  /* ---- card feed: a queue of cards, all in sight — one big, the rest small (Lh 2026-10-09, the whole queue 2026-10-15) ----
@@ -364,9 +357,10 @@
364
357
  padding: var(--delta-pad-y, var(--sp-1)) var(--delta-pad-x, var(--sp-2));
365
358
  border-radius: var(--delta-radius, var(--r-chip));
366
359
  background: var(--delta-bg, var(--card-3-bg));
367
- color: var(--delta-ink, var(--card-3-ink));
360
+ --dark: var(--card-3-dark);
361
+ color: var(--delta-ink, var(--card-ink));
368
362
  }
369
- .delta--neg { background: var(--delta-neg-bg, var(--neg)); color: var(--delta-neg-ink, var(--card-3-ink)); } /* light ink on the signal color in every slot */
363
+ .delta--neg { background: var(--delta-neg-bg, var(--neg)); --dark: var(--neg-dark); color: var(--delta-neg-ink, var(--card-ink)); }
370
364
 
371
365
  /* ---- panes: a few panes side by side inside a card or a sheet, one in sight (Lh 2026-10-02) ----
372
366
  <div class="panes" role="group"><div class="panes__rail"><div class="panes__pane">…</div>…</div><span class="pager__at">…</span></div>
package/css/choice.css CHANGED
@@ -49,6 +49,8 @@
49
49
  border-radius: var(--toggle-radius, var(--r-chip));
50
50
  border: var(--border-w) solid var(--toggle-border, var(--outline));
51
51
  background: var(--toggle-bg, var(--action-bg));
52
+ --dark: var(--action-dark); /* the paper's side: its knob at rest is the paper's muted ink, on the accent the accent's */
53
+ color: var(--card-ink);
52
54
  position: relative;
53
55
  transition: background-color var(--motion-press) ease-out, border-color var(--motion-press) ease-out;
54
56
  }
@@ -60,12 +62,11 @@
60
62
  width: calc(var(--_h) - 2 * var(--_pad) - 2 * var(--border-w));
61
63
  height: calc(var(--_h) - 2 * var(--_pad) - 2 * var(--border-w));
62
64
  border-radius: 50%;
63
- background: currentColor;
64
- opacity: 0.45;
65
- transition: translate var(--motion-quick) ease, opacity var(--motion-quick) ease;
65
+ background: var(--card-muted); /* a glyph: a shade of its side, never an opacity (0.45 of the ink was 2.4:1 on the paper) */
66
+ transition: translate var(--motion-quick) ease;
66
67
  }
67
68
  .toggle:checked { background: var(--accent); border-color: transparent; }
68
- .toggle:checked::after { translate: calc(var(--_w) - var(--_h)) 0; background: var(--on-accent); opacity: 1; }
69
+ .toggle:checked::after { translate: calc(var(--_w) - var(--_h)) 0; background: var(--on-accent); }
69
70
 
70
71
  /* ---- chip pick: a chip that is a choice — a native radio/checkbox inside the label, the picked one filled with accent.
71
72
  Full-width in a .chip-grid (one slot / many options), or hugging its word on a .chip-row with --sign (an interest
@@ -87,7 +88,7 @@
87
88
  a chip, a mood dot, a tile (media.css), a sticky's tone swatch (sticky.css), a place on a map (sheet.css), a card that is a label round a radio
88
89
  (a pickable row card: the app gives the picked one its `tone`) — the native input lies over its whole box, unseen */
89
90
  :is(.chip--pick, .mood__opt, .tile--pick, .sticky-pick__swatch, .map-pick) > input, label.card > input[type="radio"] { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: inherit; }
90
- :is(.chip--pick, .tile--pick):has(:checked) { background: var(--accent); color: var(--on-accent); border-color: transparent; }
91
+ :is(.chip--pick, .tile--pick):has(:checked) { background: var(--accent); --dark: var(--accent-dark); color: var(--card-ink); border-color: transparent; }
91
92
  /* --sign: the trailing + that turns 45° into an × when picked — one mask, one rotate, no swap (Lh 2026-09-16) */
92
93
  .chip--sign { padding-right: var(--sp-2); }
93
94
  .chip--sign::after {
@@ -121,13 +122,13 @@
121
122
  is the same shape on every day */
122
123
  padding-inline: 0;
123
124
  border-radius: var(--day-radius, var(--r-chip));
124
- opacity: var(--rest);
125
+ color: var(--day-rest-ink, var(--card-muted)); /* an unpicked day at rest: the muted words of the ground it stands on (an opacity faded it under AA) */
125
126
  }
126
127
  .day__name { font: var(--day-name-font, var(--type-caption)); }
127
128
  .day__num { font: 700 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
128
129
  /* today: a light tone, no louder than a rest; the selected pill below wins when it is both (it comes after and weighs more) */
129
- .day--today { background: var(--day-today-bg, var(--ink-tint)); color: var(--day-today-ink, currentColor); }
130
- .day[aria-selected="true"] { background: var(--day-selected-bg, var(--accent)); color: var(--day-selected-ink, var(--on-accent)); opacity: 1; }
130
+ .day--today { background: var(--day-today-bg, var(--ink-tint)); color: var(--day-today-ink, var(--card-ink)); }
131
+ .day[aria-selected="true"] { background: var(--day-selected-bg, var(--accent)); color: var(--day-selected-ink, var(--on-accent)); }
131
132
  .day__dot { grid-row: 3; align-self: center; width: var(--day-dot-size, var(--dot-xs)); height: var(--day-dot-size, var(--dot-xs)); border-radius: 50%; background: currentColor; }
132
133
 
133
134
  /* ---- calendar: month grid, 44px cells, one accent day, optional marks ----
@@ -147,8 +148,10 @@
147
148
  font-variant-numeric: tabular-nums;
148
149
  grid-column-start: var(--start, auto); /* --start on the 1st: weekday column 1–7 */
149
150
  }
150
- .calendar__day--mark { background: var(--calendar-day-mark-bg, var(--accent-soft)); }
151
- .calendar__day--mark2 { background: var(--calendar-day-mark2-bg, var(--accent-mid)); color: var(--calendar-day-mark2-ink, var(--on-accent)); }
151
+ /* a marked day keeps the ground's ink, so its fill is a light tint (12%, as --ink-tint: every tone holds its ink 4.5:1 on that); the
152
+ strong mark adds a ring of the accent. A stronger fill sat at the middle, where neither the ink nor --on-accent holds 4.5:1 */
153
+ .calendar__day--mark { background: var(--calendar-day-mark-bg, color-mix(in srgb, var(--accent) 12%, transparent)); }
154
+ .calendar__day--mark2 { background: var(--calendar-day-mark2-bg, color-mix(in srgb, var(--accent) 12%, transparent)); color: var(--calendar-day-mark2-ink, inherit); box-shadow: var(--calendar-day-mark2-shadow, inset 0 0 0 var(--border-w) var(--accent)); }
152
155
  .calendar__day[aria-selected="true"] { background: var(--calendar-day-selected-bg, var(--accent)); color: var(--calendar-day-selected-ink, var(--on-accent)); font-weight: 700; }
153
156
  .calendar__day:disabled { opacity: 0.3; }
154
157
 
@@ -204,7 +207,7 @@
204
207
  edge. The sign is INSIDE the pill: a full-ink border and a blinking bar drawn where the digit will land (the native caret
205
208
  is hidden — its height and place are the browser's; the pill's bar is one rule). A filled cell selects its digit instead. */
206
209
  }
207
- .pin__cell:not(:placeholder-shown) { color: var(--pin-cell-filled-ink, var(--accent)); } /* a typed digit: coloured words on the cell's own ground — no fill (Lh 2026-09-24; was an accent-filled pill) */
210
+ .pin__cell:not(:placeholder-shown) { color: var(--pin-cell-filled-ink, var(--action-accent)); } /* a typed digit: coloured words on the cell's own ground — no fill (Lh 2026-09-24; was an accent-filled pill) */
208
211
  /* a PIN (secret): the cell DRAWS the dot, the character itself is not painted — except for a moment, when it has just been typed
209
212
  (Lh 2026-10-01: "each digit must flash, then go at once"). It was `-webkit-text-security: disc`: a phone's browser then shows
210
213
  the last typed character of a masked field for a second or more ("password echo", Android) — per FIELD, and a Pin is six
package/css/finish.css CHANGED
@@ -20,8 +20,10 @@
20
20
  so a finish sits in any palette; the one plain white is the sticker's backing paper and the lit edge's light.
21
21
  ============================================================ */
22
22
 
23
- /* every finish: the finish IS the edge, so the wireframe's two borders step back (as [data-palette] in palette.css); ink puts its own line there below */
24
- [data-finish] { --card-border: transparent; --action-border: transparent; }
23
+ /* every finish: the finish IS the controls' edge, so the wireframe's control border steps back (as [data-palette] in palette.css). The card
24
+ keeps its --card-border: lit's ring and sticker's white cut do not reach 3:1 on a light ground (palette.css, THE CARD KEEPS AN EDGE);
25
+ print and ink draw their own line below */
26
+ [data-finish] { --action-border: transparent; }
25
27
 
26
28
  /* ============================================================
27
29
  lit — the light comes from above:
package/css/journey.css CHANGED
@@ -27,7 +27,7 @@
27
27
  .route__time { font: var(--route-time-font, var(--type-body)); font-variant-numeric: tabular-nums; }
28
28
  .route__place { font: var(--route-place-font, var(--type-caption)); color: var(--route-place-ink, var(--card-muted)); }
29
29
 
30
- .route__via { grid-column: 2; grid-row: 2; align-self: center; display: flex; align-items: center; opacity: 0.65; font-size: var(--route-via-font-size, var(--fs-body)); }
30
+ .route__via { grid-column: 2; grid-row: 2; align-self: center; display: flex; align-items: center; color: var(--route-via-ink, var(--card-muted)); font-size: var(--route-via-font-size, var(--fs-body)); }
31
31
 
32
32
  /* ---- tile badge: tiny label over a big value (gate, seat, zone) ---- */
33
33
  .tile-badge {
@@ -37,13 +37,16 @@
37
37
  padding: var(--tile-badge-pad-y, var(--sp-1)) var(--tile-badge-pad-x, var(--sp-2));
38
38
  border-radius: var(--tile-badge-radius, var(--r-badge));
39
39
  background: var(--tile-badge-bg, var(--card-3-bg));
40
- color: var(--tile-badge-ink, var(--card-3-ink));
40
+ --dark: var(--card-3-dark);
41
+ color: var(--tile-badge-ink, var(--card-ink));
41
42
  }
42
- .tile-badge__label { font: var(--tile-badge-label-font, var(--type-caption)); opacity: var(--dim); }
43
+ /* the label is the badge's own ink, smaller: a badge is a small fill of any colour (a palette paints it yellow), and only its own ink is
44
+ sure to hold 4.5:1 on it; --dim faded it to 2.4:1 */
45
+ .tile-badge__label { font: var(--tile-badge-label-font, var(--type-caption)); color: var(--tile-badge-label-ink, inherit); }
43
46
  .tile-badge__value { font: 800 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
44
47
 
45
48
  /* plain: no fill — a date box beside a title (Jun / 18) */
46
- .tile-badge--plain { background: transparent; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
49
+ .tile-badge--plain { background: transparent; --dark: inherit; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
47
50
  .tile-badge--plain .tile-badge__value { font: 800 var(--fs-h1)/1 var(--font-display); }
48
51
 
49
52
  /* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
package/css/layover.css CHANGED
@@ -69,19 +69,21 @@
69
69
  gap: var(--modal-drawer-gap, var(--sp-1));
70
70
  text-align: center;
71
71
  background: var(--modal-drawer-bg, var(--card-3-bg));
72
- color: var(--modal-drawer-ink, var(--card-3-ink));
73
- --accent: var(--card-3-ink); /* the one solid button inverts on the dark drawer */
74
- --on-accent: var(--card-3-bg);
72
+ --dark: var(--card-3-dark);
73
+ color: var(--modal-drawer-ink, var(--card-ink));
74
+ --accent: var(--card-ink); /* the one solid button inverts on the brand drawer */
75
+ --accent-dark: calc(1 - var(--dark));
75
76
  }
76
77
  .modal__drawer--light { /* light: the card's paper, dark words (Lh 2026-09-29) — the one solid button turns dark */
77
78
  background: var(--modal-drawer-light-bg, var(--card-bg));
79
+ --dark: inherit; /* the card's own side */
78
80
  color: var(--modal-drawer-light-ink, var(--card-ink));
79
81
  --accent: var(--card-ink);
80
- --on-accent: var(--card-bg);
82
+ --accent-dark: calc(1 - var(--dark));
81
83
  }
82
84
  /* the CONFIRM: a Modal with no ModalBody: the ModalAsk IS the card, as tall as its words (no fixed band, no second beat) */
83
85
  .modal:not(:has(> .modal__lift)) > .modal__drawer { height: auto; padding-block: var(--modal-confirm-pad-y, var(--sp-6)); animation: none; }
84
- .modal__drawer .t-caption { opacity: var(--dim); }
86
+ .modal__drawer .t-caption { color: var(--modal-drawer-caption-ink, var(--card-muted)); }
85
87
  .modal__drawer > h2:focus { outline: none; } /* the question takes the cursor when the modal arrives (for a screen reader, the keyboard's way in) — it is not a control: no ring */
86
88
  /* the answers as WORD buttons (a BtnRow of Btns; Lh 2026-10-11): the row fills the ask with room over it and each button keeps its words on one
87
89
  line — left to its words' width the row squeezed two buttons into round blobs, their words on two lines. A quiet button is an outline only,
@@ -190,6 +192,7 @@
190
192
  background: var(--banner-bg, var(--ink-tint));
191
193
  color: var(--banner-ink, inherit);
192
194
  border-block: var(--border-w) solid var(--banner-border, var(--hairline));
195
+ --card-muted: var(--card-ink); /* a tint of the ink is a ground where no lighter shade holds 4.5:1: its secondary words are the full ink */
193
196
  /* a band edge to edge of what holds it — the card's or the screen's inset; no radius, no side border: a band, not a card */
194
197
  margin-inline: calc(-1 * var(--card-pad, var(--screen-pad)));
195
198
  padding-inline: var(--card-pad, var(--screen-pad));
@@ -243,7 +246,8 @@
243
246
  border-radius: var(--float-bar-pill-radius, var(--r-chip));
244
247
  border: var(--border-w) solid var(--float-bar-pill-border, var(--action-border));
245
248
  background: var(--float-bar-pill-bg, color-mix(in srgb, var(--action-bg) 82%, transparent));
246
- color: var(--float-bar-pill-ink, var(--action-ink));
249
+ --dark: var(--action-dark); /* the pill is the items' ground: the paper's side */
250
+ color: var(--float-bar-pill-ink, var(--card-ink));
247
251
  backdrop-filter: blur(var(--sp-3));
248
252
  -webkit-backdrop-filter: blur(var(--sp-3));
249
253
  box-shadow: var(--float-bar-pill-shadow, var(--shadow-float));
@@ -259,14 +263,14 @@
259
263
  border-radius: var(--float-bar-item-radius, var(--r-chip));
260
264
  font: var(--float-bar-item-font, var(--type-caption));
261
265
  text-decoration: none;
262
- opacity: var(--rest);
266
+ color: var(--float-bar-item-rest-ink, var(--card-muted));
263
267
  overflow: hidden;
264
268
  white-space: nowrap;
265
269
  text-overflow: ellipsis;
266
270
  }
267
271
  /* aria-current="false" is the correct way to mark the OTHER tabs, so the
268
272
  bare attribute selector lights every one of them up */
269
- .float-bar__item[aria-current]:not([aria-current="false"]) { background: var(--accent); color: var(--on-accent); opacity: 1; }
273
+ .float-bar__item[aria-current]:not([aria-current="false"]) { background: var(--accent); color: var(--on-accent); }
270
274
  .float-bar > .icon-btn { width: var(--bar-h); height: var(--bar-h); box-shadow: var(--shadow-float); }
271
275
 
272
276
  /* ---- callout: a small card with a tail — over a map, a media, a mascot ----
@@ -303,7 +307,7 @@
303
307
  border-left: 0;
304
308
  }
305
309
  .callout__title { font: var(--callout-title-font, var(--type-title)); }
306
- .callout__sub { font: var(--callout-sub-font, var(--type-caption)); opacity: var(--dim); }
310
+ .callout__sub { font: var(--callout-sub-font, var(--type-caption)); color: var(--callout-sub-ink, var(--action-muted)); }
307
311
  /* ---- splash: the app's mark at the human centre while it boots (Lh 2026-09-24) ----
308
312
  <section class="screen screen--fill splash"><div class="splash__group"><div class="splash__mark">{the app's lottie/img}</div>
309
313
  <p class="splash__text">…</p></div></section>
@@ -346,7 +350,8 @@
346
350
  border: var(--border-w) solid var(--snackbar-border, transparent);
347
351
  border-radius: var(--snackbar-radius, var(--r-card));
348
352
  background: var(--snackbar-bg, var(--signal-ok-soft));
349
- color: var(--snackbar-ink, var(--on-surface));
353
+ --dark: var(--surface-dark); /* the signal's soft ground is a tint of the screen: the screen's side */
354
+ color: var(--snackbar-ink, var(--card-ink));
350
355
  box-shadow: var(--snackbar-shadow, var(--shadow-float));
351
356
  font: inherit; text-align: start;
352
357
  pointer-events: auto;
@@ -354,5 +359,5 @@
354
359
  .snackbar__body > .icon { flex: none; color: var(--snackbar-icon-ink, var(--signal-ok)); }
355
360
  .snackbar__words { display: grid; min-width: 0; overflow-wrap: anywhere; }
356
361
  .snackbar__text { font: var(--snackbar-text-font, var(--type-body)); }
357
- .snackbar__caption { font: var(--snackbar-caption-font, var(--type-caption)); opacity: var(--dim); }
362
+ .snackbar__caption { font: var(--snackbar-caption-font, var(--type-caption)); color: var(--snackbar-caption-ink, inherit); } /* on the signal's soft ground the full ink: a muted shade there fell under AA */
358
363
  @media (prefers-reduced-motion: reduce) { .snackbar { transition: none; } }
package/css/lists.css CHANGED
@@ -32,7 +32,6 @@
32
32
  .row__body { flex: 1; min-width: 0; display: grid; gap: var(--row-body-gap, var(--sp-0)); }
33
33
  .row__title { font: var(--row-title-font, var(--type-title)); overflow-wrap: anywhere; } /* wraps freely — no clip, so no padding trick for Thai marks (Lh 2026-09-16, same call as the card heading) */
34
34
  .row__meta { font: var(--row-meta-font, var(--type-caption)); color: var(--row-meta-ink, var(--card-muted)); display: flex; align-items: center; gap: var(--row-meta-gap, var(--sp-1)); }
35
- .card--3 .row__meta { color: var(--card-3-muted); }
36
35
  .row__end { margin-left: auto; text-align: right; display: grid; gap: var(--row-end-gap, var(--sp-0)); justify-items: end; flex: none; }
37
36
  .row--lg { min-height: var(--row-lg-min-h, var(--fab)); } /* menu rows: a taller target, round arrow at the end; .rows gives them more air */
38
37
  .row--lg .row__title { font: 700 var(--fs-lg)/1.2 var(--font-display); }
@@ -51,7 +50,6 @@
51
50
  at --tap-sm, but not an input: nothing to press. */
52
51
  .row--done { pointer-events: none; cursor: default; }
53
52
  .row--done .row__title { color: var(--card-muted); }
54
- .card--3 .row--done .row__title { color: var(--card-3-muted); }
55
53
  .row__done {
56
54
  width: var(--row-done-size, var(--tap-sm));
57
55
  height: var(--row-done-size, var(--tap-sm));
@@ -96,7 +94,6 @@
96
94
  .kv-cell > .icon { margin-bottom: var(--sp-1); }
97
95
  .kv-cell__val { font: var(--kv-cell-val-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
98
96
  .kv-cell__key { font: var(--kv-cell-key-font, var(--type-caption)); color: var(--kv-cell-key-ink, var(--card-muted)); }
99
- .card--3 .kv-cell__key { color: var(--card-3-muted); }
100
97
 
101
98
  /* ---- bubble: a chat message — theirs on the left, mine on the right (Lh 2026-09-18, replaces the chip's tail) ----
102
99
  No tail: a rounded block whose corner nearest the sender is square-ish
@@ -125,7 +122,8 @@
125
122
  border-radius: var(--bubble-body-radius, var(--r-card));
126
123
  border-bottom-left-radius: var(--r-control);
127
124
  background: var(--bubble-body-bg, var(--card-2-bg));
128
- color: var(--bubble-body-ink, var(--card-2-ink));
125
+ --dark: var(--card-2-dark);
126
+ color: var(--bubble-body-ink, var(--card-ink));
129
127
  font: var(--bubble-body-font, var(--type-body));
130
128
  overflow-wrap: anywhere;
131
129
  }
@@ -136,10 +134,9 @@
136
134
  color: var(--on-accent);
137
135
  text-align: right;
138
136
  }
139
- .bubble__meta { display: flex; align-items: center; gap: var(--bubble-meta-gap, var(--sp-1)); font: var(--bubble-meta-font, var(--type-caption)); color: var(--bubble-meta-ink, var(--on-surface-muted)); }
137
+ .bubble__meta { display: flex; align-items: center; gap: var(--bubble-meta-gap, var(--sp-1)); font: var(--bubble-meta-font, var(--type-caption)); color: var(--bubble-meta-ink, var(--card-muted)); }
140
138
  .bubble--right > .bubble__meta { justify-self: end; }
141
- .bubble__meta > .icon { color: var(--accent); }
142
- .card--3 .bubble__meta { color: var(--card-3-muted); }
139
+ .bubble__meta > .icon { color: var(--bubble-meta-icon-ink, var(--accent-ink)); }
143
140
 
144
141
  /* ---- timeline: a hairline with date chips, entries to the right ----
145
142
  <ol class="timeline"><li class="timeline__item"><span class="timeline__mark">Wed 14</span><p class="t-body">…</p></li>…</ol> */
@@ -182,7 +179,6 @@
182
179
  /* ---- legend: signal dots with captions ----
183
180
  <div class="legend"><span class="legend__item"><span class="dot"></span> very high</span>…</div> */
184
181
  .legend { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); font: var(--legend-font, var(--type-caption)); color: var(--legend-ink, var(--card-muted)); }
185
- .card--3 .legend { color: inherit; opacity: 0.8; }
186
182
  .legend__item { display: inline-flex; align-items: center; gap: var(--legend-item-gap, var(--sp-2)); min-height: var(--legend-item-min-h, var(--icon)); }
187
183
 
188
184
  /* ---- link: "See all ↗" in a card head, big enough to tap ----
@@ -203,7 +199,6 @@
203
199
  .link--muted { color: var(--link-muted-ink, var(--card-muted)); }
204
200
  .link:disabled { color: var(--link-disabled-ink, var(--card-muted)); cursor: default; } /* inactive = muted (a Countdown) */
205
201
  .link--wait { font-variant-numeric: tabular-nums; } /* the countdown's digits keep their width: the words do not jiggle each second */
206
- .card--3 .link--muted { color: var(--card-3-muted); }
207
202
 
208
203
  /* ---- bullets: a list, each item led by a small square picture as tall as a line of its words (Lh 2026-09-28, timebank #153) ----
209
204
  <ul class="bullets"><li class="bullets__item"><img class="bullets__pic" alt=""><span class="bullets__text">…</span></li></ul>
package/css/media.css CHANGED
@@ -10,9 +10,9 @@
10
10
  <div class="row">…meta…</div></article> */
11
11
  .card--cover {
12
12
  --card-bg: var(--card-3-bg);
13
- --card-ink: var(--card-3-ink);
14
- --accent: var(--card-3-ink);
15
- --on-accent: var(--card-3-bg);
13
+ --dark: var(--card-3-dark);
14
+ --accent: var(--card-ink);
15
+ --accent-dark: calc(1 - var(--dark));
16
16
  min-height: var(--_cover-min-h);
17
17
  align-content: space-between;
18
18
  overflow: hidden;
@@ -38,8 +38,6 @@
38
38
  z-index: -1;
39
39
  background: linear-gradient(to bottom, var(--scrim), transparent 40% 60%, var(--scrim));
40
40
  }
41
- .card--cover .t-muted { color: inherit; opacity: 0.75; }
42
- .card--cover .row__meta { color: inherit; opacity: 0.85; }
43
41
 
44
42
  /* ---- quote: giant mark, big text, attribution ----
45
43
  <blockquote class="quote"><p class="quote__text">It's overcast…</p><footer class="quote__by">Wednesday 15</footer></blockquote> */
@@ -74,35 +72,40 @@
74
72
  border: var(--border-w) solid var(--tile-border, var(--outline));
75
73
  border-radius: var(--tile-radius, var(--r-card));
76
74
  background: var(--tile-bg, var(--action-bg));
77
- color: var(--tile-ink, var(--action-ink));
75
+ --dark: var(--action-dark); /* a tile is a surface: the paper's side (a toned tile takes its tone's, below) */
76
+ color: var(--tile-ink, var(--card-ink));
78
77
  font: inherit;
79
78
  text-align: left;
80
79
  position: relative;
81
80
  }
82
81
  .tile__label { font: var(--tile-label-font, var(--type-title)); }
83
82
  .tile__value { font: var(--tile-value-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
84
- .tile__sub { font: var(--tile-sub-font, var(--type-caption)); opacity: var(--dim); }
83
+ .tile__sub { font: var(--tile-sub-font, var(--type-caption)); color: var(--tile-sub-ink, var(--card-muted)); } /* the muted words of the tile's ground; --dim faded the full ink to 2.4–3.4:1 */
85
84
  .tile__text { display: grid; gap: var(--tile-text-gap, var(--sp-0)); }
86
- .tile--on { background: var(--tile-on-bg, var(--accent)); color: var(--tile-on-ink, var(--on-accent)); border-color: transparent; }
85
+ .tile--on { background: var(--tile-on-bg, var(--accent)); --dark: var(--accent-dark); color: var(--tile-on-ink, var(--card-ink)); border-color: transparent; }
87
86
  /* .tile--pick's hidden input and :has(:checked) fill are declared once in choice.css, alongside .chip--pick and .mood__opt */
88
87
  .tile--wide { grid-column: 1 / -1; }
89
88
  /* stat: READ, not pressed — no frame of its own inside a card (a frame says "tap me"): icon · value · words on the card's ground */
90
- .tile--stat { border-color: transparent; background: none; padding-inline: 0; min-height: var(--tile-stat-min-h, var(--avatar-xl)); gap: var(--tile-stat-gap, var(--sp-3)); grid-template-columns: auto 1fr; align-items: center; }
89
+ /* a stat tile has no fill of its own, so it has no ink of its own: it takes the ink of what it stands on (currentColor on `color`
90
+ = the inherited colour), as the card's heading does. It kept the plain tile's --action-ink, dark on a dark card (timebank, tone 3,
91
+ Lh 2026-10-18). --tile-ink still overrides; a toned stat tile (.tile--N, below) takes its fill and its ink back */
92
+ .tile--stat { --dark: inherit; color: var(--tile-ink, currentColor); border-color: transparent; background: none; padding-inline: 0; min-height: var(--tile-stat-min-h, var(--avatar-xl)); gap: var(--tile-stat-gap, var(--sp-3)); grid-template-columns: auto 1fr; align-items: center; }
91
93
  /* upload: dashed, centred plus + label */
92
94
  .tile--upload {
93
95
  border-style: dashed;
94
96
  background: transparent;
95
97
  color: inherit;
98
+ --dark: inherit;
96
99
  align-content: center;
97
100
  justify-items: center;
98
101
  gap: var(--tile-upload-gap, var(--sp-2));
99
102
  text-align: center;
100
103
  }
101
104
  /* tinted tiles: slot colors inside a card (schedule, notes) */
102
- .tile--2 { background: var(--tile-2-bg, var(--card-2-bg)); color: var(--tile-2-ink, var(--card-2-ink)); border-color: transparent; }
103
- .tile--4 { background: var(--tile-4-bg, var(--card-4-bg)); color: var(--tile-4-ink, var(--card-4-ink)); border-color: transparent; }
104
- .tile--5 { background: var(--tile-5-bg, var(--card-5-bg)); color: var(--tile-5-ink, var(--card-5-ink)); border-color: transparent; }
105
- .tile--3 { background: var(--tile-3-bg, var(--card-3-bg)); color: var(--tile-3-ink, var(--card-3-ink)); border-color: transparent; }
105
+ .tile--2 { background: var(--tile-2-bg, var(--card-2-bg)); --dark: var(--card-2-dark); color: var(--tile-2-ink, var(--card-ink)); border-color: transparent; }
106
+ .tile--4 { background: var(--tile-4-bg, var(--card-4-bg)); --dark: var(--card-4-dark); color: var(--tile-4-ink, var(--card-ink)); border-color: transparent; }
107
+ .tile--5 { background: var(--tile-5-bg, var(--card-5-bg)); --dark: var(--card-5-dark); color: var(--tile-5-ink, var(--card-ink)); border-color: transparent; }
108
+ .tile--3 { background: var(--tile-3-bg, var(--card-3-bg)); --dark: var(--card-3-dark); color: var(--tile-3-ink, var(--card-ink)); border-color: transparent; }
106
109
 
107
110
  /* ---- fold: a sticky-note corner on any card (decorative) ----
108
111
  No border. The card's outline follows its border box, so it ran straight
@@ -225,7 +228,7 @@
225
228
  }
226
229
  .postcard__turn:focus-visible { outline-offset: calc(-1 * var(--sp-1)); } /* the ring inside the card's edge */
227
230
  /* the front: the photo (or the wireframe box) fills it; the band reads on a scrim */
228
- .postcard__front { background: var(--postcard-front-bg, var(--card-3-bg)); color: var(--postcard-front-ink, var(--card-3-ink)); }
231
+ .postcard__front { background: var(--postcard-front-bg, var(--card-3-bg)); --dark: var(--card-3-dark); color: var(--postcard-front-ink, var(--card-ink)); }
229
232
  .postcard__front .postcard__turn > img,
230
233
  .postcard__front .postcard__turn > .ph {
231
234
  position: absolute;
@@ -248,7 +251,8 @@
248
251
  /* the back: paper — the caption and the name packed at the middle, big enough to read at arm's length */
249
252
  .postcard__back {
250
253
  background: var(--postcard-back-bg, var(--card-2-bg));
251
- color: var(--postcard-back-ink, var(--card-2-ink));
254
+ --dark: var(--card-2-dark);
255
+ color: var(--postcard-back-ink, var(--card-ink));
252
256
  transform: rotateY(180deg);
253
257
  }
254
258
  .postcard__back .postcard__turn { align-content: start; padding-top: var(--postcard-back-top, 20%); gap: var(--postcard-back-gap, var(--sp-5)); } /* the name a fifth down the paper, the words after it */
@@ -266,7 +270,7 @@
266
270
  }
267
271
  .postcard__band > * { pointer-events: auto; }
268
272
  .postcard__words { display: grid; gap: var(--postcard-words-gap, var(--sp-1)); flex: 1; min-width: 0; pointer-events: none; }
269
- .postcard__front .postcard__band { --accent: var(--postcard-front-ink, var(--card-3-ink)); --on-accent: var(--postcard-front-bg, var(--card-3-bg)); }
273
+ .postcard__front .postcard__band { --accent: var(--postcard-front-ink, var(--card-ink)); --accent-dark: calc(1 - var(--dark)); }
270
274
  .postcard__band .icon-btn[aria-pressed="true"] .icon { fill: currentColor; }
271
275
  .postcard.is-flipped > .postcard__front { transform: rotateY(-180deg); }
272
276
  .postcard.is-flipped > .postcard__back { transform: rotateY(0); }