@usableapp/cardds 0.7.105 → 0.7.106

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
@@ -2,6 +2,43 @@
2
2
 
3
3
  > For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap. Each release section after 0.7.97 carries one `Components:` line, written by `node scripts/components-changed.mjs <previous tag>`: the components the release changed, or `all` when a file changed that any component can read (a token, the build), or `none`; check only the screens that import those.
4
4
 
5
+ ## 0.7.106 — 2026-10-21
6
+
7
+ The map edge to edge under a step's controls (`MapArea bleed`, and a step whose bleeding child reaches the sheet's edges); `Chip soft`; a card's head led by its date; `Row` in parts (`RowLead`, `RowBody`, `RowEnd`, and the props that drew them are gone); a turning two-tone card no longer cut at its edges; the catalog names a whole's parts.
8
+
9
+ Components: all (a new root token, `--accent-wash`; the gallery, the catalog build and the lint rules changed. The components a screen sees change are MapArea, SheetStep, Chip, ChipRow, CardHead, JourneyBadge, Card, Row and its parts; `Row`'s old props fail the typecheck, see its migration table)
10
+
11
+ ### Added
12
+ - **`MapArea bleed`: with `inline`, the map edge to edge of what holds it, no corner** (Lh 2026-10-19, timebank's ask sheet, the จุดนัด step: the map under แถวบ้าน / แถวนี้ must touch the sheet's left and right edges). Before → after: a `MapArea inline` in a `SheetStep` was a rounded box inside the step's inset, and no knob could take it to the sheet's edge (`--sheet-step-inner-pad-x: 0` and `--map-radius: 0` left it inside the sheet's own 20px, cut by the fold's clip) → `<MapArea inline bleed>` gives the sheet's or the card's inset back (the same edge declaration as `.bleed`, read from `--card-pad`) and drops its corner; a card clips its corner for a first or last one as it does for a `ChipRow bleed`. Ignored without `inline`; `cardds check` warns (`map-bleed-without-inline`). Gallery `MapArea/InStep`.
13
+
14
+ - **`Chip soft`: a status to see, quieter than `on`** (Lh 2026-10-21: keep what we used, add the other style). Before → after: a status in a card's head was a plain `Chip` (an edge) or `Chip on` (the full accent); the inbox's `Ribbon` drew a third look, the accent washed in, as a separate piece → that look is `Chip soft` (`.chip--soft`: `--chip-soft-bg`, default the new token `--accent-wash`, the accent washed into the paper of fields and buttons, opaque, so its words read on every tone; `--chip-soft-ink`), and `inbox/Ribbon` is gone. `on` wins when both are set. Gallery `Chip/Soft`.
15
+
16
+ - **A card's head that leads with a date; words that lead a ChipRow** (Lh 2026-10-21, timebank's request card re-laid). Before → after: a `CardHead` put its `ChipRow` on a line of its own under the heading, and the date beside the heading → a `CardHead` whose first child is a `JourneyBadge stretch` and second a `ChipRow` stands the date at the left and the chips beside it, the date always as tall as the chips (its three-line cap lifted there; the chips are not cut at two lines there), and the heading with its caption takes the whole width under them (`--card-head-lead-gap`, `--sp-3`). `ChipRow lead` sets words before the chips ("นัดที่") in the chips' own type, at their middle (`.chip-row__lead`, `--chip-row-lead-ink`). Gallery `Card/Turns*`, `Card/PaperBands`.
17
+ - **The catalog names a whole's parts** (Lh 2026-10-21, modular parts). Before → after: `cardds get Row` listed what Row's source renders (`composes`) but not the exports it is built of, so a session learned of `RowLead`, `RowBody` and `RowEnd` only by reading the description → `get` prints `parts: RowBody · RowEnd · RowLead` on a whole and `part of: Row` on a part (JSON `parts`, `partOf`; MCP `get_component` the same). A part is an export named `<Whole><Part>` that draws `.whole__part`.
18
+
19
+ ### Changed
20
+ - **`Card paper="bands"`: the heading and the words under it read as one paragraph** (Lh 2026-10-21). Before → after: the head band's lower edge had the card's padding above it and the card's gap below (20 + 16 at 375) → `--sp-2` each side (`--card-band-seam`), so only the colour divides the heading from what follows. Every `paper="bands"` card moves up by the difference.
21
+ - **`Chip sm`: one height wherever it stands** (Lh 2026-10-21: a foot's chip beside a head's chip read smaller). Before → after: a small chip in a card's head took the body's line height (32.6 at 375) and everywhere else the label's (27.5) → every `Chip sm` takes the body's line (`--chip-sm-line`). Every screen with a small chip outside a head grows by 5px a chip.
22
+ - **`Row` is composed of parts: `RowLead`, `RowBody`, `RowEnd` are children, and the props that drew them are gone** (Lh 2026-10-21: every component is broken into small parts, each its own export; the whole's single-part props are retired, not kept as shorthand). Before → after: a row was one tag whose props drew its pieces (`<Row leadIcon="gift" title="Promotions" meta="See what's available" end={<IconBtn … />} />`) → the row renders its children in order, lead · body · end (`<Row><RowLead icon="gift" /><RowBody title="Promotions" meta="See what's available" /><IconBtn … /></Row>`). The markup is the same: `.row__lead`, `.row__body`, `.row__end` and every `--row-*` knob stand as they were, and the style snapshot of the stories did not move. `lg`, `done`, `as`, `htmlFor`, `href`, `more`, `open`, `defaultOpen` and `onOpenChange` stay on `Row`. Migration, old prop → new child:
23
+
24
+ | before | after |
25
+ | --- | --- |
26
+ | `lead={<Avatar>DI</Avatar>}` | `<Avatar>DI</Avatar>` as the first child, unwrapped |
27
+ | `leadIcon="gift"` | `<RowLead icon="gift" />` first |
28
+ | `title="…" meta="…"` | `<RowBody title="…" meta="…" />` |
29
+ | `body={<Wave />}` | `<RowBody><Wave /></RowBody>` (with the title and meta, if any) |
30
+ | `endValue="+1.0 h" endCaption="Tue"` | `<RowEnd value="+1.0 h" caption="Tue" />` after the body |
31
+ | `end={<IconBtn … />}` | `<IconBtn … />` after the body, unwrapped; `end={<>a b</>}` gives `a b` |
32
+ | `children` | stay between the body and the end |
33
+
34
+ A row left on the old props fails the typecheck (`title`, `lead`, `end`… are no props of `Row`). The lint rule `row-label-button` reads the row's children now, so a button before a label row's `Check` is still caught.
35
+
36
+ ### Fixed
37
+ - **A date at the corner of a turning card's head kept the plain badge corner.** Before → after: the concentric corner (the card's corner less its padding) reached a head that is the card's first child only, and a `Card back` face starts with its unseen turn button → the face's head is read too (21.6 at 375, was 13).
38
+ - **A bleeding strip inside a `SheetStep` stopped inside the sheet's inset.** Before → after: the step's body clips at its own box (the fold mechanic), and that box sat inside the sheet's padding, so a `ChipRow bleed` in a step reached the step's edge and the fold's clip cut it there → the step's body takes the sheet's inset back and pads by it again (as `.sheet__body` does), and the step's inner publishes the whole inset (the sheet's plus `--sheet-step-inner-pad-x`, which is the step's own inset beyond the sheet's) as `--card-pad`, so any child that bleeds reaches the sheet's edges; the step's head bar and its content keep their place. The knob is a length: a theme's unitless `0` still insets the step, but the sum a bleed reads is then invalid and the child stays in the inset. `Home/AskSheet`'s past-asks strip follows (the style snapshot names it).
39
+
40
+ - **A two-tone card that turns is no longer cut off at its edges while it turns** (`Card paper="bands"` with `back`; seen on `Card · TurnsRoundFoot`, and on a phone the same way). Before → after: the two-tone rule matched the turning card itself as well as its faces, so the card clipped (`overflow: hidden`) and painted the tinted middle behind the faces; as a face turned, its near edge (nearer the eye than the card, up to about 60px past the card's top and bottom at 375) was cut in a straight line at the card's box, and a pale rounded box showed behind it → the look and the clip are on the faces only, the turning box paints nothing and clips nothing. A clipping box around the card (a scrolling screen, a gallery cell) still cuts the near edge when the card is within that reach of its edge. Components: Card. Nothing for a consumer to change. tests/card-turns.spec.js.
41
+
5
42
  ## 0.7.105 — 2026-10-21
6
43
 
7
44
  A card foot of big round buttons, and a card drawn as the gallery draws it.
package/bin/cardds.mjs CHANGED
@@ -106,6 +106,8 @@ const asText = {
106
106
  ...(c.role ? [`role: represents ${[c.role.represents, c.role.count, c.role.density, c.role.job].flat().join(' · ')}${c.role.standsInFor.length ? `; stands in for: ${c.role.standsInFor.map((s) => (s.condition ? `${s.name} (${s.condition})` : s.name)).join('; ')}` : ''}`] : []),
107
107
  ...(c.composes.length ? [`composes: ${c.composes.join(' · ')}`] : []),
108
108
  ...(c.composedBy.length ? [`composed by: ${c.composedBy.join(' · ')}`] : []),
109
+ ...(c.parts?.length ? [`parts: ${c.parts.join(' · ')} — its children, each its own export`] : []),
110
+ ...(c.partOf ? [`part of: ${c.partOf}`] : []),
109
111
  ...(c.params ? ['\nparameters', ...(c.params.length ? [] : [' (none)']), ...c.params.map((p) => ` ${p.name}${p.optional && !p.default ? '?' : ''}: ${p.type}${p.default ? ` = ${p.default}` : ''}`)] : [
110
112
  '\nprops',
111
113
  ...c.props.map((p) => ` ${p.name}${p.optional ? '?' : ''}: ${p.type}${p.slot ? ' [slot]' : ''}${p.description ? ` — ${one(p.description)}` : ''}`)]),
package/css/actions.css CHANGED
@@ -196,12 +196,17 @@
196
196
  .dot--error { background: var(--signal-error); }
197
197
  .dot--info { background: var(--signal-info); }
198
198
  /* --sm: a small LABEL (a name on a note, a tag in a tight head) — low and tight; never a tap target */
199
- .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)); }
199
+ /* one height wherever it stands (Lh 2026-10-21): the body's line, so a head that cuts its words with an ellipsis keeps the Thai marks over them, and a foot's or a middle's chip reads the same */
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)); line-height: var(--chip-sm-line, var(--lh-body)); }
201
+ /* soft: the accent washed into the chip's ground, no edge — a status to see, quieter than on (was the inbox Ribbon, Lh 2026-10-21) */
202
+ .chip--soft { background: var(--chip-soft-bg, var(--accent-wash)); color: var(--chip-soft-ink, var(--card-ink)); border-color: transparent; } /* the wash on the chip's own paper, opaque (--accent-wash): see-through, a dark tone came through under the light side's ink (ink.spec) */
200
203
  .chip--on { background: var(--chip-on-bg, var(--accent)); --dark: var(--accent-dark); color: var(--chip-on-ink, var(--card-ink)); border-color: transparent; }
