@usableapp/cardds 0.7.101 → 0.7.102
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 +22 -0
- package/css/base.css +20 -0
- package/css/card.css +50 -0
- package/css/choice.css +22 -0
- package/css/forms.css +15 -3
- package/css/journey.css +23 -1
- package/css/media.css +1 -10
- package/css/sheet.css +21 -1
- package/css/tokens.css +2 -2
- package/dist/cardds.css +154 -17
- package/dist/cards/Card.d.ts +25 -3
- package/dist/cards/Card.js +26 -6
- package/dist/cards/cardGo.d.ts +2 -0
- package/dist/cards/cardGo.js +5 -0
- package/dist/catalog.json +1 -1
- package/dist/choice/Calendar.js +5 -1
- package/dist/choice/CalendarWeeks.d.ts +41 -0
- package/dist/choice/CalendarWeeks.js +64 -0
- package/dist/choice/calendarDay.d.ts +13 -0
- package/dist/choice/calendarDay.js +6 -0
- package/dist/forms/Composer.d.ts +1 -1
- package/dist/forms/Composer.js +1 -1
- package/dist/forms/Field.d.ts +9 -1
- package/dist/forms/Field.js +35 -24
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/journey/JourneyBadge.d.ts +5 -2
- package/dist/journey/JourneyBadge.js +3 -3
- package/dist/media/MapArea.d.ts +10 -4
- package/dist/media/MapArea.js +8 -3
- package/dist/media/Postcard.d.ts +1 -1
- package/dist/media/Postcard.js +2 -2
- package/dist/type/Icon.d.ts +1 -1
- package/dist/type/Icon.js +1 -1
- package/dist/type/icons.d.ts +1 -1
- package/dist/type/icons.js +2 -2
- package/dist/words.d.ts +1 -1
- package/dist/words.js +2 -2
- package/icons.svg +1 -0
- package/lint/rules.mjs +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
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.102 — 2026-10-19
|
|
6
|
+
|
|
7
|
+
A framed write-in of several lines: `Field rows`; an icon among words stays on the line; a date badge in a card's corner takes the card's corner; a meeting place on a map is a pin at the centre, not the ring; a day picked from a run of weeks that scrolls, two in sight, the month and year on a head that stays; a card that turns over, a two-tone card, and a date badge that takes its form from the title beside it (Lh 2026-10-19, timebank's request card, approved design).
|
|
8
|
+
|
|
9
|
+
Components: all
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **`Field rows={n}`: several lines in the same frame** (Lh 2026-10-19, timebank's ask sheet: the request and, under it, its reason). Before → after: a member's words had only two homes, a one-line `Field` (framed) and a `Composer` (bare, display type, the one big write-in), so two things to write on the ask sheet were a `Composer` with a `ComposerArea`, which Lh rejected as the wrong meaning: each must read as a framed box → `<Field rows={4} />` over `<Field rows={2} />`: the same outlined label wrapper (`.field.field--rows`) round a `textarea` of that many lines, the placeholder still the label, the request box taller than the reason box. `error`, `required`, `icon` and `action` work as on one line (the icon on the first line, the action at the top); `mask`, `format` and `digits` are for one line and are ignored with `rows` (warns once). A frame says "tap me", so a framed write-in is a `Field`, not a `Composer`: the `Composer` stays the one big bare write-in. Knob `--field-rows-pad-y` (`--sp-3`, the room above and below the lines). Gallery `Field/Rows` · `Field/RowsDark`.
|
|
13
|
+
- **A `JourneyBadge` first or last in a `CardHead` takes a corner concentric with the card's** (timebank's date box in the card's top corner, which set `--tile-badge-radius` by hand). Before → after: a badge in the head's corner kept the badge's own corner (13), so it sat loosely in the card's big one → the card's corner less its padding (`--r-card` minus `--card-pad`: 21.6 with the defaults), set by the card's first head on the badge at its edge. A theme's `--tile-badge-radius` still wins; no new knob. A badge in the middle of a head, on a media or on a sheet keeps `--r-badge`. Every badge that already stood last in a card's first head follows (the style snapshot names them: `Card/CardHeadCustom`, `Tile/JourneyBadgeGate`, `Route/Flight`, `Route/TwoLegs`, `Route/Dark`, `NoteRow/Transit`, `NoteRow/Dark`, and the unfilled badge of `Mosaic/PhotoCard`, whose corner shows nothing); it is the same rule, not a second one. Gallery `Tile/JourneyBadgeInHeadCorner` shows first and last.
|
|
14
|
+
- **`MapArea mark="pin"`: an exact point at the centre instead of the ring** (Lh 2026-10-19: every meeting position is a clear pin at the centre of the map, never the circle). Before → after: `MapArea` always drew the ring, the rough area, at the centre of its box (behind a stage, of the band a half sheet leaves open) → `mark="pin"` draws a pin there (`<span class="map__pin">` with the new `map-pin` icon), its TIP on the point the ring's centre sat, so a map engine reads the pin's bottom-centre (`ringRef` and `ringLabel` keep their names and now name the centre mark, whatever its shape; `ringLabel` defaults to "จุดนัด" for a pin, "ย่านของคุณโดยประมาณ" for the ring). The pin's ink is the map's own ground's (`--card-ink` on the tone-2 side it stands on, like `.map__hint`), not the inherited `--accent`: in a dark sheet that accent is the card's ondark ink, cream on clay's orange map. The default `mark="ring"` changes nothing; the ring stays for a rough area. Knobs `--map-pin-size` · `--map-pin-ink` · `--map-pin-drop`. New icon `map-pin`. Gallery `MapArea/Pin`, `MapArea/PinStage`.
|
|
15
|
+
- **`CalendarWeeks`: a run of weeks that scrolls week by week, `show` rows in sight (2 by default), the month · year of the first row in sight on a sticky head** (timebank, Lh 2026-10-19: the member picks a day for the help on a step with little height). Before → after: a day was picked from `Calendar` (one month in detail, too tall for a step, turning pages with prev/next) or `DayStrip` (one week, no scroll, no month) → `<CalendarWeeks weeks={…} selected={key} onSelect={…} />` shows two week rows under the weekday heads, snaps one week per scroll, and the head's label follows the first row in sight; `onLabelClick` makes the label a button for the app's own month / year pick. It opens at the week that holds today (Lh 2026-10-19: "ปฏิทินต้องแสดงวันนี้เสมอ"), that row the first in sight at once, no scroll to watch; with no `today` in the data it opens at the first week. The app builds every date: `weeks: { label, days: { key, num, today?, disabled?, mark?: 'soft' | 'strong' }[] }[]`. The cells are Calendar's (`.calendar__day`, every `--calendar-*` knob reaches both); a day with `today` carries `aria-current="date"` (underlined). Knobs `--calendar-weeks-head-bg` · `--calendar-label-font` · `--calendar-label-ink`. Gallery `CalendarWeeks/TwoWeeks` · `Controlled` · `LabelPick` · `Thai`. On a sheet, never in a Card.
|
|
16
|
+
- **`Card back`: the whole card turns over.** Before → after: a card had one face; a thing read on both sides (a request, its QR on the back) needed two cards or a hand-built flip → `back={…}` gives the card a back middle; a tap anywhere on the card off a control turns the whole card (3D, `--motion-flip`; an instant swap under prefers-reduced-motion) and taps again turn it back. The app owns the side: `face` + `onFaceChange`, or `defaultFace`. The head and the foot are given once and drawn on both faces (so give them no `id`); the hidden face is `inert`; a hidden turn button on each face is the keyboard's way (`toBackLabel` / `toFrontLabel`, default the words' `turnToBack` / new `turnToFront`). The middle is one fixed height on both faces, knob `--card-middle-h` (default 18.75rem), so the card keeps its height. Not on `row`, `sm` or `stack` cards (warns, ignored). Gallery `Card/Turns`, `Card/TurnsTwoLineTitle`, `Card/TurnsControlled`.
|
|
17
|
+
- **`Card paper="bands"`: the two-tone card.** Before → after: `paper` made the content white and kept the head's colour → `paper="bands"` is the opposite: the head and the foot are white paper bands edge to edge over the card's padding (the foot band slim), no hairline, and the middle keeps the card's tone lightened, knob `--card-middle-bg` (default `color-mix(in srgb, var(--card-bg) 35%, var(--card-1-bg))`); the middle's words take the paper's side (35%, not timebank's 40%: at 40% the words on clay's tone 3 fall to 4.29:1). Gallery `Card/PaperBands`.
|
|
18
|
+
- **`JourneyBadge stretch`: a date in a head.** As tall as the title block beside it and no taller; two lines beside a one-line title (day over number · month), three beside a two-line title. Knob `--tile-badge-line` (0.95).
|
|
19
|
+
- **A ChipRow in a card head** takes a line of its own under the title, at most two lines, a chip's words cut with an ellipsis.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **The flip mechanic is shared.** The turn (`.flip`, `.flip__face`, `.flip__face--front`, `.flip__face--back`, `.is-flipped`) now lives once in base.css; `Postcard` and `Card back` both read it. Postcard is unchanged on screen (style snapshot: 0 property differences over 477 stories).
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **An `Icon` among words stays on the line** (timebank 2026-10-19: "clock 12:30 hourglass 1 ชั่วโมง" could not sit on one line). Before → after: `img, svg { display: block }` made an `Icon` inside a `Text` a block, so the story `Icon/InContext` put the map icon on a line above "Telok Ayer Park · 1.2 km" (a caption of 23 became 40 tall, a body of 33 became 80 with two icons) → an icon that is a direct child of a type level (`Text`) is `inline-block`, centred on the words' x-height, and the line keeps its height. An icon in a `Btn`, `Chip`, `Link`, `Row` or `IconBtn` is a flex item and does not move. Gallery `Text/IconInLine`.
|
|
26
|
+
|
|
5
27
|
## 0.7.101 — 2026-10-19
|
|
6
28
|
|
|
7
29
|
A list that keeps only a few past entries in sight, a cross that takes a row card off its list, and a postcard that keeps the caret where it was when its paper is full.
|
package/css/base.css
CHANGED
|
@@ -78,6 +78,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
78
78
|
.icon--sm { width: var(--icon-sm-size, var(--icon-sm)); height: var(--icon-sm-size, var(--icon-sm)); }
|
|
79
79
|
.icon--xs { width: var(--icon-xs-size, var(--icon-xs)); height: var(--icon-xs-size, var(--icon-xs)); } /* inside captions, chips, links */
|
|
80
80
|
.icon--lg { width: var(--icon-lg-size, var(--icon-lg)); height: var(--icon-lg-size, var(--icon-lg)); } /* empty states */
|
|
81
|
+
/* an icon among words sits ON the line: `img, svg { display: block }` above would put it on a line of its own. Only a direct child of a
|
|
82
|
+
type level is running text; an icon in a button, chip, link or row is a flex/grid item and never matches. */
|
|
83
|
+
:is(.t-display, .t-h1, .t-h2, .t-title, .t-body, .t-label, .t-caption) > .icon { display: inline-block; vertical-align: middle; }
|
|
81
84
|
|
|
82
85
|
/* mobile screen column — the standard horizontal inset, and nothing vertical.
|
|
83
86
|
Top is zero on purpose: room there is only ever a RESERVE for something
|
|
@@ -209,3 +212,20 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
209
212
|
.empty-state__action { margin-top: var(--sp-2); }
|
|
210
213
|
/* inside a card (a card's own "nothing yet"): the card's ink */
|
|
211
214
|
.card .empty-state { color: var(--card-ink); padding-block: var(--sp-6); }
|
|
215
|
+
|
|
216
|
+
/* ---- the turn: one flip mechanic for everything with two faces (a Postcard, a Card's `back`).
|
|
217
|
+
The faces share one grid cell and turn on Y at --motion-flip; where motion is off they swap at once.
|
|
218
|
+
The owner sets the box's size and the faces' look; .is-flipped on .flip shows the back. */
|
|
219
|
+
.flip { perspective: 60rem; display: grid; }
|
|
220
|
+
.flip__face {
|
|
221
|
+
grid-area: 1 / 1;
|
|
222
|
+
backface-visibility: hidden;
|
|
223
|
+
-webkit-backface-visibility: hidden;
|
|
224
|
+
transition: transform var(--motion-flip) ease-in-out;
|
|
225
|
+
}
|
|
226
|
+
.flip__face--back { transform: rotateY(180deg); }
|
|
227
|
+
.flip.is-flipped > .flip__face--front { transform: rotateY(-180deg); }
|
|
228
|
+
.flip.is-flipped > .flip__face--back { transform: rotateY(0); }
|
|
229
|
+
@media (prefers-reduced-motion: reduce) {
|
|
230
|
+
.flip__face { transition: none; }
|
|
231
|
+
}
|
package/css/card.css
CHANGED
|
@@ -96,6 +96,56 @@
|
|
|
96
96
|
.card__head:has(> .stamp) > :is(.t-caption, .t-body) { grid-column: 1; grid-row: 2; align-self: start; }
|
|
97
97
|
.card__head > .stamp { grid-column: 2; grid-row: 1 / span 2; margin-inline: 0; }
|
|
98
98
|
|
|
99
|
+
/* 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
|
|
100
|
+
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,
|
|
101
|
+
so a theme that sets that knob still wins. */
|
|
102
|
+
.card > .card__head:first-child > .tile-badge:is(:first-child, :last-child) { --_badge-radius: calc(var(--r-card) - var(--card-pad)); }
|
|
103
|
+
|
|
104
|
+
/* ---- paper bands: the two-tone card (`Card paper="bands"`, Lh 2026-10-19): the head and the foot are white paper bands edge to
|
|
105
|
+
edge over the card's padding, no hairline; the middle between them keeps the card's tone, lightened (--card-middle-bg). The card
|
|
106
|
+
clips, so the bands keep its corners. A two-faced card (below) carries the look on each face. */
|
|
107
|
+
:is(.card--bands, .card--bands > .card__face) {
|
|
108
|
+
background: var(--card-middle-bg, color-mix(in srgb, var(--card-bg) 35%, var(--card-1-bg)));
|
|
109
|
+
--dark: var(--card-1-dark); /* the middle is mostly paper: its words take the paper's side */
|
|
110
|
+
overflow: hidden;
|
|
111
|
+
}
|
|
112
|
+
:is(.card--bands, .card--bands > .card__face) > :is(.card__head, .card__foot) {
|
|
113
|
+
--card-bg: var(--card-1-bg);
|
|
114
|
+
--dark: var(--card-1-dark);
|
|
115
|
+
background: var(--card-bg);
|
|
116
|
+
color: var(--card-ink);
|
|
117
|
+
margin-inline: calc(-1 * var(--card-pad));
|
|
118
|
+
padding-inline: var(--card-pad);
|
|
119
|
+
}
|
|
120
|
+
:is(.card--bands, .card--bands > .card__face) > .card__head { margin-top: calc(-1 * var(--card-pad)); padding-block: var(--card-pad); }
|
|
121
|
+
: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 */
|
|
122
|
+
|
|
123
|
+
/* ---- two faces: the WHOLE card turns (`Card back`, Lh 2026-10-19). The article is the turning box (.flip, base.css) and only that;
|
|
124
|
+
each face is the card's paper — head · middle · foot — and both lie in one grid cell. The head and the foot are the same on both
|
|
125
|
+
faces; the middle is one fixed height (--card-middle-h, the theme's call), so the card never changes height as it turns. The hidden
|
|
126
|
+
face is inert. Each face holds an unseen turn button: the keyboard's way to turn it; its ring is drawn on the face. */
|
|
127
|
+
.card--turns { background: none; border: 0; padding: 0; cursor: pointer; touch-action: manipulation; }
|
|
128
|
+
.card__face {
|
|
129
|
+
display: grid;
|
|
130
|
+
align-content: start;
|
|
131
|
+
gap: var(--card-gap);
|
|
132
|
+
min-width: 0;
|
|
133
|
+
padding: var(--card-pad);
|
|
134
|
+
background: var(--card-bg);
|
|
135
|
+
border: var(--border-w) solid var(--card-border);
|
|
136
|
+
border-radius: inherit;
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
position: relative;
|
|
139
|
+
}
|
|
140
|
+
.card__face > * { min-width: 0; }
|
|
141
|
+
.card__middle { display: grid; align-content: start; gap: var(--card-gap); height: var(--card-middle-h, 18.75rem); overflow: hidden; }
|
|
142
|
+
.card__turn { position: absolute; inset: 0; width: 1px; height: 1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); }
|
|
143
|
+
.card__face:has(> .card__turn:focus-visible) { outline: var(--focus); outline-offset: calc(-1 * var(--sp-1)); }
|
|
144
|
+
/* 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 */
|
|
145
|
+
.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; }
|
|
146
|
+
.card__head > .chip-row > .chip { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.5; }
|
|
147
|
+
.card__head > .chip-row > .chip > .icon { display: inline-block; vertical-align: -0.15em; margin-inline-end: var(--chip-sm-gap, var(--sp-1)); }
|
|
148
|
+
|
|
99
149
|
/* ---- bleed: a scrolling track that runs to the container's edges ----
|
|
100
150
|
For a horizontal strip inside a padded container — .avatar-pick, .chip-row,
|
|
101
151
|
a tile grid: the TRACK reaches the edges so it reads as scrollable, while
|
package/css/choice.css
CHANGED
|
@@ -154,6 +154,28 @@
|
|
|
154
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)); }
|
|
155
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; }
|
|
156
156
|
.calendar__day:disabled { opacity: 0.3; }
|
|
157
|
+
.calendar__day[aria-current="date"] { text-decoration: underline; text-underline-offset: 0.2em; }
|
|
158
|
+
|
|
159
|
+
/* ---- calendar weeks: a run of week rows that scrolls week by week, `show` rows in sight; the head sticks at the top of the box
|
|
160
|
+
<div class="calendar calendar--weeks"><div class="calendar__weeks-head"><span class="calendar__label">June 2026</span><div class="calendar__grid">…wd×7</div></div>
|
|
161
|
+
<div class="calendar__grid calendar__week"><button class="calendar__day">…</button>×7</div>…</div>
|
|
162
|
+
--_weeks-h (head + `show` rows) and --_weeks-lead (the head and the gap under it, where a row snaps) are measured by src/choice/CalendarWeeks.tsx; the cells and every --calendar-* knob are Calendar's */
|
|
163
|
+
.calendar--weeks {
|
|
164
|
+
position: relative;
|
|
165
|
+
max-height: var(--_weeks-h, none);
|
|
166
|
+
overflow-y: auto;
|
|
167
|
+
overscroll-behavior: contain;
|
|
168
|
+
scroll-snap-type: y mandatory;
|
|
169
|
+
scroll-padding-block-start: var(--_weeks-lead, 0px);
|
|
170
|
+
overflow-anchor: none; /* a pick rerenders the rows; the browser must not shift the scroll to keep an anchor */
|
|
171
|
+
scrollbar-width: none;
|
|
172
|
+
}
|
|
173
|
+
.calendar--weeks::-webkit-scrollbar { display: none; }
|
|
174
|
+
.calendar__weeks-head { position: sticky; inset-block-start: 0; z-index: 1; display: grid; gap: var(--calendar-head-gap, var(--sp-2)); background: var(--calendar-weeks-head-bg, var(--card-bg)); }
|
|
175
|
+
.calendar__label { font: var(--calendar-label-font, var(--type-label)); color: var(--calendar-label-ink, inherit); justify-self: start; }
|
|
176
|
+
button.calendar__label { appearance: none; border: 0; padding: 0; background: none; cursor: pointer; border-radius: var(--sp-1); }
|
|
177
|
+
button.calendar__label:focus-visible { outline: var(--focus); outline-offset: var(--sp-0); }
|
|
178
|
+
.calendar__week { scroll-snap-align: start; }
|
|
157
179
|
|
|
158
180
|
/* ---- mood: five dots in a pill, the picked one grows ----
|
|
159
181
|
<fieldset class="mood"><label class="mood__opt"><input type="radio" name="mood"><span class="mood__dot"></span></label>×5</fieldset> */
|
package/css/forms.css
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
cardds/forms.css — inputs + inline form actions
|
|
3
3
|
.field: flat outlined input, the label IS the placeholder.
|
|
4
4
|
Markup: <label class="field"><svg?/><input placeholder="Passport Number"></label>
|
|
5
|
+
Several lines (Field rows): <label class="field field--rows"><svg?/><textarea rows="4" placeholder="…"></textarea></label>
|
|
5
6
|
============================================================ */
|
|
6
7
|
|
|
7
8
|
.field {
|
|
@@ -19,17 +20,28 @@
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
.field input,
|
|
22
|
-
.field select
|
|
23
|
+
.field select,
|
|
24
|
+
.field textarea,
|
|
25
|
+
.field--rows {
|
|
26
|
+
font: var(--field-font, 400 var(--fs-lg) / 1.4 var(--font-ui)); /* 26: the words being typed read a size up from body (Lh 2026-09-22); the Composer already does. .field--rows too, so 1lh is the textarea's line for its icon */
|
|
27
|
+
}
|
|
28
|
+
.field input,
|
|
29
|
+
.field select,
|
|
30
|
+
.field textarea {
|
|
23
31
|
border: 0;
|
|
24
32
|
background: transparent;
|
|
25
33
|
color: inherit;
|
|
26
|
-
font: var(--field-font, 400 var(--fs-lg) / 1.4 var(--font-ui)); /* 26: the words being typed read a size up from body (Lh 2026-09-22); the Composer already does */
|
|
27
34
|
width: 100%;
|
|
28
35
|
min-width: 0;
|
|
29
36
|
outline: none; /* the browser's ring drew a second box inside the field (Lh 2026-09-22) — the field itself tells focus */
|
|
30
37
|
caret-color: currentColor;
|
|
31
38
|
}
|
|
32
|
-
.field input::placeholder
|
|
39
|
+
.field input::placeholder,
|
|
40
|
+
.field textarea::placeholder { color: inherit; opacity: 0.55; font-size: calc(1em * var(--_ph-fit, 1)); } /* --_ph-fit: Field's measure — a placeholder longer than the box shrinks to show whole (Lh 2026-09-25) */
|
|
41
|
+
/* several lines in the same frame (Field rows): the textarea is as tall as its `rows` × line, the frame grows round it, the icon and the action stay at the top (Lh 2026-10-19) */
|
|
42
|
+
.field--rows { align-items: flex-start; padding-block: var(--field-rows-pad-y, var(--sp-3)); }
|
|
43
|
+
.field textarea { flex: 1; resize: none; display: block; }
|
|
44
|
+
.field--rows > .icon { margin-top: calc((1lh - var(--icon-sm)) / 2); } /* centred on the first line */
|
|
33
45
|
/* the field the cursor is in: its own border goes full ink, in every palette (the coloured ones fade it to 25% at rest) */
|
|
34
46
|
.field:has(:focus, [data-cursor]) { border-color: var(--field-active-border, currentColor); } /* [data-cursor]: SheetSteps holds the phone's keyboard for this field */
|
|
35
47
|
|
package/css/journey.css
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
justify-items: center;
|
|
36
36
|
gap: var(--tile-badge-gap, var(--sp-0));
|
|
37
37
|
padding: var(--tile-badge-pad-y, var(--sp-1)) var(--tile-badge-pad-x, var(--sp-2));
|
|
38
|
-
border-radius: var(--tile-badge-radius, var(--r-badge));
|
|
38
|
+
border-radius: var(--tile-badge-radius, var(--_badge-radius, var(--r-badge))); /* --_badge-radius: card.css, a badge in a card's corner */
|
|
39
39
|
background: var(--tile-badge-bg, var(--card-3-bg));
|
|
40
40
|
--dark: var(--card-3-dark);
|
|
41
41
|
color: var(--tile-badge-ink, var(--card-ink));
|
|
@@ -49,6 +49,28 @@
|
|
|
49
49
|
.tile-badge--plain { background: transparent; --dark: inherit; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
|
|
50
50
|
.tile-badge--plain .tile-badge__value { font: 800 var(--fs-h1)/1 var(--font-display); }
|
|
51
51
|
|
|
52
|
+
/* stretch: a date in a head (Lh 2026-10-19) — as tall as the title block beside it and no taller (the head stretches it; size
|
|
53
|
+
containment keeps its own words from adding height). Its height picks the form: short (a one-line title) = two lines, the day name
|
|
54
|
+
over the number and the month on one line; tall (a two-line title) = three lines, day · number · month. */
|
|
55
|
+
.tile-badge--stretch {
|
|
56
|
+
align-self: stretch;
|
|
57
|
+
container-type: size;
|
|
58
|
+
inline-size: calc(var(--fs-lg) * 3.25);
|
|
59
|
+
grid-template-columns: auto auto;
|
|
60
|
+
align-content: center;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
align-items: baseline;
|
|
63
|
+
column-gap: var(--sp-1);
|
|
64
|
+
row-gap: 0;
|
|
65
|
+
padding-block: var(--sp-3);
|
|
66
|
+
line-height: var(--tile-badge-line, 0.95);
|
|
67
|
+
}
|
|
68
|
+
.tile-badge--stretch > * { line-height: inherit; }
|
|
69
|
+
.tile-badge--stretch > :first-child { grid-column: 1 / -1; }
|
|
70
|
+
@container (min-height: 5.5rem) {
|
|
71
|
+
.tile-badge--stretch > * { grid-column: 1 / -1; }
|
|
72
|
+
}
|
|
73
|
+
|
|
52
74
|
/* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
|
|
53
75
|
.tile-badge--corner { position: absolute; right: var(--sp-3); bottom: var(--sp-3); }
|
|
54
76
|
|
package/css/media.css
CHANGED
|
@@ -197,8 +197,8 @@
|
|
|
197
197
|
liked it, a voice note, a reply live OUTSIDE the postcard, in a Row under it. */
|
|
198
198
|
.postcard {
|
|
199
199
|
position: relative;
|
|
200
|
+
display: block; /* its faces lie absolute over the box (.flip's grid cell is not used) */
|
|
200
201
|
min-height: var(--postcard-min-h, var(--_cover-min-h));
|
|
201
|
-
perspective: 60rem;
|
|
202
202
|
border-radius: var(--postcard-radius, var(--r-card));
|
|
203
203
|
isolation: isolate;
|
|
204
204
|
}
|
|
@@ -211,9 +211,6 @@
|
|
|
211
211
|
border-radius: inherit;
|
|
212
212
|
overflow: hidden;
|
|
213
213
|
isolation: isolate;
|
|
214
|
-
backface-visibility: hidden;
|
|
215
|
-
-webkit-backface-visibility: hidden;
|
|
216
|
-
transition: transform var(--motion-flip) ease-in-out;
|
|
217
214
|
box-shadow: var(--postcard-face-shadow, var(--shadow-float));
|
|
218
215
|
}
|
|
219
216
|
/* the turn: the button that fills the face, under the band */
|
|
@@ -253,7 +250,6 @@
|
|
|
253
250
|
background: var(--postcard-back-bg, var(--card-2-bg));
|
|
254
251
|
--dark: var(--card-2-dark);
|
|
255
252
|
color: var(--postcard-back-ink, var(--card-ink));
|
|
256
|
-
transform: rotateY(180deg);
|
|
257
253
|
}
|
|
258
254
|
.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 */
|
|
259
255
|
.postcard__caption { margin: 0; font: var(--postcard-caption-font, var(--type-title)); text-wrap: pretty; }
|
|
@@ -272,13 +268,8 @@
|
|
|
272
268
|
.postcard__words { display: grid; gap: var(--postcard-words-gap, var(--sp-1)); flex: 1; min-width: 0; pointer-events: none; }
|
|
273
269
|
.postcard__front .postcard__band { --accent: var(--postcard-front-ink, var(--card-ink)); --accent-dark: calc(1 - var(--dark)); }
|
|
274
270
|
.postcard__band .icon-btn[aria-pressed="true"] .icon { fill: currentColor; }
|
|
275
|
-
.postcard.is-flipped > .postcard__front { transform: rotateY(-180deg); }
|
|
276
|
-
.postcard.is-flipped > .postcard__back { transform: rotateY(0); }
|
|
277
271
|
/* a voice note on the back: no words to read — its timeline (a Progress), its length and the control to hear it
|
|
278
272
|
under them, at the middle over the turn, on the face's inset; the app plays and keeps the time */
|
|
279
273
|
.postcard__voice { position: absolute; inset: 0; z-index: 2; display: grid; align-content: center; justify-items: center; gap: var(--postcard-voice-gap, var(--sp-3)); padding: var(--postcard-face-pad, var(--card-pad-rest)); pointer-events: none; }
|
|
280
274
|
.postcard__voice > * { pointer-events: auto; }
|
|
281
275
|
.postcard__voice > .progress { justify-self: stretch; pointer-events: none; }
|
|
282
|
-
@media (prefers-reduced-motion: reduce) {
|
|
283
|
-
.postcard__face { transition: none; }
|
|
284
|
-
}
|
package/css/sheet.css
CHANGED
|
@@ -355,7 +355,9 @@
|
|
|
355
355
|
wireframe's street pattern, so a map is never a hole. The ring marks the
|
|
356
356
|
rough area the member is choosing: fixed at the box's centre, so dragging
|
|
357
357
|
the map underneath is what moves it (the centre IS the choice), never a
|
|
358
|
-
pin to place. The hint rides at the top, under the bar's line.
|
|
358
|
+
pin to place. The hint rides at the top, under the bar's line.
|
|
359
|
+
For a MEETING place (Lh 2026-10-19) the centre mark is a pin instead of the ring: `.map__pin`, below the ring's rule,
|
|
360
|
+
its tip on the point the ring's centre sits. */
|
|
359
361
|
.map {
|
|
360
362
|
position: absolute;
|
|
361
363
|
inset: 0;
|
|
@@ -383,6 +385,24 @@
|
|
|
383
385
|
pointer-events: none;
|
|
384
386
|
z-index: 1; /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
|
|
385
387
|
}
|
|
388
|
+
.map__pin { /* mark="pin": an exact point — the pin's TIP is where the ring's centre sits, so the engine reads the element's bottom-centre */
|
|
389
|
+
position: absolute;
|
|
390
|
+
left: 50%;
|
|
391
|
+
top: var(--_band-centre);
|
|
392
|
+
width: var(--map-pin-size, var(--tap));
|
|
393
|
+
height: var(--map-pin-size, var(--tap));
|
|
394
|
+
translate: -50% -100%;
|
|
395
|
+
/* ink follows its ground: the pin stands on the map's own tone-2 fill, not on the sheet round it — in a dark sheet the inherited
|
|
396
|
+
--accent is the card's ondark ink, cream on clay's orange map (2.8:1); so the pin takes the tone's side and its ink (tokens.css) */
|
|
397
|
+
--dark: var(--card-2-dark);
|
|
398
|
+
color: var(--map-pin-ink, var(--card-ink));
|
|
399
|
+
filter: drop-shadow(var(--map-pin-drop, var(--shadow-float)));
|
|
400
|
+
pointer-events: none;
|
|
401
|
+
z-index: 1;
|
|
402
|
+
}
|
|
403
|
+
/* the lucide tip stops 2.2 of 24 short of the box's bottom: lowered so the tip IS the bottom-centre; filled with the surface so the
|
|
404
|
+
street pattern never shows through the pin's body */
|
|
405
|
+
.map__pin > .icon { width: 100%; height: 100%; translate: 0 9%; fill: var(--surface); }
|
|
386
406
|
.map__credit { /* the tile provider's credit — required by OSM; a caption, not the engine's own control */
|
|
387
407
|
position: absolute;
|
|
388
408
|
right: var(--sp-2);
|
package/css/tokens.css
CHANGED
|
@@ -281,8 +281,8 @@
|
|
|
281
281
|
surface turns everything on it to ondark with no rule per piece. A piece with no fill that stands in for a surface (a stat
|
|
282
282
|
tile) takes its ground's side: `--dark: inherit`. inkGrounds() measures it; docs/reference.md "Ink follows its ground" */
|
|
283
283
|
:where(body, [data-palette], [data-mode], .card, .tile, .modal__drawer, .postcard__front, .postcard__back, .sticky, .post-paper,
|
|
284
|
-
.float-bar__pill, .snackbar__body, .delta, .track, .tile-badge, .avatar, .bubble__body, .map__hint, .sheet--paper > .sheet__body,
|
|
285
|
-
.postcard__band, .track__step, .toggle, .chip) {
|
|
284
|
+
.float-bar__pill, .snackbar__body, .delta, .track, .tile-badge, .avatar, .bubble__body, .map__hint, .map__pin, .sheet--paper > .sheet__body,
|
|
285
|
+
.postcard__band, .track__step, .toggle, .chip, .card__face, .card--bands > :is(.card__head, .card__foot), .card--bands > .card__face > :is(.card__head, .card__foot)) {
|
|
286
286
|
--card-ink: color-mix(in srgb, var(--ink-ondark) calc(var(--dark) * 100%), var(--ink-onlight));
|
|
287
287
|
--card-muted: color-mix(in srgb, var(--ink-ondark-muted) calc(var(--dark) * 100%), var(--ink-onlight-muted));
|
|
288
288
|
--on-accent: color-mix(in srgb, var(--ink-ondark) calc(var(--accent-dark) * 100%), var(--ink-onlight));
|
package/dist/cardds.css
CHANGED
|
@@ -322,8 +322,8 @@
|
|
|
322
322
|
surface turns everything on it to ondark with no rule per piece. A piece with no fill that stands in for a surface (a stat
|
|
323
323
|
tile) takes its ground's side: `--dark: inherit`. inkGrounds() measures it; docs/reference.md "Ink follows its ground" */
|
|
324
324
|
:where(body, [data-palette], [data-mode], .card, .tile, .modal__drawer, .postcard__front, .postcard__back, .sticky, .post-paper,
|
|
325
|
-
.float-bar__pill, .snackbar__body, .delta, .track, .tile-badge, .avatar, .bubble__body, .map__hint, .sheet--paper > .sheet__body,
|
|
326
|
-
.postcard__band, .track__step, .toggle, .chip) {
|
|
325
|
+
.float-bar__pill, .snackbar__body, .delta, .track, .tile-badge, .avatar, .bubble__body, .map__hint, .map__pin, .sheet--paper > .sheet__body,
|
|
326
|
+
.postcard__band, .track__step, .toggle, .chip, .card__face, .card--bands > :is(.card__head, .card__foot), .card--bands > .card__face > :is(.card__head, .card__foot)) {
|
|
327
327
|
--card-ink: color-mix(in srgb, var(--ink-ondark) calc(var(--dark) * 100%), var(--ink-onlight));
|
|
328
328
|
--card-muted: color-mix(in srgb, var(--ink-ondark-muted) calc(var(--dark) * 100%), var(--ink-onlight-muted));
|
|
329
329
|
--on-accent: color-mix(in srgb, var(--ink-ondark) calc(var(--accent-dark) * 100%), var(--ink-onlight));
|
|
@@ -571,6 +571,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
571
571
|
.icon--sm { width: var(--icon-sm-size, var(--icon-sm)); height: var(--icon-sm-size, var(--icon-sm)); }
|
|
572
572
|
.icon--xs { width: var(--icon-xs-size, var(--icon-xs)); height: var(--icon-xs-size, var(--icon-xs)); } /* inside captions, chips, links */
|
|
573
573
|
.icon--lg { width: var(--icon-lg-size, var(--icon-lg)); height: var(--icon-lg-size, var(--icon-lg)); } /* empty states */
|
|
574
|
+
/* an icon among words sits ON the line: `img, svg { display: block }` above would put it on a line of its own. Only a direct child of a
|
|
575
|
+
type level is running text; an icon in a button, chip, link or row is a flex/grid item and never matches. */
|
|
576
|
+
:is(.t-display, .t-h1, .t-h2, .t-title, .t-body, .t-label, .t-caption) > .icon { display: inline-block; vertical-align: middle; }
|
|
574
577
|
|
|
575
578
|
/* mobile screen column — the standard horizontal inset, and nothing vertical.
|
|
576
579
|
Top is zero on purpose: room there is only ever a RESERVE for something
|
|
@@ -703,6 +706,23 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
703
706
|
/* inside a card (a card's own "nothing yet"): the card's ink */
|
|
704
707
|
.card .empty-state { color: var(--card-ink); padding-block: var(--sp-6); }
|
|
705
708
|
|
|
709
|
+
/* ---- the turn: one flip mechanic for everything with two faces (a Postcard, a Card's `back`).
|
|
710
|
+
The faces share one grid cell and turn on Y at --motion-flip; where motion is off they swap at once.
|
|
711
|
+
The owner sets the box's size and the faces' look; .is-flipped on .flip shows the back. */
|
|
712
|
+
.flip { perspective: 60rem; display: grid; }
|
|
713
|
+
.flip__face {
|
|
714
|
+
grid-area: 1 / 1;
|
|
715
|
+
backface-visibility: hidden;
|
|
716
|
+
-webkit-backface-visibility: hidden;
|
|
717
|
+
transition: transform var(--motion-flip) ease-in-out;
|
|
718
|
+
}
|
|
719
|
+
.flip__face--back { transform: rotateY(180deg); }
|
|
720
|
+
.flip.is-flipped > .flip__face--front { transform: rotateY(-180deg); }
|
|
721
|
+
.flip.is-flipped > .flip__face--back { transform: rotateY(0); }
|
|
722
|
+
@media (prefers-reduced-motion: reduce) {
|
|
723
|
+
.flip__face { transition: none; }
|
|
724
|
+
}
|
|
725
|
+
|
|
706
726
|
/* ---- css/card.css — card boundary + list variants ---- */
|
|
707
727
|
/* ============================================================
|
|
708
728
|
cardds/card.css — the card boundary + list variants
|
|
@@ -802,6 +822,56 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
802
822
|
.card__head:has(> .stamp) > :is(.t-caption, .t-body) { grid-column: 1; grid-row: 2; align-self: start; }
|
|
803
823
|
.card__head > .stamp { grid-column: 2; grid-row: 1 / span 2; margin-inline: 0; }
|
|
804
824
|
|
|
825
|
+
/* 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
|
|
826
|
+
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,
|
|
827
|
+
so a theme that sets that knob still wins. */
|
|
828
|
+
.card > .card__head:first-child > .tile-badge:is(:first-child, :last-child) { --_badge-radius: calc(var(--r-card) - var(--card-pad)); }
|
|
829
|
+
|
|
830
|
+
/* ---- paper bands: the two-tone card (`Card paper="bands"`, Lh 2026-10-19): the head and the foot are white paper bands edge to
|
|
831
|
+
edge over the card's padding, no hairline; the middle between them keeps the card's tone, lightened (--card-middle-bg). The card
|
|
832
|
+
clips, so the bands keep its corners. A two-faced card (below) carries the look on each face. */
|
|
833
|
+
:is(.card--bands, .card--bands > .card__face) {
|
|
834
|
+
background: var(--card-middle-bg, color-mix(in srgb, var(--card-bg) 35%, var(--card-1-bg)));
|
|
835
|
+
--dark: var(--card-1-dark); /* the middle is mostly paper: its words take the paper's side */
|
|
836
|
+
overflow: hidden;
|
|
837
|
+
}
|
|
838
|
+
:is(.card--bands, .card--bands > .card__face) > :is(.card__head, .card__foot) {
|
|
839
|
+
--card-bg: var(--card-1-bg);
|
|
840
|
+
--dark: var(--card-1-dark);
|
|
841
|
+
background: var(--card-bg);
|
|
842
|
+
color: var(--card-ink);
|
|
843
|
+
margin-inline: calc(-1 * var(--card-pad));
|
|
844
|
+
padding-inline: var(--card-pad);
|
|
845
|
+
}
|
|
846
|
+
:is(.card--bands, .card--bands > .card__face) > .card__head { margin-top: calc(-1 * var(--card-pad)); padding-block: var(--card-pad); }
|
|
847
|
+
: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 */
|
|
848
|
+
|
|
849
|
+
/* ---- two faces: the WHOLE card turns (`Card back`, Lh 2026-10-19). The article is the turning box (.flip, base.css) and only that;
|
|
850
|
+
each face is the card's paper — head · middle · foot — and both lie in one grid cell. The head and the foot are the same on both
|
|
851
|
+
faces; the middle is one fixed height (--card-middle-h, the theme's call), so the card never changes height as it turns. The hidden
|
|
852
|
+
face is inert. Each face holds an unseen turn button: the keyboard's way to turn it; its ring is drawn on the face. */
|
|
853
|
+
.card--turns { background: none; border: 0; padding: 0; cursor: pointer; touch-action: manipulation; }
|
|
854
|
+
.card__face {
|
|
855
|
+
display: grid;
|
|
856
|
+
align-content: start;
|
|
857
|
+
gap: var(--card-gap);
|
|
858
|
+
min-width: 0;
|
|
859
|
+
padding: var(--card-pad);
|
|
860
|
+
background: var(--card-bg);
|
|
861
|
+
border: var(--border-w) solid var(--card-border);
|
|
862
|
+
border-radius: inherit;
|
|
863
|
+
overflow: hidden;
|
|
864
|
+
position: relative;
|
|
865
|
+
}
|
|
866
|
+
.card__face > * { min-width: 0; }
|
|
867
|
+
.card__middle { display: grid; align-content: start; gap: var(--card-gap); height: var(--card-middle-h, 18.75rem); overflow: hidden; }
|
|
868
|
+
.card__turn { position: absolute; inset: 0; width: 1px; height: 1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); }
|
|
869
|
+
.card__face:has(> .card__turn:focus-visible) { outline: var(--focus); outline-offset: calc(-1 * var(--sp-1)); }
|
|
870
|
+
/* 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 */
|
|
871
|
+
.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; }
|
|
872
|
+
.card__head > .chip-row > .chip { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.5; }
|
|
873
|
+
.card__head > .chip-row > .chip > .icon { display: inline-block; vertical-align: -0.15em; margin-inline-end: var(--chip-sm-gap, var(--sp-1)); }
|
|
874
|
+
|
|
805
875
|
/* ---- bleed: a scrolling track that runs to the container's edges ----
|
|
806
876
|
For a horizontal strip inside a padded container — .avatar-pick, .chip-row,
|
|
807
877
|
a tile grid: the TRACK reaches the edges so it reads as scrollable, while
|
|
@@ -2018,7 +2088,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2018
2088
|
wireframe's street pattern, so a map is never a hole. The ring marks the
|
|
2019
2089
|
rough area the member is choosing: fixed at the box's centre, so dragging
|
|
2020
2090
|
the map underneath is what moves it (the centre IS the choice), never a
|
|
2021
|
-
pin to place. The hint rides at the top, under the bar's line.
|
|
2091
|
+
pin to place. The hint rides at the top, under the bar's line.
|
|
2092
|
+
For a MEETING place (Lh 2026-10-19) the centre mark is a pin instead of the ring: `.map__pin`, below the ring's rule,
|
|
2093
|
+
its tip on the point the ring's centre sits. */
|
|
2022
2094
|
.map {
|
|
2023
2095
|
position: absolute;
|
|
2024
2096
|
inset: 0;
|
|
@@ -2046,6 +2118,24 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2046
2118
|
pointer-events: none;
|
|
2047
2119
|
z-index: 1; /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
|
|
2048
2120
|
}
|
|
2121
|
+
.map__pin { /* mark="pin": an exact point — the pin's TIP is where the ring's centre sits, so the engine reads the element's bottom-centre */
|
|
2122
|
+
position: absolute;
|
|
2123
|
+
left: 50%;
|
|
2124
|
+
top: var(--_band-centre);
|
|
2125
|
+
width: var(--map-pin-size, var(--tap));
|
|
2126
|
+
height: var(--map-pin-size, var(--tap));
|
|
2127
|
+
translate: -50% -100%;
|
|
2128
|
+
/* ink follows its ground: the pin stands on the map's own tone-2 fill, not on the sheet round it — in a dark sheet the inherited
|
|
2129
|
+
--accent is the card's ondark ink, cream on clay's orange map (2.8:1); so the pin takes the tone's side and its ink (tokens.css) */
|
|
2130
|
+
--dark: var(--card-2-dark);
|
|
2131
|
+
color: var(--map-pin-ink, var(--card-ink));
|
|
2132
|
+
filter: drop-shadow(var(--map-pin-drop, var(--shadow-float)));
|
|
2133
|
+
pointer-events: none;
|
|
2134
|
+
z-index: 1;
|
|
2135
|
+
}
|
|
2136
|
+
/* the lucide tip stops 2.2 of 24 short of the box's bottom: lowered so the tip IS the bottom-centre; filled with the surface so the
|
|
2137
|
+
street pattern never shows through the pin's body */
|
|
2138
|
+
.map__pin > .icon { width: 100%; height: 100%; translate: 0 9%; fill: var(--surface); }
|
|
2049
2139
|
.map__credit { /* the tile provider's credit — required by OSM; a caption, not the engine's own control */
|
|
2050
2140
|
position: absolute;
|
|
2051
2141
|
right: var(--sp-2);
|
|
@@ -2431,6 +2521,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2431
2521
|
cardds/forms.css — inputs + inline form actions
|
|
2432
2522
|
.field: flat outlined input, the label IS the placeholder.
|
|
2433
2523
|
Markup: <label class="field"><svg?/><input placeholder="Passport Number"></label>
|
|
2524
|
+
Several lines (Field rows): <label class="field field--rows"><svg?/><textarea rows="4" placeholder="…"></textarea></label>
|
|
2434
2525
|
============================================================ */
|
|
2435
2526
|
|
|
2436
2527
|
.field {
|
|
@@ -2448,17 +2539,28 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2448
2539
|
}
|
|
2449
2540
|
|
|
2450
2541
|
.field input,
|
|
2451
|
-
.field select
|
|
2542
|
+
.field select,
|
|
2543
|
+
.field textarea,
|
|
2544
|
+
.field--rows {
|
|
2545
|
+
font: var(--field-font, 400 var(--fs-lg) / 1.4 var(--font-ui)); /* 26: the words being typed read a size up from body (Lh 2026-09-22); the Composer already does. .field--rows too, so 1lh is the textarea's line for its icon */
|
|
2546
|
+
}
|
|
2547
|
+
.field input,
|
|
2548
|
+
.field select,
|
|
2549
|
+
.field textarea {
|
|
2452
2550
|
border: 0;
|
|
2453
2551
|
background: transparent;
|
|
2454
2552
|
color: inherit;
|
|
2455
|
-
font: var(--field-font, 400 var(--fs-lg) / 1.4 var(--font-ui)); /* 26: the words being typed read a size up from body (Lh 2026-09-22); the Composer already does */
|
|
2456
2553
|
width: 100%;
|
|
2457
2554
|
min-width: 0;
|
|
2458
2555
|
outline: none; /* the browser's ring drew a second box inside the field (Lh 2026-09-22) — the field itself tells focus */
|
|
2459
2556
|
caret-color: currentColor;
|
|
2460
2557
|
}
|
|
2461
|
-
.field input::placeholder
|
|
2558
|
+
.field input::placeholder,
|
|
2559
|
+
.field textarea::placeholder { color: inherit; opacity: 0.55; font-size: calc(1em * var(--_ph-fit, 1)); } /* --_ph-fit: Field's measure — a placeholder longer than the box shrinks to show whole (Lh 2026-09-25) */
|
|
2560
|
+
/* several lines in the same frame (Field rows): the textarea is as tall as its `rows` × line, the frame grows round it, the icon and the action stay at the top (Lh 2026-10-19) */
|
|
2561
|
+
.field--rows { align-items: flex-start; padding-block: var(--field-rows-pad-y, var(--sp-3)); }
|
|
2562
|
+
.field textarea { flex: 1; resize: none; display: block; }
|
|
2563
|
+
.field--rows > .icon { margin-top: calc((1lh - var(--icon-sm)) / 2); } /* centred on the first line */
|
|
2462
2564
|
/* the field the cursor is in: its own border goes full ink, in every palette (the coloured ones fade it to 25% at rest) */
|
|
2463
2565
|
.field:has(:focus, [data-cursor]) { border-color: var(--field-active-border, currentColor); } /* [data-cursor]: SheetSteps holds the phone's keyboard for this field */
|
|
2464
2566
|
|
|
@@ -2547,7 +2649,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2547
2649
|
justify-items: center;
|
|
2548
2650
|
gap: var(--tile-badge-gap, var(--sp-0));
|
|
2549
2651
|
padding: var(--tile-badge-pad-y, var(--sp-1)) var(--tile-badge-pad-x, var(--sp-2));
|
|
2550
|
-
border-radius: var(--tile-badge-radius, var(--r-badge));
|
|
2652
|
+
border-radius: var(--tile-badge-radius, var(--_badge-radius, var(--r-badge))); /* --_badge-radius: card.css, a badge in a card's corner */
|
|
2551
2653
|
background: var(--tile-badge-bg, var(--card-3-bg));
|
|
2552
2654
|
--dark: var(--card-3-dark);
|
|
2553
2655
|
color: var(--tile-badge-ink, var(--card-ink));
|
|
@@ -2561,6 +2663,28 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2561
2663
|
.tile-badge--plain { background: transparent; --dark: inherit; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
|
|
2562
2664
|
.tile-badge--plain .tile-badge__value { font: 800 var(--fs-h1)/1 var(--font-display); }
|
|
2563
2665
|
|
|
2666
|
+
/* stretch: a date in a head (Lh 2026-10-19) — as tall as the title block beside it and no taller (the head stretches it; size
|
|
2667
|
+
containment keeps its own words from adding height). Its height picks the form: short (a one-line title) = two lines, the day name
|
|
2668
|
+
over the number and the month on one line; tall (a two-line title) = three lines, day · number · month. */
|
|
2669
|
+
.tile-badge--stretch {
|
|
2670
|
+
align-self: stretch;
|
|
2671
|
+
container-type: size;
|
|
2672
|
+
inline-size: calc(var(--fs-lg) * 3.25);
|
|
2673
|
+
grid-template-columns: auto auto;
|
|
2674
|
+
align-content: center;
|
|
2675
|
+
justify-content: center;
|
|
2676
|
+
align-items: baseline;
|
|
2677
|
+
column-gap: var(--sp-1);
|
|
2678
|
+
row-gap: 0;
|
|
2679
|
+
padding-block: var(--sp-3);
|
|
2680
|
+
line-height: var(--tile-badge-line, 0.95);
|
|
2681
|
+
}
|
|
2682
|
+
.tile-badge--stretch > * { line-height: inherit; }
|
|
2683
|
+
.tile-badge--stretch > :first-child { grid-column: 1 / -1; }
|
|
2684
|
+
@container (min-height: 5.5rem) {
|
|
2685
|
+
.tile-badge--stretch > * { grid-column: 1 / -1; }
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2564
2688
|
/* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
|
|
2565
2689
|
.tile-badge--corner { position: absolute; right: var(--sp-3); bottom: var(--sp-3); }
|
|
2566
2690
|
|
|
@@ -3536,6 +3660,28 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3536
3660
|
.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)); }
|
|
3537
3661
|
.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; }
|
|
3538
3662
|
.calendar__day:disabled { opacity: 0.3; }
|
|
3663
|
+
.calendar__day[aria-current="date"] { text-decoration: underline; text-underline-offset: 0.2em; }
|
|
3664
|
+
|
|
3665
|
+
/* ---- calendar weeks: a run of week rows that scrolls week by week, `show` rows in sight; the head sticks at the top of the box
|
|
3666
|
+
<div class="calendar calendar--weeks"><div class="calendar__weeks-head"><span class="calendar__label">June 2026</span><div class="calendar__grid">…wd×7</div></div>
|
|
3667
|
+
<div class="calendar__grid calendar__week"><button class="calendar__day">…</button>×7</div>…</div>
|
|
3668
|
+
--_weeks-h (head + `show` rows) and --_weeks-lead (the head and the gap under it, where a row snaps) are measured by src/choice/CalendarWeeks.tsx; the cells and every --calendar-* knob are Calendar's */
|
|
3669
|
+
.calendar--weeks {
|
|
3670
|
+
position: relative;
|
|
3671
|
+
max-height: var(--_weeks-h, none);
|
|
3672
|
+
overflow-y: auto;
|
|
3673
|
+
overscroll-behavior: contain;
|
|
3674
|
+
scroll-snap-type: y mandatory;
|
|
3675
|
+
scroll-padding-block-start: var(--_weeks-lead, 0px);
|
|
3676
|
+
overflow-anchor: none; /* a pick rerenders the rows; the browser must not shift the scroll to keep an anchor */
|
|
3677
|
+
scrollbar-width: none;
|
|
3678
|
+
}
|
|
3679
|
+
.calendar--weeks::-webkit-scrollbar { display: none; }
|
|
3680
|
+
.calendar__weeks-head { position: sticky; inset-block-start: 0; z-index: 1; display: grid; gap: var(--calendar-head-gap, var(--sp-2)); background: var(--calendar-weeks-head-bg, var(--card-bg)); }
|
|
3681
|
+
.calendar__label { font: var(--calendar-label-font, var(--type-label)); color: var(--calendar-label-ink, inherit); justify-self: start; }
|
|
3682
|
+
button.calendar__label { appearance: none; border: 0; padding: 0; background: none; cursor: pointer; border-radius: var(--sp-1); }
|
|
3683
|
+
button.calendar__label:focus-visible { outline: var(--focus); outline-offset: var(--sp-0); }
|
|
3684
|
+
.calendar__week { scroll-snap-align: start; }
|
|
3539
3685
|
|
|
3540
3686
|
/* ---- mood: five dots in a pill, the picked one grows ----
|
|
3541
3687
|
<fieldset class="mood"><label class="mood__opt"><input type="radio" name="mood"><span class="mood__dot"></span></label>×5</fieldset> */
|
|
@@ -5161,8 +5307,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
5161
5307
|
liked it, a voice note, a reply live OUTSIDE the postcard, in a Row under it. */
|
|
5162
5308
|
.postcard {
|
|
5163
5309
|
position: relative;
|
|
5310
|
+
display: block; /* its faces lie absolute over the box (.flip's grid cell is not used) */
|
|
5164
5311
|
min-height: var(--postcard-min-h, var(--_cover-min-h));
|
|
5165
|
-
perspective: 60rem;
|
|
5166
5312
|
border-radius: var(--postcard-radius, var(--r-card));
|
|
5167
5313
|
isolation: isolate;
|
|
5168
5314
|
}
|
|
@@ -5175,9 +5321,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
5175
5321
|
border-radius: inherit;
|
|
5176
5322
|
overflow: hidden;
|
|
5177
5323
|
isolation: isolate;
|
|
5178
|
-
backface-visibility: hidden;
|
|
5179
|
-
-webkit-backface-visibility: hidden;
|
|
5180
|
-
transition: transform var(--motion-flip) ease-in-out;
|
|
5181
5324
|
box-shadow: var(--postcard-face-shadow, var(--shadow-float));
|
|
5182
5325
|
}
|
|
5183
5326
|
/* the turn: the button that fills the face, under the band */
|
|
@@ -5217,7 +5360,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
5217
5360
|
background: var(--postcard-back-bg, var(--card-2-bg));
|
|
5218
5361
|
--dark: var(--card-2-dark);
|
|
5219
5362
|
color: var(--postcard-back-ink, var(--card-ink));
|
|
5220
|
-
transform: rotateY(180deg);
|
|
5221
5363
|
}
|
|
5222
5364
|
.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 */
|
|
5223
5365
|
.postcard__caption { margin: 0; font: var(--postcard-caption-font, var(--type-title)); text-wrap: pretty; }
|
|
@@ -5236,16 +5378,11 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
5236
5378
|
.postcard__words { display: grid; gap: var(--postcard-words-gap, var(--sp-1)); flex: 1; min-width: 0; pointer-events: none; }
|
|
5237
5379
|
.postcard__front .postcard__band { --accent: var(--postcard-front-ink, var(--card-ink)); --accent-dark: calc(1 - var(--dark)); }
|
|
5238
5380
|
.postcard__band .icon-btn[aria-pressed="true"] .icon { fill: currentColor; }
|
|
5239
|
-
.postcard.is-flipped > .postcard__front { transform: rotateY(-180deg); }
|
|
5240
|
-
.postcard.is-flipped > .postcard__back { transform: rotateY(0); }
|
|
5241
5381
|
/* a voice note on the back: no words to read — its timeline (a Progress), its length and the control to hear it
|
|
5242
5382
|
under them, at the middle over the turn, on the face's inset; the app plays and keeps the time */
|
|
5243
5383
|
.postcard__voice { position: absolute; inset: 0; z-index: 2; display: grid; align-content: center; justify-items: center; gap: var(--postcard-voice-gap, var(--sp-3)); padding: var(--postcard-face-pad, var(--card-pad-rest)); pointer-events: none; }
|
|
5244
5384
|
.postcard__voice > * { pointer-events: auto; }
|
|
5245
5385
|
.postcard__voice > .progress { justify-self: stretch; pointer-events: none; }
|
|
5246
|
-
@media (prefers-reduced-motion: reduce) {
|
|
5247
|
-
.postcard__face { transition: none; }
|
|
5248
|
-
}
|
|
5249
5386
|
|
|
5250
5387
|
/* ---- css/layover.css — ask drawer, popover menu, banner, float bar, callout ---- */
|
|
5251
5388
|
/* ============================================================
|