@usableapp/cardds 0.7.93 → 0.7.94
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 +27 -0
- package/README.md +7 -4
- package/css/actions.css +33 -17
- package/css/base.css +1 -1
- package/css/card.css +1 -1
- package/css/choice.css +16 -5
- package/css/sheet.css +68 -5
- package/css/tokens.css +4 -1
- package/dist/cardds.css +123 -30
- package/dist/cards/Card.d.ts +2 -1
- package/dist/cards/CardParts.d.ts +2 -1
- package/dist/cards/CardParts.js +2 -1
- package/dist/catalog.json +1 -1
- package/dist/choice/DayStrip.d.ts +5 -3
- package/dist/choice/DayStrip.js +4 -4
- package/dist/forms/Composer.d.ts +9 -3
- package/dist/forms/Composer.js +8 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/media/MapArea.d.ts +12 -6
- package/dist/media/MapArea.js +8 -6
- package/dist/media/MapPick.d.ts +32 -0
- package/dist/media/MapPick.js +13 -0
- package/dist/scaffold/ActionBar.d.ts +7 -3
- package/dist/scaffold/ActionBar.js +5 -1
- package/lint/eslint.mjs +1 -1
- package/lint/rules.mjs +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
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.
|
|
4
4
|
|
|
5
|
+
## 0.7.94 — 2026-10-18
|
|
6
|
+
|
|
7
|
+
From timebank's help-ask sheet (Lh 2026-10-17). One look changes on existing screens: a row card's corner (41.6 → 13). Everything else is added, and a screen that does not use it is as it was.
|
|
8
|
+
|
|
9
|
+
Nothing here breaks an existing screen: one prop (`Day today`), one class (`.day--today`) and two knobs are added and none removed; a week without `today` is as it was.
|
|
10
|
+
|
|
11
|
+
One look CHANGES: a row card's corner. Before → after: `Card row` took the one card radius, `--r-card` (41.6), so a two-line row (88 tall at 375) read as a pill → it takes `--r-card-row` (13 = `--r-base` × 0.625), the big card's proportion of corner to height (Lh 2026-10-17: a small card's corner shrinks to keep the same proportion as a big card's). A project that wants the old corner sets `--card-row-radius: var(--r-card)`. One new check may fail a project's `cardds check`: a card inside a card.
|
|
12
|
+
|
|
13
|
+
Nothing here breaks an existing screen: one prop (`MapArea inline`), one component (`MapPick`), four classes (`.map--inline`, `.map-pick`, `.map-pick__mark`, `.map-pick__name`) and thirteen knobs are added, none removed. One look changes: a map's hint is no longer squeezed onto two lines.
|
|
14
|
+
|
|
15
|
+
Nothing here breaks a screen that passes `lead`, `hint` or `send`: one part (`ComposerArea`), one class (`.composer__area`) and four knobs are added and none removed. One thing a screen can see: a `Composer` with no `lead`, `hint` or `send` no longer draws an empty bar.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`Day today`: the week view says which day is today** (Lh 2026-10-17: "today" for the week view; `Calendar` is not touched). Before → after: a `DayStrip` drew every unpicked day alike, so the member could not tell today from the rest → the day with `today` gets `aria-current="date"` and a light tone behind it, the knob `--day-today-bg` (default `--ink-tint`, the ink's own 12%), its words `--day-today-ink` (default the day's own ink). It is no louder than a resting day: not the accent of the selected pill, not the dot's shape. When today is also the selected day the filled pill wins and `aria-current="date"` stays. The app says which day is today, as it says which is selected. Stories `DayStrip · Today` and `DayStrip · TodayPicked`.
|
|
19
|
+
- **`--r-card-row`, the row card's corner.** A root token on `--r-base`, so a square theme (`--r-base: 0`) squares rows too. `--card-row-radius` still sets it from outside.
|
|
20
|
+
- **A pickable row card.** Before → after: `Card row as="label"` round a radio showed the native radio → the radio lies unseen over the whole card (as a chip's or a tile's does), the focus ring is the card's while its radio has the keyboard, and the app gives the picked card its `tone`. Story `Card · CardListPick` (a `CardList` straight in a sheet's body); the proportion side by side in `Card · RowProportion`.
|
|
21
|
+
- **`cardds check` rule `card-in-card` (error).** A `CardList`, `CardFeed`, `FeedCard` or `Card` written inside a `Card` is flagged: a card never wraps a card (Lh 2026-10-17). A `Card sm` inside a card is not flagged. The ESLint plugin reports it under `cardds/check`.
|
|
22
|
+
- **`MapArea inline`: the map in the sheet** (Lh 2026-10-17: "build the map in the sheet", from timebank's help-ask, choosing where to meet). Before → after: a map could only lie behind a stage sheet (`SheetStage bg`), where a half sheet covers half of it and an `over` stage covers the sheet's head; inside a `CardMedia` it was small and its ring was centred on a stage band that was not there → `inline` puts the map in flow. In a card it is a rounded media box (`--map-inline-h`, default 1.5 × `--map-ring`; `--map-radius`, default `--r-media`). As a `SheetBody`'s only child it fills the body, inside the sheet's padding, from under the head to above the foot, so the open sheet becomes the map and its head stays. The ring is at the centre of the map's own box and the hint at its own top. Stories `MapArea · InSheet` and `MapArea · InCard`.
|
|
23
|
+
- **`MapPick`: a place on a map the member taps to choose it.** Before → after: the only thing to pin on a map was a `Callout`, a card that shows and takes no tap, and five of them crowd a phone → a small round mark (an optional icon inside) with the place's short name under it, standing on its point (`top` / `left`, as a Callout). The tap is a `--tap` square round the point, and the name takes a tap too. It is a radio: the marks of one choice share `name`, the app owns `checked` + `onChange` (or `defaultChecked`), and the picked one fills with accent. It goes in a `MapArea` after the engine's mount. Knobs `--map-pick-size`, `-bg`, `-border`, `-mark-ink`, `-shadow`, `-ink`, `-name-bg`, `-name-radius`, `-font`, `-on-bg`, `-on-ink`. Stories `MapPick · Five` and `MapPick · Plain`.
|
|
24
|
+
- **`ComposerArea`: a second writing area in the same Composer, behind a dashed rule** (Lh 2026-10-17, timebank's help ask: the request over several lines and, if the member wants, the reason under it, in ONE card and one look; a `Composer` beside a `Field` was two looks, rejected). Before → after: a reason under the request was a second `Composer` (11rem tall, nothing between the two) or a `Field` (one line, another type) → `<Composer …><ComposerArea aria-label="เหตุผล" value={why} onChange={…} /></Composer>` draws a textarea in the Composer's own type, two lines tall (`2lh`), above the bar, with a dashed rule over it that the area draws itself (`border-block-start`, no selector looks at what sits before it). It is controlled like any textarea and takes the step cursor (`useStepCursor`) as the Composer's own textarea does: the open step's first field keeps the mark. Knobs: `--composer-area-min-h` (`2lh`, its words alone: the rule and the room come on top), `--composer-area-pad` (`--sp-3`, the room under the rule), `--composer-area-border` (the rule's colour, `--card-muted`, the hint's ink); the rule is `--rule-w` wide, the system's dotted-connector width. Story `Composer · WithReason`.
|
|
25
|
+
- **`--composer-input-min-h`: the first writing area's minimum height** (default `11rem`, the value it had, hard-coded). Before → after: a shorter request area needed cardds → a project sets it.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- **The pager may say where it goes** (Lh 2026-10-17, from timebank's help-ask sheet: back and next in a stepped task name the step they lead to). Before → after: a pager button was a round `IconBtn` and never carried words (Lh 2026-09-16, "for good") → it may also be a `Btn` with the step's name and an arrow: `<ActionBar variant="pager"><Btn><Icon name="arrow-left" /> คำขอ</Btn><Btn primary>วันเวลา <Icon name="arrow-right" /></Btn></ActionBar>`, and the same in an `ActionBarTier pager`. Worded, they read as left and right like the round pair (Lh 2026-10-17, second call): each hugs its edge at its own size, words plus an arrow as big as the round pager's (the md `Icon`, not `xs`), with open ground between; two widths are normal. A `Link` between them stays in the middle. CSS: in a pager bar or tier a `.btn` is `flex: 0 0 auto` (a Btn in the plain bar still stretches) and the edge rules that placed the first `.icon-btn` now place a first `.btn` the same way; a pager with a link between is now a three-column grid (`1fr auto 1fr`), so the link stays at the bar's middle when the two buttons are two widths (the round `PagerSkip` lands on the same pixels as before: link centre 187.5 of 375); no knob added. The round pager is unchanged and still valid. The names are steps: a word that commits ("Done") is still the plain bar's one wide `Btn`. Rule rewritten in css/actions.css, `ActionBar` doc, README, CLAUDE.md, docs/guides/building-with-cardds.md, .design-sync/conventions.md. Stories `ActionBar · PagerWords`, `PagerWordsSkip`, `PagerWordsOnAStep`; tests/gallery-components.spec.js.
|
|
29
|
+
- **A map's hint is centred by its two sides.** Before → after: `left: 50%` + `translate` left the words only half the map's width to wrap in, so "Drag the map until the ring covers your area" broke onto two lines on a 375 phone → the hint is as wide as its words up to the map less `--sp-4` at each side. On the stage stories the hint pill went from 186.5px to 237px (`AddressStage`) and 327px (`TrackOnTop`) wide, one line.
|
|
30
|
+
- **`Composer` draws its bar only when it holds a `lead`, a `hint` or a `send`.** Before → after: with none of them an empty `.composer__bar` stood under the textarea and took one `--composer-gap` → nothing is drawn (`SheetSteps · ComposerFirst` is that case).
|
|
31
|
+
|
|
5
32
|
## 0.7.93 — 2026-10-17
|
|
6
33
|
|
|
7
34
|
Nothing here breaks an existing screen: one prop (`StampBook fill`), one class (`.stamp-book--fill`) and one knob are added and none removed; a book without `fill` is as it was.
|
package/README.md
CHANGED
|
@@ -437,9 +437,12 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
437
437
|
- **The bar is the screen's bottom.** Plain `.action-bar` for one move; with a
|
|
438
438
|
control that belongs to the screen (a slider), `.action-bar--tiers` stacks a
|
|
439
439
|
control tier above the buttons (it publishes the taller `--bar-reserve` the centred card still reads for its cap).
|
|
440
|
-
- **The
|
|
441
|
-
holding two
|
|
442
|
-
|
|
440
|
+
- **The pager bar** is `.action-bar--pager` (or an `.action-bar__tier--pager`)
|
|
441
|
+
holding two peers at the two edges, a skip link between or nothing: two round
|
|
442
|
+
`.icon-btn`s, or two `.btn`s that name the step each leads to with the arrow at the
|
|
443
|
+
round pager's size ("← คำขอ" · "วันเวลา →", Lh 2026-10-17; the 2026-09-16 "never words" is lifted).
|
|
444
|
+
Worded, each hugs its edge at its own size with open ground between: left and right, never two halves. The names are steps; the step that
|
|
445
|
+
commits ("Done") still goes back to one wide button (rule 3).
|
|
443
446
|
- **Page that moves** (Lh 2026-09-20) — two ways, never both on one screen:
|
|
444
447
|
- *Pages of words:* `.step > .pages > .page` (one `aria-current="step"`, the rest `inert`) — every page
|
|
445
448
|
one height, the round pager bar turns them (`slideTo()`), the words slide like a page. No sheet there.
|
|
@@ -909,7 +912,7 @@ That file is where a project's look lives. Bases and roles:
|
|
|
909
912
|
|---|---|---|
|
|
910
913
|
| room **between** things | `--sp-base` (0.25rem = 4) | every `--sp-N` = base × N → paddings, gaps, the screen's inset, the bar's reserve, the sheet's peek |
|
|
911
914
|
| type | `--fs-base` (1.375rem = 22 — the body) | every `--fs-*`, `--badge`, `--icon-xs` |
|
|
912
|
-
| corners | `--r-base` (1.3rem = 20.8) | every `--r-*`; `0` = square. `--r-chip` stays fully round |
|
|
915
|
+
| corners | `--r-base` (1.3rem = 20.8) | every `--r-*`; `0` = square. `--r-chip` stays fully round. One card radius (`--r-card`) for every card, tile, menu, sheet, fab, but a small card keeps the big card's PROPORTION, not its size: a row card is `--r-card-row` (13) |
|
|
913
916
|
| the size **of** things | `--tap-base` (3rem = 48) | `--tap*`, `--btn-h`, `--chip-h`, `--bar-h`, `--header-h`, `--fab`, `--icon*`, `--avatar*`, `--dot*`, `--ring*`, `--tile-*`. `--tap*` never under `--tap-floor` (44px) |
|
|
914
917
|
| faces | `--font-ui`, `--font-display` | |
|
|
915
918
|
| colour | `--color-surface`, `--color-ink`, `--color-paper`, `--color-brand`, `--color-on-brand` — no default: unset = the wireframe | every role: surface / words, the three card tones (paper · the brand washed 14% into the surface · the brand), the muted inks (the ink washed into their ground), accent, neutral actions, `--dot-a` |
|
package/css/actions.css
CHANGED
|
@@ -284,18 +284,24 @@
|
|
|
284
284
|
.action-bar .btn { flex: 1; box-shadow: var(--shadow-float); }
|
|
285
285
|
.action-bar .icon-btn { width: var(--btn-h); height: var(--btn-h); box-shadow: var(--shadow-float); }
|
|
286
286
|
|
|
287
|
-
/* ---- pager: previous / next as two
|
|
287
|
+
/* ---- pager: previous / next as two peers at the two edges ----
|
|
288
288
|
For a sequence you can walk BOTH ways — a three-card feature intro, a step
|
|
289
289
|
flow. Use it instead of the default pack whenever going back is as ordinary
|
|
290
290
|
as going on: a wide CTA beside a small round icon reads as one action plus
|
|
291
291
|
an afterthought, which is wrong when the two are peers.
|
|
292
292
|
|
|
293
|
-
The two are the same
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
293
|
+
The two are the same shape and size, one at each edge in thumb reach from
|
|
294
|
+
either side; the fill alone leans forward. Two ways to draw them, both
|
|
295
|
+
valid: two round .icon-btn (an arrow is the whole meaning), or two .btn
|
|
296
|
+
that SAY the step each leads to, the arrow beside the name ("← คำขอ",
|
|
297
|
+
"วันเวลา →"; Lh 2026-10-17, lifting the 2026-09-16 "never words"). Worded,
|
|
298
|
+
they read as LEFT and RIGHT like the round pair (Lh 2026-10-17, second
|
|
299
|
+
call): each hugs its edge at its own size (its words + the arrow, no
|
|
300
|
+
stretch, so two widths are normal), open ground between them, and the
|
|
301
|
+
arrow stays the round pager's size (the md .icon, not xs). The words are
|
|
302
|
+
a STEP's name, a direction: a word that commits ("Get started", "Done")
|
|
303
|
+
is not a pager button — that step goes back to the plain .action-bar
|
|
304
|
+
with one wide button. Where you are is said by the .pager__at dots — under the card
|
|
299
305
|
that changes (a .screen__centre group, card.css) for a card walk, never in
|
|
300
306
|
that bar (Lh, 2026-09-12); a walk of words instead floats its dots as the
|
|
301
307
|
bar's own first row (step.css, Lh 2026-09-23) — either way the dots move
|
|
@@ -304,7 +310,11 @@
|
|
|
304
310
|
<div class="action-bar action-bar--pager">
|
|
305
311
|
<button class="icon-btn" aria-label="ย้อนกลับ" disabled>…</button>
|
|
306
312
|
<button class="icon-btn icon-btn--invert" aria-label="ถัดไป">…</button>
|
|
307
|
-
</div> — or the same two in an .action-bar__tier--pager under a control
|
|
313
|
+
</div> — or the same two in an .action-bar__tier--pager under a control
|
|
314
|
+
<div class="action-bar action-bar--pager">
|
|
315
|
+
<button class="btn"><svg class="icon">…</svg> คำขอ</button>
|
|
316
|
+
<button class="btn btn--primary">วันเวลา <svg class="icon">…</svg></button>
|
|
317
|
+
</div> — worded: each at its edge, its own size; a skip link between stays centred */
|
|
308
318
|
.action-bar--pager { align-items: center; }
|
|
309
319
|
/* The bar belongs to the BASE: the move of a page of words — a pager turning them, or one wide button. A screen
|
|
310
320
|
with a sheet finishes on the sheet's own foot instead (sheet.css). Guidance for composing, not a rule the
|
|
@@ -342,11 +352,17 @@
|
|
|
342
352
|
:has(> .action-bar--tiers > .action-bar__tier:nth-child(4)) { --bar-reserve: calc(3 * (var(--tap) + var(--sp-3)) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6) + var(--safe-bottom)); }
|
|
343
353
|
:has(> .action-bar--tiers > .action-bar__tier:nth-child(5)) { --bar-reserve: calc(4 * (var(--tap) + var(--sp-3)) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6) + var(--safe-bottom)); }
|
|
344
354
|
|
|
345
|
-
/* the first round
|
|
346
|
-
|
|
355
|
+
/* the first button (round or worded) sits at the left edge, the last at the right; a skip link between them stays centred.
|
|
356
|
+
A worded one keeps its own size here, not the plain bar's stretch: left and right, open ground between (Lh 2026-10-17) */
|
|
357
|
+
:is(.action-bar--pager, .action-bar__tier--pager) > .btn { flex: 0 0 auto; }
|
|
358
|
+
:is(.action-bar--pager, .action-bar__tier--pager) > :is(.icon-btn, .btn):first-child { margin-right: auto; }
|
|
347
359
|
/* …and whatever goes BETWEEN the two (a "skip" link) stands in the middle: the bar places it, never the link itself */
|
|
348
|
-
:is(.action-bar--pager, .action-bar__tier--pager) > :not(.icon-btn):not(:first-child):not(:last-child) { margin-inline: auto; }
|
|
349
|
-
:is(.action-bar--pager, .action-bar__tier--pager):has(> :nth-child(3)) > .icon-btn:first-child { margin-right: 0; }
|
|
360
|
+
:is(.action-bar--pager, .action-bar__tier--pager) > :not(.icon-btn, .btn):not(:first-child):not(:last-child) { margin-inline: auto; }
|
|
361
|
+
:is(.action-bar--pager, .action-bar__tier--pager):has(> :nth-child(3)) > :is(.icon-btn, .btn):first-child { margin-right: 0; }
|
|
362
|
+
/* with a link between, three columns: two worded buttons are two widths, and only even side columns keep the link at the bar's middle */
|
|
363
|
+
:is(.action-bar--pager, .action-bar__tier--pager):has(> :nth-child(3)) { display: grid; grid-template-columns: 1fr auto 1fr; }
|
|
364
|
+
:is(.action-bar--pager, .action-bar__tier--pager):has(> :nth-child(3)) > :first-child { justify-self: start; }
|
|
365
|
+
:is(.action-bar--pager, .action-bar__tier--pager):has(> :nth-child(3)) > :last-child { justify-self: end; }
|
|
350
366
|
|
|
351
367
|
|
|
352
368
|
/* ---- action note (Lh 2026-09-24): the main button's place, the main button's size, but ON the ground — no fill, no border, no
|
|
@@ -381,7 +397,7 @@
|
|
|
381
397
|
button's, the card itself is never focused or disabled. */
|
|
382
398
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
383
399
|
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
384
|
-
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach, .stamp-on__remove) {
|
|
400
|
+
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach, .stamp-on__remove, .map-pick) {
|
|
385
401
|
cursor: pointer;
|
|
386
402
|
touch-action: manipulation;
|
|
387
403
|
transition: scale var(--motion-press) ease-out;
|
|
@@ -393,14 +409,14 @@
|
|
|
393
409
|
}
|
|
394
410
|
/* a CHOICE's fill fades in as it is picked (a plain button's colour never changes on a tap, so it does not fade) */
|
|
395
411
|
:where(.chip--pick, button.chip, .tile--pick, button.tile, .day, .segment__item, .calendar__day, .float-bar__item, .picker__item, .check,
|
|
396
|
-
.icon-btn[role="tab"], .avatar-pick__item > .avatar) {
|
|
412
|
+
.icon-btn[role="tab"], .avatar-pick__item > .avatar, .map-pick > .map-pick__mark, .map-pick > .map-pick__name) {
|
|
397
413
|
transition-property: scale, background-color, color, border-color, opacity;
|
|
398
414
|
transition-duration: var(--motion-press);
|
|
399
415
|
transition-timing-function: ease-out;
|
|
400
416
|
}
|
|
401
417
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
402
418
|
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
403
|
-
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach, .stamp-on__remove):active:not(:disabled, [aria-disabled="true"], :has(:disabled)) { scale: var(--press-scale); }
|
|
419
|
+
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach, .stamp-on__remove, .map-pick):active:not(:disabled, [aria-disabled="true"], :has(:disabled)) { scale: var(--press-scale); }
|
|
404
420
|
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .card--go, .feed-card--small, .toggle-field, .post-box__edge):active:not([aria-disabled="true"], :has(:disabled)) { opacity: var(--press-dim); }
|
|
405
421
|
.sheet-step:has(> .sheet-step__go:active) { opacity: var(--press-dim); } /* the step bar's button is invisible over it: the bar answers */
|
|
406
422
|
/* a pile of notes pressed whole (Lh 2026-10-11): the button lies invisible UNDER the notes, so it is the notes that answer — DIM, as a card does (the group dims as one, the
|
|
@@ -413,11 +429,11 @@
|
|
|
413
429
|
it darkens a touch instead (--press-shade) */
|
|
414
430
|
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn, .post-letter__sheet:not(:has(.icon-btn:active))) { cursor: pointer; touch-action: manipulation; transition: filter var(--motion-press) ease-out; }
|
|
415
431
|
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn, .post-letter__sheet:not(:has(.icon-btn:active))):active { filter: brightness(var(--press-shade)); }
|
|
416
|
-
/* the focus ring: on the control, or on the label that wraps a hidden input (chip, tile, file button, a row that is a label) */
|
|
432
|
+
/* the focus ring: on the control, or on the label that wraps a hidden input (chip, tile, file button, a row that is a label, a card that is a label round its radio) */
|
|
417
433
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
418
434
|
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
419
435
|
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach, .stamp-on__remove):focus-visible,
|
|
420
|
-
:where(.chip--pick, .tile--pick, label.btn, label.row, .toggle-field):has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
436
|
+
:where(.chip--pick, .tile--pick, label.btn, label.row, .toggle-field):has(:focus-visible), :where(label.card:has(> input[type="radio"]:focus-visible)) { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
421
437
|
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .feed-card--small, .toggle-field, .post-box__edge):not(label, .toggle-field):focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
422
438
|
/* disabled: no pointer anywhere; a control fades (a link and a row keep their own muted look) */
|
|
423
439
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
package/css/base.css
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
the member wants to copy), and inputs are untouched. Native buttons and
|
|
10
10
|
links are matched by element; the rest by the class that makes them one */
|
|
11
11
|
button, [role="button"], [role="tab"], [role="option"], [role="menuitem"], a.btn, a.icon-btn, a.chip, label.chip, .chip--pick,
|
|
12
|
-
.segment__item, .day, .calendar__day, .mood__opt, .pin__cell, .slider, .picker__item, .float-bar__item, .menu__item, .sheet__handle, .track__step, .pager__at {
|
|
12
|
+
.segment__item, .day, .calendar__day, .mood__opt, .pin__cell, .slider, .picker__item, .float-bar__item, .menu__item, .sheet__handle, .track__step, .pager__at, .map-pick {
|
|
13
13
|
user-select: none;
|
|
14
14
|
-webkit-user-select: none;
|
|
15
15
|
-webkit-touch-callout: none;
|
package/css/card.css
CHANGED
|
@@ -300,7 +300,7 @@
|
|
|
300
300
|
.card-list { display: grid; gap: var(--card-list-gap, var(--sp-3)); }
|
|
301
301
|
|
|
302
302
|
.card--row {
|
|
303
|
-
border-radius: var(--card-row-radius, var(--r-card));
|
|
303
|
+
border-radius: var(--card-row-radius, var(--r-card-row)); /* small: the big card's corner in proportion, not its size */
|
|
304
304
|
padding: var(--card-row-pad, var(--sp-4));
|
|
305
305
|
display: flex;
|
|
306
306
|
align-items: center;
|
package/css/choice.css
CHANGED
|
@@ -84,8 +84,9 @@
|
|
|
84
84
|
.chip-grid > .chip { justify-self: stretch; justify-content: center; min-height: var(--btn-h); padding-inline: var(--sp-2); }
|
|
85
85
|
.chip--pick { position: relative; } /* the input covers the whole chip, on a row as in a grid */
|
|
86
86
|
/* the hidden input, the same shape wherever a native radio/checkbox drives a picked look:
|
|
87
|
-
a chip, a mood dot, a tile (media.css), a sticky's tone swatch (sticky.css)
|
|
88
|
-
|
|
87
|
+
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
|
+
(a pickable row card: the app gives the picked one its `tone`) — the native input lies over its whole box, unseen */
|
|
89
|
+
: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; }
|
|
89
90
|
:is(.chip--pick, .tile--pick):has(:checked) { background: var(--accent); color: var(--on-accent); border-color: transparent; }
|
|
90
91
|
/* --sign: the trailing + that turns 45° into an × when picked — one mask, one rotate, no swap (Lh 2026-09-16) */
|
|
91
92
|
.chip--sign { padding-right: var(--sp-2); }
|
|
@@ -101,7 +102,7 @@
|
|
|
101
102
|
.chip--sign:has(:checked)::after { rotate: 45deg; }
|
|
102
103
|
@media (prefers-reduced-motion: no-preference) { .chip--sign::after { transition: rotate var(--motion-slide); } }
|
|
103
104
|
|
|
104
|
-
/* ---- day strip: a week, one day selected ----
|
|
105
|
+
/* ---- day strip: a week, one day selected, today a light tone ----
|
|
105
106
|
<div class="day-strip" role="tablist"><button class="day" role="tab" aria-selected="true"><span class="day__name">Thu</span><span class="day__num">16</span></button>…</div> */
|
|
106
107
|
.day-strip { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--day-strip-gap, var(--sp-1)); }
|
|
107
108
|
.day {
|
|
@@ -124,6 +125,8 @@
|
|
|
124
125
|
}
|
|
125
126
|
.day__name { font: var(--day-name-font, var(--type-caption)); }
|
|
126
127
|
.day__num { font: 700 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
|
|
128
|
+
/* 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); }
|
|
127
130
|
.day[aria-selected="true"] { background: var(--day-selected-bg, var(--accent)); color: var(--day-selected-ink, var(--on-accent)); opacity: 1; }
|
|
128
131
|
.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; }
|
|
129
132
|
|
|
@@ -237,11 +240,13 @@
|
|
|
237
240
|
|
|
238
241
|
/* ---- composer: big text area with a bottom bar (icon · hint · send) ----
|
|
239
242
|
<div class="composer"><textarea class="composer__input" placeholder="What do you need?"></textarea>
|
|
240
|
-
<div class="composer__bar"><button class="icon-btn">…</button><span class="composer__hint">Your idea is born</span><button class="icon-btn icon-btn--invert">↑</button></div></div>
|
|
243
|
+
<div class="composer__bar"><button class="icon-btn">…</button><span class="composer__hint">Your idea is born</span><button class="icon-btn icon-btn--invert">↑</button></div></div>
|
|
244
|
+
A second thing to write (the reason under the request) is <textarea class="composer__input composer__area"> after the
|
|
245
|
+
first: the same type, shorter, and the area draws its own dashed rule above it (no selector looks at what sits before it). */
|
|
241
246
|
.composer { display: grid; gap: var(--composer-gap, var(--sp-3)); }
|
|
242
247
|
.composer__input {
|
|
243
248
|
width: 100%;
|
|
244
|
-
min-height: 11rem;
|
|
249
|
+
min-height: var(--composer-input-min-h, 11rem);
|
|
245
250
|
resize: none;
|
|
246
251
|
border: 0;
|
|
247
252
|
padding: 0;
|
|
@@ -252,6 +257,12 @@
|
|
|
252
257
|
outline: none;
|
|
253
258
|
}
|
|
254
259
|
.composer__input::placeholder { color: inherit; opacity: var(--off); }
|
|
260
|
+
.composer__area {
|
|
261
|
+
box-sizing: content-box; /* its min-height is its words alone: the rule and the room under it come on top */
|
|
262
|
+
min-height: var(--composer-area-min-h, 2lh);
|
|
263
|
+
padding-block-start: var(--composer-area-pad, var(--sp-3));
|
|
264
|
+
border-block-start: var(--rule-w) dashed var(--composer-area-border, var(--card-muted)); /* the rule: the hint's ink, the system's rule width */
|
|
265
|
+
}
|
|
255
266
|
.composer__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--composer-bar-gap, var(--sp-3)); }
|
|
256
267
|
.composer__hint { font: var(--composer-hint-font, var(--type-caption)); color: var(--composer-hint-ink, var(--card-muted)); }
|
|
257
268
|
|
package/css/sheet.css
CHANGED
|
@@ -368,8 +368,7 @@
|
|
|
368
368
|
repeating-linear-gradient(-18deg, transparent 0 8.75rem, var(--ink-tint) 8.75rem 9.25rem),
|
|
369
369
|
var(--card-2-bg);
|
|
370
370
|
}
|
|
371
|
-
.map > :first-child { position: absolute; inset: 0; z-index: 0; isolation: isolate; } /* its own stacking context: an engine's layered panes stay under the ring */
|
|
372
|
-
.sheet-stage__bg .map > :first-child { background: transparent; } /* an engine's own grey ground would hide the pattern while tiles load */
|
|
371
|
+
.map > :first-child { position: absolute; inset: 0; z-index: 0; isolation: isolate; background: transparent; } /* its own stacking context: an engine's layered panes stay under the ring; an engine's own grey ground would hide the pattern while tiles load */
|
|
373
372
|
.map__ring {
|
|
374
373
|
position: absolute;
|
|
375
374
|
left: 50%;
|
|
@@ -401,9 +400,10 @@
|
|
|
401
400
|
.map__hint {
|
|
402
401
|
position: absolute;
|
|
403
402
|
top: calc(var(--_top, 0rem) + var(--sp-3));
|
|
404
|
-
left:
|
|
405
|
-
|
|
406
|
-
|
|
403
|
+
/* centred by its two sides, not left 50% + translate: that left the words only half the map to wrap in, so a short hint broke onto two lines */
|
|
404
|
+
inset-inline: var(--sp-4);
|
|
405
|
+
width: fit-content;
|
|
406
|
+
margin-inline: auto;
|
|
407
407
|
padding: var(--sp-2) var(--sp-4);
|
|
408
408
|
border-radius: var(--map-hint-radius, var(--r-chip));
|
|
409
409
|
background: var(--map-hint-bg, var(--card-3-bg));
|
|
@@ -415,6 +415,69 @@
|
|
|
415
415
|
z-index: 1;
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
+
/* ---- the map IN FLOW (Lh 2026-10-17): `.map--inline` — a box in a card, or the whole of a sheet's body ----
|
|
419
|
+
<div class="sheet__body"><div class="map map--inline"><div>…the engine…</div><label class="map-pick" …>…</label>…
|
|
420
|
+
<span class="map__ring" …></span></div></div>
|
|
421
|
+
Choosing where to meet without leaving the sheet: the open sheet becomes the map and its head stays on top (a stage
|
|
422
|
+
behind a half sheet covers the head and changes the look). No stage, so no band and no bar: the ring is at the centre
|
|
423
|
+
of the map's OWN box and the hint at its own top. In a card it is a rounded media box; as a SheetBody's only child it
|
|
424
|
+
fills the body (the part above the foot), however far the sheet slid. */
|
|
425
|
+
.map--inline {
|
|
426
|
+
--_top: 0rem;
|
|
427
|
+
--_band-centre: 50%;
|
|
428
|
+
position: relative;
|
|
429
|
+
min-height: var(--map-inline-h, calc(var(--map-ring) * 3 / 2));
|
|
430
|
+
border-radius: var(--map-radius, var(--r-media));
|
|
431
|
+
overflow: hidden;
|
|
432
|
+
}
|
|
433
|
+
.sheet__body:has(> .map--inline:only-child) { display: flex; flex-direction: column; }
|
|
434
|
+
.sheet__body > .map--inline:only-child { flex: 1 0 auto; }
|
|
435
|
+
|
|
436
|
+
/* ---- map pick: a place on the map the member taps to choose it (Lh 2026-10-17) ----
|
|
437
|
+
<label class="map-pick" style="top: 30%; left: 40%"><input type="radio" name="meet" value="library">
|
|
438
|
+
<span class="map-pick__mark">{icon}</span><span class="map-pick__name">ห้องสมุด</span></label>
|
|
439
|
+
A small round mark on its point and the place's short name under it — small, so five fit a phone's map (a Callout
|
|
440
|
+
is a card: five crowd it). The label is a --tap square centred on the point: the tap is never smaller than the
|
|
441
|
+
control floor however small the mark is drawn, and the name takes a tap too. The radios of one choice share a
|
|
442
|
+
name; the picked one fills with accent. Above the ring: the ring takes no taps, the places do. */
|
|
443
|
+
.map-pick {
|
|
444
|
+
position: absolute;
|
|
445
|
+
z-index: 2;
|
|
446
|
+
width: var(--tap);
|
|
447
|
+
height: var(--tap);
|
|
448
|
+
translate: -50% -50%;
|
|
449
|
+
display: grid;
|
|
450
|
+
place-items: center;
|
|
451
|
+
color: var(--map-pick-ink, var(--on-surface));
|
|
452
|
+
}
|
|
453
|
+
.map-pick__mark {
|
|
454
|
+
display: grid;
|
|
455
|
+
place-items: center;
|
|
456
|
+
width: var(--map-pick-size, var(--sp-6));
|
|
457
|
+
height: var(--map-pick-size, var(--sp-6));
|
|
458
|
+
border-radius: 50%;
|
|
459
|
+
border: var(--border-w) solid var(--map-pick-border, var(--accent));
|
|
460
|
+
background: var(--map-pick-bg, var(--surface));
|
|
461
|
+
color: var(--map-pick-mark-ink, var(--accent));
|
|
462
|
+
box-shadow: var(--map-pick-shadow, var(--shadow-float));
|
|
463
|
+
pointer-events: none;
|
|
464
|
+
}
|
|
465
|
+
.map-pick__name {
|
|
466
|
+
position: absolute;
|
|
467
|
+
top: calc(50% + var(--map-pick-size, var(--sp-6)) / 2 + var(--sp-1));
|
|
468
|
+
left: 50%;
|
|
469
|
+
translate: -50% 0;
|
|
470
|
+
white-space: nowrap;
|
|
471
|
+
padding: 0 var(--sp-2);
|
|
472
|
+
border-radius: var(--map-pick-name-radius, var(--r-chip));
|
|
473
|
+
background: var(--map-pick-name-bg, color-mix(in srgb, var(--surface) 85%, transparent));
|
|
474
|
+
font: var(--map-pick-font, var(--type-caption));
|
|
475
|
+
}
|
|
476
|
+
.map-pick:has(:checked) { z-index: 3; } /* the picked place's name lies over a neighbour's */
|
|
477
|
+
.map-pick:has(:checked) > :is(.map-pick__mark, .map-pick__name) { background: var(--map-pick-on-bg, var(--accent)); color: var(--map-pick-on-ink, var(--on-accent)); }
|
|
478
|
+
.map-pick:has(:focus-visible) > .map-pick__mark { outline: var(--focus); outline-offset: var(--sp-0); }
|
|
479
|
+
.map-pick:has(:disabled) { opacity: var(--off); cursor: default; }
|
|
480
|
+
|
|
418
481
|
/* EVERY sheet has one shape (Lh, 2026-09-21): the handle and the head stay at the top and never scroll · the
|
|
419
482
|
foot's button stands at the bottom, and ABOVE the keyboard when there is one · everything between them
|
|
420
483
|
scrolls, as far as it needs to. In a .step the keyboard's room is the step's (--kb, step.css); on a stage
|
package/css/tokens.css
CHANGED
|
@@ -79,7 +79,10 @@
|
|
|
79
79
|
|
|
80
80
|
/* ---- radius ---- */
|
|
81
81
|
--r-chip: 999px; /* pills, chips, buttons — always fully round */
|
|
82
|
-
--r-card: calc(var(--r-base) * 2); /* 41.6 the one card radius: every card,
|
|
82
|
+
--r-card: calc(var(--r-base) * 2); /* 41.6 the one card radius: every card, tile, menu, sheet, fab — but a row card (--r-card-row) */
|
|
83
|
+
--r-card-row: calc(var(--r-base) * 0.625); /* 13 a row card's corner (Lh 2026-10-17: a small card's corner shrinks to keep the big card's PROPORTION). Measured at 375:
|
|
84
|
+
whole cards stand 186–437 tall, corner/height 0.095–0.224 (Card · Skeleton 0.122); a two-line row stands 88, so its
|
|
85
|
+
corner is 11–20 — at 41.6 it was 0.475, a pill. 13 = 0.148 of 88. On --r-base, so a square theme squares it too */
|
|
83
86
|
--r-media: var(--r-base); /* 20.8 images inside cards */
|
|
84
87
|
--r-control: calc(var(--r-base) * 0.875); /* 18.2 fields, thumbs, menu rows */
|
|
85
88
|
--r-badge: calc(var(--r-base) * 0.625); /* 13 tile badge, bars */
|