201
204
  /* muted: an untouched default — the words wash out, the edge stays; emphasis is the difference from the neighbours (docs/reference.md 13) */
202
205
  .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 */
203
206
 
204
207
  .chip-row { display: flex; gap: var(--sp-2); }
208
+ /* the words that lead the chips (ChipRow `lead`, "นัดที่" before a place, Lh 2026-10-21): the chips' own type and size, at the middle of their height */
209
+ .chip-row__lead { align-self: center; font: var(--type-label); font-size: var(--chip-font-size, var(--fs-sm)); color: var(--chip-row-lead-ink, inherit); }
205
210
  .chip-row:not(.bleed) { flex-wrap: wrap; } /* a strip (.bleed, card.css) stays one line */
206
211
  .chip-row.bleed > .chip { flex: none; white-space: nowrap; } /* a chip of the strip keeps its words on one line at its own width: the strip scrolls, a chip never shrinks to fit it */
207
212
 
@@ -454,8 +459,10 @@
454
459
 
455
460
  /* ---- where it sits in what card.css draws (docs/reference.md rule 15) ---- */
456
461
  /* a head's ChipRow takes a line of its own under the title: at most two lines of chips, a chip's words cut with an ellipsis */
457
- .card__head > .chip-row { flex: 1 1 100%; order: 2; max-height: calc(2 * (var(--fs-sm) * 1.5 + 2 * var(--chip-sm-pad-y, var(--sp-0)) + 2 * var(--border-w)) + var(--sp-2)); overflow: hidden; }
458
- .card__head > .chip-row > .chip { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: var(--lh-body); }
462
+ .card__head > .chip-row { flex: 1 1 100%; order: 2; max-height: calc(2 * (var(--fs-sm) * var(--chip-sm-line, var(--lh-body)) + 2 * var(--chip-sm-pad-y, var(--sp-0)) + 2 * var(--border-w)) + var(--sp-2)); overflow: hidden; }
463
+ /* …beside a date that leads the head (card.css) they stand in the date's row, every chip in sight: the meeting place may take a line of its own */
464
+ .card__head:has(> .tile-badge:first-child + .chip-row) > .chip-row { grid-column: 2; grid-row: 1; max-height: none; }
465
+ .card__head > .chip-row > .chip { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
459
466
  .card__foot > .btn { flex: 1 1 0; min-width: 0; }
460
467
  /* …unless the button says its own size: `Btn form` is as wide as its words in a foot too, centred (Lh 2026-10-10: one button filling
461
468
  a card's foot read too big — which button a card wears is the project's choice, so the foot must not undo it) */
package/css/card.css CHANGED
@@ -100,19 +100,33 @@
100
100
  .card__head:has(> .stamp) { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
101
101
  .card__head:has(> .stamp) > :is(h1, h2, h3) { grid-column: 1; grid-row: 1; align-self: end; }
102
102
 
103
+ /* a head that LEADS with a date (Lh 2026-10-21, timebank's request card): a `CardHead` whose first child is a JourneyBadge and whose second is a
104
+ ChipRow. The date stands at the left and the chips beside it, the two as tall as each other; the heading and its caption take the whole
105
+ width under them, so the heading sits right over the words of the middle. The date is always as tall as the chips. The gap under the date row is --card-head-lead-gap; the date and the chips place themselves (journey.css, actions.css).
106
+ <div class="card__head"><span class="tile-badge tile-badge--stretch">…</span><div class="chip-row">…</div><div><h2 class="t-h1">…</h2>…</div></div> */
107
+ .card__head:has(> .tile-badge:first-child + .chip-row) {
108
+ display: grid;
109
+ grid-template-columns: auto minmax(0, 1fr);
110
+ align-items: center;
111
+ row-gap: var(--card-head-lead-gap, var(--sp-3));
112
+ }
113
+ .card__head:has(> .tile-badge:first-child + .chip-row) > :nth-child(n + 3) { grid-column: 1 / -1; }
114
+
103
115
  /* a JourneyBadge at the edge of the card's own first head (the date box in the top-left or top-right corner) keeps its corner concentric
104
116
  with the card's: the card's corner (--r-card) less the room the padding leaves round it. journey.css reads it behind the theme's --tile-badge-radius,
105
117
  so a theme that sets that knob still wins. */
106
- .card > .card__head:first-child > .tile-badge:is(:first-child, :last-child) { --_badge-radius: calc(var(--r-card) - var(--card-pad)); }
118
+ :is(.card > .card__head:first-child, .card__face > .card__turn + .card__head) > .tile-badge:is(:first-child, :last-child) { --_badge-radius: calc(var(--r-card) - var(--card-pad)); } /* a turning card's face: its head follows the unseen turn button */
107
119
 
108
120
  /* ---- paper bands: the two-tone card (`Card paper="bands"`, Lh 2026-10-19): the head and the foot are paper bands edge to
109
121
  edge over the card's padding, no hairline; the middle between them keeps the card's tone, lightened (--card-middle-bg). The card
110
- clips, so the bands keep its corners. A two-faced card (below) carries the look on each face. */
122
+ clips, so the bands keep its corners. A two-faced card (below) carries the look on each face, never on the turning box: a clip
123
+ there cut the face off at the card's edges while it turned, its near edge being nearer the eye than the box (2026-10-21). */
111
124
  :is(.card--bands, .card--bands > .card__face) {
112
125
  background: var(--card-middle-bg, color-mix(in srgb, var(--card-bg) 35%, var(--card-1-bg)));
113
126
  --dark: var(--card-1-dark); /* the middle is mostly paper: its words take the paper's side */
114
127
  overflow: hidden;
115
128
  }
129
+ .card--bands.card--turns { background: none; overflow: visible; } /* the turning box keeps --dark: the faces' ink is computed from it */
116
130
  :is(.card--bands, .card--bands > .card__face) > :is(.card__head, .card__foot) {
117
131
  --card-bg: var(--card-1-bg);
118
132
  --dark: var(--card-1-dark);
@@ -122,6 +136,10 @@
122
136
  padding-inline: var(--card-pad);
123
137
  }
124
138
  :is(.card--bands, .card--bands > .card__face) > .card__head { margin-top: calc(-1 * var(--card-pad)); padding-block: var(--card-pad); }
139
+ :is(.card--bands, .card--bands > .card__face) > .card__head { padding-block-end: var(--card-band-seam, var(--sp-2)); }
140
+ /* the seam: the heading and the words under it read as ONE paragraph that changes colour at the band's edge (Lh 2026-10-21), so the room on
141
+ each side of the head band's lower edge is --card-band-seam, not the card's padding and gap */
142
+ :is(.card--bands, .card--bands > .card__face) > .card__head + * { margin-block-start: calc(var(--card-band-seam, var(--sp-2)) - var(--card-gap)); }
125
143
  :is(.card--bands, .card--bands > .card__face) > .card__foot { margin-bottom: calc(-1 * var(--card-pad)); padding-block: var(--sp-3); } /* the foot band is slim */
126
144
 
127
145
  /* ---- two faces: the WHOLE card turns (`Card back`, Lh 2026-10-19). The article is the turning box (.flip, base.css) and only that;
@@ -168,7 +186,7 @@
168
186
  nothing jumps. Chromium/Safari paint it through the ::-webkit-scrollbar
169
187
  parts; Firefox gets its thin bar in the same ink. */
170
188
  .bleed {
171
- margin-inline: calc(-1 * var(--card-pad, 0rem));
189
+ margin-inline: calc(-1 * var(--card-pad, 0rem)); /* the edge a bleeding child takes back from what holds it; .map--bleed (sheet.css) reads the same --card-pad */
172
190
  padding-inline: var(--card-pad, 0rem);
173
191
  padding-block-end: var(--sp-2);
174
192
  scroll-padding-inline: var(--card-pad, 0rem);
@@ -185,7 +203,7 @@
185
203
  }
186
204
  /* a bleeding child sits over the container's rounded corner, so a strip that
187
205
  is genuinely first or last needs the corner clipped for it */
188
- .card:has(> .bleed:first-child), .card:has(> .bleed:last-child) { overflow: clip; }
206
+ .card:has(> :is(.bleed, .map--bleed):first-child), .card:has(> :is(.bleed, .map--bleed):last-child) { overflow: clip; }
189
207
 
190
208
  /* foot: the card's action buttons on one line. One button takes the width,
191
209
  up to four share it in even parts — so the gaps read even too — and from
package/css/journey.css CHANGED
@@ -54,6 +54,7 @@
54
54
  over the number and the month on one line; tall (a two-line title) = three lines, day · number · month. It grows no taller than
55
55
  its three lines (2026-10-21, timebank: beside a four-line title it was a tall empty block); held there, it stands at the head's top. */
56
56
  .tile-badge--stretch {
57
+ line-height: var(--tile-badge-line, var(--lh-badge)); /* the knob's own property; the cap below reads it too */
57
58
  align-self: stretch;
58
59
  max-block-size: calc((2 * var(--fs-caption) + var(--fs-lg)) * var(--tile-badge-line, var(--lh-badge)) + 2 * var(--sp-3));
59
60
  container-type: size;
@@ -65,7 +66,6 @@
65
66
  column-gap: var(--sp-1);
66
67
  row-gap: 0;
67
68
  padding-block: var(--sp-3);
68
- line-height: var(--tile-badge-line, var(--lh-badge));
69
69
  }
70
70
  .tile-badge--stretch > * { line-height: inherit; }
71
71
  .tile-badge--stretch > :first-child { grid-column: 1 / -1; }
@@ -73,6 +73,9 @@
73
73
  .tile-badge--stretch > * { grid-column: 1 / -1; }
74
74
  }
75
75
 
76
+ /* where it sits in a head that leads with the date (card.css): the first cell, as tall as the chips beside it however many rows, so no cap */
77
+ .card__head:has(> .tile-badge:first-child + .chip-row) > .tile-badge--stretch:first-child { grid-column: 1; grid-row: 1; max-block-size: none; }
78
+
76
79
  /* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
77
80
  .tile-badge--corner { position: absolute; right: var(--sp-3); bottom: var(--sp-3); }
78
81
 
package/css/sheet.css CHANGED
@@ -434,6 +434,7 @@
434
434
  border-radius: var(--map-radius, var(--r-media));
435
435
  overflow: hidden;
436
436
  }
437
+ .map--bleed { margin-inline: calc(-1 * var(--card-pad, 0rem)); border-radius: 0; } /* edge to edge is a band, and a band has no corner; --map-radius is for the box in the inset */
437
438
  .sheet__body:has(> .map--inline:only-child) { display: flex; flex-direction: column; }
438
439
  .sheet__body > .map--inline:only-child { flex: 1 0 auto; }
439
440
 
@@ -558,8 +559,15 @@
558
559
  }
559
560
  .sheet-step__summary { color: var(--sheet-step-summary-ink, var(--card-muted)); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
560
561
  .sheet-step__check { margin-inline-start: auto; }
561
- .sheet-step__body { min-height: 0; overflow: clip; overflow-clip-margin: var(--sp-1); }
562
- .sheet-step__inner { display: grid; gap: var(--card-gap); padding-block: var(--card-gap); padding-inline: var(--sheet-step-inner-pad-x, var(--sheet-step-bar-pad-x, var(--sp-3))); } /* the open step's content in the frame of its title's words (the bar's own inset), so it reads as that step's (Lh 2026-09-28, #153) */
562
+ /* a step's child that bleeds (ChipRow bleed, MapArea bleed, a Banner) reaches the SHEET's edges (Lh 2026-10-19, the ask sheet's
563
+ จุดนัด step: the map edge to edge under the step's buttons). The fold's clip is the step body's box, which sat inside the sheet's
564
+ inset, so the body takes that inset back and pads by it again, the way .sheet__body does; the inner keeps its own box and its own
565
+ inset (--sheet-step-inner-pad-x: the inset BEYOND the sheet's) and publishes the whole, the sheet's plus its own, as --card-pad,
566
+ so a .bleed cancels it exactly. The sum is computed on the body, before the inner's --card-pad takes over; outside a card there is
567
+ no --card-pad and the step keeps its own inset (0px, as .bleed reads it). The inner reads the knob itself, not the sum: a theme's
568
+ unitless 0 (SheetSteps · Ruled) is a length there but not in a calc, so the step stays right and only the bleed is lost. */
569
+ .sheet-step__body { --_step-pad: var(--sheet-step-inner-pad-x, var(--sheet-step-bar-pad-x, var(--sp-3))); --_pad: calc(var(--card-pad, 0rem) + var(--_step-pad)); margin-inline: calc(-1 * var(--card-pad, 0rem)); padding-inline: var(--card-pad, 0rem); min-height: 0; overflow: clip; overflow-clip-margin: var(--sp-1); }
570
+ .sheet-step__inner { --card-pad: var(--_pad); display: grid; gap: var(--card-gap); padding-block: var(--card-gap); padding-inline: var(--_step-pad); } /* the open step's content in the frame of its title's words (the bar's own inset), so it reads as that step's (Lh 2026-09-28, #153) */
563
571
  .sheet-step__inner > * { min-width: 0; }
564
572
  .sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s var(--ease-linear) var(--motion-sheet); }
565
573
  .sheet-step__go { position: absolute; inset: min(0rem, (var(--_row-h) - var(--tap-sm)) / 2) 0 auto; min-height: max(100%, var(--tap-sm)); /* a thin bar still takes a 44px tap */ margin: 0; cursor: pointer; }
package/css/tokens.css CHANGED
@@ -292,6 +292,7 @@
292
292
  --ink-tint: color-mix(in srgb, currentColor 12%, transparent); /* hover, rails, rests */
293
293
  --ink-rail: color-mix(in srgb, currentColor 30%, transparent); /* todo steps, ghost edges */
294
294
  --accent-soft: color-mix(in srgb, var(--accent) 22%, transparent); /* calendar marks */
295
+ --accent-wash: color-mix(in srgb, var(--accent) 22%, var(--action-bg)); /* the same wash laid on the paper of fields and buttons, opaque: a soft chip reads the same on any card (Chip soft) */
295
296
  --accent-mid: color-mix(in srgb, var(--accent) 50%, transparent);
296
297
  /* PASTELS (Lh 2026-10-20: a component never brings a colour of its own; every colour is the theme's): the nine pastel papers of a
297
298
  sticky note, a note chooser's swatch and a stamp's picture ground (src/sticky/tones.ts names them), and a stamp's paper. Light
@@ -3,6 +3,8 @@ import { type DotTone } from './Dot.js';
3
3
  export interface ChipProps extends ComponentProps<'span'> {
4
4
  /** filled accent: the selected / live state */
5
5
  on?: boolean;
6
+ /** a soft accent fill, no edge: a status that should be seen but is not the live one (a "new" in a card's head); `on` is the stronger */
7
+ soft?: boolean;
6
8
  /** quiet: an untouched default value — muted words, the thin edge, nothing to find the eye (docs/reference.md rule 13) */
7
9
  muted?: boolean;
8
10
  /** a leading signal dot: `a` (the accent signal), `b` (the secondary), or one of the signal set — ok · warn · error · info */
@@ -16,8 +18,8 @@ export interface ChipProps extends ComponentProps<'span'> {
16
18
  /** the chip's words */
17
19
  children?: ReactNode;
18
20
  }
19
- /** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `sm` = a small label only; `on` when it is the live one. For a choice use ChipPick (`sign` for the +/× interest picker). */
20
- export declare function Chip({ on, muted, dot, sm, as, href, className, children, ...rest }: ChipProps): import("react").DetailedReactHTMLElement<{
21
+ /** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `sm` = a small label only; `soft` for a status that should be seen, `on` when it is the live one. For a choice use ChipPick (`sign` for the +/× interest picker). */
22
+ export declare function Chip({ on, soft, muted, dot, sm, as, href, className, children, ...rest }: ChipProps): import("react").DetailedReactHTMLElement<{
21
23
  ref?: import("react").Ref<HTMLSpanElement> | undefined;
22
24
  key?: import("react").Key | null | undefined;
23
25
  defaultChecked?: boolean | undefined;
@@ -324,9 +326,11 @@ export declare function ChipPick({ type, sign, name, value, checked, defaultChec
324
326
  export interface ChipRowProps extends ComponentProps<'div'> {
325
327
  /** run the row to the card's edges as a one-line scrolling strip (`.bleed`: nowrap, overflow, the hairline ink scrollbar) */
326
328
  bleed?: boolean;
329
+ /** the words that LEAD the chips ("Meet at" before a place): set in the chips' own type, at the middle of their height */
330
+ lead?: ReactNode;
327
331
  }
328
332
  /** ChipRow — chips on a wrapping line (or a bleeding strip). */
329
- export declare function ChipRow({ bleed, className, ...rest }: ChipRowProps): import("react").JSX.Element;
333
+ export declare function ChipRow({ bleed, lead, className, children, ...rest }: ChipRowProps): import("react").JSX.Element;
330
334
  export interface ChipGridProps extends ComponentProps<'div'> {
331
335
  }
332
336
  /** ChipGrid — equal-width ChipPicks in a grid (time slots, skills). */
@@ -3,10 +3,10 @@ import { createElement } from 'react';
3
3
  import { cx } from '../cx.js';
4
4
  import { tagged } from '../pressable.js';
5
5
  import { Dot } from './Dot.js';
6
- /** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `sm` = a small label only; `on` when it is the live one. For a choice use ChipPick (`sign` for the +/× interest picker). */
7
- export function Chip({ on, muted, dot, sm, as, href, className, children, ...rest }) {
6
+ /** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `sm` = a small label only; `soft` for a status that should be seen, `on` when it is the live one. For a choice use ChipPick (`sign` for the +/× interest picker). */
7
+ export function Chip({ on, soft, muted, dot, sm, as, href, className, children, ...rest }) {
8
8
  const { el, props } = tagged(as, href);
9
- return createElement(el, { ...props, className: cx('chip', on && 'chip--on', muted && 'chip--muted', sm && 'chip--sm', className), ...rest }, dot && _jsx(Dot, { b: dot === 'b', tone: dot === 'a' || dot === 'b' ? undefined : dot }), children);
9
+ return createElement(el, { ...props, className: cx('chip', on && 'chip--on', soft && !on && 'chip--soft', muted && 'chip--muted', sm && 'chip--sm', className), ...rest }, dot && _jsx(Dot, { b: dot === 'b', tone: dot === 'a' || dot === 'b' ? undefined : dot }), children);
10
10
  }
11
11
  /** ChipPick — a chip that is a choice, on a sheet and never in a Card (the sheet is the paper the member fills in, Lh 2026-10-18): full-width in a ChipGrid (9:00 · 9:30 · …), or `sign` on a ChipRow (+ off, × on — the
12
12
  * interest picker); the picked one fills with accent. A native radio/checkbox inside the label carries the state. */
@@ -14,8 +14,8 @@ export function ChipPick({ type, sign, name, value, checked, defaultChecked, onC
14
14
  return (_jsxs("label", { className: cx('chip chip--pick', sign && 'chip--sign', className), ...rest, children: [_jsx("input", { type: type ?? (sign ? 'checkbox' : 'radio'), name: name, value: value, checked: checked, defaultChecked: defaultChecked, onChange: onChange }), children] }));
15
15
  }
16
16
  /** ChipRow — chips on a wrapping line (or a bleeding strip). */
17
- export function ChipRow({ bleed, className, ...rest }) {
18
- return _jsx("div", { className: cx('chip-row', bleed && 'bleed', className), ...rest });
17
+ export function ChipRow({ bleed, lead, className, children, ...rest }) {
18
+ return _jsxs("div", { className: cx('chip-row', bleed && 'bleed', className), ...rest, children: [lead != null && _jsx("span", { className: "chip-row__lead", children: lead }), children] });
19
19
  }
20
20
  /** ChipGrid — equal-width ChipPicks in a grid (time slots, skills). */
21
21
  export function ChipGrid({ className, ...rest }) {
@@ -12,7 +12,7 @@ export interface IconBtnGroupProps extends Omit<ComponentProps<'div'>, 'onChange
12
12
  defaultValue?: Picked;
13
13
  /** asks the app: `one` → the pressed value · `many` → the new list */
14
14
  onChange?: (value: Picked) => void;
15
- /** the buttons one OVER the other, the panel one button wide — at a `Row`'s `end`, where a panel across would take the words' room */
15
+ /** the buttons one OVER the other, the panel one button wide — at a `Row`'s end, where a panel across would take the words' room */
16
16
  vertical?: boolean;
17
17
  /** the panel as wide as its buttons (each one tap wide, or as wide as its word), in the middle of its column the way a `Btn form` is —
18
18
  * in a `Page`, a `Centre`, a `CtaPack`, and in a card's or a sheet's foot, where it keeps that width instead of filling the row.
package/dist/cardds.css CHANGED
@@ -333,6 +333,7 @@
333
333
  --ink-tint: color-mix(in srgb, currentColor 12%, transparent); /* hover, rails, rests */
334
334
  --ink-rail: color-mix(in srgb, currentColor 30%, transparent); /* todo steps, ghost edges */
335
335
  --accent-soft: color-mix(in srgb, var(--accent) 22%, transparent); /* calendar marks */
336
+ --accent-wash: color-mix(in srgb, var(--accent) 22%, var(--action-bg)); /* the same wash laid on the paper of fields and buttons, opaque: a soft chip reads the same on any card (Chip soft) */
336
337
  --accent-mid: color-mix(in srgb, var(--accent) 50%, transparent);
337
338
  /* PASTELS (Lh 2026-10-20: a component never brings a colour of its own; every colour is the theme's): the nine pastel papers of a
338
339
  sticky note, a note chooser's swatch and a stamp's picture ground (src/sticky/tones.ts names them), and a stamp's paper. Light
@@ -1025,19 +1026,33 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
1025
1026
  .card__head:has(> .stamp) { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
1026
1027
  .card__head:has(> .stamp) > :is(h1, h2, h3) { grid-column: 1; grid-row: 1; align-self: end; }
1027
1028
 
1029
+ /* a head that LEADS with a date (Lh 2026-10-21, timebank's request card): a `CardHead` whose first child is a JourneyBadge and whose second is a
1030
+ ChipRow. The date stands at the left and the chips beside it, the two as tall as each other; the heading and its caption take the whole
1031
+ width under them, so the heading sits right over the words of the middle. The date is always as tall as the chips. The gap under the date row is --card-head-lead-gap; the date and the chips place themselves (journey.css, actions.css).
1032
+ <div class="card__head"><span class="tile-badge tile-badge--stretch">…</span><div class="chip-row">…</div><div><h2 class="t-h1">…</h2>…</div></div> */
1033
+ .card__head:has(> .tile-badge:first-child + .chip-row) {
1034
+ display: grid;
1035
+ grid-template-columns: auto minmax(0, 1fr);
1036
+ align-items: center;
1037
+ row-gap: var(--card-head-lead-gap, var(--sp-3));
1038
+ }
1039
+ .card__head:has(> .tile-badge:first-child + .chip-row) > :nth-child(n + 3) { grid-column: 1 / -1; }
1040
+
1028
1041
  /* a JourneyBadge at the edge of the card's own first head (the date box in the top-left or top-right corner) keeps its corner concentric
1029
1042
  with the card's: the card's corner (--r-card) less the room the padding leaves round it. journey.css reads it behind the theme's --tile-badge-radius,
1030
1043
  so a theme that sets that knob still wins. */
1031
- .card > .card__head:first-child > .tile-badge:is(:first-child, :last-child) { --_badge-radius: calc(var(--r-card) - var(--card-pad)); }
1044
+ :is(.card > .card__head:first-child, .card__face > .card__turn + .card__head) > .tile-badge:is(:first-child, :last-child) { --_badge-radius: calc(var(--r-card) - var(--card-pad)); } /* a turning card's face: its head follows the unseen turn button */
1032
1045
 
1033
1046
  /* ---- paper bands: the two-tone card (`Card paper="bands"`, Lh 2026-10-19): the head and the foot are paper bands edge to
1034
1047
  edge over the card's padding, no hairline; the middle between them keeps the card's tone, lightened (--card-middle-bg). The card
1035
- clips, so the bands keep its corners. A two-faced card (below) carries the look on each face. */
1048
+ clips, so the bands keep its corners. A two-faced card (below) carries the look on each face, never on the turning box: a clip
1049
+ there cut the face off at the card's edges while it turned, its near edge being nearer the eye than the box (2026-10-21). */
1036
1050
  :is(.card--bands, .card--bands > .card__face) {
1037
1051
  background: var(--card-middle-bg, color-mix(in srgb, var(--card-bg) 35%, var(--card-1-bg)));
1038
1052
  --dark: var(--card-1-dark); /* the middle is mostly paper: its words take the paper's side */
1039
1053
  overflow: hidden;
1040
1054
  }
1055
+ .card--bands.card--turns { background: none; overflow: visible; } /* the turning box keeps --dark: the faces' ink is computed from it */
1041
1056
  :is(.card--bands, .card--bands > .card__face) > :is(.card__head, .card__foot) {
1042
1057
  --card-bg: var(--card-1-bg);
1043
1058
  --dark: var(--card-1-dark);
@@ -1047,6 +1062,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
1047
1062
  padding-inline: var(--card-pad);
1048
1063
  }
1049
1064
  :is(.card--bands, .card--bands > .card__face) > .card__head { margin-top: calc(-1 * var(--card-pad)); padding-block: var(--card-pad); }
1065
+ :is(.card--bands, .card--bands > .card__face) > .card__head { padding-block-end: var(--card-band-seam, var(--sp-2)); }
1066
+ /* the seam: the heading and the words under it read as ONE paragraph that changes colour at the band's edge (Lh 2026-10-21), so the room on
1067
+ each side of the head band's lower edge is --card-band-seam, not the card's padding and gap */
1068
+ :is(.card--bands, .card--bands > .card__face) > .card__head + * { margin-block-start: calc(var(--card-band-seam, var(--sp-2)) - var(--card-gap)); }
1050
1069
  :is(.card--bands, .card--bands > .card__face) > .card__foot { margin-bottom: calc(-1 * var(--card-pad)); padding-block: var(--sp-3); } /* the foot band is slim */
1051
1070
 
1052
1071
  /* ---- two faces: the WHOLE card turns (`Card back`, Lh 2026-10-19). The article is the turning box (.flip, base.css) and only that;
@@ -1093,7 +1112,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
1093
1112
  nothing jumps. Chromium/Safari paint it through the ::-webkit-scrollbar
1094
1113
  parts; Firefox gets its thin bar in the same ink. */
1095
1114
  .bleed {
1096
- margin-inline: calc(-1 * var(--card-pad, 0rem));
1115
+ margin-inline: calc(-1 * var(--card-pad, 0rem)); /* the edge a bleeding child takes back from what holds it; .map--bleed (sheet.css) reads the same --card-pad */
1097
1116
  padding-inline: var(--card-pad, 0rem);
1098
1117
  padding-block-end: var(--sp-2);
1099
1118
  scroll-padding-inline: var(--card-pad, 0rem);
@@ -1110,7 +1129,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
1110
1129
  }
1111
1130
  /* a bleeding child sits over the container's rounded corner, so a strip that
1112
1131
  is genuinely first or last needs the corner clipped for it */
1113
- .card:has(> .bleed:first-child), .card:has(> .bleed:last-child) { overflow: clip; }
1132
+ .card:has(> :is(.bleed, .map--bleed):first-child), .card:has(> :is(.bleed, .map--bleed):last-child) { overflow: clip; }
1114
1133
 
1115
1134
  /* foot: the card's action buttons on one line. One button takes the width,
1116
1135
  up to four share it in even parts — so the gaps read even too — and from
@@ -2435,6 +2454,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
2435
2454
  border-radius: var(--map-radius, var(--r-media));
2436
2455
  overflow: hidden;
2437
2456
  }
2457
+ .map--bleed { margin-inline: calc(-1 * var(--card-pad, 0rem)); border-radius: 0; } /* edge to edge is a band, and a band has no corner; --map-radius is for the box in the inset */
2438
2458
  .sheet__body:has(> .map--inline:only-child) { display: flex; flex-direction: column; }
2439
2459
  .sheet__body > .map--inline:only-child { flex: 1 0 auto; }
2440
2460
 
@@ -2559,8 +2579,15 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
2559
2579
  }
2560
2580
  .sheet-step__summary { color: var(--sheet-step-summary-ink, var(--card-muted)); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2561
2581
  .sheet-step__check { margin-inline-start: auto; }
2562
- .sheet-step__body { min-height: 0; overflow: clip; overflow-clip-margin: var(--sp-1); }
2563
- .sheet-step__inner { display: grid; gap: var(--card-gap); padding-block: var(--card-gap); padding-inline: var(--sheet-step-inner-pad-x, var(--sheet-step-bar-pad-x, var(--sp-3))); } /* the open step's content in the frame of its title's words (the bar's own inset), so it reads as that step's (Lh 2026-09-28, #153) */
2582
+ /* a step's child that bleeds (ChipRow bleed, MapArea bleed, a Banner) reaches the SHEET's edges (Lh 2026-10-19, the ask sheet's
2583
+ จุดนัด step: the map edge to edge under the step's buttons). The fold's clip is the step body's box, which sat inside the sheet's
2584
+ inset, so the body takes that inset back and pads by it again, the way .sheet__body does; the inner keeps its own box and its own
2585
+ inset (--sheet-step-inner-pad-x: the inset BEYOND the sheet's) and publishes the whole, the sheet's plus its own, as --card-pad,
2586
+ so a .bleed cancels it exactly. The sum is computed on the body, before the inner's --card-pad takes over; outside a card there is
2587
+ no --card-pad and the step keeps its own inset (0px, as .bleed reads it). The inner reads the knob itself, not the sum: a theme's
2588
+ unitless 0 (SheetSteps · Ruled) is a length there but not in a calc, so the step stays right and only the bleed is lost. */
2589
+ .sheet-step__body { --_step-pad: var(--sheet-step-inner-pad-x, var(--sheet-step-bar-pad-x, var(--sp-3))); --_pad: calc(var(--card-pad, 0rem) + var(--_step-pad)); margin-inline: calc(-1 * var(--card-pad, 0rem)); padding-inline: var(--card-pad, 0rem); min-height: 0; overflow: clip; overflow-clip-margin: var(--sp-1); }
2590
+ .sheet-step__inner { --card-pad: var(--_pad); display: grid; gap: var(--card-gap); padding-block: var(--card-gap); padding-inline: var(--_step-pad); } /* the open step's content in the frame of its title's words (the bar's own inset), so it reads as that step's (Lh 2026-09-28, #153) */
2564
2591
  .sheet-step__inner > * { min-width: 0; }
2565
2592
  .sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s var(--ease-linear) var(--motion-sheet); }
2566
2593
  .sheet-step__go { position: absolute; inset: min(0rem, (var(--_row-h) - var(--tap-sm)) / 2) 0 auto; min-height: max(100%, var(--tap-sm)); /* a thin bar still takes a 44px tap */ margin: 0; cursor: pointer; }
@@ -2850,6 +2877,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
2850
2877
  over the number and the month on one line; tall (a two-line title) = three lines, day · number · month. It grows no taller than
2851
2878
  its three lines (2026-10-21, timebank: beside a four-line title it was a tall empty block); held there, it stands at the head's top. */
2852
2879
  .tile-badge--stretch {
2880
+ line-height: var(--tile-badge-line, var(--lh-badge)); /* the knob's own property; the cap below reads it too */
2853
2881
  align-self: stretch;
2854
2882
  max-block-size: calc((2 * var(--fs-caption) + var(--fs-lg)) * var(--tile-badge-line, var(--lh-badge)) + 2 * var(--sp-3));
2855
2883
  container-type: size;
@@ -2861,7 +2889,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
2861
2889
  column-gap: var(--sp-1);
2862
2890
  row-gap: 0;
2863
2891
  padding-block: var(--sp-3);
2864
- line-height: var(--tile-badge-line, var(--lh-badge));
2865
2892
  }
2866
2893
  .tile-badge--stretch > * { line-height: inherit; }
2867
2894
  .tile-badge--stretch > :first-child { grid-column: 1 / -1; }
@@ -2869,6 +2896,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
2869
2896
  .tile-badge--stretch > * { grid-column: 1 / -1; }
2870
2897
  }
2871
2898
 
2899
+ /* where it sits in a head that leads with the date (card.css): the first cell, as tall as the chips beside it however many rows, so no cap */
2900
+ .card__head:has(> .tile-badge:first-child + .chip-row) > .tile-badge--stretch:first-child { grid-column: 1; grid-row: 1; max-block-size: none; }
2901
+
2872
2902
  /* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
2873
2903
  .tile-badge--corner { position: absolute; right: var(--sp-3); bottom: var(--sp-3); }
2874
2904
 
@@ -3080,12 +3110,17 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
3080
3110
  .dot--error { background: var(--signal-error); }
3081
3111
  .dot--info { background: var(--signal-info); }
3082
3112
  /* --sm: a small LABEL (a name on a note, a tag in a tight head) — low and tight; never a tap target */
3083
- .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)); }
3113
+ /* one height wherever it stands (Lh 2026-10-21): the body's line, so a head that cuts its words with an ellipsis keeps the Thai marks over them, and a foot's or a middle's chip reads the same */
3114
+ .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)); line-height: var(--chip-sm-line, var(--lh-body)); }
3115
+ /* soft: the accent washed into the chip's ground, no edge — a status to see, quieter than on (was the inbox Ribbon, Lh 2026-10-21) */
3116
+ .chip--soft { background: var(--chip-soft-bg, var(--accent-wash)); color: var(--chip-soft-ink, var(--card-ink)); border-color: transparent; } /* the wash on the chip's own paper, opaque (--accent-wash): see-through, a dark tone came through under the light side's ink (ink.spec) */
3084
3117
  .chip--on { background: var(--chip-on-bg, var(--accent)); --dark: var(--accent-dark); color: var(--chip-on-ink, var(--card-ink)); border-color: transparent; }
3085
3118
  /* muted: an untouched default — the words wash out, the edge stays; emphasis is the difference from the neighbours (docs/reference.md 13) */
3086
3119
  .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 */
3087
3120
 
3088
3121
  .chip-row { display: flex; gap: var(--sp-2); }
3122
+ /* the words that lead the chips (ChipRow `lead`, "นัดที่" before a place, Lh 2026-10-21): the chips' own type and size, at the middle of their height */
3123
+ .chip-row__lead { align-self: center; font: var(--type-label); font-size: var(--chip-font-size, var(--fs-sm)); color: var(--chip-row-lead-ink, inherit); }
3089
3124
  .chip-row:not(.bleed) { flex-wrap: wrap; } /* a strip (.bleed, card.css) stays one line */
3090
3125
  .chip-row.bleed > .chip { flex: none; white-space: nowrap; } /* a chip of the strip keeps its words on one line at its own width: the strip scrolls, a chip never shrinks to fit it */
3091
3126
 
@@ -3338,8 +3373,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
3338
3373
 
3339
3374
  /* ---- where it sits in what card.css draws (docs/reference.md rule 15) ---- */
3340
3375
  /* a head's ChipRow takes a line of its own under the title: at most two lines of chips, a chip's words cut with an ellipsis */
3341
- .card__head > .chip-row { flex: 1 1 100%; order: 2; max-height: calc(2 * (var(--fs-sm) * 1.5 + 2 * var(--chip-sm-pad-y, var(--sp-0)) + 2 * var(--border-w)) + var(--sp-2)); overflow: hidden; }
3342
- .card__head > .chip-row > .chip { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: var(--lh-body); }
3376
+ .card__head > .chip-row { flex: 1 1 100%; order: 2; max-height: calc(2 * (var(--fs-sm) * var(--chip-sm-line, var(--lh-body)) + 2 * var(--chip-sm-pad-y, var(--sp-0)) + 2 * var(--border-w)) + var(--sp-2)); overflow: hidden; }
3377
+ /* …beside a date that leads the head (card.css) they stand in the date's row, every chip in sight: the meeting place may take a line of its own */
3378
+ .card__head:has(> .tile-badge:first-child + .chip-row) > .chip-row { grid-column: 2; grid-row: 1; max-height: none; }
3379
+ .card__head > .chip-row > .chip { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3343
3380
  .card__foot > .btn { flex: 1 1 0; min-width: 0; }
3344
3381
  /* …unless the button says its own size: `Btn form` is as wide as its words in a foot too, centred (Lh 2026-10-10: one button filling
3345
3382
  a card's foot read too big — which button a card wears is the project's choice, so the foot must not undo it) */